xbintsc 0.3.25 → 0.3.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/dist/src/cli/main.js +124 -30
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/calls/access.js +8 -1
- package/dist/src/codegen/generator/calls/access.js.map +1 -1
- package/dist/src/codegen/generator/state.js +20 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +6 -0
- package/dist/src/codegen/generator/tables.js +38 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/commonjs.d.ts +52 -0
- package/dist/src/driver/bundler/commonjs.js +493 -0
- package/dist/src/driver/bundler/commonjs.js.map +1 -0
- package/dist/src/driver/bundler/graph.js +14 -0
- package/dist/src/driver/bundler/graph.js.map +1 -1
- package/dist/src/driver/bundler/merge.js +14 -0
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/bundler/types.d.ts +8 -0
- package/dist/src/driver/compiler.d.ts +8 -1
- package/dist/src/driver/compiler.js +73 -5
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/config.d.ts +53 -0
- package/dist/src/driver/config.js +141 -0
- package/dist/src/driver/config.js.map +1 -0
- package/dist/src/driver/icon.d.ts +62 -0
- package/dist/src/driver/icon.js +151 -0
- package/dist/src/driver/icon.js.map +1 -0
- package/dist/src/driver/index.d.ts +4 -0
- package/dist/src/driver/index.js +4 -0
- package/dist/src/driver/index.js.map +1 -1
- package/dist/src/driver/mac-bundle.d.ts +41 -0
- package/dist/src/driver/mac-bundle.js +123 -0
- package/dist/src/driver/mac-bundle.js.map +1 -0
- package/dist/src/driver/win-icon.d.ts +27 -0
- package/dist/src/driver/win-icon.js +80 -0
- package/dist/src/driver/win-icon.js.map +1 -0
- package/dist/src/parser/statements.js +17 -4
- package/dist/src/parser/statements.js.map +1 -1
- package/dist/tests/cli/main.test.js +46 -1
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/driver/config.test.d.ts +1 -0
- package/dist/tests/driver/config.test.js +124 -0
- package/dist/tests/driver/config.test.js.map +1 -0
- package/dist/tests/driver/icon.test.d.ts +1 -0
- package/dist/tests/driver/icon.test.js +421 -0
- package/dist/tests/driver/icon.test.js.map +1 -0
- package/dist/tests/driver/modules.test.js +109 -0
- package/dist/tests/driver/modules.test.js.map +1 -1
- package/dist/tests/e2e/fetch.test.d.ts +12 -0
- package/dist/tests/e2e/fetch.test.js +272 -0
- package/dist/tests/e2e/fetch.test.js.map +1 -0
- package/dist/tests/e2e/gui-dom.test.d.ts +5 -0
- package/dist/tests/e2e/gui-dom.test.js +102 -0
- package/dist/tests/e2e/gui-dom.test.js.map +1 -0
- package/dist/tests/e2e/gui-events.test.d.ts +5 -0
- package/dist/tests/e2e/gui-events.test.js +60 -0
- package/dist/tests/e2e/gui-events.test.js.map +1 -0
- package/dist/tests/e2e/gui-helpers.d.ts +31 -0
- package/dist/tests/e2e/gui-helpers.js +74 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -0
- package/dist/tests/e2e/gui-layout.test.d.ts +4 -0
- package/dist/tests/e2e/gui-layout.test.js +88 -0
- package/dist/tests/e2e/gui-layout.test.js.map +1 -0
- package/dist/tests/e2e/gui-lifecycle.test.d.ts +5 -0
- package/dist/tests/e2e/gui-lifecycle.test.js +58 -0
- package/dist/tests/e2e/gui-lifecycle.test.js.map +1 -0
- package/dist/tests/e2e/gui-paint.test.d.ts +5 -0
- package/dist/tests/e2e/gui-paint.test.js +111 -0
- package/dist/tests/e2e/gui-paint.test.js.map +1 -0
- package/dist/tests/e2e/gui-script.test.d.ts +5 -0
- package/dist/tests/e2e/gui-script.test.js +78 -0
- package/dist/tests/e2e/gui-script.test.js.map +1 -0
- package/dist/tests/e2e/gui-style.test.d.ts +5 -0
- package/dist/tests/e2e/gui-style.test.js +175 -0
- package/dist/tests/e2e/gui-style.test.js.map +1 -0
- package/dist/tests/e2e/icon.test.d.ts +7 -0
- package/dist/tests/e2e/icon.test.js +80 -0
- package/dist/tests/e2e/icon.test.js.map +1 -0
- package/dist/tests/e2e/npm-modules.test.d.ts +4 -1
- package/dist/tests/e2e/npm-modules.test.js +47 -1
- package/dist/tests/e2e/npm-modules.test.js.map +1 -1
- package/dist/tests/e2e/runtime-coverage-typed-array.test.d.ts +9 -0
- package/dist/tests/e2e/runtime-coverage-typed-array.test.js +140 -0
- package/dist/tests/e2e/runtime-coverage-typed-array.test.js.map +1 -0
- package/dist/tests/parser/statements.test.js +11 -0
- package/dist/tests/parser/statements.test.js.map +1 -1
- package/package.json +1 -1
- package/runtime/ext_gui/css.cpp +4 -517
- package/runtime/ext_gui/css_internal.h +27 -0
- package/runtime/ext_gui/css_match.cpp +195 -0
- package/runtime/ext_gui/css_selector.cpp +207 -0
- package/runtime/ext_gui/css_sheet.cpp +129 -0
- package/runtime/ext_gui/dom.cpp +7 -403
- package/runtime/ext_gui/dom_api.cpp +63 -1146
- package/runtime/ext_gui/dom_api_document.cpp +96 -0
- package/runtime/ext_gui/dom_api_events.cpp +123 -0
- package/runtime/ext_gui/dom_api_internal.h +135 -0
- package/runtime/ext_gui/dom_api_node.cpp +329 -0
- package/runtime/ext_gui/dom_api_node_read.cpp +294 -0
- package/runtime/ext_gui/dom_api_proto.cpp +130 -0
- package/runtime/ext_gui/dom_api_style.cpp +192 -0
- package/runtime/ext_gui/dom_internal.h +39 -0
- package/runtime/ext_gui/dom_parse.cpp +348 -0
- package/runtime/ext_gui/dom_serialize.cpp +54 -0
- package/runtime/ext_gui/gui.cpp +56 -286
- package/runtime/ext_gui/gui_events.cpp +294 -0
- package/runtime/ext_gui/gui_internal.h +29 -0
- package/runtime/ext_gui/layout.cpp +6 -597
- package/runtime/ext_gui/layout_flex.cpp +200 -0
- package/runtime/ext_gui/layout_flow.cpp +191 -0
- package/runtime/ext_gui/layout_flow_internal.h +149 -0
- package/runtime/ext_gui/layout_inline.cpp +181 -0
- package/runtime/ext_gui/layout_internal.h +20 -0
- package/runtime/ext_gui/renderer.cpp +15 -679
- package/runtime/ext_gui/renderer_image.cpp +233 -0
- package/runtime/ext_gui/renderer_internal.h +112 -0
- package/runtime/ext_gui/renderer_shaders.h +108 -0
- package/runtime/ext_gui/renderer_text.cpp +342 -0
- package/runtime/ext_gui/style.cpp +12 -660
- package/runtime/ext_gui/style_internal.h +33 -0
- package/runtime/ext_gui/style_print.cpp +113 -0
- package/runtime/ext_gui/style_ua.cpp +98 -0
- package/runtime/ext_gui/style_values.cpp +468 -0
- package/runtime/rt.h +6 -0
- package/runtime/rt_internal.h +4 -0
- package/runtime/xt_builtins.c +1 -0
- package/runtime/xt_containers/access.inc +32 -1
- package/runtime/xt_fetch/buffer.inc +64 -0
- package/runtime/xt_fetch/headers.inc +312 -0
- package/runtime/xt_fetch/request.inc +386 -0
- package/runtime/xt_fetch/response.inc +107 -0
- package/runtime/xt_fetch/socket.inc +72 -0
- package/runtime/xt_fetch/url.inc +108 -0
- package/runtime/xt_fetch.c +39 -0
- package/runtime/xt_stdlib2/set.inc +1 -1
- package/runtime/xt_typed_array.c +614 -0
- package/src/cli/main.ts +137 -29
- package/src/codegen/generator/calls/access.ts +9 -1
- package/src/codegen/generator/state.ts +20 -0
- package/src/codegen/generator/tables.ts +40 -0
- package/src/driver/bundler/commonjs.ts +553 -0
- package/src/driver/bundler/graph.ts +14 -0
- package/src/driver/bundler/merge.ts +15 -0
- package/src/driver/bundler/types.ts +8 -0
- package/src/driver/compiler.ts +83 -5
- package/src/driver/config.ts +173 -0
- package/src/driver/icon.ts +184 -0
- package/src/driver/index.ts +30 -0
- package/src/driver/mac-bundle.ts +156 -0
- package/src/driver/win-icon.ts +96 -0
- package/src/parser/statements.ts +17 -4
- package/dist/tests/e2e/gui.test.d.ts +0 -12
- package/dist/tests/e2e/gui.test.js +0 -702
- package/dist/tests/e2e/gui.test.js.map +0 -1
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/* -- Headers -------------------------------------------------------------- */
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* A Headers object is an ordinary JS object carrying the case-insensitive
|
|
5
|
+
* header map under `__map` (lower-cased name -> value) plus a `__set-cookie`
|
|
6
|
+
* array so `getSetCookie()` can preserve duplicate cookies. Methods are stored
|
|
7
|
+
* as own closure properties, so `headers.get(...)` dispatches through the
|
|
8
|
+
* generic `xt_call_method` path with the Headers object as `this`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
static xt_value xt_headers_map(xt_value headers) {
|
|
12
|
+
xt_value map = xt_object_get_cstr(headers, "__map");
|
|
13
|
+
if (!XT_IS_OBJECT(map)) {
|
|
14
|
+
map = xt_object_new();
|
|
15
|
+
xt_object_set(headers, xt_string_from_cstr("__map"), map);
|
|
16
|
+
}
|
|
17
|
+
return map;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static xt_value xt_headers_cookies(xt_value headers) {
|
|
21
|
+
xt_value cookies = xt_object_get_cstr(headers, "__set-cookie");
|
|
22
|
+
if (!XT_IS_ARRAY(cookies)) {
|
|
23
|
+
cookies = xt_array_new(0, NULL);
|
|
24
|
+
xt_object_set(headers, xt_string_from_cstr("__set-cookie"), cookies);
|
|
25
|
+
}
|
|
26
|
+
return cookies;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static void xt_headers_append_raw(xt_value headers, const char *name, const char *value) {
|
|
30
|
+
char key[256];
|
|
31
|
+
xt_fetch_lowercase(name, key, sizeof(key));
|
|
32
|
+
if (strcmp(key, "set-cookie") == 0) {
|
|
33
|
+
xt_array_push(xt_headers_cookies(headers), xt_string_from_cstr(value));
|
|
34
|
+
}
|
|
35
|
+
xt_value map = xt_headers_map(headers);
|
|
36
|
+
xt_value keyValue = xt_string_from_cstr(key);
|
|
37
|
+
xt_value existing = xt_object_get(map, keyValue);
|
|
38
|
+
if (XT_IS_STRING(existing)) {
|
|
39
|
+
const char *previous = xt_string_data(existing);
|
|
40
|
+
size_t total = strlen(previous) + 2 + strlen(value);
|
|
41
|
+
char *joined = (char *)malloc(total + 1);
|
|
42
|
+
if (!joined) abort();
|
|
43
|
+
snprintf(joined, total + 1, "%s, %s", previous, value);
|
|
44
|
+
xt_object_set(map, keyValue, xt_string_from_cstr(joined));
|
|
45
|
+
free(joined);
|
|
46
|
+
} else {
|
|
47
|
+
xt_object_set(map, keyValue, xt_string_from_cstr(value));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static void xt_headers_set_raw(xt_value headers, const char *name, const char *value) {
|
|
52
|
+
char key[256];
|
|
53
|
+
xt_fetch_lowercase(name, key, sizeof(key));
|
|
54
|
+
if (strcmp(key, "set-cookie") == 0) {
|
|
55
|
+
xt_value cookies = xt_headers_cookies(headers);
|
|
56
|
+
xt_as_array(cookies)->length = 0;
|
|
57
|
+
xt_array_push(cookies, xt_string_from_cstr(value));
|
|
58
|
+
}
|
|
59
|
+
xt_object_set(xt_headers_map(headers), xt_string_from_cstr(key), xt_string_from_cstr(value));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static const char *xt_headers_lookup(xt_value headers, const char *name) {
|
|
63
|
+
char key[256];
|
|
64
|
+
xt_fetch_lowercase(name, key, sizeof(key));
|
|
65
|
+
xt_value value = xt_object_get(xt_headers_map(headers), xt_string_from_cstr(key));
|
|
66
|
+
return XT_IS_STRING(value) ? xt_string_data(value) : NULL;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static xt_value xt_headers_get_value(xt_value headers, const char *name) {
|
|
70
|
+
char key[256];
|
|
71
|
+
xt_fetch_lowercase(name, key, sizeof(key));
|
|
72
|
+
xt_value value = xt_object_get(xt_headers_map(headers), xt_string_from_cstr(key));
|
|
73
|
+
return value == XT_UNDEFINED ? XT_NULL : value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static void xt_fetch_set_method(xt_value object, const char *name, void *fn) {
|
|
77
|
+
xt_object_set(object, xt_string_from_cstr(name), xt_closure_new(fn, 0, NULL));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* -- Headers methods ------------------------------------------------------ */
|
|
81
|
+
|
|
82
|
+
static xt_value xt_headers_method_get(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
|
|
83
|
+
(void)env;
|
|
84
|
+
xt_value name = xt_to_string(xt_arg_at(argc, argv, 0));
|
|
85
|
+
const char *text = xt_string_data(name);
|
|
86
|
+
if (!text) return XT_NULL;
|
|
87
|
+
return xt_headers_get_value(thisValue, text);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static xt_value xt_headers_method_has(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
|
|
91
|
+
(void)env;
|
|
92
|
+
xt_value name = xt_to_string(xt_arg_at(argc, argv, 0));
|
|
93
|
+
const char *text = xt_string_data(name);
|
|
94
|
+
if (!text) return XT_FALSE;
|
|
95
|
+
return xt_headers_lookup(thisValue, text) != NULL ? XT_TRUE : XT_FALSE;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static xt_value xt_headers_method_set(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
|
|
99
|
+
(void)env;
|
|
100
|
+
const char *name = xt_string_data(xt_to_string(xt_arg_at(argc, argv, 0)));
|
|
101
|
+
const char *value = xt_string_data(xt_to_string(xt_arg_at(argc, argv, 1)));
|
|
102
|
+
if (name && value) xt_headers_set_raw(thisValue, name, value);
|
|
103
|
+
return XT_UNDEFINED;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static xt_value xt_headers_method_append(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
|
|
107
|
+
(void)env;
|
|
108
|
+
const char *name = xt_string_data(xt_to_string(xt_arg_at(argc, argv, 0)));
|
|
109
|
+
const char *value = xt_string_data(xt_to_string(xt_arg_at(argc, argv, 1)));
|
|
110
|
+
if (name && value) xt_headers_append_raw(thisValue, name, value);
|
|
111
|
+
return XT_UNDEFINED;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static xt_value xt_headers_method_delete(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
|
|
115
|
+
(void)env;
|
|
116
|
+
const char *name = xt_string_data(xt_to_string(xt_arg_at(argc, argv, 0)));
|
|
117
|
+
if (!name) return XT_UNDEFINED;
|
|
118
|
+
char key[256];
|
|
119
|
+
xt_fetch_lowercase(name, key, sizeof(key));
|
|
120
|
+
xt_delete(xt_headers_map(thisValue), xt_string_from_cstr(key));
|
|
121
|
+
return XT_UNDEFINED;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static xt_value xt_headers_method_forEach(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
|
|
125
|
+
(void)env;
|
|
126
|
+
xt_value callback = xt_arg_at(argc, argv, 0);
|
|
127
|
+
if (!XT_IS_FUNCTION(callback)) return XT_UNDEFINED;
|
|
128
|
+
xt_value thisArg = xt_arg_at(argc, argv, 1);
|
|
129
|
+
xt_value keys = xt_object_keys(xt_headers_map(thisValue));
|
|
130
|
+
int32_t count = (int32_t)xt_to_number(xt_array_length(keys));
|
|
131
|
+
for (int32_t i = 0; i < count; i++) {
|
|
132
|
+
xt_value key = xt_array_get(keys, xt_number((double)i));
|
|
133
|
+
xt_value args[3];
|
|
134
|
+
args[0] = xt_object_get(xt_headers_map(thisValue), key);
|
|
135
|
+
args[1] = key;
|
|
136
|
+
args[2] = thisValue;
|
|
137
|
+
xt_call_with_this(callback, thisArg, 3, args);
|
|
138
|
+
}
|
|
139
|
+
return XT_UNDEFINED;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static xt_value xt_headers_method_keys(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
|
|
143
|
+
(void)env;
|
|
144
|
+
(void)argc;
|
|
145
|
+
(void)argv;
|
|
146
|
+
return xt_object_keys(xt_headers_map(thisValue));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static xt_value xt_headers_method_values(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
|
|
150
|
+
(void)env;
|
|
151
|
+
(void)argc;
|
|
152
|
+
(void)argv;
|
|
153
|
+
return xt_object_values(xt_headers_map(thisValue));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
static xt_value xt_headers_method_entries(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
|
|
157
|
+
(void)env;
|
|
158
|
+
(void)argc;
|
|
159
|
+
(void)argv;
|
|
160
|
+
xt_value keys = xt_object_keys(xt_headers_map(thisValue));
|
|
161
|
+
int32_t count = (int32_t)xt_to_number(xt_array_length(keys));
|
|
162
|
+
xt_value entries = xt_array_new(0, NULL);
|
|
163
|
+
for (int32_t i = 0; i < count; i++) {
|
|
164
|
+
xt_value key = xt_array_get(keys, xt_number((double)i));
|
|
165
|
+
xt_value pair[2];
|
|
166
|
+
pair[0] = key;
|
|
167
|
+
pair[1] = xt_object_get(xt_headers_map(thisValue), key);
|
|
168
|
+
xt_array_push(entries, xt_array_new(2, pair));
|
|
169
|
+
}
|
|
170
|
+
return entries;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
static xt_value xt_headers_method_get_set_cookie(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
|
|
174
|
+
(void)env;
|
|
175
|
+
(void)argc;
|
|
176
|
+
(void)argv;
|
|
177
|
+
xt_value cookies = xt_object_get_cstr(thisValue, "__set-cookie");
|
|
178
|
+
return XT_IS_ARRAY(cookies) ? cookies : xt_array_new(0, NULL);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
static xt_value xt_headers_method_to_string(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
|
|
182
|
+
(void)env;
|
|
183
|
+
(void)argc;
|
|
184
|
+
(void)argv;
|
|
185
|
+
xt_value keys = xt_object_keys(xt_headers_map(thisValue));
|
|
186
|
+
int32_t count = (int32_t)xt_to_number(xt_array_length(keys));
|
|
187
|
+
xt_fetch_buffer out;
|
|
188
|
+
xt_fetch_buffer_init(&out);
|
|
189
|
+
for (int32_t i = 0; i < count; i++) {
|
|
190
|
+
xt_value key = xt_array_get(keys, xt_number((double)i));
|
|
191
|
+
xt_value value = xt_object_get(xt_headers_map(thisValue), key);
|
|
192
|
+
const char *name = xt_string_data(key);
|
|
193
|
+
const char *text = xt_string_data(xt_to_string(value));
|
|
194
|
+
if (!name || !text) continue;
|
|
195
|
+
xt_fetch_buffer_append_cstr(&out, name);
|
|
196
|
+
xt_fetch_buffer_append_cstr(&out, ": ");
|
|
197
|
+
xt_fetch_buffer_append_cstr(&out, text);
|
|
198
|
+
xt_fetch_buffer_append_cstr(&out, "\r\n");
|
|
199
|
+
}
|
|
200
|
+
xt_value result = xt_string_new(out.data ? out.data : "", out.length);
|
|
201
|
+
xt_fetch_buffer_free(&out);
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* -- construction --------------------------------------------------------- */
|
|
206
|
+
|
|
207
|
+
static xt_value xt_headers_new(void) {
|
|
208
|
+
xt_value headers = xt_object_new();
|
|
209
|
+
xt_object_set(headers, xt_string_from_cstr("__map"), xt_object_new());
|
|
210
|
+
xt_fetch_set_method(headers, "get", (void *)xt_headers_method_get);
|
|
211
|
+
xt_fetch_set_method(headers, "has", (void *)xt_headers_method_has);
|
|
212
|
+
xt_fetch_set_method(headers, "set", (void *)xt_headers_method_set);
|
|
213
|
+
xt_fetch_set_method(headers, "append", (void *)xt_headers_method_append);
|
|
214
|
+
xt_fetch_set_method(headers, "delete", (void *)xt_headers_method_delete);
|
|
215
|
+
xt_fetch_set_method(headers, "forEach", (void *)xt_headers_method_forEach);
|
|
216
|
+
xt_fetch_set_method(headers, "keys", (void *)xt_headers_method_keys);
|
|
217
|
+
xt_fetch_set_method(headers, "values", (void *)xt_headers_method_values);
|
|
218
|
+
xt_fetch_set_method(headers, "entries", (void *)xt_headers_method_entries);
|
|
219
|
+
xt_fetch_set_method(headers, "getSetCookie", (void *)xt_headers_method_get_set_cookie);
|
|
220
|
+
xt_fetch_set_method(headers, "toString", (void *)xt_headers_method_to_string);
|
|
221
|
+
return headers;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Normalise a `headers` init value (plain object or Headers) into a Headers. */
|
|
225
|
+
static xt_value xt_headers_from_source(xt_value source) {
|
|
226
|
+
xt_value headers = xt_headers_new();
|
|
227
|
+
if (source == XT_UNDEFINED || source == XT_NULL) return headers;
|
|
228
|
+
if (XT_IS_ARRAY(source)) {
|
|
229
|
+
int32_t count = (int32_t)xt_to_number(xt_array_length(source));
|
|
230
|
+
for (int32_t i = 0; i < count; i++) {
|
|
231
|
+
xt_value pair = xt_array_get(source, xt_number((double)i));
|
|
232
|
+
if (!XT_IS_ARRAY(pair) || xt_to_number(xt_array_length(pair)) < 2) continue;
|
|
233
|
+
const char *name = xt_string_data(xt_to_string(xt_array_get(pair, xt_number(0))));
|
|
234
|
+
const char *text = xt_string_data(xt_to_string(xt_array_get(pair, xt_number(1))));
|
|
235
|
+
if (name && text) xt_headers_append_raw(headers, name, text);
|
|
236
|
+
}
|
|
237
|
+
return headers;
|
|
238
|
+
}
|
|
239
|
+
if (!XT_IS_OBJECT(source)) return headers;
|
|
240
|
+
|
|
241
|
+
xt_value map = xt_object_get_cstr(source, "__map");
|
|
242
|
+
if (XT_IS_OBJECT(map)) {
|
|
243
|
+
xt_value keys = xt_object_keys(map);
|
|
244
|
+
int32_t count = (int32_t)xt_to_number(xt_array_length(keys));
|
|
245
|
+
for (int32_t i = 0; i < count; i++) {
|
|
246
|
+
xt_value key = xt_array_get(keys, xt_number((double)i));
|
|
247
|
+
xt_value value = xt_object_get(map, key);
|
|
248
|
+
const char *name = xt_string_data(key);
|
|
249
|
+
const char *text = xt_string_data(xt_to_string(value));
|
|
250
|
+
if (name && text) xt_headers_append_raw(headers, name, text);
|
|
251
|
+
}
|
|
252
|
+
return headers;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
xt_value keys = xt_object_keys(source);
|
|
256
|
+
int32_t count = (int32_t)xt_to_number(xt_array_length(keys));
|
|
257
|
+
for (int32_t i = 0; i < count; i++) {
|
|
258
|
+
xt_value key = xt_array_get(keys, xt_number((double)i));
|
|
259
|
+
const char *name = xt_string_data(key);
|
|
260
|
+
if (!name || strncmp(name, "__", 2) == 0) continue;
|
|
261
|
+
xt_value value = xt_object_get(source, key);
|
|
262
|
+
if (XT_IS_FUNCTION(value)) continue;
|
|
263
|
+
if (XT_IS_ARRAY(value)) {
|
|
264
|
+
xt_value items = value;
|
|
265
|
+
int32_t itemCount = (int32_t)xt_to_number(xt_array_length(items));
|
|
266
|
+
for (int32_t j = 0; j < itemCount; j++) {
|
|
267
|
+
const char *text = xt_string_data(xt_to_string(xt_array_get(items, xt_number((double)j))));
|
|
268
|
+
if (text) xt_headers_append_raw(headers, name, text);
|
|
269
|
+
}
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const char *text = xt_string_data(xt_to_string(value));
|
|
273
|
+
if (text) xt_headers_append_raw(headers, name, text);
|
|
274
|
+
}
|
|
275
|
+
return headers;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* -- wire parsing --------------------------------------------------------- */
|
|
279
|
+
|
|
280
|
+
/** Parse the header block `[start, start+length)` (status line already read). */
|
|
281
|
+
static void xt_headers_parse_block(xt_value headers, const char *start, size_t length) {
|
|
282
|
+
size_t i = 0;
|
|
283
|
+
while (i < length) {
|
|
284
|
+
size_t lineEnd = i;
|
|
285
|
+
while (lineEnd + 1 < length && !(start[lineEnd] == '\r' && start[lineEnd + 1] == '\n')) lineEnd++;
|
|
286
|
+
if (lineEnd + 1 >= length) break;
|
|
287
|
+
const char *colon = (const char *)memchr(start + i, ':', lineEnd - i);
|
|
288
|
+
if (colon) {
|
|
289
|
+
const char *name = start + i;
|
|
290
|
+
const char *nameEnd = colon;
|
|
291
|
+
while (nameEnd > name && (nameEnd[-1] == ' ' || nameEnd[-1] == '\t')) nameEnd--;
|
|
292
|
+
const char *value = colon + 1;
|
|
293
|
+
while (value < start + lineEnd && (*value == ' ' || *value == '\t')) value++;
|
|
294
|
+
const char *valueEnd = start + lineEnd;
|
|
295
|
+
while (valueEnd > value && (valueEnd[-1] == ' ' || valueEnd[-1] == '\t')) valueEnd--;
|
|
296
|
+
if (nameEnd > name) {
|
|
297
|
+
char nameBuffer[256];
|
|
298
|
+
size_t nameLength = (size_t)(nameEnd - name);
|
|
299
|
+
if (nameLength >= sizeof(nameBuffer)) nameLength = sizeof(nameBuffer) - 1;
|
|
300
|
+
memcpy(nameBuffer, name, nameLength);
|
|
301
|
+
nameBuffer[nameLength] = '\0';
|
|
302
|
+
char valueBuffer[8192];
|
|
303
|
+
size_t valueLength = (size_t)(valueEnd - value);
|
|
304
|
+
if (valueLength >= sizeof(valueBuffer)) valueLength = sizeof(valueBuffer) - 1;
|
|
305
|
+
memcpy(valueBuffer, value, valueLength);
|
|
306
|
+
valueBuffer[valueLength] = '\0';
|
|
307
|
+
xt_headers_append_raw(headers, nameBuffer, valueBuffer);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
i = lineEnd + 2;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/* -- request / response exchange ------------------------------------------ */
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* The actual `fetch` implementation. Because the runtime has no event loop,
|
|
5
|
+
* this is a synchronous, blocking HTTP/1.1 exchange; the promise it returns is
|
|
6
|
+
* already settled, which is all the current `await` machinery needs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Index just past the blank line that terminates the response headers. */
|
|
10
|
+
static int xt_fetch_head_end(const xt_fetch_buffer *raw) {
|
|
11
|
+
for (size_t i = 0; i + 3 < raw->length; i++) {
|
|
12
|
+
if (raw->data[i] == '\r' && raw->data[i + 1] == '\n' && raw->data[i + 2] == '\r' && raw->data[i + 3] == '\n') {
|
|
13
|
+
return (int)(i + 4);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return -1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static int xt_fetch_hex_digit(char c) {
|
|
20
|
+
if (c >= '0' && c <= '9') return c - '0';
|
|
21
|
+
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
22
|
+
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
23
|
+
return -1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 1 when the chunked body is complete, 0 when more bytes are needed, -1 on a
|
|
27
|
+
* malformed encoding. `decoded` accumulates the total decoded body length. */
|
|
28
|
+
static int xt_fetch_chunked_complete(const char *data, size_t length, size_t offset, size_t *decoded) {
|
|
29
|
+
size_t pos = offset;
|
|
30
|
+
*decoded = 0;
|
|
31
|
+
for (;;) {
|
|
32
|
+
if (pos >= length) return 0;
|
|
33
|
+
size_t lineEnd = pos;
|
|
34
|
+
while (lineEnd + 1 < length && !(data[lineEnd] == '\r' && data[lineEnd + 1] == '\n')) lineEnd++;
|
|
35
|
+
if (lineEnd + 1 >= length) return 0;
|
|
36
|
+
size_t size = 0;
|
|
37
|
+
int any = 0;
|
|
38
|
+
for (size_t k = pos; k < lineEnd; k++) {
|
|
39
|
+
int digit = xt_fetch_hex_digit(data[k]);
|
|
40
|
+
if (digit < 0) break;
|
|
41
|
+
size = size * 16 + (size_t)digit;
|
|
42
|
+
any = 1;
|
|
43
|
+
if (size > ((size_t)1 << 30)) return -1;
|
|
44
|
+
}
|
|
45
|
+
if (!any) return -1;
|
|
46
|
+
size_t dataStart = lineEnd + 2;
|
|
47
|
+
if (size == 0) {
|
|
48
|
+
if (dataStart + 1 < length && data[dataStart] == '\r' && data[dataStart + 1] == '\n') return 1;
|
|
49
|
+
for (size_t i = dataStart; i + 3 < length; i++) {
|
|
50
|
+
if (data[i] == '\r' && data[i + 1] == '\n' && data[i + 2] == '\r' && data[i + 3] == '\n') return 1;
|
|
51
|
+
}
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
if (dataStart + size + 1 >= length) return 0;
|
|
55
|
+
if (data[dataStart + size] != '\r' || data[dataStart + size + 1] != '\n') return -1;
|
|
56
|
+
*decoded += size;
|
|
57
|
+
pos = dataStart + size + 2;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Copy the decoded payload of a complete chunked body into `out`. */
|
|
62
|
+
static void xt_fetch_decode_chunked(const char *data, size_t length, size_t offset, xt_fetch_buffer *out) {
|
|
63
|
+
size_t pos = offset;
|
|
64
|
+
for (;;) {
|
|
65
|
+
size_t lineEnd = pos;
|
|
66
|
+
while (lineEnd + 1 < length && !(data[lineEnd] == '\r' && data[lineEnd + 1] == '\n')) lineEnd++;
|
|
67
|
+
size_t size = 0;
|
|
68
|
+
for (size_t k = pos; k < lineEnd; k++) {
|
|
69
|
+
int digit = xt_fetch_hex_digit(data[k]);
|
|
70
|
+
if (digit < 0) break;
|
|
71
|
+
size = size * 16 + (size_t)digit;
|
|
72
|
+
}
|
|
73
|
+
size_t dataStart = lineEnd + 2;
|
|
74
|
+
if (size == 0) return;
|
|
75
|
+
if (dataStart + size <= length) xt_fetch_buffer_append(out, data + dataStart, size);
|
|
76
|
+
pos = dataStart + size + 2;
|
|
77
|
+
if (pos >= length) return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static int xt_fetch_name_equals(const char *name, size_t length, const char *literal) {
|
|
82
|
+
size_t end = length;
|
|
83
|
+
while (end > 0 && (name[end - 1] == ' ' || name[end - 1] == '\t')) end--;
|
|
84
|
+
if (end != strlen(literal)) return 0;
|
|
85
|
+
for (size_t i = 0; i < end; i++) {
|
|
86
|
+
char a = name[i];
|
|
87
|
+
char b = literal[i];
|
|
88
|
+
if (a >= 'A' && a <= 'Z') a = (char)(a + 32);
|
|
89
|
+
if (b >= 'A' && b <= 'Z') b = (char)(b + 32);
|
|
90
|
+
if (a != b) return 0;
|
|
91
|
+
}
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Pull `Content-Length` / `Transfer-Encoding` out of the raw header block. */
|
|
96
|
+
static void xt_fetch_scan_headers(const char *data, size_t start, size_t end, size_t *contentLength,
|
|
97
|
+
int *hasContentLength, int *chunked) {
|
|
98
|
+
*contentLength = 0;
|
|
99
|
+
*hasContentLength = 0;
|
|
100
|
+
*chunked = 0;
|
|
101
|
+
size_t i = start;
|
|
102
|
+
while (i < end) {
|
|
103
|
+
size_t lineEnd = i;
|
|
104
|
+
while (lineEnd + 1 < end && !(data[lineEnd] == '\r' && data[lineEnd + 1] == '\n')) lineEnd++;
|
|
105
|
+
if (lineEnd + 1 > end) break;
|
|
106
|
+
const char *colon = (const char *)memchr(data + i, ':', lineEnd - i);
|
|
107
|
+
if (colon) {
|
|
108
|
+
size_t nameLength = (size_t)(colon - (data + i));
|
|
109
|
+
const char *value = colon + 1;
|
|
110
|
+
const char *valueEnd = data + lineEnd;
|
|
111
|
+
while (value < valueEnd && (*value == ' ' || *value == '\t')) value++;
|
|
112
|
+
if (xt_fetch_name_equals(data + i, nameLength, "content-length")) {
|
|
113
|
+
size_t parsed = 0;
|
|
114
|
+
for (const char *p = value; p < valueEnd && *p >= '0' && *p <= '9'; p++) parsed = parsed * 10 + (size_t)(*p - '0');
|
|
115
|
+
*contentLength = parsed;
|
|
116
|
+
*hasContentLength = 1;
|
|
117
|
+
} else if (xt_fetch_name_equals(data + i, nameLength, "transfer-encoding")) {
|
|
118
|
+
char lower[128];
|
|
119
|
+
size_t n = (size_t)(valueEnd - value);
|
|
120
|
+
if (n >= sizeof(lower)) n = sizeof(lower) - 1;
|
|
121
|
+
for (size_t k = 0; k < n; k++) {
|
|
122
|
+
char c = value[k];
|
|
123
|
+
lower[k] = (c >= 'A' && c <= 'Z') ? (char)(c + 32) : c;
|
|
124
|
+
}
|
|
125
|
+
lower[n] = '\0';
|
|
126
|
+
if (strstr(lower, "chunked")) *chunked = 1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (lineEnd + 2 <= i) break;
|
|
130
|
+
i = lineEnd + 2;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Parse `HTTP/1.1 200 OK`; `headerStart` receives the offset of the headers. */
|
|
135
|
+
static int xt_fetch_parse_status_line(const char *data, size_t length, int *status, char *text, size_t textSize,
|
|
136
|
+
size_t *headerStart) {
|
|
137
|
+
size_t lineEnd = 0;
|
|
138
|
+
while (lineEnd + 1 < length && !(data[lineEnd] == '\r' && data[lineEnd + 1] == '\n')) lineEnd++;
|
|
139
|
+
if (lineEnd + 1 >= length) return -1;
|
|
140
|
+
*headerStart = lineEnd + 2;
|
|
141
|
+
size_t i = 0;
|
|
142
|
+
while (i < lineEnd && data[i] != ' ') i++;
|
|
143
|
+
if (i >= lineEnd) return -1;
|
|
144
|
+
i++;
|
|
145
|
+
int code = 0;
|
|
146
|
+
size_t digits = 0;
|
|
147
|
+
while (i < lineEnd && data[i] >= '0' && data[i] <= '9') {
|
|
148
|
+
code = code * 10 + (data[i] - '0');
|
|
149
|
+
i++;
|
|
150
|
+
digits++;
|
|
151
|
+
}
|
|
152
|
+
if (digits < 3) return -1;
|
|
153
|
+
while (i < lineEnd && data[i] == ' ') i++;
|
|
154
|
+
size_t j = 0;
|
|
155
|
+
while (i < lineEnd && j + 1 < textSize) text[j++] = data[i++];
|
|
156
|
+
text[j] = '\0';
|
|
157
|
+
*status = code;
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
static int xt_fetch_method_has_body(const char *method) {
|
|
162
|
+
return strcmp(method, "GET") != 0 && strcmp(method, "HEAD") != 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
static void xt_fetch_build_request(const char *method, const xt_fetch_url *url, xt_value headers,
|
|
166
|
+
const xt_fetch_buffer *body, xt_fetch_buffer *out) {
|
|
167
|
+
xt_fetch_buffer_append_cstr(out, method);
|
|
168
|
+
xt_fetch_buffer_append_cstr(out, " ");
|
|
169
|
+
xt_fetch_buffer_append_cstr(out, url->path);
|
|
170
|
+
xt_fetch_buffer_append_cstr(out, " HTTP/1.1\r\n");
|
|
171
|
+
|
|
172
|
+
char hostHeader[300];
|
|
173
|
+
int defaultPort = url->secure ? 443 : 80;
|
|
174
|
+
if (atoi(url->port) == defaultPort) {
|
|
175
|
+
snprintf(hostHeader, sizeof(hostHeader), "%s", url->host);
|
|
176
|
+
} else {
|
|
177
|
+
snprintf(hostHeader, sizeof(hostHeader), "%s:%s", url->host, url->port);
|
|
178
|
+
}
|
|
179
|
+
if (!xt_headers_lookup(headers, "host")) xt_headers_set_raw(headers, "host", hostHeader);
|
|
180
|
+
if (!xt_headers_lookup(headers, "connection")) xt_headers_set_raw(headers, "connection", "close");
|
|
181
|
+
if (!xt_headers_lookup(headers, "accept")) xt_headers_set_raw(headers, "accept", "*/*");
|
|
182
|
+
if (!xt_headers_lookup(headers, "accept-encoding")) xt_headers_set_raw(headers, "accept-encoding", "identity");
|
|
183
|
+
if (!xt_headers_lookup(headers, "user-agent")) xt_headers_set_raw(headers, "user-agent", "xbintsc-fetch");
|
|
184
|
+
if (!xt_headers_lookup(headers, "content-length") && (body || xt_fetch_method_has_body(method))) {
|
|
185
|
+
char buffer[32];
|
|
186
|
+
snprintf(buffer, sizeof(buffer), "%zu", body ? body->length : (size_t)0);
|
|
187
|
+
xt_headers_set_raw(headers, "content-length", buffer);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
xt_value map = xt_headers_map(headers);
|
|
191
|
+
xt_value keys = xt_object_keys(map);
|
|
192
|
+
int32_t count = (int32_t)xt_to_number(xt_array_length(keys));
|
|
193
|
+
for (int32_t i = 0; i < count; i++) {
|
|
194
|
+
xt_value key = xt_array_get(keys, xt_number((double)i));
|
|
195
|
+
const char *name = xt_string_data(key);
|
|
196
|
+
const char *value = xt_string_data(xt_to_string(xt_object_get(map, key)));
|
|
197
|
+
if (!name || !value) continue;
|
|
198
|
+
xt_fetch_buffer_append_cstr(out, name);
|
|
199
|
+
xt_fetch_buffer_append_cstr(out, ": ");
|
|
200
|
+
xt_fetch_buffer_append_cstr(out, value);
|
|
201
|
+
xt_fetch_buffer_append_cstr(out, "\r\n");
|
|
202
|
+
}
|
|
203
|
+
xt_fetch_buffer_append_cstr(out, "\r\n");
|
|
204
|
+
if (body && body->length > 0) xt_fetch_buffer_append(out, body->data, body->length);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
static xt_value xt_fetch_reject(const char *message) {
|
|
208
|
+
xt_value argument = xt_string_from_cstr(message);
|
|
209
|
+
return xt_promise_reject(xt_type_error_ctor(1, &argument));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
xt_value xt_fetch(int32_t argc, xt_value *argv) {
|
|
213
|
+
xt_value input = xt_arg_at(argc, argv, 0);
|
|
214
|
+
xt_value init = xt_arg_at(argc, argv, 1);
|
|
215
|
+
if (input == XT_UNDEFINED || input == XT_NULL) return xt_fetch_reject("Failed to parse URL from undefined");
|
|
216
|
+
|
|
217
|
+
xt_value urlValue = input;
|
|
218
|
+
if (XT_IS_OBJECT(input)) {
|
|
219
|
+
xt_value candidate = xt_object_get_cstr(input, "url");
|
|
220
|
+
if (XT_IS_STRING(candidate)) urlValue = candidate;
|
|
221
|
+
}
|
|
222
|
+
const char *urlText = xt_string_data(xt_to_string(urlValue));
|
|
223
|
+
if (!urlText) return xt_fetch_reject("Failed to parse URL");
|
|
224
|
+
|
|
225
|
+
char currentUrl[4096];
|
|
226
|
+
snprintf(currentUrl, sizeof(currentUrl), "%s", urlText);
|
|
227
|
+
|
|
228
|
+
char method[16];
|
|
229
|
+
snprintf(method, sizeof(method), "GET");
|
|
230
|
+
xt_value headersSource = XT_UNDEFINED;
|
|
231
|
+
xt_value bodySource = XT_UNDEFINED;
|
|
232
|
+
const char *redirectMode = "follow";
|
|
233
|
+
if (XT_IS_OBJECT(init)) {
|
|
234
|
+
xt_value methodValue = xt_object_get_cstr(init, "method");
|
|
235
|
+
if (XT_IS_STRING(methodValue)) {
|
|
236
|
+
const char *text = xt_string_data(methodValue);
|
|
237
|
+
size_t i = 0;
|
|
238
|
+
for (; text && text[i] && i + 1 < sizeof(method); i++) {
|
|
239
|
+
char c = text[i];
|
|
240
|
+
method[i] = (c >= 'a' && c <= 'z') ? (char)(c - 32) : c;
|
|
241
|
+
}
|
|
242
|
+
method[i] = '\0';
|
|
243
|
+
}
|
|
244
|
+
headersSource = xt_object_get_cstr(init, "headers");
|
|
245
|
+
bodySource = xt_object_get_cstr(init, "body");
|
|
246
|
+
xt_value redirectValue = xt_object_get_cstr(init, "redirect");
|
|
247
|
+
if (XT_IS_STRING(redirectValue)) redirectMode = xt_string_data(redirectValue);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
xt_fetch_buffer body;
|
|
251
|
+
xt_fetch_buffer_init(&body);
|
|
252
|
+
if (bodySource != XT_UNDEFINED && bodySource != XT_NULL) {
|
|
253
|
+
xt_value text = xt_to_string(bodySource);
|
|
254
|
+
const char *data = xt_string_data(text);
|
|
255
|
+
if (data) xt_fetch_buffer_append(&body, data, (size_t)xt_string_length_value(text));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (int redirects = 0; redirects <= 20; redirects++) {
|
|
259
|
+
xt_fetch_url parsed;
|
|
260
|
+
if (xt_fetch_parse_url(currentUrl, &parsed) != 0) {
|
|
261
|
+
xt_fetch_buffer_free(&body);
|
|
262
|
+
return xt_fetch_reject("Failed to parse URL");
|
|
263
|
+
}
|
|
264
|
+
if (parsed.secure) {
|
|
265
|
+
xt_fetch_buffer_free(&body);
|
|
266
|
+
return xt_fetch_reject("fetch failed: https is not supported");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
int fd = xt_fetch_connect(&parsed);
|
|
270
|
+
if (fd < 0) {
|
|
271
|
+
xt_fetch_buffer_free(&body);
|
|
272
|
+
return xt_fetch_reject("fetch failed");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
xt_value requestHeaders = xt_headers_from_source(headersSource);
|
|
276
|
+
xt_fetch_buffer request;
|
|
277
|
+
xt_fetch_buffer_init(&request);
|
|
278
|
+
xt_fetch_build_request(method, &parsed, requestHeaders, body.length > 0 ? &body : NULL, &request);
|
|
279
|
+
if (xt_fetch_send_all(fd, request.data, request.length) != 0) {
|
|
280
|
+
xt_fetch_close_socket(fd);
|
|
281
|
+
xt_fetch_buffer_free(&request);
|
|
282
|
+
xt_fetch_buffer_free(&body);
|
|
283
|
+
return xt_fetch_reject("fetch failed");
|
|
284
|
+
}
|
|
285
|
+
xt_fetch_buffer_free(&request);
|
|
286
|
+
|
|
287
|
+
xt_fetch_buffer raw;
|
|
288
|
+
xt_fetch_buffer_init(&raw);
|
|
289
|
+
char chunk[16384];
|
|
290
|
+
for (;;) {
|
|
291
|
+
int headEnd = xt_fetch_head_end(&raw);
|
|
292
|
+
if (headEnd > 0) {
|
|
293
|
+
int status = 0;
|
|
294
|
+
char statusText[256];
|
|
295
|
+
size_t headerStart = 0;
|
|
296
|
+
if (xt_fetch_parse_status_line(raw.data, raw.length, &status, statusText, sizeof(statusText), &headerStart) == 0) {
|
|
297
|
+
int stop = 0;
|
|
298
|
+
if (status == 204 || status == 304 || (status >= 100 && status < 200) || strcmp(method, "HEAD") == 0) {
|
|
299
|
+
stop = 1;
|
|
300
|
+
} else {
|
|
301
|
+
size_t contentLength = 0;
|
|
302
|
+
int hasContentLength = 0;
|
|
303
|
+
int chunked = 0;
|
|
304
|
+
xt_fetch_scan_headers(raw.data, headerStart, (size_t)headEnd - 2, &contentLength, &hasContentLength, &chunked);
|
|
305
|
+
if (chunked) {
|
|
306
|
+
size_t decoded = 0;
|
|
307
|
+
if (xt_fetch_chunked_complete(raw.data, raw.length, (size_t)headEnd, &decoded) == 1) stop = 1;
|
|
308
|
+
} else if (hasContentLength && raw.length - (size_t)headEnd >= contentLength) {
|
|
309
|
+
stop = 1;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (stop) break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
int count = xt_fetch_recv(fd, chunk, sizeof(chunk));
|
|
316
|
+
if (count > 0) {
|
|
317
|
+
xt_fetch_buffer_append(&raw, chunk, (size_t)count);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
xt_fetch_close_socket(fd);
|
|
323
|
+
|
|
324
|
+
int headEnd = xt_fetch_head_end(&raw);
|
|
325
|
+
int status = 0;
|
|
326
|
+
char statusText[256];
|
|
327
|
+
size_t headerStart = 0;
|
|
328
|
+
if (headEnd <= 0 ||
|
|
329
|
+
xt_fetch_parse_status_line(raw.data, raw.length, &status, statusText, sizeof(statusText), &headerStart) != 0) {
|
|
330
|
+
xt_fetch_buffer_free(&raw);
|
|
331
|
+
xt_fetch_buffer_free(&body);
|
|
332
|
+
return xt_fetch_reject("fetch failed: invalid response");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
xt_value responseHeaders = xt_headers_new();
|
|
336
|
+
xt_headers_parse_block(responseHeaders, raw.data + headerStart, (size_t)headEnd - 2 - headerStart);
|
|
337
|
+
|
|
338
|
+
int isRedirect = status == 301 || status == 302 || status == 303 || status == 307 || status == 308;
|
|
339
|
+
if (isRedirect && strcmp(redirectMode, "error") == 0) {
|
|
340
|
+
xt_fetch_buffer_free(&raw);
|
|
341
|
+
xt_fetch_buffer_free(&body);
|
|
342
|
+
return xt_fetch_reject("fetch failed: redirect not allowed");
|
|
343
|
+
}
|
|
344
|
+
if (isRedirect && strcmp(redirectMode, "manual") != 0 && redirects < 20) {
|
|
345
|
+
const char *location = xt_headers_lookup(responseHeaders, "location");
|
|
346
|
+
char resolved[4096];
|
|
347
|
+
if (location && xt_fetch_resolve_location(&parsed, location, resolved, sizeof(resolved)) == 0) {
|
|
348
|
+
if (status == 303 || ((status == 301 || status == 302) && strcmp(method, "POST") == 0)) {
|
|
349
|
+
snprintf(method, sizeof(method), "GET");
|
|
350
|
+
body.length = 0;
|
|
351
|
+
}
|
|
352
|
+
snprintf(currentUrl, sizeof(currentUrl), "%s", resolved);
|
|
353
|
+
xt_fetch_buffer_free(&raw);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
size_t contentLength = 0;
|
|
359
|
+
int hasContentLength = 0;
|
|
360
|
+
int chunked = 0;
|
|
361
|
+
xt_fetch_scan_headers(raw.data, headerStart, (size_t)headEnd - 2, &contentLength, &hasContentLength, &chunked);
|
|
362
|
+
xt_fetch_buffer responseBody;
|
|
363
|
+
xt_fetch_buffer_init(&responseBody);
|
|
364
|
+
if (status == 204 || status == 304 || (status >= 100 && status < 200) || strcmp(method, "HEAD") == 0) {
|
|
365
|
+
/* No body by definition. */
|
|
366
|
+
} else if (chunked) {
|
|
367
|
+
xt_fetch_decode_chunked(raw.data, raw.length, (size_t)headEnd, &responseBody);
|
|
368
|
+
} else if (hasContentLength) {
|
|
369
|
+
size_t available = raw.length - (size_t)headEnd;
|
|
370
|
+
size_t take = contentLength < available ? contentLength : available;
|
|
371
|
+
xt_fetch_buffer_append(&responseBody, raw.data + headEnd, take);
|
|
372
|
+
} else {
|
|
373
|
+
xt_fetch_buffer_append(&responseBody, raw.data + headEnd, raw.length - (size_t)headEnd);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
xt_value response = xt_response_new(responseHeaders, status, statusText, currentUrl,
|
|
377
|
+
responseBody.data ? responseBody.data : "", responseBody.length);
|
|
378
|
+
xt_fetch_buffer_free(&responseBody);
|
|
379
|
+
xt_fetch_buffer_free(&raw);
|
|
380
|
+
xt_fetch_buffer_free(&body);
|
|
381
|
+
return xt_promise_resolve(response);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
xt_fetch_buffer_free(&body);
|
|
385
|
+
return xt_fetch_reject("fetch failed: too many redirects");
|
|
386
|
+
}
|