Skip to content

Commit e12424d

Browse files
Merge pull request #188 from SceneTech/fix/http-cookie-websocket-propagation
Propagate scoped HTTP cookies to fetch and WebSocket
2 parents bdc91ea + 5aa5f6b commit e12424d

19 files changed

Lines changed: 1162 additions & 74 deletions

‎experiments/WebScene.NativeEngine.Probe/native/webscene_native_engine.cpp‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,8 @@ struct webscene_engine final {
413413
void* resource_load_v2_user_data_{nullptr};
414414
webscene_resource_load_callback_v3 resource_load_callback_v3_{nullptr};
415415
void* resource_load_v3_user_data_{nullptr};
416+
webscene_resource_load_callback_v4 resource_load_callback_v4_{nullptr};
417+
void* resource_load_v4_user_data_{nullptr};
416418
webscene_stylesheet_consumed_callback stylesheet_consumed_callback_{nullptr};
417419
void* stylesheet_consumed_user_data_{nullptr};
418420
webscene_webgpu_policy_callback webgpu_policy_callback_{nullptr};
@@ -937,7 +939,9 @@ webscene_engine* webscene_engine_create_with_options(const webscene_engine_optio
937939
options->struct_size >= offsetof(webscene_engine_options, stylesheet_consumed_callback);
938940
const auto has_stylesheet_consumed_callback =
939941
options->struct_size >= offsetof(webscene_engine_options, webgpu_policy_callback);
940-
const auto has_webgpu_policy = options->struct_size >= sizeof(webscene_engine_options);
942+
const auto has_webgpu_policy = options->struct_size >= offsetof(
943+
webscene_engine_options, resource_load_callback_v4);
944+
const auto has_resource_callback_v4 = options->struct_size >= sizeof(webscene_engine_options);
941945
return new webscene_engine(
942946
options->simulated_chart_command_count,
943947
std::move(cache_directory),
@@ -947,6 +951,8 @@ webscene_engine* webscene_engine_create_with_options(const webscene_engine_optio
947951
has_resource_callback_v2 ? options->resource_load_v2_user_data : nullptr,
948952
has_resource_callback_v3 ? options->resource_load_callback_v3 : nullptr,
949953
has_resource_callback_v3 ? options->resource_load_v3_user_data : nullptr,
954+
has_resource_callback_v4 ? options->resource_load_callback_v4 : nullptr,
955+
has_resource_callback_v4 ? options->resource_load_v4_user_data : nullptr,
950956
has_scene_published_callback ? options->scene_published_callback : nullptr,
951957
has_scene_published_callback ? options->scene_published_user_data : nullptr,
952958
has_text_measure_callback ? options->text_measure_callback : nullptr,

‎experiments/WebScene.NativeEngine.Probe/native/webscene_native_engine.h‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,68 @@ typedef size_t (*webscene_resource_load_callback_v3)(
727727
char* destination,
728728
size_t destination_capacity);
729729

730+
typedef enum webscene_fetch_credentials {
731+
WEBSCENE_FETCH_CREDENTIALS_OMIT = 0,
732+
WEBSCENE_FETCH_CREDENTIALS_SAME_ORIGIN = 1,
733+
WEBSCENE_FETCH_CREDENTIALS_INCLUDE = 2
734+
} webscene_fetch_credentials;
735+
736+
typedef struct webscene_resource_header_v4 {
737+
uint32_t struct_size;
738+
const char* name;
739+
size_t name_length;
740+
const char* value;
741+
size_t value_length;
742+
} webscene_resource_header_v4;
743+
744+
/* Response metadata is borrowed only for the callback invocation. The engine
745+
* copies accepted fields before returning to the host. Header count and total
746+
* bytes are bounded; Set-Cookie is consumed by the cookie jar and is never
747+
* exposed through the JavaScript Headers object. */
748+
typedef struct webscene_resource_response_v4 {
749+
uint32_t struct_size;
750+
uint32_t status;
751+
const char* status_text;
752+
size_t status_text_length;
753+
const char* final_url;
754+
size_t final_url_length;
755+
const webscene_resource_header_v4* headers;
756+
size_t header_count;
757+
} webscene_resource_response_v4;
758+
759+
typedef struct webscene_resource_request_context_v4 {
760+
uint32_t struct_size;
761+
uint32_t initiator;
762+
const char* origin;
763+
size_t origin_length;
764+
const char* referrer;
765+
size_t referrer_length;
766+
uint32_t mode;
767+
uint32_t destination;
768+
const char* method;
769+
size_t method_length;
770+
const char* body;
771+
size_t body_length;
772+
const char* content_type;
773+
size_t content_type_length;
774+
uint32_t credentials;
775+
const char* cookie;
776+
size_t cookie_length;
777+
} webscene_resource_request_context_v4;
778+
779+
typedef size_t (*webscene_resource_load_callback_v4)(
780+
void* user_data,
781+
uint32_t kind,
782+
const char* url,
783+
size_t url_length,
784+
const char* entity_tag,
785+
size_t entity_tag_length,
786+
int64_t last_modified_unix_seconds,
787+
const webscene_resource_request_context_v4* request_context,
788+
webscene_resource_response_v4* response,
789+
char* destination,
790+
size_t destination_capacity);
791+
730792
/*
731793
* Asynchronous notification emitted after an immutable scene has been
732794
* published. Consumers use this edge to schedule a compositor paint; they
@@ -836,6 +898,8 @@ typedef struct webscene_engine_options {
836898
void* stylesheet_consumed_user_data;
837899
webscene_webgpu_policy_callback webgpu_policy_callback;
838900
void* webgpu_policy_user_data;
901+
webscene_resource_load_callback_v4 resource_load_callback_v4;
902+
void* resource_load_v4_user_data;
839903
} webscene_engine_options;
840904

841905
enum {

‎experiments/WebScene.NativeEngine.Probe/native/webscene_native_engine_lifecycle.inc‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
void* resource_load_v2_user_data = nullptr,
88
webscene_resource_load_callback_v3 resource_load_callback_v3 = nullptr,
99
void* resource_load_v3_user_data = nullptr,
10+
webscene_resource_load_callback_v4 resource_load_callback_v4 = nullptr,
11+
void* resource_load_v4_user_data = nullptr,
1012
webscene_scene_published_callback scene_published_callback = nullptr,
1113
void* scene_published_user_data = nullptr,
1214
webscene_text_measure_callback text_measure_callback = nullptr,
@@ -36,6 +38,8 @@
3638
, resource_load_v2_user_data_(resource_load_v2_user_data)
3739
, resource_load_callback_v3_(resource_load_callback_v3)
3840
, resource_load_v3_user_data_(resource_load_v3_user_data)
41+
, resource_load_callback_v4_(resource_load_callback_v4)
42+
, resource_load_v4_user_data_(resource_load_v4_user_data)
3943
, stylesheet_consumed_callback_(stylesheet_consumed_callback)
4044
, stylesheet_consumed_user_data_(stylesheet_consumed_user_data)
4145
, webgpu_policy_callback_(webgpu_policy_callback)

‎experiments/WebScene.NativeEngine.Probe/native/webscene_native_engine_worker.inc‎

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@
5252
const std::string& entity_tag,
5353
int64_t last_modified_unix_seconds,
5454
webscene_native::v8_dom_runtime::resource_response& response) {
55-
if (resource_load_callback_v3_ == nullptr
55+
if (resource_load_callback_v4_ == nullptr
56+
&& resource_load_callback_v3_ == nullptr
5657
&& resource_load_callback_v2_ == nullptr
5758
&& resource_load_callback_ == nullptr) return false;
5859
const auto started = std::chrono::steady_clock::now();
@@ -120,8 +121,35 @@
120121
request_context.body.size(),
121122
request_context.content_type.data(),
122123
request_context.content_type.size()};
124+
const webscene_resource_request_context_v4 native_context_v4{
125+
sizeof(webscene_resource_request_context_v4),
126+
request_context.initiator,
127+
request_context.origin.data(),
128+
request_context.origin.size(),
129+
request_context.referrer.data(),
130+
request_context.referrer.size(),
131+
request_context.mode,
132+
request_context.destination,
133+
request_context.method.data(),
134+
request_context.method.size(),
135+
request_context.body.data(),
136+
request_context.body.size(),
137+
request_context.content_type.data(),
138+
request_context.content_type.size(),
139+
request_context.credentials,
140+
request_context.cookie.data(),
141+
request_context.cookie.size()};
142+
webscene_resource_response_v4 native_response{};
123143
const auto invoke = [&](char* destination, size_t capacity) {
124-
return resource_load_callback_v3_ != nullptr
144+
native_response = {};
145+
native_response.struct_size = sizeof(native_response);
146+
return resource_load_callback_v4_ != nullptr
147+
? resource_load_callback_v4_(
148+
resource_load_v4_user_data_, kind, url.data(), url.size(),
149+
entity_tag.data(), entity_tag.size(),
150+
last_modified_unix_seconds, &native_context_v4,
151+
&native_response, destination, capacity)
152+
: resource_load_callback_v3_ != nullptr
125153
? resource_load_callback_v3_(
126154
resource_load_v3_user_data_, kind, url.data(), url.size(),
127155
entity_tag.data(), entity_tag.size(),
@@ -185,6 +213,70 @@
185213
response.content.assign(
186214
buffer + envelope_header_size + response_tag_length,
187215
required - envelope_header_size - response_tag_length);
216+
if (resource_load_callback_v4_ != nullptr) {
217+
response.has_http_metadata = true;
218+
constexpr size_t maximum_response_headers = 64U;
219+
constexpr size_t maximum_response_metadata_bytes = 64U * 1024U;
220+
constexpr size_t maximum_response_url_bytes = 8U * 1024U;
221+
if (native_response.status < 100U || native_response.status > 599U
222+
|| native_response.status_text_length > 1024U
223+
|| native_response.final_url_length > maximum_response_url_bytes
224+
|| native_response.header_count > maximum_response_headers
225+
|| (native_response.status_text_length != 0U
226+
&& native_response.status_text == nullptr)
227+
|| (native_response.final_url_length != 0U
228+
&& native_response.final_url == nullptr)
229+
|| (native_response.header_count != 0U
230+
&& native_response.headers == nullptr)) {
231+
return failed("invalid-response-metadata");
232+
}
233+
response.status = native_response.status;
234+
response.status_text.assign(
235+
native_response.status_text == nullptr ? "" : native_response.status_text,
236+
native_response.status_text_length);
237+
response.final_url.assign(
238+
native_response.final_url == nullptr ? "" : native_response.final_url,
239+
native_response.final_url_length);
240+
auto metadata_bytes = response.status_text.size() + response.final_url.size();
241+
response.headers.reserve(native_response.header_count);
242+
for (size_t index = 0; index < native_response.header_count; ++index) {
243+
const auto& header = native_response.headers[index];
244+
if (header.struct_size < sizeof(webscene_resource_header_v4)
245+
|| header.name == nullptr || header.name_length == 0U
246+
|| (header.value_length != 0U && header.value == nullptr)) {
247+
return failed("invalid-response-metadata");
248+
}
249+
metadata_bytes += header.name_length + header.value_length;
250+
if (metadata_bytes > maximum_response_metadata_bytes) {
251+
return failed("response-metadata-too-large");
252+
}
253+
std::string name(header.name, header.name_length);
254+
std::string value(
255+
header.value == nullptr ? "" : header.value,
256+
header.value_length);
257+
const auto valid_name = std::all_of(
258+
name.begin(), name.end(), [](unsigned char character) {
259+
return std::isalnum(character) || character == '!' || character == '#'
260+
|| character == '$' || character == '%' || character == '&'
261+
|| character == '\'' || character == '*' || character == '+'
262+
|| character == '-' || character == '.' || character == '^'
263+
|| character == '_' || character == '`' || character == '|'
264+
|| character == '~';
265+
});
266+
if (!valid_name || value.find_first_of("\r\n") != std::string::npos) {
267+
return failed("invalid-response-header");
268+
}
269+
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char character) {
270+
return static_cast<char>(std::tolower(character));
271+
});
272+
response.headers.emplace_back(std::move(name), std::move(value));
273+
if (response.headers.back().first == "set-cookie") {
274+
response.cacheable = false;
275+
}
276+
}
277+
} else {
278+
response.final_url = url;
279+
}
188280
return true;
189281
},
190282
[this] {

‎experiments/WebScene.NativeEngine.Probe/native/webscene_native_websocket.cpp‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ native_websocket_transport::~native_websocket_transport()
9090
uint64_t native_websocket_transport::open(
9191
std::string url,
9292
std::string origin,
93+
std::string cookie,
9394
std::vector<std::string> protocols)
9495
{
9596
auto shared_state = state_;
@@ -107,9 +108,10 @@ uint64_t native_websocket_transport::open(
107108
record->socket->setUrl(url);
108109
record->socket->disableAutomaticReconnection();
109110
record->socket->setHandshakeTimeout(30);
110-
if (!origin.empty()) {
111-
record->socket->setExtraHeaders({{"Origin", std::move(origin)}});
112-
}
111+
ix::WebSocketHttpHeaders headers;
112+
if (!origin.empty()) headers.emplace("Origin", std::move(origin));
113+
if (!cookie.empty()) headers.emplace("Cookie", std::move(cookie));
114+
if (!headers.empty()) record->socket->setExtraHeaders(std::move(headers));
113115
for (auto& protocol : protocols) {
114116
record->socket->addSubProtocol(protocol);
115117
}

‎experiments/WebScene.NativeEngine.Probe/native/webscene_native_websocket.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class native_websocket_transport final {
4242
uint64_t open(
4343
std::string url,
4444
std::string origin,
45+
std::string cookie,
4546
std::vector<std::string> protocols);
4647
bool send(uint64_t socket_id, const uint8_t* data, size_t size, bool binary);
4748
bool close(uint64_t socket_id, uint16_t code, std::string_view reason);

0 commit comments

Comments
 (0)