From b952b60a1351fd436a1633086baf64ea0fcea352 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Mon, 8 Dec 2025 10:40:34 +0000 Subject: [PATCH 1/6] feat: add client fingerprint properties (tlsJA4, h2Fingerprint, ohFingerprint) Add three new properties to ClientInfo for client fingerprinting: - tlsJA4: JA4 TLS fingerprint (human-readable string format) - h2Fingerprint: HTTP/2 fingerprint - ohFingerprint: Original Header fingerprint These properties provide parity with the Rust and Go Compute SDKs. --- CHANGELOG.md | 8 ++ runtime/fastly/builtins/fetch-event.cpp | 90 +++++++++++++++++++++++ runtime/fastly/builtins/fetch-event.h | 6 ++ runtime/fastly/host-api/fastly.h | 9 +++ runtime/fastly/host-api/host_api.cpp | 84 +++++++++++++++++++++ runtime/fastly/host-api/host_api_fastly.h | 6 ++ types/globals.d.ts | 3 + 7 files changed, 206 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fa6152025..f28798ffe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +### Added + +* Add `tlsJA4` property to `ClientInfo` for JA4 TLS fingerprint +* Add `h2Fingerprint` property to `ClientInfo` for HTTP/2 fingerprint +* Add `ohFingerprint` property to `ClientInfo` for Original Header fingerprint + ## 3.36.0 (2025-11-18) ### Added diff --git a/runtime/fastly/builtins/fetch-event.cpp b/runtime/fastly/builtins/fetch-event.cpp index 835c7f73ee..fdc706f28c 100644 --- a/runtime/fastly/builtins/fetch-event.cpp +++ b/runtime/fastly/builtins/fetch-event.cpp @@ -48,6 +48,18 @@ JSString *ja3(JSObject *obj) { JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::JA3)); return val.isString() ? val.toString() : nullptr; } +JSString *ja4(JSObject *obj) { + JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::JA4)); + return val.isString() ? val.toString() : nullptr; +} +JSString *h2Fingerprint(JSObject *obj) { + JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::H2Fingerprint)); + return val.isString() ? val.toString() : nullptr; +} +JSString *ohFingerprint(JSObject *obj) { + JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::OHFingerprint)); + return val.isString() ? val.toString() : nullptr; +} JSObject *clientHello(JSObject *obj) { JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::ClientHello)); return val.isObject() ? val.toObjectOrNull() : nullptr; @@ -208,6 +220,81 @@ bool ClientInfo::tls_ja3_md5_get(JSContext *cx, unsigned argc, JS::Value *vp) { return true; } +bool ClientInfo::tls_ja4_get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(0); + + JS::RootedString result(cx, ja4(self)); + if (!result) { + auto res = host_api::HttpReq::http_req_downstream_tls_ja4(); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } + + if (!res.unwrap().has_value()) { + args.rval().setNull(); + return true; + } + + auto ja4_str = std::move(res.unwrap().value()); + result.set(JS_NewStringCopyN(cx, ja4_str.ptr.get(), ja4_str.len)); + JS::SetReservedSlot(self, static_cast(ClientInfo::Slots::JA4), + JS::StringValue(result)); + } + args.rval().setString(result); + return true; +} + +bool ClientInfo::h2_fingerprint_get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(0); + + JS::RootedString result(cx, h2Fingerprint(self)); + if (!result) { + auto res = host_api::HttpReq::http_req_downstream_client_h2_fingerprint(); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } + + if (!res.unwrap().has_value()) { + args.rval().setNull(); + return true; + } + + auto h2fp_str = std::move(res.unwrap().value()); + result.set(JS_NewStringCopyN(cx, h2fp_str.ptr.get(), h2fp_str.len)); + JS::SetReservedSlot(self, static_cast(ClientInfo::Slots::H2Fingerprint), + JS::StringValue(result)); + } + args.rval().setString(result); + return true; +} + +bool ClientInfo::oh_fingerprint_get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(0); + + JS::RootedString result(cx, ohFingerprint(self)); + if (!result) { + auto res = host_api::HttpReq::http_req_downstream_client_oh_fingerprint(); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } + + if (!res.unwrap().has_value()) { + args.rval().setNull(); + return true; + } + + auto ohfp_str = std::move(res.unwrap().value()); + result.set(JS_NewStringCopyN(cx, ohfp_str.ptr.get(), ohfp_str.len)); + JS::SetReservedSlot(self, static_cast(ClientInfo::Slots::OHFingerprint), + JS::StringValue(result)); + } + args.rval().setString(result); + return true; +} + bool ClientInfo::tls_client_hello_get(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(0); @@ -323,6 +410,9 @@ const JSPropertySpec ClientInfo::properties[] = { JS_PSG("tlsCipherOpensslName", tls_cipher_openssl_name_get, JSPROP_ENUMERATE), JS_PSG("tlsProtocol", tls_protocol_get, JSPROP_ENUMERATE), JS_PSG("tlsJA3MD5", tls_ja3_md5_get, JSPROP_ENUMERATE), + JS_PSG("tlsJA4", tls_ja4_get, JSPROP_ENUMERATE), + JS_PSG("h2Fingerprint", h2_fingerprint_get, JSPROP_ENUMERATE), + JS_PSG("ohFingerprint", oh_fingerprint_get, JSPROP_ENUMERATE), JS_PSG("tlsClientCertificate", tls_client_certificate_get, JSPROP_ENUMERATE), JS_PSG("tlsClientHello", tls_client_hello_get, JSPROP_ENUMERATE), JS_PS_END, diff --git a/runtime/fastly/builtins/fetch-event.h b/runtime/fastly/builtins/fetch-event.h index 27f2348e0d..4e216b98c7 100644 --- a/runtime/fastly/builtins/fetch-event.h +++ b/runtime/fastly/builtins/fetch-event.h @@ -15,6 +15,9 @@ class ClientInfo final : public builtins::BuiltinNoConstructor { static bool tls_protocol_get(JSContext *cx, unsigned argc, JS::Value *vp); static bool tls_client_hello_get(JSContext *cx, unsigned argc, JS::Value *vp); static bool tls_ja3_md5_get(JSContext *cx, unsigned argc, JS::Value *vp); + static bool tls_ja4_get(JSContext *cx, unsigned argc, JS::Value *vp); + static bool h2_fingerprint_get(JSContext *cx, unsigned argc, JS::Value *vp); + static bool oh_fingerprint_get(JSContext *cx, unsigned argc, JS::Value *vp); static bool tls_client_certificate_get(JSContext *cx, unsigned argc, JS::Value *vp); public: @@ -27,6 +30,9 @@ class ClientInfo final : public builtins::BuiltinNoConstructor { Protocol, ClientHello, JA3, + JA4, + H2Fingerprint, + OHFingerprint, ClientCert, Count, }; diff --git a/runtime/fastly/host-api/fastly.h b/runtime/fastly/host-api/fastly.h index d1e2e86d8c..64b822e859 100644 --- a/runtime/fastly/host-api/fastly.h +++ b/runtime/fastly/host-api/fastly.h @@ -532,6 +532,15 @@ int req_downstream_tls_raw_client_certificate(uint8_t *ret, size_t ret_len, size WASM_IMPORT("fastly_http_req", "downstream_tls_ja3_md5") int req_downstream_tls_ja3_md5(uint8_t *ret, size_t *nwritten); +WASM_IMPORT("fastly_http_req", "downstream_tls_ja4") +int req_downstream_tls_ja4(uint8_t *ret, size_t ret_len, size_t *nwritten); + +WASM_IMPORT("fastly_http_req", "downstream_client_h2_fingerprint") +int req_downstream_client_h2_fingerprint(uint8_t *ret, size_t ret_len, size_t *nwritten); + +WASM_IMPORT("fastly_http_req", "downstream_client_oh_fingerprint") +int req_downstream_client_oh_fingerprint(uint8_t *ret, size_t ret_len, size_t *nwritten); + WASM_IMPORT("fastly_http_req", "new") int req_new(uint32_t *req_handle_out); diff --git a/runtime/fastly/host-api/host_api.cpp b/runtime/fastly/host-api/host_api.cpp index e435174464..115f3005e1 100644 --- a/runtime/fastly/host-api/host_api.cpp +++ b/runtime/fastly/host-api/host_api.cpp @@ -1807,6 +1807,90 @@ Result> HttpReq::http_req_downstream_tls_ja3_md5() { return res; } +// http-req-downstream-tls-ja4: func() -> result, error> +Result> HttpReq::http_req_downstream_tls_ja4() { + TRACE_CALL() + Result> res; + + fastly::fastly_host_error err; + fastly::fastly_world_string ret; + auto default_size = 128; + ret.ptr = static_cast(cabi_malloc(default_size, 4)); + auto status = fastly::req_downstream_tls_ja4(ret.ptr, default_size, &ret.len); + if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { + ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); + status = fastly::req_downstream_tls_ja4(ret.ptr, ret.len, &ret.len); + } + if (!convert_result(status, &err)) { + cabi_free(ret.ptr); + if (error_is_optional_none(err)) { + res.emplace(std::nullopt); + } else { + res.emplace_err(err); + } + } else { + res.emplace(make_host_string(ret)); + } + + return res; +} + +// http-req-downstream-client-h2-fingerprint: func() -> result, error> +Result> HttpReq::http_req_downstream_client_h2_fingerprint() { + TRACE_CALL() + Result> res; + + fastly::fastly_host_error err; + fastly::fastly_world_string ret; + auto default_size = 128; + ret.ptr = static_cast(cabi_malloc(default_size, 4)); + auto status = fastly::req_downstream_client_h2_fingerprint(ret.ptr, default_size, &ret.len); + if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { + ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); + status = fastly::req_downstream_client_h2_fingerprint(ret.ptr, ret.len, &ret.len); + } + if (!convert_result(status, &err)) { + cabi_free(ret.ptr); + if (error_is_optional_none(err)) { + res.emplace(std::nullopt); + } else { + res.emplace_err(err); + } + } else { + res.emplace(make_host_string(ret)); + } + + return res; +} + +// http-req-downstream-client-oh-fingerprint: func() -> result, error> +Result> HttpReq::http_req_downstream_client_oh_fingerprint() { + TRACE_CALL() + Result> res; + + fastly::fastly_host_error err; + fastly::fastly_world_string ret; + auto default_size = 128; + ret.ptr = static_cast(cabi_malloc(default_size, 4)); + auto status = fastly::req_downstream_client_oh_fingerprint(ret.ptr, default_size, &ret.len); + if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { + ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); + status = fastly::req_downstream_client_oh_fingerprint(ret.ptr, ret.len, &ret.len); + } + if (!convert_result(status, &err)) { + cabi_free(ret.ptr); + if (error_is_optional_none(err)) { + res.emplace(std::nullopt); + } else { + res.emplace_err(err); + } + } else { + res.emplace(make_host_string(ret)); + } + + return res; +} + bool HttpReq::is_valid() const { return this->handle != HttpReq::invalid; } Result HttpReq::get_version() const { diff --git a/runtime/fastly/host-api/host_api_fastly.h b/runtime/fastly/host-api/host_api_fastly.h index 74afd94ae4..2efc7af178 100644 --- a/runtime/fastly/host-api/host_api_fastly.h +++ b/runtime/fastly/host-api/host_api_fastly.h @@ -573,6 +573,12 @@ class HttpReq final : public HttpBase { static Result> http_req_downstream_tls_ja3_md5(); + static Result> http_req_downstream_tls_ja4(); + + static Result> http_req_downstream_client_h2_fingerprint(); + + static Result> http_req_downstream_client_oh_fingerprint(); + Result auto_decompress_gzip(); /// Send this request synchronously, and wait for the response. diff --git a/types/globals.d.ts b/types/globals.d.ts index 84b0b52aff..3b77ec6d2f 100644 --- a/types/globals.d.ts +++ b/types/globals.d.ts @@ -436,6 +436,9 @@ declare interface ClientInfo { readonly address: string; readonly geo: import('fastly:geolocation').Geolocation | null; readonly tlsJA3MD5: string | null; + readonly tlsJA4: string | null; + readonly h2Fingerprint: string | null; + readonly ohFingerprint: string | null; readonly tlsCipherOpensslName: string | null; readonly tlsProtocol: string | null; readonly tlsClientCertificate: ArrayBuffer | null; From f2343011a3e80f1c31cefbcd4bbdbfb4e583a2fb Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Mon, 8 Dec 2025 11:51:08 +0000 Subject: [PATCH 2/6] docs: add v3.37.0 changelog entry for client fingerprint properties --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f28798ffe5..40ad37cc33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 3.37.0 (2025-12-08) ### Added From 1b45494b17ebf9157aef13de26a50aa398ae6d51 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Mon, 8 Dec 2025 12:02:12 +0000 Subject: [PATCH 3/6] test: add integration tests for client fingerprint properties Add tests for tlsJA4, h2Fingerprint, and ohFingerprint properties to ensure they return null locally and string on edge. --- .../js-compute/fixtures/app/src/client.js | 38 +++++++++++++++++++ .../js-compute/fixtures/app/tests.json | 3 ++ 2 files changed, 41 insertions(+) diff --git a/integration-tests/js-compute/fixtures/app/src/client.js b/integration-tests/js-compute/fixtures/app/src/client.js index d41b1fc9bc..59d7aa945a 100644 --- a/integration-tests/js-compute/fixtures/app/src/client.js +++ b/integration-tests/js-compute/fixtures/app/src/client.js @@ -74,3 +74,41 @@ routes.set('/client/tlsProtocol', (event) => { ); } }); + +routes.set('/client/tlsJA4', (event) => { + if (isRunningLocally()) { + strictEqual(event.client.tlsJA4, null); + } else { + strictEqual( + typeof event.client.tlsJA4, + 'string', + 'typeof event.client.tlsJA4', + ); + } +}); + +routes.set('/client/h2Fingerprint', (event) => { + if (isRunningLocally()) { + strictEqual(event.client.h2Fingerprint, null); + } else { + // h2Fingerprint may be null for HTTP/1.1 connections + const fp = event.client.h2Fingerprint; + strictEqual( + fp === null || typeof fp === 'string', + true, + 'event.client.h2Fingerprint is null or string', + ); + } +}); + +routes.set('/client/ohFingerprint', (event) => { + if (isRunningLocally()) { + strictEqual(event.client.ohFingerprint, null); + } else { + strictEqual( + typeof event.client.ohFingerprint, + 'string', + 'typeof event.client.ohFingerprint', + ); + } +}); diff --git a/integration-tests/js-compute/fixtures/app/tests.json b/integration-tests/js-compute/fixtures/app/tests.json index 6c4763e156..361de03b88 100644 --- a/integration-tests/js-compute/fixtures/app/tests.json +++ b/integration-tests/js-compute/fixtures/app/tests.json @@ -449,6 +449,9 @@ "GET /client/tlsClientCertificate": {}, "GET /client/tlsCipherOpensslName": {}, "GET /client/tlsProtocol": {}, + "GET /client/tlsJA4": {}, + "GET /client/h2Fingerprint": {}, + "GET /client/ohFingerprint": {}, "GET /config-store": { "flake": true }, From 1bcbcecb761ad8356ef026aa449f10dd1f921730 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Mon, 8 Dec 2025 17:15:26 +0000 Subject: [PATCH 4/6] docs: add documentation for client fingerprint properties Document tlsJA4, h2Fingerprint, and ohFingerprint properties in FetchEvent.mdx. --- documentation/docs/globals/FetchEvent/FetchEvent.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/documentation/docs/globals/FetchEvent/FetchEvent.mdx b/documentation/docs/globals/FetchEvent/FetchEvent.mdx index f380d715da..b6f45447bf 100644 --- a/documentation/docs/globals/FetchEvent/FetchEvent.mdx +++ b/documentation/docs/globals/FetchEvent/FetchEvent.mdx @@ -31,6 +31,12 @@ It provides the [`event.respondWith()`](./prototype/respondWith.mdx) method, whi - : Either `null` or an ArrayBuffer containing the raw client certificate in the mutual TLS handshake message. It is in PEM format. Returns an empty ArrayBuffer if this is not mTLS or available. - `FetchEvent.client.tlsClientHello` _**readonly**_ - : Either `null` or an ArrayBuffer containing the raw bytes sent by the client in the TLS ClientHello message. + - `FetchEvent.client.tlsJA4` _**readonly**_ + - : Either `null` or a string representation of the JA4 fingerprint of the TLS ClientHello message. + - `FetchEvent.client.h2Fingerprint` _**readonly**_ + - : Either `null` or a string representation of the HTTP/2 fingerprint for HTTP/2 connections. Returns `null` for HTTP/1.1 connections. + - `FetchEvent.client.ohFingerprint` _**readonly**_ + - : Either `null` or a string representation of the Original Header fingerprint based on the order and presence of request headers. - `FetchEvent.server` _**readonly**_ - : Information about the server receiving the request for the Fastly Compute service. - `FetchEvent.server.address` _**readonly**_ From b734b7954b26872f96064420592228e4cc4e8154 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Mon, 8 Dec 2025 17:52:52 +0000 Subject: [PATCH 5/6] revert: remove CHANGELOG modifications Maintainers will handle versioning on merge. --- CHANGELOG.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40ad37cc33..7fa6152025 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,5 @@ # Changelog -## 3.37.0 (2025-12-08) - -### Added - -* Add `tlsJA4` property to `ClientInfo` for JA4 TLS fingerprint -* Add `h2Fingerprint` property to `ClientInfo` for HTTP/2 fingerprint -* Add `ohFingerprint` property to `ClientInfo` for Original Header fingerprint - ## 3.36.0 (2025-11-18) ### Added From 6b31f4e1b0836b8d6eba497e3393a2ec8fb4c634 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Tue, 9 Dec 2025 10:25:59 +0000 Subject: [PATCH 6/6] Migrate downstream APIs from fastly_http_req to fastly_http_downstream - Convert all downstream functions to use the new fastly_http_downstream module - Functions now require request handle parameter instead of being static - Add tlsClientServername property to ClientInfo (SNI hostname) - Store request handle in ClientInfo/ServerInfo slots for lazy access Migrated functions: - downstream_client_ip_addr - downstream_server_ip_addr - downstream_tls_cipher_openssl_name - downstream_tls_protocol - downstream_tls_client_hello - downstream_tls_raw_client_certificate - downstream_tls_ja3_md5 - downstream_tls_ja4 - downstream_client_h2_fingerprint - downstream_client_oh_fingerprint New property: - client.tlsClientServername - Returns TLS SNI hostname Also includes: - TypeScript types for tlsClientServername - Integration tests for tlsClientServername - Documentation updates --- .../docs/globals/FetchEvent/FetchEvent.mdx | 2 + .../js-compute/fixtures/app/src/client.js | 12 ++ .../js-compute/fixtures/app/tests.json | 1 + runtime/fastly/builtins/fetch-event.cpp | 100 +++++++++++++--- runtime/fastly/builtins/fetch-event.h | 8 +- runtime/fastly/host-api/fastly.h | 44 +++---- runtime/fastly/host-api/host_api.cpp | 107 +++++++++++------- runtime/fastly/host-api/host_api_fastly.h | 34 ++++-- types/globals.d.ts | 1 + 9 files changed, 222 insertions(+), 87 deletions(-) diff --git a/documentation/docs/globals/FetchEvent/FetchEvent.mdx b/documentation/docs/globals/FetchEvent/FetchEvent.mdx index b6f45447bf..a582fd7566 100644 --- a/documentation/docs/globals/FetchEvent/FetchEvent.mdx +++ b/documentation/docs/globals/FetchEvent/FetchEvent.mdx @@ -37,6 +37,8 @@ It provides the [`event.respondWith()`](./prototype/respondWith.mdx) method, whi - : Either `null` or a string representation of the HTTP/2 fingerprint for HTTP/2 connections. Returns `null` for HTTP/1.1 connections. - `FetchEvent.client.ohFingerprint` _**readonly**_ - : Either `null` or a string representation of the Original Header fingerprint based on the order and presence of request headers. + - `FetchEvent.client.tlsClientServername` _**readonly**_ + - : Either `null` or a string representation of the SNI (Server Name Indication) hostname from the TLS handshake. - `FetchEvent.server` _**readonly**_ - : Information about the server receiving the request for the Fastly Compute service. - `FetchEvent.server.address` _**readonly**_ diff --git a/integration-tests/js-compute/fixtures/app/src/client.js b/integration-tests/js-compute/fixtures/app/src/client.js index 59d7aa945a..5c7624119d 100644 --- a/integration-tests/js-compute/fixtures/app/src/client.js +++ b/integration-tests/js-compute/fixtures/app/src/client.js @@ -112,3 +112,15 @@ routes.set('/client/ohFingerprint', (event) => { ); } }); + +routes.set('/client/tlsClientServername', (event) => { + if (isRunningLocally()) { + strictEqual(event.client.tlsClientServername, null); + } else { + strictEqual( + typeof event.client.tlsClientServername, + 'string', + 'typeof event.client.tlsClientServername', + ); + } +}); diff --git a/integration-tests/js-compute/fixtures/app/tests.json b/integration-tests/js-compute/fixtures/app/tests.json index 361de03b88..cc81ece127 100644 --- a/integration-tests/js-compute/fixtures/app/tests.json +++ b/integration-tests/js-compute/fixtures/app/tests.json @@ -452,6 +452,7 @@ "GET /client/tlsJA4": {}, "GET /client/h2Fingerprint": {}, "GET /client/ohFingerprint": {}, + "GET /client/tlsClientServername": {}, "GET /config-store": { "flake": true }, diff --git a/runtime/fastly/builtins/fetch-event.cpp b/runtime/fastly/builtins/fetch-event.cpp index fdc706f28c..94fa28b695 100644 --- a/runtime/fastly/builtins/fetch-event.cpp +++ b/runtime/fastly/builtins/fetch-event.cpp @@ -60,6 +60,10 @@ JSString *ohFingerprint(JSObject *obj) { JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::OHFingerprint)); return val.isString() ? val.toString() : nullptr; } +JSString *servername(JSObject *obj) { + JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::Servername)); + return val.isString() ? val.toString() : nullptr; +} JSObject *clientHello(JSObject *obj) { JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::ClientHello)); return val.isObject() ? val.toObjectOrNull() : nullptr; @@ -73,8 +77,14 @@ JSString *protocol(JSObject *obj) { return val.isString() ? val.toString() : nullptr; } +host_api::HttpReq get_request_handle(JSObject *obj) { + JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::RequestHandle)); + return host_api::HttpReq{static_cast(val.toInt32())}; +} + static JSString *retrieve_client_address(JSContext *cx, JS::HandleObject self) { - auto res = host_api::HttpReq::downstream_client_ip_addr(); + auto req = get_request_handle(self); + auto res = req.downstream_client_ip_addr(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return nullptr; @@ -171,7 +181,8 @@ bool ClientInfo::tls_cipher_openssl_name_get(JSContext *cx, unsigned argc, JS::V JS::RootedString result(cx, cipher(self)); if (!result) { - auto res = host_api::HttpReq::http_req_downstream_tls_cipher_openssl_name(); + auto req = get_request_handle(self); + auto res = req.downstream_tls_cipher_openssl_name(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -197,7 +208,8 @@ bool ClientInfo::tls_ja3_md5_get(JSContext *cx, unsigned argc, JS::Value *vp) { JS::RootedString result(cx, ja3(self)); if (!result) { - auto res = host_api::HttpReq::http_req_downstream_tls_ja3_md5(); + auto req = get_request_handle(self); + auto res = req.downstream_tls_ja3_md5(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -225,7 +237,8 @@ bool ClientInfo::tls_ja4_get(JSContext *cx, unsigned argc, JS::Value *vp) { JS::RootedString result(cx, ja4(self)); if (!result) { - auto res = host_api::HttpReq::http_req_downstream_tls_ja4(); + auto req = get_request_handle(self); + auto res = req.downstream_tls_ja4(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -250,7 +263,8 @@ bool ClientInfo::h2_fingerprint_get(JSContext *cx, unsigned argc, JS::Value *vp) JS::RootedString result(cx, h2Fingerprint(self)); if (!result) { - auto res = host_api::HttpReq::http_req_downstream_client_h2_fingerprint(); + auto req = get_request_handle(self); + auto res = req.downstream_client_h2_fingerprint(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -275,7 +289,8 @@ bool ClientInfo::oh_fingerprint_get(JSContext *cx, unsigned argc, JS::Value *vp) JS::RootedString result(cx, ohFingerprint(self)); if (!result) { - auto res = host_api::HttpReq::http_req_downstream_client_oh_fingerprint(); + auto req = get_request_handle(self); + auto res = req.downstream_client_oh_fingerprint(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -300,7 +315,8 @@ bool ClientInfo::tls_client_hello_get(JSContext *cx, unsigned argc, JS::Value *v JS::RootedObject buffer(cx, clientHello(self)); if (!buffer) { - auto res = host_api::HttpReq::http_req_downstream_tls_client_hello(); + auto req = get_request_handle(self); + auto res = req.downstream_tls_client_hello(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -335,7 +351,8 @@ bool ClientInfo::tls_client_certificate_get(JSContext *cx, unsigned argc, JS::Va JS::RootedObject buffer(cx, clientCert(self)); if (!buffer) { - auto res = host_api::HttpReq::http_req_downstream_tls_raw_client_certificate(); + auto req = get_request_handle(self); + auto res = req.downstream_tls_raw_client_certificate(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -371,7 +388,8 @@ bool ClientInfo::tls_protocol_get(JSContext *cx, unsigned argc, JS::Value *vp) { JS::RootedString result(cx, protocol(self)); if (!result) { - auto res = host_api::HttpReq::http_req_downstream_tls_protocol(); + auto req = get_request_handle(self); + auto res = req.downstream_tls_protocol(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -392,6 +410,33 @@ bool ClientInfo::tls_protocol_get(JSContext *cx, unsigned argc, JS::Value *vp) { return true; } +bool ClientInfo::tls_client_servername_get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(0); + + JS::RootedString result(cx, servername(self)); + if (!result) { + auto req = get_request_handle(self); + auto res = req.downstream_tls_client_servername(); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } + + if (!res.unwrap().has_value()) { + args.rval().setNull(); + return true; + } + + auto sni = std::move(res.unwrap().value()); + result.set(JS_NewStringCopyN(cx, sni.ptr.get(), sni.len)); + JS::SetReservedSlot(self, static_cast(ClientInfo::Slots::Servername), + JS::StringValue(result)); + } + + args.rval().setString(result); + return true; +} + const JSFunctionSpec ClientInfo::static_methods[] = { JS_FS_END, }; @@ -409,6 +454,7 @@ const JSPropertySpec ClientInfo::properties[] = { JS_PSG("geo", geo_get, JSPROP_ENUMERATE), JS_PSG("tlsCipherOpensslName", tls_cipher_openssl_name_get, JSPROP_ENUMERATE), JS_PSG("tlsProtocol", tls_protocol_get, JSPROP_ENUMERATE), + JS_PSG("tlsClientServername", tls_client_servername_get, JSPROP_ENUMERATE), JS_PSG("tlsJA3MD5", tls_ja3_md5_get, JSPROP_ENUMERATE), JS_PSG("tlsJA4", tls_ja4_get, JSPROP_ENUMERATE), JS_PSG("h2Fingerprint", h2_fingerprint_get, JSPROP_ENUMERATE), @@ -418,8 +464,13 @@ const JSPropertySpec ClientInfo::properties[] = { JS_PS_END, }; -JSObject *ClientInfo::create(JSContext *cx) { - return JS_NewObjectWithGivenProto(cx, &class_, proto_obj); +JSObject *ClientInfo::create(JSContext *cx, uint32_t req_handle) { + JS::RootedObject obj(cx, JS_NewObjectWithGivenProto(cx, &class_, proto_obj)); + if (!obj) + return nullptr; + JS::SetReservedSlot(obj, static_cast(Slots::RequestHandle), + JS::Int32Value(req_handle)); + return obj; } namespace { @@ -429,8 +480,14 @@ JSString *server_address(JSObject *obj) { return val.isString() ? val.toString() : nullptr; } +host_api::HttpReq get_server_request_handle(JSObject *obj) { + JS::Value val = JS::GetReservedSlot(obj, static_cast(ServerInfo::Slots::RequestHandle)); + return host_api::HttpReq{static_cast(val.toInt32())}; +} + static JSString *retrieve_server_address(JSContext *cx, JS::HandleObject self) { - auto res = host_api::HttpReq::downstream_server_ip_addr(); + auto req = get_server_request_handle(self); + auto res = req.downstream_server_ip_addr(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return nullptr; @@ -479,8 +536,13 @@ const JSPropertySpec ServerInfo::properties[] = { JS_PS_END, }; -JSObject *ServerInfo::create(JSContext *cx) { - return JS_NewObjectWithGivenProto(cx, &class_, proto_obj); +JSObject *ServerInfo::create(JSContext *cx, uint32_t req_handle) { + JS::RootedObject obj(cx, JS_NewObjectWithGivenProto(cx, &class_, proto_obj)); + if (!obj) + return nullptr; + JS::SetReservedSlot(obj, static_cast(Slots::RequestHandle), + JS::Int32Value(req_handle)); + return obj; } namespace { @@ -542,7 +604,10 @@ bool FetchEvent::client_get(JSContext *cx, unsigned argc, JS::Value *vp) { JS::GetReservedSlot(self, static_cast(Slots::ClientInfo))); if (clientInfo.isUndefined()) { - JS::RootedObject obj(cx, ClientInfo::create(cx)); + JS::RootedObject request( + cx, &JS::GetReservedSlot(self, static_cast(Slots::Request)).toObject()); + uint32_t req_handle = Request::request_handle(request).handle; + JS::RootedObject obj(cx, ClientInfo::create(cx, req_handle)); if (!obj) return false; clientInfo.setObject(*obj); @@ -560,7 +625,10 @@ bool FetchEvent::server_get(JSContext *cx, unsigned argc, JS::Value *vp) { JS::GetReservedSlot(self, static_cast(Slots::ServerInfo))); if (serverInfo.isUndefined()) { - JS::RootedObject obj(cx, ServerInfo::create(cx)); + JS::RootedObject request( + cx, &JS::GetReservedSlot(self, static_cast(Slots::Request)).toObject()); + uint32_t req_handle = Request::request_handle(request).handle; + JS::RootedObject obj(cx, ServerInfo::create(cx, req_handle)); if (!obj) { return false; } diff --git a/runtime/fastly/builtins/fetch-event.h b/runtime/fastly/builtins/fetch-event.h index 4e216b98c7..c398b4cd01 100644 --- a/runtime/fastly/builtins/fetch-event.h +++ b/runtime/fastly/builtins/fetch-event.h @@ -19,11 +19,13 @@ class ClientInfo final : public builtins::BuiltinNoConstructor { static bool h2_fingerprint_get(JSContext *cx, unsigned argc, JS::Value *vp); static bool oh_fingerprint_get(JSContext *cx, unsigned argc, JS::Value *vp); static bool tls_client_certificate_get(JSContext *cx, unsigned argc, JS::Value *vp); + static bool tls_client_servername_get(JSContext *cx, unsigned argc, JS::Value *vp); public: static constexpr const char *class_name = "ClientInfo"; enum class Slots { + RequestHandle, Address, GeoInfo, Cipher, @@ -34,6 +36,7 @@ class ClientInfo final : public builtins::BuiltinNoConstructor { H2Fingerprint, OHFingerprint, ClientCert, + Servername, Count, }; static const JSFunctionSpec static_methods[]; @@ -41,7 +44,7 @@ class ClientInfo final : public builtins::BuiltinNoConstructor { static const JSFunctionSpec methods[]; static const JSPropertySpec properties[]; - static JSObject *create(JSContext *cx); + static JSObject *create(JSContext *cx, uint32_t req_handle); }; class ServerInfo final : public builtins::BuiltinNoConstructor { @@ -51,6 +54,7 @@ class ServerInfo final : public builtins::BuiltinNoConstructor { static constexpr const char *class_name = "ServerInfo"; enum class Slots { + RequestHandle, Address, Count, }; @@ -59,7 +63,7 @@ class ServerInfo final : public builtins::BuiltinNoConstructor { static const JSFunctionSpec methods[]; static const JSPropertySpec properties[]; - static JSObject *create(JSContext *cx); + static JSObject *create(JSContext *cx, uint32_t req_handle); }; void dispatch_fetch_event(HandleObject event); diff --git a/runtime/fastly/host-api/fastly.h b/runtime/fastly/host-api/fastly.h index 64b822e859..c24409c37e 100644 --- a/runtime/fastly/host-api/fastly.h +++ b/runtime/fastly/host-api/fastly.h @@ -511,35 +511,39 @@ int req_auto_decompress_response_set(uint32_t req_handle, int tag); * Otherwise, if `nwritten` will is `16`, the value in `octets` is an IPv6 address. * Otherwise, `nwritten` will be `0`, and no address is available. */ -WASM_IMPORT("fastly_http_req", "downstream_client_ip_addr") -int req_downstream_client_ip_addr_get(uint8_t *octets, size_t *nwritten); +// fastly_http_downstream module - APIs that take request handle +WASM_IMPORT("fastly_http_downstream", "downstream_client_ip_addr") +int downstream_client_ip_addr(uint32_t req_handle, uint8_t *octets, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_server_ip_addr") -int req_downstream_server_ip_addr_get(uint8_t *octets, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_server_ip_addr") +int downstream_server_ip_addr(uint32_t req_handle, uint8_t *octets, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_tls_cipher_openssl_name") -int req_downstream_tls_cipher_openssl_name(char *ret, size_t ret_len, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_tls_cipher_openssl_name") +int downstream_tls_cipher_openssl_name(uint32_t req_handle, char *ret, size_t ret_len, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_tls_protocol") -int req_downstream_tls_protocol(char *ret, size_t ret_len, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_tls_protocol") +int downstream_tls_protocol(uint32_t req_handle, char *ret, size_t ret_len, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_tls_client_hello") -int req_downstream_tls_client_hello(uint8_t *ret, size_t ret_len, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_tls_client_hello") +int downstream_tls_client_hello(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_tls_raw_client_certificate") -int req_downstream_tls_raw_client_certificate(uint8_t *ret, size_t ret_len, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_tls_raw_client_certificate") +int downstream_tls_raw_client_certificate(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_tls_ja3_md5") -int req_downstream_tls_ja3_md5(uint8_t *ret, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_tls_ja3_md5") +int downstream_tls_ja3_md5(uint32_t req_handle, uint8_t *ret, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_tls_ja4") -int req_downstream_tls_ja4(uint8_t *ret, size_t ret_len, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_tls_ja4") +int downstream_tls_ja4(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_client_h2_fingerprint") -int req_downstream_client_h2_fingerprint(uint8_t *ret, size_t ret_len, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_client_h2_fingerprint") +int downstream_client_h2_fingerprint(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_client_oh_fingerprint") -int req_downstream_client_oh_fingerprint(uint8_t *ret, size_t ret_len, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_client_oh_fingerprint") +int downstream_client_oh_fingerprint(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten); + +WASM_IMPORT("fastly_http_downstream", "downstream_tls_client_servername") +int downstream_tls_client_servername(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten); WASM_IMPORT("fastly_http_req", "new") int req_new(uint32_t *req_handle_out); diff --git a/runtime/fastly/host-api/host_api.cpp b/runtime/fastly/host-api/host_api.cpp index 115f3005e1..d9546bafc3 100644 --- a/runtime/fastly/host-api/host_api.cpp +++ b/runtime/fastly/host-api/host_api.cpp @@ -1634,7 +1634,7 @@ Result HttpReq::downstream_client_ip_addr() { fastly::fastly_world_list_u8 octets; octets.ptr = static_cast(cabi_malloc(16, 1)); fastly::fastly_host_error err; - if (!convert_result(fastly::req_downstream_client_ip_addr_get(octets.ptr, &octets.len), &err)) { + if (!convert_result(fastly::downstream_client_ip_addr(this->handle, octets.ptr, &octets.len), &err)) { cabi_free(octets.ptr); res.emplace_err(err); } else { @@ -1651,7 +1651,7 @@ Result HttpReq::downstream_server_ip_addr() { fastly::fastly_world_list_u8 octets; octets.ptr = static_cast(cabi_malloc(16, 1)); fastly::fastly_host_error err; - if (!convert_result(fastly::req_downstream_server_ip_addr_get(octets.ptr, &octets.len), &err)) { + if (!convert_result(fastly::downstream_server_ip_addr(this->handle, octets.ptr, &octets.len), &err)) { cabi_free(octets.ptr); res.emplace_err(err); } else { @@ -1661,8 +1661,8 @@ Result HttpReq::downstream_server_ip_addr() { return res; } -// http-req-downstream-tls-cipher-openssl-name: func() -> result -Result> HttpReq::http_req_downstream_tls_cipher_openssl_name() { +// downstream-tls-cipher-openssl-name: func(req_handle) -> result +Result> HttpReq::downstream_tls_cipher_openssl_name() { TRACE_CALL() Result> res; @@ -1670,12 +1670,14 @@ Result> HttpReq::http_req_downstream_tls_cipher_openss fastly::fastly_world_string ret; auto default_size = 128; ret.ptr = static_cast(cabi_malloc(default_size, 4)); - auto status = fastly::req_downstream_tls_cipher_openssl_name(reinterpret_cast(ret.ptr), - default_size, &ret.len); + auto status = fastly::downstream_tls_cipher_openssl_name(this->handle, + reinterpret_cast(ret.ptr), + default_size, &ret.len); if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); - status = fastly::req_downstream_tls_cipher_openssl_name(reinterpret_cast(ret.ptr), - ret.len, &ret.len); + status = fastly::downstream_tls_cipher_openssl_name(this->handle, + reinterpret_cast(ret.ptr), + ret.len, &ret.len); } if (!convert_result(status, &err)) { @@ -1692,8 +1694,8 @@ Result> HttpReq::http_req_downstream_tls_cipher_openss return res; } -// http-req-downstream-tls-protocol: func() -> result -Result> HttpReq::http_req_downstream_tls_protocol() { +// downstream-tls-protocol: func(req_handle) -> result +Result> HttpReq::downstream_tls_protocol() { TRACE_CALL() Result> res; @@ -1701,12 +1703,13 @@ Result> HttpReq::http_req_downstream_tls_protocol() { fastly::fastly_world_string ret; auto default_size = 32; ret.ptr = static_cast(cabi_malloc(default_size, 4)); - auto status = fastly::req_downstream_tls_protocol(reinterpret_cast(ret.ptr), default_size, - &ret.len); + auto status = fastly::downstream_tls_protocol(this->handle, + reinterpret_cast(ret.ptr), default_size, + &ret.len); if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); - status = - fastly::req_downstream_tls_protocol(reinterpret_cast(ret.ptr), ret.len, &ret.len); + status = fastly::downstream_tls_protocol(this->handle, + reinterpret_cast(ret.ptr), ret.len, &ret.len); } if (!convert_result(status, &err)) { cabi_free(ret.ptr); @@ -1722,8 +1725,8 @@ Result> HttpReq::http_req_downstream_tls_protocol() { return res; } -// http-req-downstream-tls-client-hello: func() -> result, error> -Result> HttpReq::http_req_downstream_tls_client_hello() { +// downstream-tls-client-hello: func(req_handle) -> result, error> +Result> HttpReq::downstream_tls_client_hello() { TRACE_CALL() Result> res; @@ -1731,10 +1734,10 @@ Result> HttpReq::http_req_downstream_tls_client_hello() fastly::fastly_host_error err; auto default_size = 512; ret.ptr = static_cast(cabi_malloc(default_size, 4)); - auto status = fastly::req_downstream_tls_client_hello(ret.ptr, default_size, &ret.len); + auto status = fastly::downstream_tls_client_hello(this->handle, ret.ptr, default_size, &ret.len); if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); - status = fastly::req_downstream_tls_client_hello(ret.ptr, ret.len, &ret.len); + status = fastly::downstream_tls_client_hello(this->handle, ret.ptr, ret.len, &ret.len); } if (!convert_result(status, &err)) { @@ -1751,8 +1754,8 @@ Result> HttpReq::http_req_downstream_tls_client_hello() return res; } -// http-req-downstream-tls-raw-client-certificate: func() -> result, error> -Result> HttpReq::http_req_downstream_tls_raw_client_certificate() { +// downstream-tls-raw-client-certificate: func(req_handle) -> result, error> +Result> HttpReq::downstream_tls_raw_client_certificate() { TRACE_CALL() Result> res; @@ -1760,10 +1763,10 @@ Result> HttpReq::http_req_downstream_tls_raw_client_cer fastly::fastly_host_error err; auto default_size = 4096; ret.ptr = static_cast(cabi_malloc(default_size, 4)); - auto status = fastly::req_downstream_tls_raw_client_certificate(ret.ptr, default_size, &ret.len); + auto status = fastly::downstream_tls_raw_client_certificate(this->handle, ret.ptr, default_size, &ret.len); if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); - status = fastly::req_downstream_tls_raw_client_certificate(ret.ptr, ret.len, &ret.len); + status = fastly::downstream_tls_raw_client_certificate(this->handle, ret.ptr, ret.len, &ret.len); } if (!convert_result(status, &err)) { cabi_free(ret.ptr); @@ -1779,8 +1782,8 @@ Result> HttpReq::http_req_downstream_tls_raw_client_cer return res; } -// http-req-downstream-tls-ja3-md5: func() -> result, error> -Result> HttpReq::http_req_downstream_tls_ja3_md5() { +// downstream-tls-ja3-md5: func(req_handle) -> result, error> +Result> HttpReq::downstream_tls_ja3_md5() { TRACE_CALL() Result> res; @@ -1788,10 +1791,10 @@ Result> HttpReq::http_req_downstream_tls_ja3_md5() { fastly::fastly_host_error err; auto default_size = 16; ret.ptr = static_cast(cabi_malloc(default_size, 4)); - auto status = fastly::req_downstream_tls_ja3_md5(ret.ptr, &ret.len); + auto status = fastly::downstream_tls_ja3_md5(this->handle, ret.ptr, &ret.len); if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); - status = fastly::req_downstream_tls_ja3_md5(ret.ptr, &ret.len); + status = fastly::downstream_tls_ja3_md5(this->handle, ret.ptr, &ret.len); } if (!convert_result(status, &err)) { cabi_free(ret.ptr); @@ -1807,8 +1810,8 @@ Result> HttpReq::http_req_downstream_tls_ja3_md5() { return res; } -// http-req-downstream-tls-ja4: func() -> result, error> -Result> HttpReq::http_req_downstream_tls_ja4() { +// downstream-tls-ja4: func(req_handle) -> result, error> +Result> HttpReq::downstream_tls_ja4() { TRACE_CALL() Result> res; @@ -1816,10 +1819,10 @@ Result> HttpReq::http_req_downstream_tls_ja4() { fastly::fastly_world_string ret; auto default_size = 128; ret.ptr = static_cast(cabi_malloc(default_size, 4)); - auto status = fastly::req_downstream_tls_ja4(ret.ptr, default_size, &ret.len); + auto status = fastly::downstream_tls_ja4(this->handle, ret.ptr, default_size, &ret.len); if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); - status = fastly::req_downstream_tls_ja4(ret.ptr, ret.len, &ret.len); + status = fastly::downstream_tls_ja4(this->handle, ret.ptr, ret.len, &ret.len); } if (!convert_result(status, &err)) { cabi_free(ret.ptr); @@ -1835,8 +1838,8 @@ Result> HttpReq::http_req_downstream_tls_ja4() { return res; } -// http-req-downstream-client-h2-fingerprint: func() -> result, error> -Result> HttpReq::http_req_downstream_client_h2_fingerprint() { +// downstream-client-h2-fingerprint: func(req_handle) -> result, error> +Result> HttpReq::downstream_client_h2_fingerprint() { TRACE_CALL() Result> res; @@ -1844,10 +1847,10 @@ Result> HttpReq::http_req_downstream_client_h2_fingerp fastly::fastly_world_string ret; auto default_size = 128; ret.ptr = static_cast(cabi_malloc(default_size, 4)); - auto status = fastly::req_downstream_client_h2_fingerprint(ret.ptr, default_size, &ret.len); + auto status = fastly::downstream_client_h2_fingerprint(this->handle, ret.ptr, default_size, &ret.len); if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); - status = fastly::req_downstream_client_h2_fingerprint(ret.ptr, ret.len, &ret.len); + status = fastly::downstream_client_h2_fingerprint(this->handle, ret.ptr, ret.len, &ret.len); } if (!convert_result(status, &err)) { cabi_free(ret.ptr); @@ -1863,8 +1866,8 @@ Result> HttpReq::http_req_downstream_client_h2_fingerp return res; } -// http-req-downstream-client-oh-fingerprint: func() -> result, error> -Result> HttpReq::http_req_downstream_client_oh_fingerprint() { +// downstream-client-oh-fingerprint: func(req_handle) -> result, error> +Result> HttpReq::downstream_client_oh_fingerprint() { TRACE_CALL() Result> res; @@ -1872,10 +1875,38 @@ Result> HttpReq::http_req_downstream_client_oh_fingerp fastly::fastly_world_string ret; auto default_size = 128; ret.ptr = static_cast(cabi_malloc(default_size, 4)); - auto status = fastly::req_downstream_client_oh_fingerprint(ret.ptr, default_size, &ret.len); + auto status = fastly::downstream_client_oh_fingerprint(this->handle, ret.ptr, default_size, &ret.len); if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); - status = fastly::req_downstream_client_oh_fingerprint(ret.ptr, ret.len, &ret.len); + status = fastly::downstream_client_oh_fingerprint(this->handle, ret.ptr, ret.len, &ret.len); + } + if (!convert_result(status, &err)) { + cabi_free(ret.ptr); + if (error_is_optional_none(err)) { + res.emplace(std::nullopt); + } else { + res.emplace_err(err); + } + } else { + res.emplace(make_host_string(ret)); + } + + return res; +} + +// downstream-tls-client-servername: func(req_handle) -> result, error> +Result> HttpReq::downstream_tls_client_servername() { + TRACE_CALL() + Result> res; + + fastly::fastly_host_error err; + fastly::fastly_world_string ret; + auto default_size = 256; + ret.ptr = static_cast(cabi_malloc(default_size, 4)); + auto status = fastly::downstream_tls_client_servername(this->handle, ret.ptr, default_size, &ret.len); + if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { + ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); + status = fastly::downstream_tls_client_servername(this->handle, ret.ptr, ret.len, &ret.len); } if (!convert_result(status, &err)) { cabi_free(ret.ptr); diff --git a/runtime/fastly/host-api/host_api_fastly.h b/runtime/fastly/host-api/host_api_fastly.h index 2efc7af178..e984de8a69 100644 --- a/runtime/fastly/host-api/host_api_fastly.h +++ b/runtime/fastly/host-api/host_api_fastly.h @@ -558,26 +558,38 @@ class HttpReq final : public HttpBase { /// Fetch the downstream request/body pair static Result downstream_get(); - /// Get the downstream ip address. - static Result downstream_client_ip_addr(); + /// Get the downstream client ip address. + Result downstream_client_ip_addr(); - static Result downstream_server_ip_addr(); + /// Get the downstream server ip address. + Result downstream_server_ip_addr(); - static Result> http_req_downstream_tls_cipher_openssl_name(); + /// Get the TLS cipher OpenSSL name. + Result> downstream_tls_cipher_openssl_name(); - static Result> http_req_downstream_tls_protocol(); + /// Get the TLS protocol version. + Result> downstream_tls_protocol(); - static Result> http_req_downstream_tls_client_hello(); + /// Get the TLS ClientHello. + Result> downstream_tls_client_hello(); - static Result> http_req_downstream_tls_raw_client_certificate(); + /// Get the raw client certificate. + Result> downstream_tls_raw_client_certificate(); - static Result> http_req_downstream_tls_ja3_md5(); + /// Get the JA3 MD5 hash. + Result> downstream_tls_ja3_md5(); - static Result> http_req_downstream_tls_ja4(); + /// Get the JA4 fingerprint. + Result> downstream_tls_ja4(); - static Result> http_req_downstream_client_h2_fingerprint(); + /// Get the HTTP/2 fingerprint. + Result> downstream_client_h2_fingerprint(); - static Result> http_req_downstream_client_oh_fingerprint(); + /// Get the Original Header fingerprint. + Result> downstream_client_oh_fingerprint(); + + /// Get the TLS client servername (SNI). + Result> downstream_tls_client_servername(); Result auto_decompress_gzip(); diff --git a/types/globals.d.ts b/types/globals.d.ts index 3b77ec6d2f..b40ddadbc5 100644 --- a/types/globals.d.ts +++ b/types/globals.d.ts @@ -441,6 +441,7 @@ declare interface ClientInfo { readonly ohFingerprint: string | null; readonly tlsCipherOpensslName: string | null; readonly tlsProtocol: string | null; + readonly tlsClientServername: string | null; readonly tlsClientCertificate: ArrayBuffer | null; readonly tlsClientHello: ArrayBuffer | null; }