quickwin 1.3.0 → 2.1.0

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 (167) hide show
  1. package/README.en.md +18 -24
  2. package/README.md +17 -23
  3. package/examples/PdfCanvas.js +8 -37
  4. package/examples/PdfCanvas.tsx +11 -41
  5. package/examples/exec_server.js +20155 -1420
  6. package/examples/exec_server.tsx +489 -0
  7. package/examples/exec_server_worker.js +383 -33
  8. package/examples/exec_server_worker.ts +408 -30
  9. package/examples/pdf_preview2.js +25 -82
  10. package/examples/pdf_preview2.ts +27 -84
  11. package/examples/pdf_viewer.js +1424 -1180
  12. package/examples/pdf_viewer.tsx +16 -67
  13. package/examples/setres.js +22 -47
  14. package/examples/setres.ts +23 -57
  15. package/examples/test_gallery.js +1418 -1075
  16. package/examples/test_react_lazy.js +1391 -1059
  17. package/examples/test_react_listbox.js +1390 -1058
  18. package/examples/test_root_window.js +1384 -1052
  19. package/examples/test_tab.js +1391 -1059
  20. package/lib/abort.js +70 -0
  21. package/lib/abort.ts +69 -0
  22. package/lib/certs.js +219 -0
  23. package/lib/certs.ts +229 -0
  24. package/lib/exec-log.js +15 -0
  25. package/lib/exec-log.ts +30 -0
  26. package/lib/fetch.js +88 -7
  27. package/lib/fetch.ts +73 -7
  28. package/lib/ffi/bind.js +441 -0
  29. package/lib/ffi/bind.ts +539 -0
  30. package/lib/ffi/ctype.js +194 -0
  31. package/lib/ffi/ctype.ts +369 -0
  32. package/lib/ffi/index.js +6 -0
  33. package/lib/ffi/index.ts +6 -0
  34. package/lib/ffi/struct.js +490 -0
  35. package/lib/ffi/struct.ts +753 -0
  36. package/lib/http-common.js +43 -5
  37. package/lib/http-common.ts +48 -6
  38. package/lib/http-server.js +187 -29
  39. package/lib/http-server.ts +177 -26
  40. package/lib/react-qw/components/DateTimePicker.js +18 -37
  41. package/lib/react-qw/components/Input.js +3 -10
  42. package/lib/react-qw/components/Link.js +6 -19
  43. package/lib/react-qw/components/ListView.js +50 -154
  44. package/lib/react-qw/components/PathPicker.js +28 -88
  45. package/lib/react-qw/components/ScrollView.js +15 -8
  46. package/lib/react-qw/components/Slider.js +2 -1
  47. package/lib/react-qw/components/Tab.js +6 -22
  48. package/lib/react-qw/components/Tooltip.js +10 -21
  49. package/lib/react-qw/components/TreeView.js +8 -31
  50. package/lib/react-qw/index.js +5 -3
  51. package/lib/react-qw/index.ts +8 -3
  52. package/lib/react-qw/nmhdr.js +3 -24
  53. package/lib/react-qw/nmhdr.ts +5 -30
  54. package/lib/react-qw/props.js +7 -2
  55. package/lib/react-qw/props.ts +7 -3
  56. package/lib/react-qw/reconciler.js +23 -13
  57. package/lib/react-qw/reconciler.ts +22 -13
  58. package/lib/text-measure.js +20 -23
  59. package/lib/text-measure.ts +22 -26
  60. package/lib/websocket.js +44 -4
  61. package/lib/websocket.ts +40 -4
  62. package/lib/windows/advapi32.js +53 -0
  63. package/lib/windows/advapi32.ts +64 -0
  64. package/lib/windows/comctl32.js +18 -0
  65. package/lib/windows/comctl32.ts +19 -0
  66. package/lib/windows/comdlg32.js +8 -0
  67. package/lib/windows/comdlg32.ts +10 -0
  68. package/lib/windows/gdi32.js +392 -0
  69. package/lib/windows/gdi32.ts +420 -0
  70. package/lib/windows/kernel32.js +262 -0
  71. package/lib/windows/kernel32.ts +291 -0
  72. package/lib/windows/msimg32.js +38 -0
  73. package/lib/windows/msimg32.ts +45 -0
  74. package/lib/windows/ntdll.js +9 -0
  75. package/lib/windows/ntdll.ts +11 -0
  76. package/lib/windows/ole32.js +7 -0
  77. package/lib/windows/ole32.ts +9 -0
  78. package/lib/windows/psapi.js +32 -0
  79. package/lib/windows/psapi.ts +37 -0
  80. package/lib/windows/shell32.js +13 -0
  81. package/lib/windows/shell32.ts +16 -0
  82. package/lib/windows/structs.js +519 -0
  83. package/lib/windows/structs.ts +566 -0
  84. package/lib/windows/user32.js +572 -0
  85. package/lib/windows/user32.ts +607 -0
  86. package/lib/windows/version.js +29 -0
  87. package/lib/windows/version.ts +35 -0
  88. package/lib/windows/winspool.js +71 -0
  89. package/lib/windows/winspool.ts +80 -0
  90. package/package.json +1 -1
  91. package/quickwin.d.ts +31 -40
  92. package/quickwin_const.d.ts +137 -0
  93. package/qwin-nowasm-x86.exe +0 -0
  94. package/qwin-nowasm.exe +0 -0
  95. package/qwin-x86.exe +0 -0
  96. package/qwin.exe +0 -0
  97. package/test/bench_ffi_callraw.js +236 -0
  98. package/test/bench_ffi_callraw.ts +236 -0
  99. package/test/bench_ffi_overhead.js +139 -0
  100. package/test/bench_ffi_overhead.ts +141 -0
  101. package/test/bench_sendmessage.js +104 -0
  102. package/test/bench_sendmessage.ts +100 -0
  103. package/test/bench_sendmessage_nonnull.js +187 -0
  104. package/test/bench_sendmessage_nonnull.ts +188 -0
  105. package/test/run.js +19 -4
  106. package/test/run.ts +20 -5
  107. package/test/test_basic_fetch.js +29 -0
  108. package/test/test_basic_fetch.ts +26 -0
  109. package/test/test_ffi.js +27 -45
  110. package/test/test_ffi.ts +35 -54
  111. package/test/test_ffi_abi.js +290 -0
  112. package/test/test_ffi_abi.ts +274 -0
  113. package/test/test_ffi_array.js +116 -0
  114. package/test/test_ffi_array.ts +141 -0
  115. package/test/test_ffi_bind.js +620 -8
  116. package/test/test_ffi_bind.ts +630 -8
  117. package/test/test_ffi_struct.js +632 -88
  118. package/test/test_ffi_struct.ts +666 -91
  119. package/test/test_ffi_struct_layout.js +495 -0
  120. package/test/test_ffi_struct_layout.ts +543 -0
  121. package/test/test_ffi_user32.js +103 -0
  122. package/test/test_ffi_user32.ts +113 -0
  123. package/test/test_ffi_windows.js +101 -0
  124. package/test/test_ffi_windows.ts +113 -0
  125. package/test/test_files.js +102 -0
  126. package/test/test_files.ts +121 -0
  127. package/test/test_gdi_draw.js +681 -0
  128. package/test/test_gdi_draw.ts +725 -0
  129. package/test/test_helper.js +8 -1
  130. package/test/test_helper.ts +9 -1
  131. package/test/test_http_import.js +18 -0
  132. package/test/test_http_import.ts +19 -0
  133. package/test/test_http_server.js +73 -1
  134. package/test/test_http_server.ts +75 -1
  135. package/test/test_ipv6.js +2 -2
  136. package/test/test_ipv6.ts +2 -2
  137. package/test/test_net_fetch.js +74 -11
  138. package/test/test_net_fetch.ts +73 -10
  139. package/test/test_net_websocket.js +1 -1
  140. package/test/test_net_websocket.ts +1 -1
  141. package/test/test_printer.js +137 -0
  142. package/test/test_printer.ts +148 -0
  143. package/test/test_proc.js +141 -0
  144. package/test/test_proc.ts +163 -0
  145. package/test/test_react_complex.js +1401 -1069
  146. package/test/test_react_counter.js +1391 -1059
  147. package/test/test_react_flex.js +1411 -1072
  148. package/test/test_react_hidden.js +1394 -1062
  149. package/test/test_react_listview_link.js +1391 -1059
  150. package/test/test_react_notify.js +18638 -0
  151. package/test/test_react_render.js +1391 -1059
  152. package/test/test_registry.js +74 -0
  153. package/test/test_registry.ts +95 -0
  154. package/test/test_sysinfo.js +122 -0
  155. package/test/test_sysinfo.ts +141 -0
  156. package/test/test_wolfssl_read.js +45 -43
  157. package/test/test_wolfssl_read.ts +42 -40
  158. package/test/test_worker_net.js +35 -17
  159. package/test/test_worker_net.ts +27 -15
  160. package/tsconfig.json +1 -1
  161. package/examples/exec_server.ts +0 -80
  162. package/lib/ffi-bind.js +0 -85
  163. package/lib/ffi-bind.ts +0 -170
  164. package/lib/ffi-struct.js +0 -224
  165. package/lib/ffi-struct.ts +0 -239
  166. package/lib/win-common-structs.js +0 -39
  167. package/lib/win-common-structs.ts +0 -41
package/lib/fetch.js CHANGED
@@ -26,6 +26,27 @@ function parseHeaders(data) {
26
26
  }
27
27
  return { status, statusText, headers };
28
28
  }
29
+ /** True when the chunked body is complete. With a declared `Trailer`,
30
+ * the terminating block is followed by 0..n header lines and a final
31
+ * empty line (`\r\n0\r\n<trailer...>\r\n\r\n`) instead of the bare
32
+ * `\r\n0\r\n\r\n` form. */
33
+ function chunkedEndReached(data, hasTrailer) {
34
+ if (!hasTrailer)
35
+ return _hasChunkedEnd(data);
36
+ const tail = '\r\n0\r\n';
37
+ for (let i = data.length - 1; i >= 0; i--) {
38
+ let j = 0;
39
+ while (j < tail.length && i + j < data.length && data[i + j] === tail.charCodeAt(j))
40
+ j++;
41
+ if (j === tail.length) {
42
+ const after = i + tail.length;
43
+ if (data.length >= after + 2 &&
44
+ data[data.length - 2] === 0x0D && data[data.length - 1] === 0x0A)
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ }
29
50
  // ── State machine constants ──
30
51
  const ST_CONNECTING = 0;
31
52
  const ST_HANDSHAKE = 1;
@@ -35,6 +56,9 @@ const ST_DONE = 5;
35
56
  // ── Main fetch request ──
36
57
  async function fetchRequest(req) {
37
58
  const parsedUrl = new URL(req.url);
59
+ if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
60
+ throw new TypeError('fetch: unsupported protocol "' + parsedUrl.protocol + '"');
61
+ }
38
62
  const method = req.method;
39
63
  const headers = new HeadersImpl(req.headers);
40
64
  const timeout = req.timeout || 30000;
@@ -76,6 +100,7 @@ async function fetchRequest(req) {
76
100
  let _controller = null;
77
101
  let headerRaw = new Uint8Array(0);
78
102
  let isChunked = false;
103
+ let hasTrailer = false;
79
104
  let contentLength = 0;
80
105
  let chunkedParts = [];
81
106
  let receivedBytes = 0;
@@ -168,15 +193,25 @@ async function fetchRequest(req) {
168
193
  doReject(new Error('SSL_CTX_new failed'));
169
194
  return;
170
195
  }
171
- wolfssl.wolfSSL_CTX_set_verify(ctx, 0 /* wolfssl.VerifyMode.SSL_VERIFY_NONE */);
196
+ if (req.rejectUnauthorized === false) {
197
+ wolfssl.wolfSSL_CTX_set_verify(ctx, 0 /* wolfssl.VerifyMode.SSL_VERIFY_NONE */);
198
+ }
199
+ else {
200
+ wolfssl.wolfSSL_CTX_set_verify(ctx, 1 /* wolfssl.VerifyMode.SSL_VERIFY_PEER */);
201
+ wolfssl.loadTrustedCerts(ctx);
202
+ }
172
203
  ssl = wolfssl.wolfSSL_new(ctx);
173
204
  if (!ssl) {
174
205
  doReject(new Error('SSL_new failed'));
175
206
  return;
176
207
  }
177
208
  wolfssl.wolfSSL_set_fd(ssl, sock.get_fd(fd));
178
- if (parsedUrl.hostname)
209
+ if (parsedUrl.hostname) {
179
210
  wolfssl.wolfSSL_UseSNI(ssl, 0 /* wolfssl.SniType.WOLFSSL_SNI_HOST_NAME */, parsedUrl.hostname);
211
+ if (req.rejectUnauthorized !== false) {
212
+ wolfssl.wolfSSL_check_domain_name(ssl, parsedUrl.hostname);
213
+ }
214
+ }
180
215
  state = ST_HANDSHAKE;
181
216
  }
182
217
  else {
@@ -209,7 +244,21 @@ async function fetchRequest(req) {
209
244
  break;
210
245
  let data;
211
246
  if (isHTTPS && ssl) {
212
- data = wolfssl.wolfSSL_read(ssl, 8192);
247
+ const buf = new ArrayBuffer(8192);
248
+ const n = wolfssl.wolfSSL_read(ssl, buf);
249
+ if (n > 0) {
250
+ data = buf.slice(0, n);
251
+ }
252
+ else {
253
+ const err = wolfssl.wolfSSL_get_error(ssl, n);
254
+ if (err === 2 /* wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_READ */ ||
255
+ err === 3 /* wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_WRITE */)
256
+ break;
257
+ if (n === 0 || err === 6 /* wolfssl.ErrorCode.WOLFSSL_ERROR_ZERO_RETURN */)
258
+ break;
259
+ doReject(new Error('TLS read failed: ' + err));
260
+ return;
261
+ }
213
262
  }
214
263
  else if (s !== null && s >= 0) {
215
264
  data = sock.recv(s, 8192);
@@ -237,6 +286,7 @@ async function fetchRequest(req) {
237
286
  }
238
287
  const trailingBodyBytes = headerRaw.subarray(headerEnd + 4);
239
288
  isChunked = (parsed.headers.get('transfer-encoding') || '').toLowerCase().includes('chunked');
289
+ hasTrailer = parsed.headers.get('trailer') != null;
240
290
  contentLength = isChunked ? 0 : parseInt(parsed.headers.get('content-length') || '0', 10);
241
291
  _controller = null;
242
292
  stream = new ReadableStream({
@@ -246,7 +296,7 @@ async function fetchRequest(req) {
246
296
  if (trailingBodyBytes.length > 0) {
247
297
  if (isChunked) {
248
298
  chunkedParts = [trailingBodyBytes];
249
- if (_hasChunkedEnd(trailingBodyBytes)) {
299
+ if (chunkedEndReached(trailingBodyBytes, hasTrailer)) {
250
300
  const decoded = decodeChunked(trailingBodyBytes);
251
301
  _controller.enqueue(decoded);
252
302
  _controller.close();
@@ -281,7 +331,31 @@ async function fetchRequest(req) {
281
331
  break;
282
332
  let data;
283
333
  if (isHTTPS && ssl) {
284
- data = wolfssl.wolfSSL_read(ssl, 8192);
334
+ const buf = new ArrayBuffer(8192);
335
+ const n = wolfssl.wolfSSL_read(ssl, buf);
336
+ if (n > 0) {
337
+ data = buf.slice(0, n);
338
+ }
339
+ else {
340
+ const err = wolfssl.wolfSSL_get_error(ssl, n);
341
+ if (err === 2 /* wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_READ */ ||
342
+ err === 3 /* wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_WRITE */)
343
+ break;
344
+ if (n === 0 || err === 6 /* wolfssl.ErrorCode.WOLFSSL_ERROR_ZERO_RETURN */)
345
+ break;
346
+ // Fatal TLS error mid-body: the response already
347
+ // resolved, so fail the body stream instead of
348
+ // silently waiting for an FD event that never comes.
349
+ if (_controller) {
350
+ try {
351
+ _controller.error(new Error('TLS read failed: ' + err));
352
+ }
353
+ catch { /* already closed */ }
354
+ }
355
+ stream = null;
356
+ cleanupSocket();
357
+ return;
358
+ }
285
359
  }
286
360
  else if (s !== null && s >= 0) {
287
361
  data = sock.recv(s, 8192);
@@ -294,7 +368,7 @@ async function fetchRequest(req) {
294
368
  if (isChunked) {
295
369
  chunkedParts.push(new Uint8Array(data));
296
370
  const combined = _concat(chunkedParts);
297
- if (_hasChunkedEnd(combined)) {
371
+ if (chunkedEndReached(combined, hasTrailer)) {
298
372
  const decoded = decodeChunked(combined);
299
373
  _controller.enqueue(decoded);
300
374
  _controller.close();
@@ -329,7 +403,14 @@ async function fetchRequest(req) {
329
403
  break;
330
404
  let data;
331
405
  if (isHTTPS && ssl) {
332
- data = wolfssl.wolfSSL_read(ssl, 8192);
406
+ const buf = new ArrayBuffer(8192);
407
+ const n = wolfssl.wolfSSL_read(ssl, buf);
408
+ // Best-effort drain: any non-data result (EOF, WANT_*,
409
+ // late error) ends the drain — the stream closes below.
410
+ if (n > 0)
411
+ data = buf.slice(0, n);
412
+ else
413
+ break;
333
414
  }
334
415
  else if (s !== null && s >= 0) {
335
416
  data = sock.recv(s, 8192);
package/lib/fetch.ts CHANGED
@@ -47,6 +47,25 @@ function parseHeaders(data: string): ParsedResponse | null {
47
47
  return { status, statusText, headers }
48
48
  }
49
49
 
50
+ /** True when the chunked body is complete. With a declared `Trailer`,
51
+ * the terminating block is followed by 0..n header lines and a final
52
+ * empty line (`\r\n0\r\n<trailer...>\r\n\r\n`) instead of the bare
53
+ * `\r\n0\r\n\r\n` form. */
54
+ function chunkedEndReached(data: Uint8Array, hasTrailer: boolean): boolean {
55
+ if (!hasTrailer) return _hasChunkedEnd(data)
56
+ const tail = '\r\n0\r\n'
57
+ for (let i = data.length - 1; i >= 0; i--) {
58
+ let j = 0
59
+ while (j < tail.length && i + j < data.length && data[i + j] === tail.charCodeAt(j)) j++
60
+ if (j === tail.length) {
61
+ const after = i + tail.length
62
+ if (data.length >= after + 2 &&
63
+ data[data.length - 2] === 0x0D && data[data.length - 1] === 0x0A) return true
64
+ }
65
+ }
66
+ return false
67
+ }
68
+
50
69
  // ── State machine constants ──
51
70
 
52
71
  const ST_CONNECTING = 0
@@ -59,6 +78,9 @@ const ST_DONE = 5
59
78
 
60
79
  async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
61
80
  const parsedUrl = new URL(req.url)
81
+ if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
82
+ throw new TypeError('fetch: unsupported protocol "' + parsedUrl.protocol + '"')
83
+ }
62
84
  const method = req.method
63
85
  const headers = new HeadersImpl(req.headers)
64
86
  const timeout = req.timeout || 30000
@@ -101,6 +123,7 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
101
123
  let _controller: ReadableStreamDefaultController<Uint8Array> | null = null
102
124
  let headerRaw: Uint8Array = new Uint8Array(0)
103
125
  let isChunked = false
126
+ let hasTrailer = false
104
127
  let contentLength = 0
105
128
  let chunkedParts: Uint8Array[] = []
106
129
  let receivedBytes = 0
@@ -163,11 +186,21 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
163
186
  const tlsMethod = wolfssl.wolfTLSv1_2_client_method()
164
187
  ctx = wolfssl.wolfSSL_CTX_new(tlsMethod)
165
188
  if (!ctx) { doReject(new Error('SSL_CTX_new failed')); return }
166
- wolfssl.wolfSSL_CTX_set_verify(ctx, wolfssl.VerifyMode.SSL_VERIFY_NONE)
189
+ if (req.rejectUnauthorized === false) {
190
+ wolfssl.wolfSSL_CTX_set_verify(ctx, wolfssl.VerifyMode.SSL_VERIFY_NONE)
191
+ } else {
192
+ wolfssl.wolfSSL_CTX_set_verify(ctx, wolfssl.VerifyMode.SSL_VERIFY_PEER)
193
+ wolfssl.loadTrustedCerts(ctx)
194
+ }
167
195
  ssl = wolfssl.wolfSSL_new(ctx)
168
196
  if (!ssl) { doReject(new Error('SSL_new failed')); return }
169
197
  wolfssl.wolfSSL_set_fd(ssl, sock.get_fd(fd))
170
- if (parsedUrl.hostname) wolfssl.wolfSSL_UseSNI(ssl, wolfssl.SniType.WOLFSSL_SNI_HOST_NAME, parsedUrl.hostname)
198
+ if (parsedUrl.hostname) {
199
+ wolfssl.wolfSSL_UseSNI(ssl, wolfssl.SniType.WOLFSSL_SNI_HOST_NAME, parsedUrl.hostname)
200
+ if (req.rejectUnauthorized !== false) {
201
+ wolfssl.wolfSSL_check_domain_name(ssl, parsedUrl.hostname)
202
+ }
203
+ }
171
204
  state = ST_HANDSHAKE
172
205
  } else {
173
206
  sock.send(fd, httpRequest)
@@ -195,7 +228,18 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
195
228
  if (s !== null && s < 0 && !ssl) break
196
229
  let data: ArrayBuffer | null
197
230
  if (isHTTPS && ssl) {
198
- data = wolfssl.wolfSSL_read(ssl, 8192)
231
+ const buf = new ArrayBuffer(8192)
232
+ const n = wolfssl.wolfSSL_read(ssl, buf)
233
+ if (n > 0) {
234
+ data = buf.slice(0, n)
235
+ } else {
236
+ const err = wolfssl.wolfSSL_get_error(ssl, n)
237
+ if (err === wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_READ ||
238
+ err === wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_WRITE) break
239
+ if (n === 0 || err === wolfssl.ErrorCode.WOLFSSL_ERROR_ZERO_RETURN) break
240
+ doReject(new Error('TLS read failed: ' + err))
241
+ return
242
+ }
199
243
  } else if (s !== null && s >= 0) {
200
244
  data = sock.recv(s, 8192)
201
245
  } else { break }
@@ -217,6 +261,7 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
217
261
  const trailingBodyBytes = headerRaw.subarray(headerEnd + 4)
218
262
 
219
263
  isChunked = (parsed.headers.get('transfer-encoding') || '').toLowerCase().includes('chunked')
264
+ hasTrailer = parsed.headers.get('trailer') != null
220
265
  contentLength = isChunked ? 0 : parseInt(
221
266
  parsed.headers.get('content-length') || '0', 10
222
267
  )
@@ -228,7 +273,7 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
228
273
  if (trailingBodyBytes.length > 0) {
229
274
  if (isChunked) {
230
275
  chunkedParts = [trailingBodyBytes]
231
- if (_hasChunkedEnd(trailingBodyBytes)) {
276
+ if (chunkedEndReached(trailingBodyBytes, hasTrailer)) {
232
277
  const decoded = decodeChunked(trailingBodyBytes)
233
278
  _controller!.enqueue(decoded)
234
279
  _controller!.close()
@@ -264,7 +309,23 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
264
309
  if (s !== null && s < 0 && !ssl) break
265
310
  let data: ArrayBuffer | null
266
311
  if (isHTTPS && ssl) {
267
- data = wolfssl.wolfSSL_read(ssl, 8192)
312
+ const buf = new ArrayBuffer(8192)
313
+ const n = wolfssl.wolfSSL_read(ssl, buf)
314
+ if (n > 0) {
315
+ data = buf.slice(0, n)
316
+ } else {
317
+ const err = wolfssl.wolfSSL_get_error(ssl, n)
318
+ if (err === wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_READ ||
319
+ err === wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_WRITE) break
320
+ if (n === 0 || err === wolfssl.ErrorCode.WOLFSSL_ERROR_ZERO_RETURN) break
321
+ // Fatal TLS error mid-body: the response already
322
+ // resolved, so fail the body stream instead of
323
+ // silently waiting for an FD event that never comes.
324
+ if (_controller) { try { _controller.error(new Error('TLS read failed: ' + err)) } catch { /* already closed */ } }
325
+ stream = null
326
+ cleanupSocket()
327
+ return
328
+ }
268
329
  } else if (s !== null && s >= 0) {
269
330
  data = sock.recv(s, 8192)
270
331
  } else { break }
@@ -272,7 +333,7 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
272
333
  if (isChunked) {
273
334
  chunkedParts.push(new Uint8Array(data))
274
335
  const combined = _concat(chunkedParts)
275
- if (_hasChunkedEnd(combined)) {
336
+ if (chunkedEndReached(combined, hasTrailer)) {
276
337
  const decoded = decodeChunked(combined)
277
338
  _controller!.enqueue(decoded)
278
339
  _controller!.close()
@@ -304,7 +365,12 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
304
365
  if (s !== null && s < 0 && !ssl) break
305
366
  let data: ArrayBuffer | null
306
367
  if (isHTTPS && ssl) {
307
- data = wolfssl.wolfSSL_read(ssl, 8192)
368
+ const buf = new ArrayBuffer(8192)
369
+ const n = wolfssl.wolfSSL_read(ssl, buf)
370
+ // Best-effort drain: any non-data result (EOF, WANT_*,
371
+ // late error) ends the drain — the stream closes below.
372
+ if (n > 0) data = buf.slice(0, n)
373
+ else break
308
374
  } else if (s !== null && s >= 0) {
309
375
  data = sock.recv(s, 8192)
310
376
  } else { break }