visaljs 0.0.9 → 0.0.11

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 (232) hide show
  1. package/README.md +12 -4
  2. package/THIRD_PARTY_LICENSES +31 -1
  3. package/cli.js +3745 -890
  4. package/node/include/js_node.h +12 -1
  5. package/node/prelude/_http_common.js +29 -0
  6. package/node/prelude/async_hooks.js +50 -11
  7. package/node/prelude/child_process.js +31 -4
  8. package/node/prelude/console.js +311 -143
  9. package/node/prelude/crypto.js +6 -4
  10. package/node/prelude/dgram.js +1169 -0
  11. package/node/prelude/diagnostics_channel.js +3 -1
  12. package/node/prelude/dns.js +250 -45
  13. package/node/prelude/dns_promises.js +8 -2
  14. package/node/prelude/fs_promises.js +9 -1
  15. package/node/prelude/http.js +405 -94
  16. package/node/prelude/https.js +13 -4
  17. package/node/prelude/internal/console_class.js +7 -0
  18. package/node/prelude/internal/console_global.js +25 -0
  19. package/node/prelude/internal/crypto/key_encryption.js +217 -0
  20. package/node/prelude/internal/crypto/keys.js +161 -26
  21. package/node/prelude/internal/crypto/random.js +24 -0
  22. package/node/prelude/internal/crypto/util.js +2 -1
  23. package/node/prelude/internal/crypto_x509.js +161 -0
  24. package/node/prelude/internal/dns_classes.js +6 -0
  25. package/node/prelude/internal/dns_resolver.js +371 -0
  26. package/node/prelude/internal/errors.js +9 -0
  27. package/node/prelude/internal/events_on.js +82 -37
  28. package/node/prelude/internal/features.js +8 -4
  29. package/node/prelude/internal/fs_filehandle.js +10 -4
  30. package/node/prelude/internal/http_chars.ts +22 -0
  31. package/node/prelude/internal/perf_histogram.js +1 -1
  32. package/node/prelude/internal/process_more.js +32 -17
  33. package/node/prelude/internal/process_signals.js +8 -2
  34. package/node/prelude/internal/punycode.ts +163 -0
  35. package/node/prelude/internal/readline.js +648 -274
  36. package/node/prelude/internal/readline_editor.js +1240 -0
  37. package/node/prelude/internal/string_width.js +43 -0
  38. package/node/prelude/internal/tls/common.js +145 -33
  39. package/node/prelude/internal/tls/wrap.js +182 -49
  40. package/node/prelude/internal/tls/x509.js +4 -1
  41. package/node/prelude/internal/udp.js +177 -0
  42. package/node/prelude/net.js +30 -14
  43. package/node/prelude/os.js +24 -6
  44. package/node/prelude/perf_hooks.js +37 -2
  45. package/node/prelude/punycode.js +2 -130
  46. package/node/prelude/readline.js +113 -4
  47. package/node/prelude/readline_promises.js +56 -32
  48. package/node/prelude/tls.js +21 -4
  49. package/node/prelude/util.js +3 -16
  50. package/node/src/host_dns.c +623 -0
  51. package/node/src/host_http.c +12 -0
  52. package/node/src/host_net.c +46 -0
  53. package/node/src/host_os.c +1 -6
  54. package/node/src/host_sys.c +79 -0
  55. package/node/src/host_udp.c +418 -0
  56. package/node/third_party/c-ares/LICENSE.md +24 -0
  57. package/node/third_party/c-ares/config/darwin/ares_config.h +573 -0
  58. package/node/third_party/c-ares/config/freebsd/ares_config.h +573 -0
  59. package/node/third_party/c-ares/config/linux/ares_config.h +573 -0
  60. package/node/third_party/c-ares/config/openbsd/ares_config.h +583 -0
  61. package/node/third_party/c-ares/include/ares.h +1237 -0
  62. package/node/third_party/c-ares/include/ares_build.h +202 -0
  63. package/node/third_party/c-ares/include/ares_dns.h +127 -0
  64. package/node/third_party/c-ares/include/ares_dns_record.h +1119 -0
  65. package/node/third_party/c-ares/include/ares_nameser.h +510 -0
  66. package/node/third_party/c-ares/include/ares_version.h +47 -0
  67. package/node/third_party/c-ares/src/lib/ares_addrinfo2hostent.c +305 -0
  68. package/node/third_party/c-ares/src/lib/ares_addrinfo_localhost.c +245 -0
  69. package/node/third_party/c-ares/src/lib/ares_android.c +496 -0
  70. package/node/third_party/c-ares/src/lib/ares_android.h +38 -0
  71. package/node/third_party/c-ares/src/lib/ares_cancel.c +87 -0
  72. package/node/third_party/c-ares/src/lib/ares_close_sockets.c +137 -0
  73. package/node/third_party/c-ares/src/lib/ares_conn.c +511 -0
  74. package/node/third_party/c-ares/src/lib/ares_conn.h +202 -0
  75. package/node/third_party/c-ares/src/lib/ares_cookie.c +461 -0
  76. package/node/third_party/c-ares/src/lib/ares_data.c +168 -0
  77. package/node/third_party/c-ares/src/lib/ares_data.h +92 -0
  78. package/node/third_party/c-ares/src/lib/ares_destroy.c +159 -0
  79. package/node/third_party/c-ares/src/lib/ares_free_hostent.c +54 -0
  80. package/node/third_party/c-ares/src/lib/ares_free_string.c +33 -0
  81. package/node/third_party/c-ares/src/lib/ares_freeaddrinfo.c +67 -0
  82. package/node/third_party/c-ares/src/lib/ares_getaddrinfo.c +806 -0
  83. package/node/third_party/c-ares/src/lib/ares_getenv.c +38 -0
  84. package/node/third_party/c-ares/src/lib/ares_getenv.h +34 -0
  85. package/node/third_party/c-ares/src/lib/ares_gethostbyaddr.c +229 -0
  86. package/node/third_party/c-ares/src/lib/ares_gethostbyname.c +341 -0
  87. package/node/third_party/c-ares/src/lib/ares_getnameinfo.c +437 -0
  88. package/node/third_party/c-ares/src/lib/ares_hosts_file.c +960 -0
  89. package/node/third_party/c-ares/src/lib/ares_inet_net_pton.h +31 -0
  90. package/node/third_party/c-ares/src/lib/ares_init.c +631 -0
  91. package/node/third_party/c-ares/src/lib/ares_ipv6.h +115 -0
  92. package/node/third_party/c-ares/src/lib/ares_library_init.c +190 -0
  93. package/node/third_party/c-ares/src/lib/ares_metrics.c +261 -0
  94. package/node/third_party/c-ares/src/lib/ares_options.c +492 -0
  95. package/node/third_party/c-ares/src/lib/ares_parse_into_addrinfo.c +179 -0
  96. package/node/third_party/c-ares/src/lib/ares_private.h +648 -0
  97. package/node/third_party/c-ares/src/lib/ares_process.c +1649 -0
  98. package/node/third_party/c-ares/src/lib/ares_qcache.c +442 -0
  99. package/node/third_party/c-ares/src/lib/ares_query.c +151 -0
  100. package/node/third_party/c-ares/src/lib/ares_search.c +623 -0
  101. package/node/third_party/c-ares/src/lib/ares_send.c +300 -0
  102. package/node/third_party/c-ares/src/lib/ares_set_socket_functions.c +590 -0
  103. package/node/third_party/c-ares/src/lib/ares_setup.h +224 -0
  104. package/node/third_party/c-ares/src/lib/ares_socket.c +427 -0
  105. package/node/third_party/c-ares/src/lib/ares_socket.h +163 -0
  106. package/node/third_party/c-ares/src/lib/ares_sortaddrinfo.c +447 -0
  107. package/node/third_party/c-ares/src/lib/ares_strerror.c +91 -0
  108. package/node/third_party/c-ares/src/lib/ares_sysconfig.c +642 -0
  109. package/node/third_party/c-ares/src/lib/ares_sysconfig_files.c +851 -0
  110. package/node/third_party/c-ares/src/lib/ares_sysconfig_mac.c +373 -0
  111. package/node/third_party/c-ares/src/lib/ares_sysconfig_win.c +673 -0
  112. package/node/third_party/c-ares/src/lib/ares_timeout.c +162 -0
  113. package/node/third_party/c-ares/src/lib/ares_update_servers.c +1376 -0
  114. package/node/third_party/c-ares/src/lib/ares_version.c +36 -0
  115. package/node/third_party/c-ares/src/lib/config-dos.h +114 -0
  116. package/node/third_party/c-ares/src/lib/config-win32.h +396 -0
  117. package/node/third_party/c-ares/src/lib/dsa/ares_array.c +409 -0
  118. package/node/third_party/c-ares/src/lib/dsa/ares_htable.c +451 -0
  119. package/node/third_party/c-ares/src/lib/dsa/ares_htable.h +174 -0
  120. package/node/third_party/c-ares/src/lib/dsa/ares_htable_asvp.c +224 -0
  121. package/node/third_party/c-ares/src/lib/dsa/ares_htable_dict.c +230 -0
  122. package/node/third_party/c-ares/src/lib/dsa/ares_htable_strvp.c +210 -0
  123. package/node/third_party/c-ares/src/lib/dsa/ares_htable_szvp.c +188 -0
  124. package/node/third_party/c-ares/src/lib/dsa/ares_htable_vpstr.c +186 -0
  125. package/node/third_party/c-ares/src/lib/dsa/ares_htable_vpvp.c +194 -0
  126. package/node/third_party/c-ares/src/lib/dsa/ares_llist.c +385 -0
  127. package/node/third_party/c-ares/src/lib/dsa/ares_slist.c +479 -0
  128. package/node/third_party/c-ares/src/lib/dsa/ares_slist.h +207 -0
  129. package/node/third_party/c-ares/src/lib/event/ares_event.h +191 -0
  130. package/node/third_party/c-ares/src/lib/event/ares_event_configchg.c +746 -0
  131. package/node/third_party/c-ares/src/lib/event/ares_event_epoll.c +192 -0
  132. package/node/third_party/c-ares/src/lib/event/ares_event_kqueue.c +248 -0
  133. package/node/third_party/c-ares/src/lib/event/ares_event_poll.c +140 -0
  134. package/node/third_party/c-ares/src/lib/event/ares_event_select.c +159 -0
  135. package/node/third_party/c-ares/src/lib/event/ares_event_thread.c +575 -0
  136. package/node/third_party/c-ares/src/lib/event/ares_event_wake_pipe.c +172 -0
  137. package/node/third_party/c-ares/src/lib/event/ares_event_win32.c +981 -0
  138. package/node/third_party/c-ares/src/lib/event/ares_event_win32.h +161 -0
  139. package/node/third_party/c-ares/src/lib/include/ares_array.h +276 -0
  140. package/node/third_party/c-ares/src/lib/include/ares_buf.h +732 -0
  141. package/node/third_party/c-ares/src/lib/include/ares_htable_asvp.h +130 -0
  142. package/node/third_party/c-ares/src/lib/include/ares_htable_dict.h +123 -0
  143. package/node/third_party/c-ares/src/lib/include/ares_htable_strvp.h +130 -0
  144. package/node/third_party/c-ares/src/lib/include/ares_htable_szvp.h +118 -0
  145. package/node/third_party/c-ares/src/lib/include/ares_htable_vpstr.h +111 -0
  146. package/node/third_party/c-ares/src/lib/include/ares_htable_vpvp.h +128 -0
  147. package/node/third_party/c-ares/src/lib/include/ares_llist.h +239 -0
  148. package/node/third_party/c-ares/src/lib/include/ares_mem.h +41 -0
  149. package/node/third_party/c-ares/src/lib/include/ares_str.h +261 -0
  150. package/node/third_party/c-ares/src/lib/inet_net_pton.c +451 -0
  151. package/node/third_party/c-ares/src/lib/inet_ntop.c +219 -0
  152. package/node/third_party/c-ares/src/lib/legacy/ares_create_query.c +78 -0
  153. package/node/third_party/c-ares/src/lib/legacy/ares_expand_name.c +99 -0
  154. package/node/third_party/c-ares/src/lib/legacy/ares_expand_string.c +107 -0
  155. package/node/third_party/c-ares/src/lib/legacy/ares_fds.c +80 -0
  156. package/node/third_party/c-ares/src/lib/legacy/ares_getsock.c +85 -0
  157. package/node/third_party/c-ares/src/lib/legacy/ares_parse_a_reply.c +107 -0
  158. package/node/third_party/c-ares/src/lib/legacy/ares_parse_aaaa_reply.c +109 -0
  159. package/node/third_party/c-ares/src/lib/legacy/ares_parse_caa_reply.c +137 -0
  160. package/node/third_party/c-ares/src/lib/legacy/ares_parse_mx_reply.c +110 -0
  161. package/node/third_party/c-ares/src/lib/legacy/ares_parse_naptr_reply.c +132 -0
  162. package/node/third_party/c-ares/src/lib/legacy/ares_parse_ns_reply.c +154 -0
  163. package/node/third_party/c-ares/src/lib/legacy/ares_parse_ptr_reply.c +213 -0
  164. package/node/third_party/c-ares/src/lib/legacy/ares_parse_soa_reply.c +115 -0
  165. package/node/third_party/c-ares/src/lib/legacy/ares_parse_srv_reply.c +114 -0
  166. package/node/third_party/c-ares/src/lib/legacy/ares_parse_txt_reply.c +144 -0
  167. package/node/third_party/c-ares/src/lib/legacy/ares_parse_uri_reply.c +113 -0
  168. package/node/third_party/c-ares/src/lib/record/ares_dns_mapping.c +982 -0
  169. package/node/third_party/c-ares/src/lib/record/ares_dns_multistring.c +309 -0
  170. package/node/third_party/c-ares/src/lib/record/ares_dns_multistring.h +72 -0
  171. package/node/third_party/c-ares/src/lib/record/ares_dns_name.c +736 -0
  172. package/node/third_party/c-ares/src/lib/record/ares_dns_parse.c +1360 -0
  173. package/node/third_party/c-ares/src/lib/record/ares_dns_private.h +273 -0
  174. package/node/third_party/c-ares/src/lib/record/ares_dns_record.c +1701 -0
  175. package/node/third_party/c-ares/src/lib/record/ares_dns_write.c +1229 -0
  176. package/node/third_party/c-ares/src/lib/str/ares_buf.c +1510 -0
  177. package/node/third_party/c-ares/src/lib/str/ares_str.c +562 -0
  178. package/node/third_party/c-ares/src/lib/str/ares_strsplit.c +90 -0
  179. package/node/third_party/c-ares/src/lib/str/ares_strsplit.h +51 -0
  180. package/node/third_party/c-ares/src/lib/thirdparty/apple/dnsinfo.h +122 -0
  181. package/node/third_party/c-ares/src/lib/util/ares_iface_ips.c +640 -0
  182. package/node/third_party/c-ares/src/lib/util/ares_iface_ips.h +140 -0
  183. package/node/third_party/c-ares/src/lib/util/ares_math.c +167 -0
  184. package/node/third_party/c-ares/src/lib/util/ares_math.h +58 -0
  185. package/node/third_party/c-ares/src/lib/util/ares_rand.c +389 -0
  186. package/node/third_party/c-ares/src/lib/util/ares_rand.h +36 -0
  187. package/node/third_party/c-ares/src/lib/util/ares_threads.c +613 -0
  188. package/node/third_party/c-ares/src/lib/util/ares_threads.h +60 -0
  189. package/node/third_party/c-ares/src/lib/util/ares_time.h +49 -0
  190. package/node/third_party/c-ares/src/lib/util/ares_timeval.c +95 -0
  191. package/node/third_party/c-ares/src/lib/util/ares_uri.c +1627 -0
  192. package/node/third_party/c-ares/src/lib/util/ares_uri.h +252 -0
  193. package/node/third_party/c-ares/src/lib/windows_port.c +29 -0
  194. package/package.json +1 -1
  195. package/prebuilt/darwin-arm64/cares-a6286c65ca9374a3.a +0 -0
  196. package/prebuilt/darwin-x64/cares-a6286c65ca9374a3.a +0 -0
  197. package/prebuilt/linux-arm64/cares-4ba05ac8e948af8e.a +0 -0
  198. package/prebuilt/linux-arm64-musl/cares-4ba05ac8e948af8e.a +0 -0
  199. package/prebuilt/linux-x64/cares-4ba05ac8e948af8e.a +0 -0
  200. package/prebuilt/linux-x64-musl/cares-4ba05ac8e948af8e.a +0 -0
  201. package/prebuilt/win32-x64/boringssl-ce573858472b7fc9.a +0 -0
  202. package/prebuilt/win32-x64/brotli-20d86a61f30baeb3.a +0 -0
  203. package/prebuilt/win32-x64/cares-49c9ec95162b912d.a +0 -0
  204. package/prebuilt/win32-x64/uv-b11bae65d7fced82.a +0 -0
  205. package/prebuilt/win32-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
  206. package/prebuilt/win32-x64/zstd-015315ac7c784c73.a +0 -0
  207. package/runtime/include/js_buffer.h +185 -0
  208. package/runtime/include/js_builtins.h +58 -0
  209. package/runtime/include/js_crypto.h +11 -0
  210. package/runtime/include/js_exception.h +3 -0
  211. package/runtime/include/js_function.h +1 -1
  212. package/runtime/include/js_gc.h +13 -10
  213. package/runtime/include/js_jobs.h +2 -0
  214. package/runtime/include/js_object.h +79 -3
  215. package/runtime/include/js_ops.h +27 -0
  216. package/runtime/include/js_runtime.h +1 -0
  217. package/runtime/include/js_web.h +0 -3
  218. package/runtime/include/jscontext.h +8 -0
  219. package/runtime/src/builtins.c +17 -4
  220. package/runtime/src/crypto_internal.h +1 -0
  221. package/runtime/src/crypto_subtle.c +66 -7
  222. package/runtime/src/crypto_tls.c +265 -19
  223. package/runtime/src/function.c +37 -10
  224. package/runtime/src/gc.c +76 -93
  225. package/runtime/src/inspect.c +18 -1
  226. package/runtime/src/iterator.c +5 -1
  227. package/runtime/src/jobs.c +37 -22
  228. package/runtime/src/object.c +91 -8
  229. package/runtime/src/os.c +16 -6
  230. package/runtime/src/os.h +2 -0
  231. package/runtime/src/process.c +97 -10
  232. package/runtime/src/typedarray.c +23 -0
@@ -7,9 +7,11 @@
7
7
  // upgrade (or CONNECT) hands the socket over with 'upgrade' ('connect'). Idle keep-alive
8
8
  // connections close after keepAliveTimeout; server.timeout and setTimeout are the sockets'.
9
9
  // Not yet: the client, headersTimeout and requestTimeout, trailers.
10
+ import { hasInvalidHeaderChar, isHttpToken } from "./internal/http_chars.ts";
10
11
  import {
11
- httpAttach, httpBusy, httpDate, httpPending, httpWatchDrain, httpDetach, httpExpired, httpFedClose, httpFedOpen, httpFeed, httpFlush, httpParsers, httpWrite, timerUnref,
12
+ httpAttach, httpBusy, httpDate, httpPending, httpWatchDrain, httpWritten, httpDetach, httpExpired, httpFedClose, httpFedOpen, httpFeed, httpFlush, httpParsers, httpWrite, timerUnref,
12
13
  } from "visaljs:host";
14
+ import { channel } from "./diagnostics_channel.js";
13
15
  import { bindContext, currentContext, runInContext } from "./internal/async_context.js";
14
16
  import { codeError, invalidArgType, invalidArgValue, outOfRange, validateInteger, validateString } from "./internal/errors.js";
15
17
  import { Agent, globalAgent, setGlobalAgent } from "./internal/http_agent.js";
@@ -18,6 +20,14 @@ import { createConnection, Server as NetServer } from "./net.js";
18
20
  import { addAbortSignal, Readable, Stream } from "./stream.js";
19
21
  import { callableClass } from "./internal/features.js";
20
22
 
23
+ const onRequestStartChannel = channel("http.server.request.start");
24
+ const onResponseCreatedChannel = channel("http.server.response.created");
25
+ const onResponseFinishChannel = channel("http.server.response.finish");
26
+ const onClientRequestCreatedChannel = channel("http.client.request.created");
27
+ const onClientRequestStartChannel = channel("http.client.request.start");
28
+ const onClientRequestErrorChannel = channel("http.client.request.error");
29
+ const onClientResponseFinishChannel = channel("http.client.response.finish");
30
+
21
31
  export const METHODS = ["ACL", "BIND", "CHECKOUT", "CONNECT", "COPY", "DELETE", "GET", "HEAD", "LINK", "LOCK", "M-SEARCH", "MERGE",
22
32
  "MKACTIVITY", "MKCALENDAR", "MKCOL", "MOVE", "NOTIFY", "OPTIONS", "PATCH", "POST", "PROPFIND", "PROPPATCH", "PURGE", "PUT", "QUERY",
23
33
  "REBIND", "REPORT", "SEARCH", "SOURCE", "SUBSCRIBE", "TRACE", "UNBIND", "UNLINK", "UNLOCK", "UNSUBSCRIBE"];
@@ -64,7 +74,8 @@ function chunkLength(chunk) {
64
74
  /* A request: a Readable of the body's Buffers, pushed as llhttp parses them. */
65
75
  export class IncomingMessage extends Readable {
66
76
  constructor(socket) {
67
- super();
77
+ const highWaterMark = socket?._readableState?.highWaterMark;
78
+ super(highWaterMark !== undefined && highWaterMark !== 65536 ? { highWaterMark } : undefined); /* the socket's, as Node's */
68
79
  this.socket = socket;
69
80
  this.httpVersionMajor = 1;
70
81
  this.httpVersionMinor = 1;
@@ -88,6 +99,31 @@ export class IncomingMessage extends Readable {
88
99
  return this.socket;
89
100
  }
90
101
 
102
+ set connection(value) {
103
+ this.socket = value;
104
+ }
105
+
106
+ _addHeaderLine(field, value, dest) {
107
+ addHeader(dest, field, value, this.joinDuplicateHeaders);
108
+ }
109
+
110
+ /* Node's: each name's values as they came, made when first asked for */
111
+ get headersDistinct() {
112
+ return (this._headersDistinct ??= distinct(this.rawHeaders));
113
+ }
114
+
115
+ set headersDistinct(value) {
116
+ this._headersDistinct = value;
117
+ }
118
+
119
+ get trailersDistinct() {
120
+ return (this._trailersDistinct ??= distinct(this.rawTrailers));
121
+ }
122
+
123
+ set trailersDistinct(value) {
124
+ this._trailersDistinct = value;
125
+ }
126
+
91
127
  _read() {
92
128
  this._consuming = true;
93
129
  }
@@ -165,6 +201,16 @@ const SINGLE = new Set(["age", "authorization", "content-length", "content-type"
165
201
  "if-modified-since", "if-unmodified-since", "last-modified", "location", "max-forwards", "proxy-authorization", "referer",
166
202
  "retry-after", "server", "user-agent"]);
167
203
 
204
+ function distinct(raw) {
205
+ const dest = { __proto__: null };
206
+ for (let i = 0; i < raw.length; i += 2) {
207
+ const key = raw[i].toLowerCase();
208
+ if (dest[key]) dest[key].push(raw[i + 1]);
209
+ else dest[key] = [raw[i + 1]];
210
+ }
211
+ return dest;
212
+ }
213
+
168
214
  function addHeader(headers, name, value, join) {
169
215
  const key = name.toLowerCase();
170
216
  const existing = headers[key];
@@ -183,20 +229,80 @@ function addHeader(headers, name, value, join) {
183
229
  /* ---- OutgoingMessage: a response's (and a request's) headers ------------------------------------ */
184
230
 
185
231
  /* Node's checks: a header name is an HTTP token; a value has no control characters */
186
- const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
187
- const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
188
232
 
189
233
  export function validateHeaderName(name, label = "Header name") {
190
- if (typeof name !== "string" || !name || !tokenRegExp.test(name)) {
234
+ if (typeof name !== "string" || !isHttpToken(name)) {
191
235
  throw codeError(TypeError, "ERR_INVALID_HTTP_TOKEN", label + " must be a valid HTTP token [\"" + name + "\"]");
192
236
  }
193
237
  }
194
238
 
195
239
  export function validateHeaderValue(name, value) {
196
240
  if (value === undefined) throw codeError(TypeError, "ERR_HTTP_INVALID_HEADER_VALUE", "Invalid value \"" + value + "\" for header \"" + name + "\"");
197
- if (headerCharRegex.test(value)) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in header content [\"" + name + "\"]");
241
+ if (hasInvalidHeaderChar(String(value))) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in header content [\"" + name + "\"]");
198
242
  }
199
243
 
244
+ /* Node's processHeader: an array is a line each, but cookies and the unique headers are one, joined by "; " */
245
+ function processHeader(msg, state, key, value, validate) {
246
+ if (validate) validateHeaderName(key);
247
+ if (Array.isArray(value)) {
248
+ const lower = key.toLowerCase();
249
+ if ((value.length < 2 || lower !== "cookie") && !msg._uniqueHeaders?.has(lower)) {
250
+ for (const v of value) storeHeader(msg, state, key, v, validate);
251
+ return;
252
+ }
253
+ value = value.join("; ");
254
+ }
255
+ storeHeader(msg, state, key, value, validate);
256
+ }
257
+
258
+ function storeHeader(msg, state, key, value, validate) {
259
+ if (validate) validateHeaderValue(key, value);
260
+ state.header += key + ": " + value + "\r\n";
261
+ switch (key.toLowerCase()) {
262
+ case "connection":
263
+ state.connection = true;
264
+ msg._removedConnection = false;
265
+ if (/(?:^|\W)close(?:$|\W)/i.test(value)) msg._last = true;
266
+ else msg.shouldKeepAlive = true;
267
+ break;
268
+ case "transfer-encoding":
269
+ state.te = true;
270
+ msg._removedTE = false;
271
+ if (/(?:^|\W)chunked(?:$|\W)/i.test(value)) msg.chunkedEncoding = true;
272
+ break;
273
+ case "content-length":
274
+ state.contLen = true;
275
+ msg._contentLength = +value;
276
+ msg._removedContLen = false;
277
+ break;
278
+ case "date":
279
+ case "expect":
280
+ case "trailer":
281
+ state[key.toLowerCase()] = true;
282
+ break;
283
+ }
284
+ }
285
+
286
+ /* Node's parseUniqueHeadersOption */
287
+ function uniqueHeadersOption(headers) {
288
+ return Array.isArray(headers) ? new Set(headers.map((h) => h.toLowerCase())) : null;
289
+ }
290
+
291
+ /* Node's validateLinkHeaderValue: one link, or links joined with ", " */
292
+ const linkValue = /^(?:<[^>\r\n]*>)(?:\s*;\s*[^;"\s]+(?:=(")?[^;"\s]*\1)?)*$/;
293
+ function linkHeaderValue(hints) {
294
+ const check = (link) => {
295
+ if (typeof link !== "string" || !linkValue.test(link)) {
296
+ throw invalidArgValue("hints", "must be an array or string of format \"</styles.css>; rel=preload; as=style\"", link);
297
+ }
298
+ return link;
299
+ };
300
+ if (typeof hints === "string") return check(hints);
301
+ if (Array.isArray(hints)) return hints.map(check).join(", ");
302
+ throw invalidArgValue("hints", "must be an array or string of format \"</styles.css>; rel=preload; as=style\"", hints);
303
+ }
304
+
305
+ const bodyNotAllowed = () => codeError(Error, "ERR_HTTP_BODY_NOT_ALLOWED", "Adding content for this request method or response status is not allowed.");
200
306
  const headersSent = (what) => codeError(Error, "ERR_HTTP_HEADERS_SENT", "Cannot " + what + " headers after they are sent to the client");
201
307
 
202
308
  /* Node's write_: a chunk is a string or bytes */
@@ -366,14 +472,19 @@ export class OutgoingMessage extends Stream {
366
472
  }
367
473
 
368
474
  hasHeader(name) {
475
+ validateString(name, "name");
369
476
  return this._headers.has(String(name).toLowerCase());
370
477
  }
371
478
 
372
479
  removeHeader(name) {
373
480
  validateString(name, "name");
374
481
  if (this._header || this.headersSent) throw headersSent("remove");
375
- if (name.toLowerCase() === "content-length") this._removedContLen = true;
376
- this._headers.delete(String(name).toLowerCase());
482
+ const key = name.toLowerCase();
483
+ if (key === "connection") this._removedConnection = true;
484
+ else if (key === "content-length") this._removedContLen = true;
485
+ else if (key === "transfer-encoding") this._removedTE = true;
486
+ else if (key === "date") this.sendDate = false;
487
+ this._headers.delete(key);
377
488
  }
378
489
 
379
490
  getHeaderNames() {
@@ -392,51 +503,52 @@ export class OutgoingMessage extends Stream {
392
503
 
393
504
  /* Node's OutgoingMessage, as a ClientRequest (and an OutgoingMessage made by hand) writes: the
394
505
  * head stored, then the bytes through the socket (ServerResponse has its own, on llhttp's
395
- * connection). _storeHeader: the first line, the headers as set (or as given in an array), then
506
+ * connection). Node's _storeHeader: the first line, the headers as set (or as given), then Date,
396
507
  * Connection and the body's framing */
397
- _storeHeader(firstLine, headersArray) {
398
- let head = firstLine;
399
- let hasConnection = false, hasLength = false, hasTE = false, hasTrailer = false;
400
- const add = (name, value) => {
401
- const key = String(name).toLowerCase();
402
- if (key === "connection") {
403
- hasConnection = true;
404
- if (/close/i.test(String(value))) this._last = true;
405
- else this.shouldKeepAlive = true;
406
- } else if (key === "content-length") hasLength = true;
407
- else if (key === "transfer-encoding") {
408
- hasTE = true;
409
- if (/chunked/i.test(String(value))) this.chunkedEncoding = true;
410
- } else if (key === "trailer") hasTrailer = true;
411
- if (Array.isArray(value)) for (const v of value) head += name + ": " + v + "\r\n";
412
- else head += name + ": " + value + "\r\n";
413
- };
414
- if (headersArray) {
415
- if (Array.isArray(headersArray[0])) for (const [name, value] of headersArray) add(name, value);
416
- else for (let i = 0; i < headersArray.length; i += 2) add(headersArray[i], headersArray[i + 1]);
508
+ _storeHeader(firstLine, headers) {
509
+ const state = { connection: false, contLen: false, te: false, date: false, expect: false, trailer: false, header: firstLine };
510
+ if (headers) {
511
+ if (Array.isArray(headers)) {
512
+ if (headers.length && Array.isArray(headers[0])) for (const [name, value] of headers) processHeader(this, state, name, value, true);
513
+ else {
514
+ if (headers.length % 2 !== 0) throw invalidArgValue("headers", "is invalid", headers);
515
+ for (let i = 0; i < headers.length; i += 2) processHeader(this, state, headers[i], headers[i + 1], true);
516
+ }
517
+ } else {
518
+ for (const key of Object.keys(headers)) processHeader(this, state, key, headers[key], true);
519
+ }
417
520
  } else {
418
- for (const [, [name, value]] of this._headers) add(name, value);
419
- }
420
- if (!hasConnection) {
421
- const shouldSendKeepAlive = this.shouldKeepAlive && (hasLength || this.useChunkedEncodingByDefault || this.agent);
521
+ for (const [, [name, value]] of this._headers) processHeader(this, state, name, value, false);
522
+ }
523
+ let head = state.header;
524
+ if (this.sendDate && !state.date) head += "Date: " + httpDate() + "\r\n";
525
+ if (this._removedConnection) {
526
+ this._last = !this.shouldKeepAlive;
527
+ } else if (!state.connection) {
528
+ const shouldSendKeepAlive = this.shouldKeepAlive && (state.contLen || this.useChunkedEncodingByDefault || this.agent);
422
529
  if (shouldSendKeepAlive) head += "Connection: keep-alive\r\n";
423
530
  else {
424
531
  this._last = true;
425
532
  head += "Connection: close\r\n";
426
533
  }
427
534
  }
428
- if (!hasLength && !hasTE) {
535
+ if (!state.contLen && !state.te) {
429
536
  if (!this._hasBody) this.chunkedEncoding = false;
430
537
  else if (!this.useChunkedEncodingByDefault) this._last = true;
431
- else if (!hasTrailer && !this._removedContLen && typeof this._contentLength === "number") head += "Content-Length: " + this._contentLength + "\r\n";
432
- else {
538
+ else if (!state.trailer && !this._removedContLen && typeof this._contentLength === "number") head += "Content-Length: " + this._contentLength + "\r\n";
539
+ else if (!this._removedTE) {
433
540
  head += "Transfer-Encoding: chunked\r\n";
434
541
  this.chunkedEncoding = true;
542
+ } else {
543
+ this._last = true; /* neither length nor chunks: the body ends with the connection */
435
544
  }
436
545
  }
546
+ if (this.chunkedEncoding !== true && state.trailer) throw codeError(Error, "ERR_HTTP_TRAILER_INVALID", "Trailers are invalid with this transfer encoding");
437
547
  this._header = head + "\r\n";
438
548
  this._headerSent = false;
439
549
  this.headersSent = true;
550
+ /* as Node's: the head goes now when the server is to answer 100 Continue before the body */
551
+ if (state.expect) this._send("");
440
552
  }
441
553
 
442
554
  /* bytes for the socket: written now, or kept until it's assigned */
@@ -562,7 +674,6 @@ export class OutgoingMessage extends Stream {
562
674
  else process.nextTick(finish);
563
675
  this.finished = true;
564
676
  this.writableEnded = true;
565
- this.writable = false;
566
677
  return this;
567
678
  }
568
679
 
@@ -582,15 +693,22 @@ export class OutgoingMessage extends Stream {
582
693
  const entries = Array.isArray(headers) ? headers : Object.keys(headers).map((key) => [key, headers[key]]);
583
694
  for (const [field, value] of entries) {
584
695
  validateHeaderName(field, "Trailer name");
585
- const values = Array.isArray(value) && value.length > 1 ? value : [Array.isArray(value) ? value.join("; ") : value];
696
+ const several = Array.isArray(value) && value.length > 1 && !this._uniqueHeaders?.has(field.toLowerCase());
697
+ const values = several ? value : [Array.isArray(value) ? value.join("; ") : value];
586
698
  for (const v of values) {
587
- if (headerCharRegex.test(v)) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in trailer content [\"" + field + "\"]");
699
+ if (hasInvalidHeaderChar(String(v))) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in trailer content [\"" + field + "\"]");
588
700
  this._trailer += field + ": " + v + "\r\n";
589
701
  }
590
702
  }
591
703
  }
592
704
  }
593
705
 
706
+ /* Node's fields that are rarely set: defaults on the prototype keep them off every instance */
707
+ Object.assign(OutgoingMessage.prototype, {
708
+ _removedConnection: false, _removedTE: false, strictContentLength: false, _uniqueHeaders: null, maxRequestsOnConnectionReached: false,
709
+ _maxRequestsPerSocket: 0, _rejectNonStandardBodyWrites: false,
710
+ });
711
+
594
712
  /* ---- ServerResponse ------------------------------------------------------------------------- */
595
713
 
596
714
  export class ServerResponse extends OutgoingMessage {
@@ -608,13 +726,68 @@ export class ServerResponse extends OutgoingMessage {
608
726
  /* one made by hand (new ServerResponse(req)) is kept alive, without a Keep-Alive timeout, as Node's */
609
727
  this._keepAlive = keepAlive ?? true;
610
728
  this._keepAliveTimeout = server ? server.keepAliveTimeout : 0;
729
+ /* only a server that sets them: each field an instance gets costs every response */
730
+ if (server?.maxRequestsPerSocket) this._maxRequestsPerSocket = server.maxRequestsPerSocket;
731
+ if (server?._uniqueHeaders) this._uniqueHeaders = server._uniqueHeaders;
732
+ if (server?.rejectNonStandardBodyWrites) this._rejectNonStandardBodyWrites = true;
611
733
  this._head = req.method === "HEAD";
734
+ if (onResponseCreatedChannel.hasSubscribers) onResponseCreatedChannel.publish({ request: req, response: this });
735
+ }
736
+
737
+ /* a 1xx response before the final one, as Node's */
738
+ writeInformation(statusCode, headers, callback) {
739
+ if (this.headersSent) throw headersSent("write");
740
+ validateInteger(statusCode, "statusCode");
741
+ if (statusCode < 100 || statusCode > 199) throw outOfRange("statusCode", ">= 100 && <= 199", statusCode);
742
+ if (statusCode === 101) throw codeError(RangeError, "ERR_HTTP_INVALID_STATUS_CODE", `Invalid status code: ${statusCode}`);
743
+ let head = "HTTP/1.1 " + statusCode + " " + (STATUS_CODES[statusCode] || "unknown") + "\r\n";
744
+ const line = (name, value) => {
745
+ validateHeaderName(name);
746
+ validateHeaderValue(name, value);
747
+ head += name + ": " + value + "\r\n";
748
+ };
749
+ if (Array.isArray(headers)) {
750
+ if (headers.length && Array.isArray(headers[0])) for (const [name, value] of headers) line(name, value);
751
+ else {
752
+ if (headers.length % 2 !== 0) throw invalidArgValue("headers", "is invalid", headers);
753
+ for (let i = 0; i < headers.length; i += 2) line(headers[i], headers[i + 1]);
754
+ }
755
+ } else if (headers !== undefined && headers !== null) {
756
+ if (typeof headers !== "object") throw invalidArgType("headers", "of type object", headers);
757
+ for (const name of Object.keys(headers)) line(name, headers[name]);
758
+ }
759
+ this._send(head + "\r\n", true);
760
+ if (typeof callback === "function") queueMicrotask(callback);
761
+ return true;
762
+ }
763
+
764
+ writeContinue(callback) {
765
+ this.writeInformation(100, null, callback);
766
+ this._sent100 = true;
767
+ }
768
+
769
+ writeProcessing(callback) {
770
+ this.writeInformation(102, null, callback);
771
+ }
772
+
773
+ writeEarlyHints(hints, callback) {
774
+ if (typeof hints !== "object" || hints === null || Array.isArray(hints)) throw invalidArgType("hints", "of type object", hints);
775
+ if (hints.link === null || hints.link === undefined) return;
776
+ const link = linkHeaderValue(hints.link);
777
+ if (link.length === 0) return;
778
+ if (hasInvalidHeaderChar(link)) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in header content [\"Link\"]");
779
+ const headers = { __proto__: null, Link: link };
780
+ for (const key of Object.keys(hints)) if (key !== "link") headers[key] = hints[key];
781
+ this.writeInformation(103, headers, callback);
612
782
  }
613
783
 
614
784
  /* the response's bytes: to the connection's parser, which corks them; or, for a response made by
615
785
  * hand (new ServerResponse(req), Node's assignSocket), to its socket, kept until it has one */
616
786
  _send(data, latin1 = false) {
617
- if (this._queued) (this._unsent ??= []).push([data, latin1]); /* behind the connection's earlier responses */
787
+ if (this._queued) { /* behind the connection's earlier responses: Node's outputData */
788
+ (this._unsent ??= []).push([data, latin1]);
789
+ this.outputSize += data.length;
790
+ }
618
791
  else if (this._conn !== undefined) httpWrite(this._conn, data, latin1);
619
792
  else if (this.socket) this.socket.write(data, latin1 ? "latin1" : undefined);
620
793
  else (this._unsent ??= []).push([data, latin1]);
@@ -665,7 +838,6 @@ export class ServerResponse extends OutgoingMessage {
665
838
  if (this.destroyed) return this;
666
839
  this.destroyed = true;
667
840
  this._errored = error;
668
- this.writable = false;
669
841
  this._closeEmitted = true;
670
842
  if (this.socket) this.socket.destroy();
671
843
  queueMicrotask(() => {
@@ -680,11 +852,11 @@ export class ServerResponse extends OutgoingMessage {
680
852
  if (this._closeEmitted) return;
681
853
  this._closeEmitted = true;
682
854
  this.destroyed = true;
683
- this.writable = false;
684
855
  queueMicrotask(() => this.emit("close"));
685
856
  }
686
857
 
687
858
  writeHead(statusCode, statusMessage, headers) {
859
+ if (this.headersSent) throw headersSent("write");
688
860
  /* as Node's: an integer status from 100 to 999 */
689
861
  const originalStatusCode = statusCode;
690
862
  statusCode |= 0;
@@ -693,12 +865,16 @@ export class ServerResponse extends OutgoingMessage {
693
865
  throw codeError(RangeError, "ERR_HTTP_INVALID_STATUS_CODE", `Invalid status code: ${shown}`);
694
866
  }
695
867
  if (typeof statusMessage !== "string") {
696
- headers = statusMessage;
868
+ headers ??= statusMessage;
697
869
  statusMessage = undefined;
698
870
  }
699
871
  this.statusCode = statusCode;
700
872
  if (statusMessage !== undefined) this.statusMessage = statusMessage;
701
- if (Array.isArray(headers)) {
873
+ else this.statusMessage ||= STATUS_CODES[statusCode] || "unknown";
874
+ if (Array.isArray(headers) && Array.isArray(headers[0]) && this._headers.size === 0) {
875
+ /* as Node's when only writeHead sets headers: [[name, value], …] too */
876
+ for (const [name, value] of headers) this.appendHeader(name, value);
877
+ } else if (Array.isArray(headers)) {
702
878
  /* as Node's: they replace the headers set before, and may repeat a name among themselves */
703
879
  if (headers.length % 2 !== 0) throw invalidArgValue("headers", "is invalid", headers);
704
880
  for (let i = 0; i < headers.length; i += 2) if (this.hasHeader(headers[i])) this.removeHeader(headers[i]);
@@ -708,6 +884,7 @@ export class ServerResponse extends OutgoingMessage {
708
884
  }
709
885
  /* Node renders the head now (sent with the first write): without a known length, the body
710
886
  * is chunked even when end() brings all of it */
887
+ if (this.statusMessage && hasInvalidHeaderChar(this.statusMessage)) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in statusMessage");
711
888
  if (!this.headersSent) this._pendingHead = this._renderHead(this._implicitLength);
712
889
  return this;
713
890
  }
@@ -724,42 +901,57 @@ export class ServerResponse extends OutgoingMessage {
724
901
  _renderHead(length) {
725
902
  const status = this.statusCode;
726
903
  const noBody = status === 204 || status === 304 || (status >= 100 && status < 200);
904
+ if (noBody) this._hasBody = false;
727
905
  /* an empty statusMessage gets the standard one, as in Node (Next.js's route handlers set "") */
728
906
  let head = "HTTP/1.1 " + status + " " + (this.statusMessage || STATUS_CODES[status] || "unknown") + "\r\n";
729
- for (const [, [name, value]] of this._headers) {
730
- if (Array.isArray(value)) for (const v of value) head += name + ": " + v + "\r\n";
731
- else head += name + ": " + value + "\r\n";
907
+ for (const [key, [name, value]] of this._headers) {
908
+ if (!Array.isArray(value)) head += name + ": " + value + "\r\n";
909
+ else if (this._uniqueHeaders?.has(key)) head += name + ": " + value.join("; ") + "\r\n";
910
+ else for (const v of value) head += name + ": " + v + "\r\n";
732
911
  }
733
912
  if (this.sendDate && !this._headers.has("date")) head += "Date: " + httpDate() + "\r\n";
734
- const connection = this._headers.get("connection");
735
- if (connection !== undefined && String(connection[1]).toLowerCase().includes("close")) this._keepAlive = false;
736
- if (connection === undefined) {
737
- if (!this._keepAlive) head += "Connection: close\r\n";
738
- else {
739
- head += "Connection: keep-alive\r\n";
740
- if (this._keepAliveTimeout) head += "Keep-Alive: timeout=" + Math.floor(this._keepAliveTimeout / 1000) + "\r\n";
741
- }
742
- }
743
913
  /* Node: no length for HEAD; an HTTP/1.0 client gets a body that ends with the connection */
744
914
  const http10 = this.req.httpVersionMajor < 1 || this.req.httpVersionMinor < 1;
745
- /* a Transfer-Encoding: chunked the program set: the body is chunk-encoded, as Node does */
915
+ /* a Transfer-Encoding: chunked the program set: the body is chunk-encoded, as Node does; on a
916
+ * 204 or 304 there's no body, and the connection closes */
746
917
  const te = this._headers.get("transfer-encoding");
747
- if (te !== undefined && /\bchunked\b/i.test(String(te[1])) && !noBody) this.chunkedEncoding = true;
748
- if (!noBody && !this._head && !http10 && !this._headers.has("content-length") && te === undefined) {
918
+ if (te !== undefined && /\bchunked\b/i.test(String(te[1]))) {
919
+ if (status === 204 || status === 304) this._keepAlive = false;
920
+ else if (!noBody) this.chunkedEncoding = true;
921
+ }
922
+ const hasTrailer = this._headers.has("trailer");
923
+ let framing = "";
924
+ if (!noBody && this._hasBody && !http10 && !this._headers.has("content-length") && te === undefined) {
749
925
  if (this._contentLength !== null && this._contentLength !== undefined) length = this._contentLength;
750
- if (length !== undefined) {
751
- head += "Content-Length: " + length + "\r\n";
752
- } else {
753
- head += "Transfer-Encoding: chunked\r\n";
926
+ if (length !== undefined && !this._removedContLen && !hasTrailer) {
927
+ framing = "Content-Length: " + length + "\r\n";
928
+ } else if (!this._removedTE) {
929
+ framing = "Transfer-Encoding: chunked\r\n";
754
930
  this.chunkedEncoding = true;
931
+ } else {
932
+ this._keepAlive = false; /* neither length nor chunks: the body ends with the connection */
755
933
  }
756
934
  }
935
+ const connection = this._headers.get("connection");
936
+ if (connection !== undefined && String(connection[1]).toLowerCase().includes("close")) this._keepAlive = false;
937
+ if (connection === undefined && !this._removedConnection) {
938
+ const limit = this._maxRequestsPerSocket;
939
+ if (limit > 0 && this.maxRequestsOnConnectionReached) this._keepAlive = false;
940
+ if (!this._keepAlive) head += "Connection: close\r\n";
941
+ else if (!this._keepAliveTimeout || this._headers.has("keep-alive")) head += "Connection: keep-alive\r\n";
942
+ else if (limit > 0) head += "Connection: keep-alive\r\nKeep-Alive: timeout=" + Math.floor(this._keepAliveTimeout / 1000) + ", max=" + limit + "\r\n";
943
+ else head += "Connection: keep-alive\r\nKeep-Alive: timeout=" + Math.floor(this._keepAliveTimeout / 1000) + "\r\n";
944
+ }
945
+ if (framing !== "") head += framing;
757
946
  /* as Node's _storeHeader: only a chunked body can have trailers (the head isn't sent) */
758
- if (this.chunkedEncoding !== true && this._headers.has("trailer")) {
947
+ if (this.chunkedEncoding !== true && hasTrailer) {
759
948
  throw codeError(Error, "ERR_HTTP_TRAILER_INVALID", "Trailers are invalid with this transfer encoding");
760
949
  }
761
950
  this.headersSent = true;
762
- return head + "\r\n";
951
+ /* as Node's: middleware such as compression tests _header, not headersSent, before calling
952
+ * _implicitHeader, which throws once the head is rendered */
953
+ this._header = head + "\r\n";
954
+ return this._header;
763
955
  }
764
956
 
765
957
  write(chunk, encoding, callback) {
@@ -772,9 +964,11 @@ export class ServerResponse extends OutgoingMessage {
772
964
  const e = new Error(ended ? "write after end" : "Cannot call write after a stream was destroyed");
773
965
  e.code = ended ? "ERR_STREAM_WRITE_AFTER_END" : "ERR_STREAM_DESTROYED";
774
966
  if (!this.destroyed) {
967
+ /* Node's emitErrorNt asks again on the next tick, but this response is destroyed sooner
968
+ * than Node's (its 'close' doesn't wait for the socket's write) */
775
969
  process.nextTick(() => {
776
970
  if (typeof callback === "function") callback(e);
777
- if (!this.destroyed) this.emit("error", e);
971
+ this.emit("error", e);
778
972
  });
779
973
  } else if (typeof callback === "function") {
780
974
  process.nextTick(callback, e);
@@ -782,11 +976,13 @@ export class ServerResponse extends OutgoingMessage {
782
976
  return false;
783
977
  }
784
978
  if (typeof chunk === "string" && typeof encoding === "string" && encoding !== "utf8" && encoding !== "utf-8") chunk = Buffer.from(chunk, encoding);
979
+ if (this.strictContentLength) this._countStrict(chunk, false);
785
980
  /* a writeHead of the response's own (a wrapper) sees the implicit head, as in Node */
786
981
  if (!this.headersSent && Object.hasOwn(this, "writeHead")) this._implicitHeader(undefined);
787
982
  const head = this._takeHead(undefined);
788
983
  if (head) this._send(head, true);
789
- if (chunk !== undefined && chunk !== null && chunkLength(chunk) > 0 && !this._head) {
984
+ if (!this._hasBody && this._rejectNonStandardBodyWrites) throw bodyNotAllowed();
985
+ if (chunk !== undefined && chunk !== null && chunkLength(chunk) > 0 && this._hasBody) {
790
986
  if (this.chunkedEncoding) {
791
987
  this._send(chunkLength(chunk).toString(16) + "\r\n");
792
988
  this._send(chunk);
@@ -803,6 +999,22 @@ export class ServerResponse extends OutgoingMessage {
803
999
  return this._pendingBytes();
804
1000
  }
805
1001
 
1002
+ /* Node's strictContentLength: the body's bytes against the Content-Length set, checked as written
1003
+ * and, at end(), for all of it */
1004
+ _countStrict(chunk, fromEnd) {
1005
+ const len = chunk === undefined || chunk === null ? 0 : chunkLength(chunk);
1006
+ const set = this._headers.get("content-length");
1007
+ const declared = set !== undefined ? +set[1] : this._contentLength;
1008
+ const written = this._bytesWritten ?? 0;
1009
+ const strict = declared !== null && declared !== undefined && this._hasBody && !this._removedContLen && !this.chunkedEncoding &&
1010
+ !this._headers.has("transfer-encoding");
1011
+ if (strict && (fromEnd ? written + len !== declared : written + len > declared)) {
1012
+ throw codeError(Error, "ERR_HTTP_CONTENT_LENGTH_MISMATCH",
1013
+ `Response body's content-length of ${written + len} byte(s) does not match the content-length of ${declared} byte(s) set in header`);
1014
+ }
1015
+ if (!fromEnd) this._bytesWritten = written + len; /* end()'s chunk is counted by the write() it makes */
1016
+ }
1017
+
806
1018
  /* bytes written that haven't gone out: corked in C and in libuv's queue, or a TLS socket's */
807
1019
  _pendingBytes() {
808
1020
  if (this._conn === undefined || this._queued) return 0;
@@ -842,31 +1054,40 @@ export class ServerResponse extends OutgoingMessage {
842
1054
  if (chunk !== undefined && chunk !== null) validateChunk(chunk);
843
1055
  if (this.writableEnded) return this;
844
1056
  if (typeof chunk === "string" && typeof encoding === "string" && encoding !== "utf8" && encoding !== "utf-8") chunk = Buffer.from(chunk, encoding);
1057
+ if (this.strictContentLength) this._countStrict(chunk, true);
845
1058
  if (!this.headersSent && Object.hasOwn(this, "writeHead")) this._implicitHeader(chunk !== undefined && chunk !== null ? chunkLength(chunk) : 0);
846
1059
  if (!this.headersSent) {
847
1060
  const hasChunk = chunk !== undefined && chunk !== null;
848
1061
  const length = hasChunk ? chunkLength(chunk) : 0;
849
- if (hasChunk && typeof chunk === "string" && !this._head) {
1062
+ const head = this._renderHead(length);
1063
+ if (this.chunkedEncoding) { /* Transfer-Encoding or a Trailer set: the one chunk, then the last */
1064
+ this._send(head, true);
1065
+ if (length && this._hasBody) {
1066
+ this._send(length.toString(16) + "\r\n");
1067
+ this._send(chunk);
1068
+ this._send("\r\n");
1069
+ }
1070
+ if (this._hasBody) this._send("0\r\n" + this._trailer + "\r\n", true);
1071
+ } else if (hasChunk && typeof chunk === "string" && this._hasBody) {
850
1072
  /* the head's bytes are latin1, the body's UTF-8: corked into one write by the parser's
851
1073
  * connection; a response made by hand writes them as one string, as Node's _send */
852
1074
  if (this._conn === undefined) {
853
- this._send(this._renderHead(length) + chunk);
1075
+ this._send(head + chunk);
854
1076
  } else {
855
- this._send(this._renderHead(length), true);
1077
+ this._send(head, true);
856
1078
  this._send(chunk);
857
1079
  }
858
1080
  } else {
859
- this._send(this._renderHead(length), true);
860
- if (hasChunk && length && !this._head) this._send(chunk);
1081
+ this._send(head, true);
1082
+ if (hasChunk && length && this._hasBody) this._send(chunk);
861
1083
  }
862
1084
  } else {
863
1085
  if (chunk !== undefined && chunk !== null) this.write(chunk);
864
1086
  const head = this._takeHead(undefined);
865
1087
  if (head) this._send(head, true);
866
- if (this.chunkedEncoding && !this._head) this._send("0\r\n" + this._trailer + "\r\n", true);
1088
+ if (this.chunkedEncoding && this._hasBody) this._send("0\r\n" + this._trailer + "\r\n", true);
867
1089
  }
868
- this.writableEnded = this.finished = true;
869
- this.writable = false;
1090
+ this.writableEnded = this.finished = true; /* writable stays true, as Node's (nodejs/node#15029) */
870
1091
  if (this._queued) { /* finished once it has gone out, after the responses before it */
871
1092
  this._endCallback = callback;
872
1093
  return this;
@@ -878,6 +1099,7 @@ export class ServerResponse extends OutgoingMessage {
878
1099
  /* a response that waited behind the connection's earlier ones: its turn (Node's assignSocket) */
879
1100
  _activate() {
880
1101
  this._queued = false;
1102
+ this.outputSize = 0;
881
1103
  const unsent = this._unsent;
882
1104
  this._unsent = undefined;
883
1105
  if (unsent) for (const [data, latin1] of unsent) httpWrite(this._conn, data, latin1);
@@ -887,6 +1109,7 @@ export class ServerResponse extends OutgoingMessage {
887
1109
  /* the response is all written: the socket ends after the last one (not kept alive, or the client
888
1110
  * ended its side), then 'finish' and 'close' */
889
1111
  _afterEnd(callback) {
1112
+ if (this.destroyed) return this; /* its connection closed: no 'finish', as Node's (the socket's write never completes) */
890
1113
  /* the response is all written: sent now, so that by 'finish' it has gone to the socket, as Node's */
891
1114
  if (this._conn !== undefined) httpFlush(this._conn);
892
1115
  if ((!this._keepAlive || this._last) && this.socket) { /* the response goes out, then the socket ends */
@@ -955,6 +1178,16 @@ class ServerClass extends NetServer {
955
1178
  this.insecureHTTPParser = boolean("insecureHTTPParser", undefined);
956
1179
  this.requireHostHeader = boolean("requireHostHeader", true);
957
1180
  this.joinDuplicateHeaders = boolean("joinDuplicateHeaders", undefined);
1181
+ this._uniqueHeaders = uniqueHeadersOption(options.uniqueHeaders);
1182
+ this.rejectNonStandardBodyWrites = boolean("rejectNonStandardBodyWrites", false);
1183
+ if (options.shouldUpgradeCallback !== undefined) {
1184
+ if (typeof options.shouldUpgradeCallback !== "function") throw invalidArgType("options.shouldUpgradeCallback", "of type function", options.shouldUpgradeCallback);
1185
+ this.shouldUpgradeCallback = options.shouldUpgradeCallback;
1186
+ } else {
1187
+ this.shouldUpgradeCallback = function () {
1188
+ return this.listenerCount("upgrade") > 0;
1189
+ };
1190
+ }
958
1191
  /* the classes requests and responses are made of (subclasses of node:http's), as Node's */
959
1192
  this._IncomingMessage = options.IncomingMessage || IncomingMessage;
960
1193
  this._ServerResponse = options.ServerResponse || ServerResponse;
@@ -965,6 +1198,7 @@ class ServerClass extends NetServer {
965
1198
  throw outOfRange("headersTimeout", "<= requestTimeout", this.headersTimeout);
966
1199
  }
967
1200
  this.keepAliveTimeout = integer("keepAliveTimeout", 5000);
1201
+ this.keepAliveTimeoutBuffer = integer("keepAliveTimeoutBuffer", 1000);
968
1202
  this.connectionsCheckingInterval = integer("connectionsCheckingInterval", 30_000);
969
1203
  this._parsers = -1;
970
1204
  this._states = new Map(); /* connection → { socket, req, res, keepAliveTimeoutSet } */
@@ -1087,10 +1321,12 @@ class ServerClass extends NetServer {
1087
1321
  const conn = connOf(socket);
1088
1322
  if (conn === null) return;
1089
1323
  socket.server = this; /* as Node's: the http server, even for a socket another server accepted */
1324
+ socket.setEncoding = socketSetEncoding;
1325
+ if (!isFed(socket)) socket._httpWritten = () => httpWritten(conn);
1090
1326
  /* as Node's connection state: the request being parsed (req); those whose responses aren't
1091
1327
  * finished (incoming); the response writing to the connection (res), and those waiting behind it
1092
1328
  * (outgoing: pipelined requests are answered in order) */
1093
- const state = { socket, conn, req: null, incoming: [], res: null, outgoing: [], keepAliveTimeoutSet: false, listeners: null };
1329
+ const state = { socket, conn, req: null, incoming: [], res: null, outgoing: [], keepAliveTimeoutSet: false, listeners: null, requestsCount: 0 };
1094
1330
  this._states.set(conn, state);
1095
1331
  if (this.timeout && typeof socket.setTimeout === "function") socket.setTimeout(this.timeout);
1096
1332
  const onTimeout = () => this._onTimeout(state);
@@ -1160,8 +1396,11 @@ class ServerClass extends NetServer {
1160
1396
  req.httpVersionMinor = minor;
1161
1397
  req.httpVersion = major + "." + minor;
1162
1398
  req.rawHeaders = raw;
1163
- for (let i = 0; i < raw.length; i += 2) addHeader(req.headers, raw[i], raw[i + 1], this.joinDuplicateHeaders);
1164
- req.upgrade = upgrade && (method === "CONNECT" || this.listenerCount("upgrade") > 0);
1399
+ /* as Node's parser: maxHeadersCount's pairs (2000 entries when not a number, all for 0) */
1400
+ const pairs = typeof this.maxHeadersCount === "number" ? this.maxHeadersCount << 1 : 2000;
1401
+ const n = pairs > 0 && pairs < raw.length ? pairs : raw.length;
1402
+ for (let i = 0; i < n; i += 2) addHeader(req.headers, raw[i], raw[i + 1], this.joinDuplicateHeaders);
1403
+ req.upgrade = upgrade && (method === "CONNECT" || !!this.shouldUpgradeCallback(req));
1165
1404
  if (req.upgrade) {
1166
1405
  state.upgradeReq = req;
1167
1406
  return 2;
@@ -1176,30 +1415,69 @@ class ServerClass extends NetServer {
1176
1415
  } else {
1177
1416
  state.res = res;
1178
1417
  }
1179
- res.once("finish", () => this._resOnFinish(state, req, res));
1180
- if (this.requireHostHeader && major === 1 && minor === 1 && !req.headers.host) { /* Node: a 400, and the connection ends */
1418
+ res.on("finish", () => this._resOnFinish(state, req, res, socket)); /* Node's resOnFinish, an `on` listener too */
1419
+ if (onRequestStartChannel.hasSubscribers) onRequestStartChannel.publish({ request: req, response: res, socket, server: this });
1420
+ if (this.requireHostHeader && major === 1 && minor === 1 && req.headers.host === undefined) { /* Node: a 400, and the connection ends */
1181
1421
  res.writeHead(400, ["Connection", "close"]);
1182
1422
  res.end();
1183
1423
  return 0;
1184
1424
  }
1185
- this.emit("request", req, res);
1425
+ const limited = typeof this.maxRequestsPerSocket === "number" && this.maxRequestsPerSocket > 0;
1426
+ if (limited) {
1427
+ state.requestsCount++;
1428
+ res.maxRequestsOnConnectionReached = this.maxRequestsPerSocket <= state.requestsCount;
1429
+ }
1430
+ if (limited && this.maxRequestsPerSocket < state.requestsCount) {
1431
+ this.emit("dropRequest", req, socket);
1432
+ res.writeHead(503);
1433
+ res.end();
1434
+ } else if (req.headers.expect !== undefined) {
1435
+ if (/(?:^|\W)100-continue(?:$|\W)/i.test(req.headers.expect)) {
1436
+ res._expect_continue = true;
1437
+ if (this.listenerCount("checkContinue") > 0) this.emit("checkContinue", req, res);
1438
+ else {
1439
+ res.writeContinue();
1440
+ this.emit("request", req, res);
1441
+ }
1442
+ } else if (this.listenerCount("checkExpectation") > 0) {
1443
+ this.emit("checkExpectation", req, res);
1444
+ } else {
1445
+ res.writeHead(417);
1446
+ res.end();
1447
+ }
1448
+ } else {
1449
+ this.emit("request", req, res);
1450
+ }
1186
1451
  return 0;
1187
1452
  }
1188
1453
 
1189
1454
  /* Node's resOnFinish: the request is done with; the next response's turn, or the idle keep-alive
1190
1455
  * timeout */
1191
- _resOnFinish(state, req, res) {
1192
- const i = state.incoming.indexOf(req);
1193
- if (i !== -1) state.incoming.splice(i, 1);
1456
+ _resOnFinish(state, req, res, socket) {
1457
+ if (onResponseFinishChannel.hasSubscribers) onResponseFinishChannel.publish({ request: req, response: res, socket, server: this });
1458
+ if (state.incoming[0] === req) state.incoming.shift();
1459
+ else {
1460
+ const i = state.incoming.indexOf(req);
1461
+ if (i !== -1) state.incoming.splice(i, 1);
1462
+ }
1194
1463
  if (state.req === req && req.complete) state.req = null;
1195
1464
  if (state.res === res) state.res = null;
1465
+ /* as Node's: a request nobody read has its body discarded. Not when it's all parsed with
1466
+ * nothing buffered and nobody waits for its 'end' or 'close': a bodiless GET costs ~25k
1467
+ * instructions to drain */
1468
+ const readable = req._readableState, events = req._events;
1469
+ if (!req._consuming && !readable.resumeScheduled &&
1470
+ (!req.complete || readable.length > 0 || events.end !== undefined || events.close !== undefined)) req._dump();
1196
1471
  if (res._last || !res._keepAlive) return; /* the socket is ending */
1197
1472
  const next = state.outgoing.shift();
1198
1473
  if (next) {
1199
1474
  state.res = next;
1200
1475
  next._activate();
1201
1476
  } else if (this.keepAliveTimeout && state.socket.writable) {
1202
- state.socket.setTimeout(this.keepAliveTimeout);
1477
+ /* as Node's: past the advertised timeout by keepAliveTimeoutBuffer, so the client closes first */
1478
+ const buffer = typeof this.keepAliveTimeoutBuffer === "number" && this.keepAliveTimeoutBuffer >= 0 && this.keepAliveTimeoutBuffer !== Infinity
1479
+ ? this.keepAliveTimeoutBuffer : 1000;
1480
+ state.socket.setTimeout(this.keepAliveTimeout + buffer);
1203
1481
  state.keepAliveTimeoutSet = true;
1204
1482
  }
1205
1483
  }
@@ -1263,6 +1541,10 @@ function unfeed(socket) {
1263
1541
  httpFedClose(fed.conn);
1264
1542
  }
1265
1543
 
1544
+ function socketSetEncoding() {
1545
+ throw codeError(Error, "ERR_HTTP_SOCKET_ENCODING", "Changing the socket encoding is not allowed per RFC7230 Section 3.");
1546
+ }
1547
+
1266
1548
  /* Node's socketOnError: 'clientError', or a 400 (when nothing was written yet) and the end */
1267
1549
  function socketOnError(e) {
1268
1550
  this.removeListener("error", socketOnError);
@@ -1271,8 +1553,8 @@ function socketOnError(e) {
1271
1553
  if (!server.emit("clientError", e, this)) {
1272
1554
  const state = server._states.get(connKey(this));
1273
1555
  const res = state?.res;
1556
+ if (state) httpFlush(state.conn); /* what the responses wrote goes out first, as Node's socket had it */
1274
1557
  if (this.writable && (!res || !res.headersSent)) {
1275
- if (state) httpFlush(state.conn);
1276
1558
  this.write(e.code === "HPE_HEADER_OVERFLOW" ? headersTooLargeResponse : e.code === "ERR_HTTP_REQUEST_TIMEOUT" ? requestTimeoutResponse : badRequestResponse);
1277
1559
  }
1278
1560
  this.destroy(e);
@@ -1323,7 +1605,9 @@ function clientParsersId() {
1323
1605
  res.httpVersionMinor = minor;
1324
1606
  res.httpVersion = major + "." + minor;
1325
1607
  res.rawHeaders = raw;
1326
- for (let i = 0; i < raw.length; i += 2) addHeader(res.headers, raw[i], raw[i + 1], req.joinDuplicateHeaders);
1608
+ const pairs = typeof req.maxHeadersCount === "number" ? req.maxHeadersCount << 1 : 2000;
1609
+ const n = pairs > 0 && pairs < raw.length ? pairs : raw.length;
1610
+ for (let i = 0; i < n; i += 2) addHeader(res.headers, raw[i], raw[i + 1], req.joinDuplicateHeaders);
1327
1611
  res.upgrade = upgrade;
1328
1612
  return parserOnIncomingClient(req, socket, res, keepAlive);
1329
1613
  },
@@ -1362,7 +1646,7 @@ function clientParsersId() {
1362
1646
  }
1363
1647
  if (req) {
1364
1648
  socket._hadError = true;
1365
- req.emit("error", e);
1649
+ emitErrorEvent(req, e);
1366
1650
  }
1367
1651
  },
1368
1652
  ].map(inRequestContext));
@@ -1468,8 +1752,14 @@ function clientUpgrade(req, socket, head) {
1468
1752
  }
1469
1753
  }
1470
1754
 
1755
+ function emitErrorEvent(request, error) {
1756
+ if (onClientRequestErrorChannel.hasSubscribers) onClientRequestErrorChannel.publish({ request, error });
1757
+ request.emit("error", error);
1758
+ }
1759
+
1471
1760
  function responseOnEnd() {
1472
1761
  const req = this.req;
1762
+ if (onClientResponseFinishChannel.hasSubscribers) onClientResponseFinishChannel.publish({ request: req, response: this });
1473
1763
  const socket = req.socket;
1474
1764
  if (socket) {
1475
1765
  if (req.timeoutCb) socket.removeListener("timeout", emitRequestTimeout);
@@ -1535,7 +1825,7 @@ function socketCloseListener() {
1535
1825
  } else {
1536
1826
  if (!this._hadError) { /* closed before a response: the request's error */
1537
1827
  this._hadError = true;
1538
- req.emit("error", connReset("socket hang up"));
1828
+ emitErrorEvent(req, connReset("socket hang up"));
1539
1829
  }
1540
1830
  req._closed = true;
1541
1831
  req.emit("close");
@@ -1548,7 +1838,7 @@ function socketErrorListener(err) {
1548
1838
  const req = this._httpMessage;
1549
1839
  if (req) {
1550
1840
  this._hadError = true;
1551
- req.emit("error", err);
1841
+ emitErrorEvent(req, err);
1552
1842
  }
1553
1843
  freeParser(this);
1554
1844
  this.removeListener("end", socketOnEnd);
@@ -1559,7 +1849,7 @@ function socketOnEnd() {
1559
1849
  const req = this._httpMessage;
1560
1850
  if (!req.res && !this._hadError) {
1561
1851
  this._hadError = true;
1562
- req.emit("error", connReset("socket hang up"));
1852
+ emitErrorEvent(req, connReset("socket hang up"));
1563
1853
  }
1564
1854
  freeParser(this);
1565
1855
  this.destroy();
@@ -1612,6 +1902,7 @@ export class ClientRequest extends OutgoingMessage {
1612
1902
  };
1613
1903
  const host = optsWithoutSignal.host = validHost(options.hostname, "hostname") || validHost(options.host, "host") || "localhost";
1614
1904
  const setHost = options.setHost !== undefined ? Boolean(options.setHost) : options.setDefaultHeaders !== false;
1905
+ this._removedConnection = this._removedContLen = this._removedTE = options.setDefaultHeaders === false;
1615
1906
  this.socketPath = options.socketPath;
1616
1907
  if (options.timeout !== undefined) {
1617
1908
  if (typeof options.timeout !== "number") throw invalidArgType("timeout", "of type number", options.timeout);
@@ -1625,7 +1916,7 @@ export class ClientRequest extends OutgoingMessage {
1625
1916
  let method = options.method;
1626
1917
  if (method !== null && method !== undefined && typeof method !== "string") throw invalidArgType("options.method", "of type string", method);
1627
1918
  if (method) {
1628
- if (!tokenRegExp.test(method)) throw codeError(TypeError, "ERR_INVALID_HTTP_TOKEN", "Method must be a valid HTTP token [\"" + method + "\"]");
1919
+ if (!isHttpToken(method)) throw codeError(TypeError, "ERR_INVALID_HTTP_TOKEN", "Method must be a valid HTTP token [\"" + method + "\"]");
1629
1920
  method = this.method = method.toUpperCase();
1630
1921
  } else {
1631
1922
  method = this.method = "GET";
@@ -1653,6 +1944,7 @@ export class ClientRequest extends OutgoingMessage {
1653
1944
  throw invalidArgType("options.joinDuplicateHeaders", "of type boolean", options.joinDuplicateHeaders);
1654
1945
  }
1655
1946
  this.joinDuplicateHeaders = options.joinDuplicateHeaders;
1947
+ this._uniqueHeaders = uniqueHeadersOption(options.uniqueHeaders);
1656
1948
  this[kPath] = options.path || "/";
1657
1949
  if (cb) this.once("response", cb);
1658
1950
  this.useChunkedEncodingByDefault = !(method === "GET" || method === "HEAD" || method === "DELETE" || method === "OPTIONS" ||
@@ -1712,7 +2004,7 @@ export class ClientRequest extends OutgoingMessage {
1712
2004
  const oncreate = (err, socket) => {
1713
2005
  if (done) return;
1714
2006
  done = true;
1715
- if (err) process.nextTick(() => this.emit("error", err));
2007
+ if (err) process.nextTick(() => emitErrorEvent(this, err));
1716
2008
  else this.onSocket(socket);
1717
2009
  };
1718
2010
  try {
@@ -1725,6 +2017,25 @@ export class ClientRequest extends OutgoingMessage {
1725
2017
  this.onSocket(createConnection(opts));
1726
2018
  }
1727
2019
  }
2020
+ if (onClientRequestCreatedChannel.hasSubscribers) onClientRequestCreatedChannel.publish({ request: this });
2021
+ }
2022
+
2023
+ /* Node's _finish, which publishes the start: once the ended request is all on its socket, at
2024
+ * end() or when the socket is assigned */
2025
+ end(chunk, encoding, callback) {
2026
+ const finished = this.finished;
2027
+ super.end(chunk, encoding, callback);
2028
+ if (!finished && this.outputData.length === 0 && this.socket?._httpMessage === this) this._publishStart();
2029
+ return this;
2030
+ }
2031
+
2032
+ _flush() {
2033
+ super._flush();
2034
+ if (this.finished && this.socket?.writable) this._publishStart();
2035
+ }
2036
+
2037
+ _publishStart() {
2038
+ if (onClientRequestStartChannel.hasSubscribers) onClientRequestStartChannel.publish({ request: this });
1728
2039
  }
1729
2040
 
1730
2041
  _implicitHeader() {
@@ -1745,7 +2056,7 @@ export class ClientRequest extends OutgoingMessage {
1745
2056
  this.destroyed = true;
1746
2057
  const done = (er) => {
1747
2058
  if (!this.aborted && !er) er = connReset("socket hang up");
1748
- if (er) this.emit("error", er);
2059
+ if (er) emitErrorEvent(this, er);
1749
2060
  this._closed = true;
1750
2061
  this.emit("close");
1751
2062
  };