xbintsc 0.3.18 → 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.
Files changed (194) hide show
  1. package/README.md +33 -0
  2. package/dist/src/cli/main.js +124 -30
  3. package/dist/src/cli/main.js.map +1 -1
  4. package/dist/src/codegen/generator/calls/access.js +8 -1
  5. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  6. package/dist/src/codegen/generator/state.js +20 -0
  7. package/dist/src/codegen/generator/state.js.map +1 -1
  8. package/dist/src/codegen/generator/tables.d.ts +6 -0
  9. package/dist/src/codegen/generator/tables.js +38 -0
  10. package/dist/src/codegen/generator/tables.js.map +1 -1
  11. package/dist/src/driver/bundler/commonjs.d.ts +52 -0
  12. package/dist/src/driver/bundler/commonjs.js +493 -0
  13. package/dist/src/driver/bundler/commonjs.js.map +1 -0
  14. package/dist/src/driver/bundler/graph.d.ts +2 -1
  15. package/dist/src/driver/bundler/graph.js +32 -3
  16. package/dist/src/driver/bundler/graph.js.map +1 -1
  17. package/dist/src/driver/bundler/merge.d.ts +2 -1
  18. package/dist/src/driver/bundler/merge.js +16 -2
  19. package/dist/src/driver/bundler/merge.js.map +1 -1
  20. package/dist/src/driver/bundler/types.d.ts +8 -0
  21. package/dist/src/driver/compiler.d.ts +8 -1
  22. package/dist/src/driver/compiler.js +75 -7
  23. package/dist/src/driver/compiler.js.map +1 -1
  24. package/dist/src/driver/config.d.ts +53 -0
  25. package/dist/src/driver/config.js +141 -0
  26. package/dist/src/driver/config.js.map +1 -0
  27. package/dist/src/driver/icon.d.ts +62 -0
  28. package/dist/src/driver/icon.js +151 -0
  29. package/dist/src/driver/icon.js.map +1 -0
  30. package/dist/src/driver/index.d.ts +4 -0
  31. package/dist/src/driver/index.js +4 -0
  32. package/dist/src/driver/index.js.map +1 -1
  33. package/dist/src/driver/mac-bundle.d.ts +41 -0
  34. package/dist/src/driver/mac-bundle.js +123 -0
  35. package/dist/src/driver/mac-bundle.js.map +1 -0
  36. package/dist/src/driver/win-icon.d.ts +27 -0
  37. package/dist/src/driver/win-icon.js +80 -0
  38. package/dist/src/driver/win-icon.js.map +1 -0
  39. package/dist/src/extensions/gui/html.d.ts +25 -0
  40. package/dist/src/extensions/gui/html.js +203 -0
  41. package/dist/src/extensions/gui/html.js.map +1 -0
  42. package/dist/src/extensions/gui/index.js +11 -0
  43. package/dist/src/extensions/gui/index.js.map +1 -1
  44. package/dist/src/extensions/registry.d.ts +29 -0
  45. package/dist/src/extensions/registry.js +14 -0
  46. package/dist/src/extensions/registry.js.map +1 -1
  47. package/dist/src/index.d.ts +1 -1
  48. package/dist/src/index.js.map +1 -1
  49. package/dist/src/lexer/scanner/identifiers.js +4 -12
  50. package/dist/src/lexer/scanner/identifiers.js.map +1 -1
  51. package/dist/src/parser/statements.js +17 -4
  52. package/dist/src/parser/statements.js.map +1 -1
  53. package/dist/tests/cli/main.test.js +46 -1
  54. package/dist/tests/cli/main.test.js.map +1 -1
  55. package/dist/tests/driver/bundler-resolve.test.d.ts +1 -0
  56. package/dist/tests/driver/bundler-resolve.test.js +230 -0
  57. package/dist/tests/driver/bundler-resolve.test.js.map +1 -0
  58. package/dist/tests/driver/config.test.d.ts +1 -0
  59. package/dist/tests/driver/config.test.js +124 -0
  60. package/dist/tests/driver/config.test.js.map +1 -0
  61. package/dist/tests/driver/icon.test.d.ts +1 -0
  62. package/dist/tests/driver/icon.test.js +421 -0
  63. package/dist/tests/driver/icon.test.js.map +1 -0
  64. package/dist/tests/driver/modules.test.js +177 -0
  65. package/dist/tests/driver/modules.test.js.map +1 -1
  66. package/dist/tests/e2e/fetch.test.d.ts +12 -0
  67. package/dist/tests/e2e/fetch.test.js +272 -0
  68. package/dist/tests/e2e/fetch.test.js.map +1 -0
  69. package/dist/tests/e2e/gui-dom.test.d.ts +5 -0
  70. package/dist/tests/e2e/gui-dom.test.js +102 -0
  71. package/dist/tests/e2e/gui-dom.test.js.map +1 -0
  72. package/dist/tests/e2e/gui-events.test.d.ts +5 -0
  73. package/dist/tests/e2e/gui-events.test.js +60 -0
  74. package/dist/tests/e2e/gui-events.test.js.map +1 -0
  75. package/dist/tests/e2e/gui-helpers.d.ts +31 -0
  76. package/dist/tests/e2e/gui-helpers.js +74 -0
  77. package/dist/tests/e2e/gui-helpers.js.map +1 -0
  78. package/dist/tests/e2e/gui-layout.test.d.ts +4 -0
  79. package/dist/tests/e2e/gui-layout.test.js +88 -0
  80. package/dist/tests/e2e/gui-layout.test.js.map +1 -0
  81. package/dist/tests/e2e/gui-lifecycle.test.d.ts +5 -0
  82. package/dist/tests/e2e/gui-lifecycle.test.js +58 -0
  83. package/dist/tests/e2e/gui-lifecycle.test.js.map +1 -0
  84. package/dist/tests/e2e/gui-paint.test.d.ts +5 -0
  85. package/dist/tests/e2e/gui-paint.test.js +111 -0
  86. package/dist/tests/e2e/gui-paint.test.js.map +1 -0
  87. package/dist/tests/e2e/gui-script.test.d.ts +5 -0
  88. package/dist/tests/e2e/gui-script.test.js +78 -0
  89. package/dist/tests/e2e/gui-script.test.js.map +1 -0
  90. package/dist/tests/e2e/gui-style.test.d.ts +5 -0
  91. package/dist/tests/e2e/gui-style.test.js +175 -0
  92. package/dist/tests/e2e/gui-style.test.js.map +1 -0
  93. package/dist/tests/e2e/icon.test.d.ts +7 -0
  94. package/dist/tests/e2e/icon.test.js +80 -0
  95. package/dist/tests/e2e/icon.test.js.map +1 -0
  96. package/dist/tests/e2e/npm-modules.test.d.ts +4 -1
  97. package/dist/tests/e2e/npm-modules.test.js +47 -1
  98. package/dist/tests/e2e/npm-modules.test.js.map +1 -1
  99. package/dist/tests/e2e/runtime-coverage-typed-array.test.d.ts +9 -0
  100. package/dist/tests/e2e/runtime-coverage-typed-array.test.js +140 -0
  101. package/dist/tests/e2e/runtime-coverage-typed-array.test.js.map +1 -0
  102. package/dist/tests/extensions/gui-index.test.d.ts +1 -0
  103. package/dist/tests/extensions/gui-index.test.js +49 -0
  104. package/dist/tests/extensions/gui-index.test.js.map +1 -0
  105. package/dist/tests/extensions/gui.test.d.ts +1 -0
  106. package/dist/tests/extensions/gui.test.js +177 -0
  107. package/dist/tests/extensions/gui.test.js.map +1 -0
  108. package/dist/tests/extensions/registry.test.js +36 -0
  109. package/dist/tests/extensions/registry.test.js.map +1 -1
  110. package/dist/tests/parser/statements.test.js +11 -0
  111. package/dist/tests/parser/statements.test.js.map +1 -1
  112. package/dist/tests/public-api.test.d.ts +1 -0
  113. package/dist/tests/public-api.test.js +31 -0
  114. package/dist/tests/public-api.test.js.map +1 -0
  115. package/package.json +1 -1
  116. package/runtime/ext_gui/css.cpp +4 -517
  117. package/runtime/ext_gui/css_internal.h +27 -0
  118. package/runtime/ext_gui/css_match.cpp +195 -0
  119. package/runtime/ext_gui/css_selector.cpp +207 -0
  120. package/runtime/ext_gui/css_sheet.cpp +129 -0
  121. package/runtime/ext_gui/document.cpp +90 -0
  122. package/runtime/ext_gui/document.h +23 -0
  123. package/runtime/ext_gui/dom.cpp +43 -403
  124. package/runtime/ext_gui/dom.h +7 -0
  125. package/runtime/ext_gui/dom_api.cpp +164 -0
  126. package/runtime/ext_gui/dom_api.h +12 -0
  127. package/runtime/ext_gui/dom_api_document.cpp +96 -0
  128. package/runtime/ext_gui/dom_api_events.cpp +123 -0
  129. package/runtime/ext_gui/dom_api_internal.h +135 -0
  130. package/runtime/ext_gui/dom_api_node.cpp +329 -0
  131. package/runtime/ext_gui/dom_api_node_read.cpp +294 -0
  132. package/runtime/ext_gui/dom_api_proto.cpp +130 -0
  133. package/runtime/ext_gui/dom_api_style.cpp +192 -0
  134. package/runtime/ext_gui/dom_internal.h +39 -0
  135. package/runtime/ext_gui/dom_parse.cpp +348 -0
  136. package/runtime/ext_gui/dom_serialize.cpp +54 -0
  137. package/runtime/ext_gui/gui.cpp +61 -245
  138. package/runtime/ext_gui/gui_engine.h +54 -0
  139. package/runtime/ext_gui/gui_events.cpp +294 -0
  140. package/runtime/ext_gui/gui_internal.h +29 -0
  141. package/runtime/ext_gui/layout.cpp +6 -597
  142. package/runtime/ext_gui/layout_flex.cpp +200 -0
  143. package/runtime/ext_gui/layout_flow.cpp +191 -0
  144. package/runtime/ext_gui/layout_flow_internal.h +149 -0
  145. package/runtime/ext_gui/layout_inline.cpp +181 -0
  146. package/runtime/ext_gui/layout_internal.h +20 -0
  147. package/runtime/ext_gui/renderer.cpp +15 -679
  148. package/runtime/ext_gui/renderer_image.cpp +233 -0
  149. package/runtime/ext_gui/renderer_internal.h +112 -0
  150. package/runtime/ext_gui/renderer_shaders.h +108 -0
  151. package/runtime/ext_gui/renderer_text.cpp +342 -0
  152. package/runtime/ext_gui/script.cpp +63 -0
  153. package/runtime/ext_gui/style.cpp +12 -660
  154. package/runtime/ext_gui/style_internal.h +33 -0
  155. package/runtime/ext_gui/style_print.cpp +113 -0
  156. package/runtime/ext_gui/style_ua.cpp +98 -0
  157. package/runtime/ext_gui/style_values.cpp +468 -0
  158. package/runtime/ext_gui/window.cpp +60 -0
  159. package/runtime/rt.h +6 -0
  160. package/runtime/rt_internal.h +4 -0
  161. package/runtime/xt_builtins.c +1 -0
  162. package/runtime/xt_containers/access.inc +32 -1
  163. package/runtime/xt_fetch/buffer.inc +64 -0
  164. package/runtime/xt_fetch/headers.inc +312 -0
  165. package/runtime/xt_fetch/request.inc +386 -0
  166. package/runtime/xt_fetch/response.inc +107 -0
  167. package/runtime/xt_fetch/socket.inc +72 -0
  168. package/runtime/xt_fetch/url.inc +108 -0
  169. package/runtime/xt_fetch.c +39 -0
  170. package/runtime/xt_stdlib2/set.inc +1 -1
  171. package/runtime/xt_typed_array.c +614 -0
  172. package/src/cli/main.ts +137 -29
  173. package/src/codegen/generator/calls/access.ts +9 -1
  174. package/src/codegen/generator/state.ts +20 -0
  175. package/src/codegen/generator/tables.ts +40 -0
  176. package/src/driver/bundler/commonjs.ts +553 -0
  177. package/src/driver/bundler/graph.ts +35 -2
  178. package/src/driver/bundler/merge.ts +18 -1
  179. package/src/driver/bundler/types.ts +8 -0
  180. package/src/driver/compiler.ts +85 -7
  181. package/src/driver/config.ts +173 -0
  182. package/src/driver/icon.ts +184 -0
  183. package/src/driver/index.ts +30 -0
  184. package/src/driver/mac-bundle.ts +156 -0
  185. package/src/driver/win-icon.ts +96 -0
  186. package/src/extensions/gui/html.ts +236 -0
  187. package/src/extensions/gui/index.ts +11 -0
  188. package/src/extensions/registry.ts +39 -0
  189. package/src/index.ts +1 -1
  190. package/src/lexer/scanner/identifiers.ts +4 -11
  191. package/src/parser/statements.ts +17 -4
  192. package/dist/tests/e2e/gui.test.d.ts +0 -12
  193. package/dist/tests/e2e/gui.test.js +0 -493
  194. package/dist/tests/e2e/gui.test.js.map +0 -1
@@ -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
+ }
@@ -0,0 +1,107 @@
1
+ /* -- Response ------------------------------------------------------------- */
2
+
3
+ /*
4
+ * `fetch` resolves with a plain object shaped like a `Response`: the status
5
+ * fields are own properties, `headers` is a Headers object, and the body is
6
+ * kept verbatim under `__body`. The body consumers (`text` / `json` /
7
+ * `arrayBuffer` / `bytes`) return promises, matching the platform API even
8
+ * though the value is already available.
9
+ */
10
+
11
+ static void *xt_response_method_impl(xt_value name);
12
+ static xt_value xt_method_value_for_response(xt_value name);
13
+
14
+ static xt_value xt_response_body(xt_value response) {
15
+ xt_value body = xt_object_get_cstr(response, "__body");
16
+ return XT_IS_STRING(body) ? body : xt_string_from_cstr("");
17
+ }
18
+
19
+ static xt_value xt_response_method_text(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
20
+ (void)env;
21
+ (void)argc;
22
+ (void)argv;
23
+ return xt_promise_resolve(xt_response_body(thisValue));
24
+ }
25
+
26
+ static xt_value xt_response_method_json(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
27
+ (void)env;
28
+ (void)argc;
29
+ (void)argv;
30
+ xt_value body = xt_response_body(thisValue);
31
+ return xt_promise_resolve(xt_json_parse(1, &body));
32
+ }
33
+
34
+ static xt_value xt_response_method_array_buffer(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
35
+ (void)env;
36
+ (void)argc;
37
+ (void)argv;
38
+ xt_value body = xt_response_body(thisValue);
39
+ const char *data = xt_string_data(body);
40
+ int32_t length = xt_string_length_value(body);
41
+ if (!data || length < 0) length = 0;
42
+ xt_value bytes = xt_array_new(0, NULL);
43
+ for (int32_t i = 0; i < length; i++) {
44
+ xt_array_push(bytes, xt_number((double)(unsigned char)data[i]));
45
+ }
46
+ return xt_promise_resolve(bytes);
47
+ }
48
+
49
+ static xt_value xt_response_method_clone(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
50
+ (void)env;
51
+ (void)argc;
52
+ (void)argv;
53
+ xt_value clone = xt_object_new();
54
+ xt_value keys = xt_object_keys(thisValue);
55
+ int32_t count = (int32_t)xt_to_number(xt_array_length(keys));
56
+ for (int32_t i = 0; i < count; i++) {
57
+ xt_value key = xt_array_get(keys, xt_number((double)i));
58
+ xt_value value = xt_object_get(thisValue, key);
59
+ if (XT_IS_FUNCTION(value)) {
60
+ /* Re-use the method implementation with the new receiver. */
61
+ xt_value name = key;
62
+ xt_object_set(clone, name, xt_method_value_for_response(name));
63
+ } else {
64
+ xt_object_set(clone, key, value);
65
+ }
66
+ }
67
+ return clone;
68
+ }
69
+
70
+ /* Map a Response method name back to its implementation so `clone()` can copy
71
+ * the methods without duplicating the dispatch table. */
72
+ static void *xt_response_method_impl(xt_value name) {
73
+ const char *method = xt_string_data(name);
74
+ if (!method) return NULL;
75
+ if (strcmp(method, "text") == 0) return (void *)xt_response_method_text;
76
+ if (strcmp(method, "json") == 0) return (void *)xt_response_method_json;
77
+ if (strcmp(method, "arrayBuffer") == 0) return (void *)xt_response_method_array_buffer;
78
+ if (strcmp(method, "bytes") == 0) return (void *)xt_response_method_array_buffer;
79
+ if (strcmp(method, "clone") == 0) return (void *)xt_response_method_clone;
80
+ return NULL;
81
+ }
82
+
83
+ static xt_value xt_method_value_for_response(xt_value name) {
84
+ void *impl = xt_response_method_impl(name);
85
+ return impl ? xt_closure_new(impl, 0, NULL) : XT_UNDEFINED;
86
+ }
87
+
88
+ static xt_value xt_response_new(xt_value headers, int status, const char *statusText, const char *url,
89
+ const char *body, size_t bodyLength) {
90
+ xt_value response = xt_object_new();
91
+ xt_object_set(response, xt_string_from_cstr("ok"),
92
+ (status >= 200 && status < 300) ? XT_TRUE : XT_FALSE);
93
+ xt_object_set(response, xt_string_from_cstr("status"), xt_number((double)status));
94
+ xt_object_set(response, xt_string_from_cstr("statusText"), xt_string_from_cstr(statusText ? statusText : ""));
95
+ xt_object_set(response, xt_string_from_cstr("url"), xt_string_from_cstr(url ? url : ""));
96
+ xt_object_set(response, xt_string_from_cstr("redirected"), XT_FALSE);
97
+ xt_object_set(response, xt_string_from_cstr("type"), xt_string_from_cstr("basic"));
98
+ xt_object_set(response, xt_string_from_cstr("bodyUsed"), XT_FALSE);
99
+ xt_object_set(response, xt_string_from_cstr("headers"), headers);
100
+ xt_object_set(response, xt_string_from_cstr("__body"), xt_string_new(body ? body : "", bodyLength));
101
+ xt_fetch_set_method(response, "text", (void *)xt_response_method_text);
102
+ xt_fetch_set_method(response, "json", (void *)xt_response_method_json);
103
+ xt_fetch_set_method(response, "arrayBuffer", (void *)xt_response_method_array_buffer);
104
+ xt_fetch_set_method(response, "bytes", (void *)xt_response_method_array_buffer);
105
+ xt_fetch_set_method(response, "clone", (void *)xt_response_method_clone);
106
+ return response;
107
+ }
@@ -0,0 +1,72 @@
1
+ /* -- TCP socket helpers --------------------------------------------------- */
2
+
3
+ /*
4
+ * `fetch` performs a conventional blocking request/response exchange: connect,
5
+ * write the request, read until the response is complete (or the peer closes).
6
+ * The socket carries a receive timeout so a stalled server cannot hang the
7
+ * program forever; the timeout surfaces as a rejected promise.
8
+ */
9
+ #define XT_FETCH_TIMEOUT_SECONDS 30
10
+
11
+ static void xt_fetch_set_timeouts(int fd) {
12
+ #if defined(_WIN32)
13
+ DWORD ms = (DWORD)XT_FETCH_TIMEOUT_SECONDS * 1000;
14
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&ms, (int)sizeof(ms));
15
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&ms, (int)sizeof(ms));
16
+ #else
17
+ struct timeval timeout;
18
+ timeout.tv_sec = XT_FETCH_TIMEOUT_SECONDS;
19
+ timeout.tv_usec = 0;
20
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
21
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
22
+ #endif
23
+ }
24
+
25
+ static int xt_fetch_connect(const xt_fetch_url *url) {
26
+ struct addrinfo hints;
27
+ memset(&hints, 0, sizeof(hints));
28
+ hints.ai_family = AF_UNSPEC;
29
+ hints.ai_socktype = SOCK_STREAM;
30
+ struct addrinfo *result = NULL;
31
+ if (getaddrinfo(url->host, url->port, &hints, &result) != 0) return -1;
32
+ int fd = -1;
33
+ for (struct addrinfo *info = result; info; info = info->ai_next) {
34
+ int candidate = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
35
+ if (candidate < 0) continue;
36
+ if (connect(candidate, info->ai_addr, (xt_fetch_socklen_t)info->ai_addrlen) == 0) {
37
+ fd = candidate;
38
+ break;
39
+ }
40
+ xt_fetch_close_socket(candidate);
41
+ }
42
+ freeaddrinfo(result);
43
+ if (fd >= 0) xt_fetch_set_timeouts(fd);
44
+ return fd;
45
+ }
46
+
47
+ static int xt_fetch_send_all(int fd, const char *data, size_t length) {
48
+ size_t sent = 0;
49
+ while (sent < length) {
50
+ #if defined(_WIN32)
51
+ int written = send(fd, data + sent, (int)(length - sent), 0);
52
+ #else
53
+ ssize_t written = send(fd, data + sent, length - sent, 0);
54
+ #endif
55
+ if (written > 0) {
56
+ sent += (size_t)written;
57
+ continue;
58
+ }
59
+ return -1;
60
+ }
61
+ return 0;
62
+ }
63
+
64
+ /** Receive one chunk; returns the byte count, `0` on close, `-1` on error. */
65
+ static int xt_fetch_recv(int fd, char *buffer, size_t capacity) {
66
+ #if defined(_WIN32)
67
+ return recv(fd, buffer, (int)capacity, 0);
68
+ #else
69
+ ssize_t count = recv(fd, buffer, capacity, 0);
70
+ return count > 2147483647 ? 2147483647 : (int)count;
71
+ #endif
72
+ }
@@ -0,0 +1,108 @@
1
+ /* -- URL parsing ---------------------------------------------------------- */
2
+
3
+ /*
4
+ * `fetch` accepts absolute `http://` / `https://` URLs (there is no document
5
+ * base to resolve relative ones against). The parsed pieces are kept in a
6
+ * fixed-size struct because `fetch` hands them straight to `getaddrinfo`.
7
+ */
8
+ typedef struct {
9
+ int secure;
10
+ char host[256];
11
+ char port[8];
12
+ char path[4096];
13
+ } xt_fetch_url;
14
+
15
+ /** Parse `url` into `out`. Returns 0 on success, -1 on a malformed URL. */
16
+ static int xt_fetch_parse_url(const char *url, xt_fetch_url *out) {
17
+ if (!url) return -1;
18
+ out->secure = 0;
19
+ const char *cursor = url;
20
+ if (strncmp(cursor, "http://", 7) == 0) {
21
+ cursor += 7;
22
+ snprintf(out->port, sizeof(out->port), "80");
23
+ } else if (strncmp(cursor, "https://", 8) == 0) {
24
+ cursor += 8;
25
+ out->secure = 1;
26
+ snprintf(out->port, sizeof(out->port), "443");
27
+ } else {
28
+ return -1;
29
+ }
30
+
31
+ /* The authority ends at the first `/`, `?` or `#` (or the end of the URL). */
32
+ const char *authorityEnd = cursor;
33
+ while (*authorityEnd && *authorityEnd != '/' && *authorityEnd != '?' && *authorityEnd != '#') authorityEnd++;
34
+
35
+ /* Drop any `user:password@` prefix. */
36
+ const char *hostStart = cursor;
37
+ for (const char *p = cursor; p < authorityEnd; p++) {
38
+ if (*p == '@') hostStart = p + 1;
39
+ }
40
+
41
+ const char *hostEnd = authorityEnd;
42
+ const char *portStart = NULL;
43
+ if (hostStart < authorityEnd && *hostStart == '[') {
44
+ /* IPv6 literal: `[::1]:8080`. */
45
+ const char *close = (const char *)memchr(hostStart, ']', (size_t)(authorityEnd - hostStart));
46
+ if (!close) return -1;
47
+ hostEnd = close;
48
+ if (close + 1 < authorityEnd && close[1] == ':') portStart = close + 2;
49
+ hostStart++;
50
+ } else {
51
+ const char *colon = (const char *)memchr(hostStart, ':', (size_t)(authorityEnd - hostStart));
52
+ if (colon) {
53
+ hostEnd = colon;
54
+ portStart = colon + 1;
55
+ }
56
+ }
57
+
58
+ size_t hostLength = (size_t)(hostEnd - hostStart);
59
+ if (hostLength == 0 || hostLength >= sizeof(out->host)) return -1;
60
+ memcpy(out->host, hostStart, hostLength);
61
+ out->host[hostLength] = '\0';
62
+
63
+ if (portStart && portStart < authorityEnd) {
64
+ size_t portLength = (size_t)(authorityEnd - portStart);
65
+ if (portLength >= sizeof(out->port)) return -1;
66
+ memcpy(out->port, portStart, portLength);
67
+ out->port[portLength] = '\0';
68
+ for (const char *p = out->port; *p; p++) {
69
+ if (*p < '0' || *p > '9') return -1;
70
+ }
71
+ }
72
+
73
+ /* The request target keeps the query string but drops the fragment. */
74
+ if (*authorityEnd == '\0' || *authorityEnd == '#') {
75
+ snprintf(out->path, sizeof(out->path), "/");
76
+ } else {
77
+ const char *pathEnd = authorityEnd;
78
+ while (*pathEnd && *pathEnd != '#') pathEnd++;
79
+ size_t pathLength = (size_t)(pathEnd - authorityEnd);
80
+ if (pathLength >= sizeof(out->path)) return -1;
81
+ memcpy(out->path, authorityEnd, pathLength);
82
+ out->path[pathLength] = '\0';
83
+ }
84
+ return 0;
85
+ }
86
+
87
+ /*
88
+ * Resolve a `Location` header against the URL just requested. Only absolute
89
+ * URLs and root-relative paths are supported; anything else is reported as a
90
+ * failure so the caller can return the 3xx response unchanged.
91
+ */
92
+ static int xt_fetch_resolve_location(const xt_fetch_url *base, const char *location, char *out, size_t size) {
93
+ if (strncmp(location, "http://", 7) == 0 || strncmp(location, "https://", 8) == 0) {
94
+ snprintf(out, size, "%s", location);
95
+ return 0;
96
+ }
97
+ const char *scheme = base->secure ? "https" : "http";
98
+ if (location[0] == '/') {
99
+ int defaultPort = base->secure ? 443 : 80;
100
+ if (atoi(base->port) == defaultPort) {
101
+ snprintf(out, size, "%s://%s%s", scheme, base->host, location);
102
+ } else {
103
+ snprintf(out, size, "%s://%s:%s%s", scheme, base->host, base->port, location);
104
+ }
105
+ return 0;
106
+ }
107
+ return -1;
108
+ }