visaljs 0.0.10 → 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 (221) hide show
  1. package/README.md +12 -4
  2. package/THIRD_PARTY_LICENSES +31 -1
  3. package/cli.js +962 -68
  4. package/node/include/js_node.h +11 -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 +395 -86
  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/perf_histogram.js +1 -1
  31. package/node/prelude/internal/process_more.js +32 -17
  32. package/node/prelude/internal/process_signals.js +8 -2
  33. package/node/prelude/internal/punycode.ts +163 -0
  34. package/node/prelude/internal/readline.js +648 -274
  35. package/node/prelude/internal/readline_editor.js +1240 -0
  36. package/node/prelude/internal/string_width.js +43 -0
  37. package/node/prelude/internal/tls/common.js +145 -33
  38. package/node/prelude/internal/tls/wrap.js +182 -49
  39. package/node/prelude/internal/tls/x509.js +4 -1
  40. package/node/prelude/internal/udp.js +177 -0
  41. package/node/prelude/net.js +13 -1
  42. package/node/prelude/os.js +24 -6
  43. package/node/prelude/perf_hooks.js +37 -2
  44. package/node/prelude/punycode.js +2 -130
  45. package/node/prelude/readline.js +113 -4
  46. package/node/prelude/readline_promises.js +56 -32
  47. package/node/prelude/tls.js +21 -4
  48. package/node/prelude/util.js +3 -16
  49. package/node/src/host_dns.c +623 -0
  50. package/node/src/host_http.c +12 -0
  51. package/node/src/host_net.c +46 -0
  52. package/node/src/host_os.c +1 -6
  53. package/node/src/host_sys.c +70 -0
  54. package/node/src/host_udp.c +418 -0
  55. package/node/third_party/c-ares/LICENSE.md +24 -0
  56. package/node/third_party/c-ares/config/darwin/ares_config.h +573 -0
  57. package/node/third_party/c-ares/config/freebsd/ares_config.h +573 -0
  58. package/node/third_party/c-ares/config/linux/ares_config.h +573 -0
  59. package/node/third_party/c-ares/config/openbsd/ares_config.h +583 -0
  60. package/node/third_party/c-ares/include/ares.h +1237 -0
  61. package/node/third_party/c-ares/include/ares_build.h +202 -0
  62. package/node/third_party/c-ares/include/ares_dns.h +127 -0
  63. package/node/third_party/c-ares/include/ares_dns_record.h +1119 -0
  64. package/node/third_party/c-ares/include/ares_nameser.h +510 -0
  65. package/node/third_party/c-ares/include/ares_version.h +47 -0
  66. package/node/third_party/c-ares/src/lib/ares_addrinfo2hostent.c +305 -0
  67. package/node/third_party/c-ares/src/lib/ares_addrinfo_localhost.c +245 -0
  68. package/node/third_party/c-ares/src/lib/ares_android.c +496 -0
  69. package/node/third_party/c-ares/src/lib/ares_android.h +38 -0
  70. package/node/third_party/c-ares/src/lib/ares_cancel.c +87 -0
  71. package/node/third_party/c-ares/src/lib/ares_close_sockets.c +137 -0
  72. package/node/third_party/c-ares/src/lib/ares_conn.c +511 -0
  73. package/node/third_party/c-ares/src/lib/ares_conn.h +202 -0
  74. package/node/third_party/c-ares/src/lib/ares_cookie.c +461 -0
  75. package/node/third_party/c-ares/src/lib/ares_data.c +168 -0
  76. package/node/third_party/c-ares/src/lib/ares_data.h +92 -0
  77. package/node/third_party/c-ares/src/lib/ares_destroy.c +159 -0
  78. package/node/third_party/c-ares/src/lib/ares_free_hostent.c +54 -0
  79. package/node/third_party/c-ares/src/lib/ares_free_string.c +33 -0
  80. package/node/third_party/c-ares/src/lib/ares_freeaddrinfo.c +67 -0
  81. package/node/third_party/c-ares/src/lib/ares_getaddrinfo.c +806 -0
  82. package/node/third_party/c-ares/src/lib/ares_getenv.c +38 -0
  83. package/node/third_party/c-ares/src/lib/ares_getenv.h +34 -0
  84. package/node/third_party/c-ares/src/lib/ares_gethostbyaddr.c +229 -0
  85. package/node/third_party/c-ares/src/lib/ares_gethostbyname.c +341 -0
  86. package/node/third_party/c-ares/src/lib/ares_getnameinfo.c +437 -0
  87. package/node/third_party/c-ares/src/lib/ares_hosts_file.c +960 -0
  88. package/node/third_party/c-ares/src/lib/ares_inet_net_pton.h +31 -0
  89. package/node/third_party/c-ares/src/lib/ares_init.c +631 -0
  90. package/node/third_party/c-ares/src/lib/ares_ipv6.h +115 -0
  91. package/node/third_party/c-ares/src/lib/ares_library_init.c +190 -0
  92. package/node/third_party/c-ares/src/lib/ares_metrics.c +261 -0
  93. package/node/third_party/c-ares/src/lib/ares_options.c +492 -0
  94. package/node/third_party/c-ares/src/lib/ares_parse_into_addrinfo.c +179 -0
  95. package/node/third_party/c-ares/src/lib/ares_private.h +648 -0
  96. package/node/third_party/c-ares/src/lib/ares_process.c +1649 -0
  97. package/node/third_party/c-ares/src/lib/ares_qcache.c +442 -0
  98. package/node/third_party/c-ares/src/lib/ares_query.c +151 -0
  99. package/node/third_party/c-ares/src/lib/ares_search.c +623 -0
  100. package/node/third_party/c-ares/src/lib/ares_send.c +300 -0
  101. package/node/third_party/c-ares/src/lib/ares_set_socket_functions.c +590 -0
  102. package/node/third_party/c-ares/src/lib/ares_setup.h +224 -0
  103. package/node/third_party/c-ares/src/lib/ares_socket.c +427 -0
  104. package/node/third_party/c-ares/src/lib/ares_socket.h +163 -0
  105. package/node/third_party/c-ares/src/lib/ares_sortaddrinfo.c +447 -0
  106. package/node/third_party/c-ares/src/lib/ares_strerror.c +91 -0
  107. package/node/third_party/c-ares/src/lib/ares_sysconfig.c +642 -0
  108. package/node/third_party/c-ares/src/lib/ares_sysconfig_files.c +851 -0
  109. package/node/third_party/c-ares/src/lib/ares_sysconfig_mac.c +373 -0
  110. package/node/third_party/c-ares/src/lib/ares_sysconfig_win.c +673 -0
  111. package/node/third_party/c-ares/src/lib/ares_timeout.c +162 -0
  112. package/node/third_party/c-ares/src/lib/ares_update_servers.c +1376 -0
  113. package/node/third_party/c-ares/src/lib/ares_version.c +36 -0
  114. package/node/third_party/c-ares/src/lib/config-dos.h +114 -0
  115. package/node/third_party/c-ares/src/lib/config-win32.h +396 -0
  116. package/node/third_party/c-ares/src/lib/dsa/ares_array.c +409 -0
  117. package/node/third_party/c-ares/src/lib/dsa/ares_htable.c +451 -0
  118. package/node/third_party/c-ares/src/lib/dsa/ares_htable.h +174 -0
  119. package/node/third_party/c-ares/src/lib/dsa/ares_htable_asvp.c +224 -0
  120. package/node/third_party/c-ares/src/lib/dsa/ares_htable_dict.c +230 -0
  121. package/node/third_party/c-ares/src/lib/dsa/ares_htable_strvp.c +210 -0
  122. package/node/third_party/c-ares/src/lib/dsa/ares_htable_szvp.c +188 -0
  123. package/node/third_party/c-ares/src/lib/dsa/ares_htable_vpstr.c +186 -0
  124. package/node/third_party/c-ares/src/lib/dsa/ares_htable_vpvp.c +194 -0
  125. package/node/third_party/c-ares/src/lib/dsa/ares_llist.c +385 -0
  126. package/node/third_party/c-ares/src/lib/dsa/ares_slist.c +479 -0
  127. package/node/third_party/c-ares/src/lib/dsa/ares_slist.h +207 -0
  128. package/node/third_party/c-ares/src/lib/event/ares_event.h +191 -0
  129. package/node/third_party/c-ares/src/lib/event/ares_event_configchg.c +746 -0
  130. package/node/third_party/c-ares/src/lib/event/ares_event_epoll.c +192 -0
  131. package/node/third_party/c-ares/src/lib/event/ares_event_kqueue.c +248 -0
  132. package/node/third_party/c-ares/src/lib/event/ares_event_poll.c +140 -0
  133. package/node/third_party/c-ares/src/lib/event/ares_event_select.c +159 -0
  134. package/node/third_party/c-ares/src/lib/event/ares_event_thread.c +575 -0
  135. package/node/third_party/c-ares/src/lib/event/ares_event_wake_pipe.c +172 -0
  136. package/node/third_party/c-ares/src/lib/event/ares_event_win32.c +981 -0
  137. package/node/third_party/c-ares/src/lib/event/ares_event_win32.h +161 -0
  138. package/node/third_party/c-ares/src/lib/include/ares_array.h +276 -0
  139. package/node/third_party/c-ares/src/lib/include/ares_buf.h +732 -0
  140. package/node/third_party/c-ares/src/lib/include/ares_htable_asvp.h +130 -0
  141. package/node/third_party/c-ares/src/lib/include/ares_htable_dict.h +123 -0
  142. package/node/third_party/c-ares/src/lib/include/ares_htable_strvp.h +130 -0
  143. package/node/third_party/c-ares/src/lib/include/ares_htable_szvp.h +118 -0
  144. package/node/third_party/c-ares/src/lib/include/ares_htable_vpstr.h +111 -0
  145. package/node/third_party/c-ares/src/lib/include/ares_htable_vpvp.h +128 -0
  146. package/node/third_party/c-ares/src/lib/include/ares_llist.h +239 -0
  147. package/node/third_party/c-ares/src/lib/include/ares_mem.h +41 -0
  148. package/node/third_party/c-ares/src/lib/include/ares_str.h +261 -0
  149. package/node/third_party/c-ares/src/lib/inet_net_pton.c +451 -0
  150. package/node/third_party/c-ares/src/lib/inet_ntop.c +219 -0
  151. package/node/third_party/c-ares/src/lib/legacy/ares_create_query.c +78 -0
  152. package/node/third_party/c-ares/src/lib/legacy/ares_expand_name.c +99 -0
  153. package/node/third_party/c-ares/src/lib/legacy/ares_expand_string.c +107 -0
  154. package/node/third_party/c-ares/src/lib/legacy/ares_fds.c +80 -0
  155. package/node/third_party/c-ares/src/lib/legacy/ares_getsock.c +85 -0
  156. package/node/third_party/c-ares/src/lib/legacy/ares_parse_a_reply.c +107 -0
  157. package/node/third_party/c-ares/src/lib/legacy/ares_parse_aaaa_reply.c +109 -0
  158. package/node/third_party/c-ares/src/lib/legacy/ares_parse_caa_reply.c +137 -0
  159. package/node/third_party/c-ares/src/lib/legacy/ares_parse_mx_reply.c +110 -0
  160. package/node/third_party/c-ares/src/lib/legacy/ares_parse_naptr_reply.c +132 -0
  161. package/node/third_party/c-ares/src/lib/legacy/ares_parse_ns_reply.c +154 -0
  162. package/node/third_party/c-ares/src/lib/legacy/ares_parse_ptr_reply.c +213 -0
  163. package/node/third_party/c-ares/src/lib/legacy/ares_parse_soa_reply.c +115 -0
  164. package/node/third_party/c-ares/src/lib/legacy/ares_parse_srv_reply.c +114 -0
  165. package/node/third_party/c-ares/src/lib/legacy/ares_parse_txt_reply.c +144 -0
  166. package/node/third_party/c-ares/src/lib/legacy/ares_parse_uri_reply.c +113 -0
  167. package/node/third_party/c-ares/src/lib/record/ares_dns_mapping.c +982 -0
  168. package/node/third_party/c-ares/src/lib/record/ares_dns_multistring.c +309 -0
  169. package/node/third_party/c-ares/src/lib/record/ares_dns_multistring.h +72 -0
  170. package/node/third_party/c-ares/src/lib/record/ares_dns_name.c +736 -0
  171. package/node/third_party/c-ares/src/lib/record/ares_dns_parse.c +1360 -0
  172. package/node/third_party/c-ares/src/lib/record/ares_dns_private.h +273 -0
  173. package/node/third_party/c-ares/src/lib/record/ares_dns_record.c +1701 -0
  174. package/node/third_party/c-ares/src/lib/record/ares_dns_write.c +1229 -0
  175. package/node/third_party/c-ares/src/lib/str/ares_buf.c +1510 -0
  176. package/node/third_party/c-ares/src/lib/str/ares_str.c +562 -0
  177. package/node/third_party/c-ares/src/lib/str/ares_strsplit.c +90 -0
  178. package/node/third_party/c-ares/src/lib/str/ares_strsplit.h +51 -0
  179. package/node/third_party/c-ares/src/lib/thirdparty/apple/dnsinfo.h +122 -0
  180. package/node/third_party/c-ares/src/lib/util/ares_iface_ips.c +640 -0
  181. package/node/third_party/c-ares/src/lib/util/ares_iface_ips.h +140 -0
  182. package/node/third_party/c-ares/src/lib/util/ares_math.c +167 -0
  183. package/node/third_party/c-ares/src/lib/util/ares_math.h +58 -0
  184. package/node/third_party/c-ares/src/lib/util/ares_rand.c +389 -0
  185. package/node/third_party/c-ares/src/lib/util/ares_rand.h +36 -0
  186. package/node/third_party/c-ares/src/lib/util/ares_threads.c +613 -0
  187. package/node/third_party/c-ares/src/lib/util/ares_threads.h +60 -0
  188. package/node/third_party/c-ares/src/lib/util/ares_time.h +49 -0
  189. package/node/third_party/c-ares/src/lib/util/ares_timeval.c +95 -0
  190. package/node/third_party/c-ares/src/lib/util/ares_uri.c +1627 -0
  191. package/node/third_party/c-ares/src/lib/util/ares_uri.h +252 -0
  192. package/node/third_party/c-ares/src/lib/windows_port.c +29 -0
  193. package/package.json +1 -1
  194. package/prebuilt/darwin-arm64/cares-a6286c65ca9374a3.a +0 -0
  195. package/prebuilt/darwin-x64/cares-a6286c65ca9374a3.a +0 -0
  196. package/prebuilt/linux-arm64/cares-4ba05ac8e948af8e.a +0 -0
  197. package/prebuilt/linux-arm64-musl/cares-4ba05ac8e948af8e.a +0 -0
  198. package/prebuilt/linux-x64/cares-4ba05ac8e948af8e.a +0 -0
  199. package/prebuilt/linux-x64-musl/cares-4ba05ac8e948af8e.a +0 -0
  200. package/prebuilt/win32-x64/boringssl-ce573858472b7fc9.a +0 -0
  201. package/prebuilt/win32-x64/brotli-20d86a61f30baeb3.a +0 -0
  202. package/prebuilt/win32-x64/cares-49c9ec95162b912d.a +0 -0
  203. package/prebuilt/win32-x64/uv-b11bae65d7fced82.a +0 -0
  204. package/prebuilt/win32-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
  205. package/prebuilt/win32-x64/zstd-015315ac7c784c73.a +0 -0
  206. package/runtime/include/js_builtins.h +5 -0
  207. package/runtime/include/js_crypto.h +11 -0
  208. package/runtime/include/js_gc.h +13 -10
  209. package/runtime/include/js_jobs.h +2 -0
  210. package/runtime/include/js_object.h +58 -0
  211. package/runtime/include/jscontext.h +1 -0
  212. package/runtime/src/crypto_internal.h +1 -0
  213. package/runtime/src/crypto_subtle.c +66 -7
  214. package/runtime/src/crypto_tls.c +265 -19
  215. package/runtime/src/gc.c +73 -92
  216. package/runtime/src/inspect.c +18 -1
  217. package/runtime/src/jobs.c +37 -22
  218. package/runtime/src/object.c +49 -2
  219. package/runtime/src/os.c +16 -6
  220. package/runtime/src/os.h +2 -0
  221. package/runtime/src/process.c +97 -10
@@ -9,8 +9,9 @@
9
9
  // Not yet: the client, headersTimeout and requestTimeout, trailers.
10
10
  import { hasInvalidHeaderChar, isHttpToken } from "./internal/http_chars.ts";
11
11
  import {
12
- 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,
13
13
  } from "visaljs:host";
14
+ import { channel } from "./diagnostics_channel.js";
14
15
  import { bindContext, currentContext, runInContext } from "./internal/async_context.js";
15
16
  import { codeError, invalidArgType, invalidArgValue, outOfRange, validateInteger, validateString } from "./internal/errors.js";
16
17
  import { Agent, globalAgent, setGlobalAgent } from "./internal/http_agent.js";
@@ -19,6 +20,14 @@ import { createConnection, Server as NetServer } from "./net.js";
19
20
  import { addAbortSignal, Readable, Stream } from "./stream.js";
20
21
  import { callableClass } from "./internal/features.js";
21
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
+
22
31
  export const METHODS = ["ACL", "BIND", "CHECKOUT", "CONNECT", "COPY", "DELETE", "GET", "HEAD", "LINK", "LOCK", "M-SEARCH", "MERGE",
23
32
  "MKACTIVITY", "MKCALENDAR", "MKCOL", "MOVE", "NOTIFY", "OPTIONS", "PATCH", "POST", "PROPFIND", "PROPPATCH", "PURGE", "PUT", "QUERY",
24
33
  "REBIND", "REPORT", "SEARCH", "SOURCE", "SUBSCRIBE", "TRACE", "UNBIND", "UNLINK", "UNLOCK", "UNSUBSCRIBE"];
@@ -65,7 +74,8 @@ function chunkLength(chunk) {
65
74
  /* A request: a Readable of the body's Buffers, pushed as llhttp parses them. */
66
75
  export class IncomingMessage extends Readable {
67
76
  constructor(socket) {
68
- super();
77
+ const highWaterMark = socket?._readableState?.highWaterMark;
78
+ super(highWaterMark !== undefined && highWaterMark !== 65536 ? { highWaterMark } : undefined); /* the socket's, as Node's */
69
79
  this.socket = socket;
70
80
  this.httpVersionMajor = 1;
71
81
  this.httpVersionMinor = 1;
@@ -89,6 +99,31 @@ export class IncomingMessage extends Readable {
89
99
  return this.socket;
90
100
  }
91
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
+
92
127
  _read() {
93
128
  this._consuming = true;
94
129
  }
@@ -166,6 +201,16 @@ const SINGLE = new Set(["age", "authorization", "content-length", "content-type"
166
201
  "if-modified-since", "if-unmodified-since", "last-modified", "location", "max-forwards", "proxy-authorization", "referer",
167
202
  "retry-after", "server", "user-agent"]);
168
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
+
169
214
  function addHeader(headers, name, value, join) {
170
215
  const key = name.toLowerCase();
171
216
  const existing = headers[key];
@@ -196,6 +241,68 @@ export function validateHeaderValue(name, value) {
196
241
  if (hasInvalidHeaderChar(String(value))) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in header content [\"" + name + "\"]");
197
242
  }
198
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.");
199
306
  const headersSent = (what) => codeError(Error, "ERR_HTTP_HEADERS_SENT", "Cannot " + what + " headers after they are sent to the client");
200
307
 
201
308
  /* Node's write_: a chunk is a string or bytes */
@@ -365,14 +472,19 @@ export class OutgoingMessage extends Stream {
365
472
  }
366
473
 
367
474
  hasHeader(name) {
475
+ validateString(name, "name");
368
476
  return this._headers.has(String(name).toLowerCase());
369
477
  }
370
478
 
371
479
  removeHeader(name) {
372
480
  validateString(name, "name");
373
481
  if (this._header || this.headersSent) throw headersSent("remove");
374
- if (name.toLowerCase() === "content-length") this._removedContLen = true;
375
- 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);
376
488
  }
377
489
 
378
490
  getHeaderNames() {
@@ -391,51 +503,52 @@ export class OutgoingMessage extends Stream {
391
503
 
392
504
  /* Node's OutgoingMessage, as a ClientRequest (and an OutgoingMessage made by hand) writes: the
393
505
  * head stored, then the bytes through the socket (ServerResponse has its own, on llhttp's
394
- * 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,
395
507
  * Connection and the body's framing */
396
- _storeHeader(firstLine, headersArray) {
397
- let head = firstLine;
398
- let hasConnection = false, hasLength = false, hasTE = false, hasTrailer = false;
399
- const add = (name, value) => {
400
- const key = String(name).toLowerCase();
401
- if (key === "connection") {
402
- hasConnection = true;
403
- if (/close/i.test(String(value))) this._last = true;
404
- else this.shouldKeepAlive = true;
405
- } else if (key === "content-length") hasLength = true;
406
- else if (key === "transfer-encoding") {
407
- hasTE = true;
408
- if (/chunked/i.test(String(value))) this.chunkedEncoding = true;
409
- } else if (key === "trailer") hasTrailer = true;
410
- if (Array.isArray(value)) for (const v of value) head += name + ": " + v + "\r\n";
411
- else head += name + ": " + value + "\r\n";
412
- };
413
- if (headersArray) {
414
- if (Array.isArray(headersArray[0])) for (const [name, value] of headersArray) add(name, value);
415
- 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
+ }
416
520
  } else {
417
- for (const [, [name, value]] of this._headers) add(name, value);
418
- }
419
- if (!hasConnection) {
420
- 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);
421
529
  if (shouldSendKeepAlive) head += "Connection: keep-alive\r\n";
422
530
  else {
423
531
  this._last = true;
424
532
  head += "Connection: close\r\n";
425
533
  }
426
534
  }
427
- if (!hasLength && !hasTE) {
535
+ if (!state.contLen && !state.te) {
428
536
  if (!this._hasBody) this.chunkedEncoding = false;
429
537
  else if (!this.useChunkedEncodingByDefault) this._last = true;
430
- else if (!hasTrailer && !this._removedContLen && typeof this._contentLength === "number") head += "Content-Length: " + this._contentLength + "\r\n";
431
- else {
538
+ else if (!state.trailer && !this._removedContLen && typeof this._contentLength === "number") head += "Content-Length: " + this._contentLength + "\r\n";
539
+ else if (!this._removedTE) {
432
540
  head += "Transfer-Encoding: chunked\r\n";
433
541
  this.chunkedEncoding = true;
542
+ } else {
543
+ this._last = true; /* neither length nor chunks: the body ends with the connection */
434
544
  }
435
545
  }
546
+ if (this.chunkedEncoding !== true && state.trailer) throw codeError(Error, "ERR_HTTP_TRAILER_INVALID", "Trailers are invalid with this transfer encoding");
436
547
  this._header = head + "\r\n";
437
548
  this._headerSent = false;
438
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("");
439
552
  }
440
553
 
441
554
  /* bytes for the socket: written now, or kept until it's assigned */
@@ -561,7 +674,6 @@ export class OutgoingMessage extends Stream {
561
674
  else process.nextTick(finish);
562
675
  this.finished = true;
563
676
  this.writableEnded = true;
564
- this.writable = false;
565
677
  return this;
566
678
  }
567
679
 
@@ -581,7 +693,8 @@ export class OutgoingMessage extends Stream {
581
693
  const entries = Array.isArray(headers) ? headers : Object.keys(headers).map((key) => [key, headers[key]]);
582
694
  for (const [field, value] of entries) {
583
695
  validateHeaderName(field, "Trailer name");
584
- 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];
585
698
  for (const v of values) {
586
699
  if (hasInvalidHeaderChar(String(v))) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in trailer content [\"" + field + "\"]");
587
700
  this._trailer += field + ": " + v + "\r\n";
@@ -590,6 +703,12 @@ export class OutgoingMessage extends Stream {
590
703
  }
591
704
  }
592
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
+
593
712
  /* ---- ServerResponse ------------------------------------------------------------------------- */
594
713
 
595
714
  export class ServerResponse extends OutgoingMessage {
@@ -607,13 +726,68 @@ export class ServerResponse extends OutgoingMessage {
607
726
  /* one made by hand (new ServerResponse(req)) is kept alive, without a Keep-Alive timeout, as Node's */
608
727
  this._keepAlive = keepAlive ?? true;
609
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;
610
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);
611
782
  }
612
783
 
613
784
  /* the response's bytes: to the connection's parser, which corks them; or, for a response made by
614
785
  * hand (new ServerResponse(req), Node's assignSocket), to its socket, kept until it has one */
615
786
  _send(data, latin1 = false) {
616
- 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
+ }
617
791
  else if (this._conn !== undefined) httpWrite(this._conn, data, latin1);
618
792
  else if (this.socket) this.socket.write(data, latin1 ? "latin1" : undefined);
619
793
  else (this._unsent ??= []).push([data, latin1]);
@@ -664,7 +838,6 @@ export class ServerResponse extends OutgoingMessage {
664
838
  if (this.destroyed) return this;
665
839
  this.destroyed = true;
666
840
  this._errored = error;
667
- this.writable = false;
668
841
  this._closeEmitted = true;
669
842
  if (this.socket) this.socket.destroy();
670
843
  queueMicrotask(() => {
@@ -679,11 +852,11 @@ export class ServerResponse extends OutgoingMessage {
679
852
  if (this._closeEmitted) return;
680
853
  this._closeEmitted = true;
681
854
  this.destroyed = true;
682
- this.writable = false;
683
855
  queueMicrotask(() => this.emit("close"));
684
856
  }
685
857
 
686
858
  writeHead(statusCode, statusMessage, headers) {
859
+ if (this.headersSent) throw headersSent("write");
687
860
  /* as Node's: an integer status from 100 to 999 */
688
861
  const originalStatusCode = statusCode;
689
862
  statusCode |= 0;
@@ -692,12 +865,16 @@ export class ServerResponse extends OutgoingMessage {
692
865
  throw codeError(RangeError, "ERR_HTTP_INVALID_STATUS_CODE", `Invalid status code: ${shown}`);
693
866
  }
694
867
  if (typeof statusMessage !== "string") {
695
- headers = statusMessage;
868
+ headers ??= statusMessage;
696
869
  statusMessage = undefined;
697
870
  }
698
871
  this.statusCode = statusCode;
699
872
  if (statusMessage !== undefined) this.statusMessage = statusMessage;
700
- 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)) {
701
878
  /* as Node's: they replace the headers set before, and may repeat a name among themselves */
702
879
  if (headers.length % 2 !== 0) throw invalidArgValue("headers", "is invalid", headers);
703
880
  for (let i = 0; i < headers.length; i += 2) if (this.hasHeader(headers[i])) this.removeHeader(headers[i]);
@@ -707,6 +884,7 @@ export class ServerResponse extends OutgoingMessage {
707
884
  }
708
885
  /* Node renders the head now (sent with the first write): without a known length, the body
709
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");
710
888
  if (!this.headersSent) this._pendingHead = this._renderHead(this._implicitLength);
711
889
  return this;
712
890
  }
@@ -723,42 +901,57 @@ export class ServerResponse extends OutgoingMessage {
723
901
  _renderHead(length) {
724
902
  const status = this.statusCode;
725
903
  const noBody = status === 204 || status === 304 || (status >= 100 && status < 200);
904
+ if (noBody) this._hasBody = false;
726
905
  /* an empty statusMessage gets the standard one, as in Node (Next.js's route handlers set "") */
727
906
  let head = "HTTP/1.1 " + status + " " + (this.statusMessage || STATUS_CODES[status] || "unknown") + "\r\n";
728
- for (const [, [name, value]] of this._headers) {
729
- if (Array.isArray(value)) for (const v of value) head += name + ": " + v + "\r\n";
730
- 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";
731
911
  }
732
912
  if (this.sendDate && !this._headers.has("date")) head += "Date: " + httpDate() + "\r\n";
733
- const connection = this._headers.get("connection");
734
- if (connection !== undefined && String(connection[1]).toLowerCase().includes("close")) this._keepAlive = false;
735
- if (connection === undefined) {
736
- if (!this._keepAlive) head += "Connection: close\r\n";
737
- else {
738
- head += "Connection: keep-alive\r\n";
739
- if (this._keepAliveTimeout) head += "Keep-Alive: timeout=" + Math.floor(this._keepAliveTimeout / 1000) + "\r\n";
740
- }
741
- }
742
913
  /* Node: no length for HEAD; an HTTP/1.0 client gets a body that ends with the connection */
743
914
  const http10 = this.req.httpVersionMajor < 1 || this.req.httpVersionMinor < 1;
744
- /* 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 */
745
917
  const te = this._headers.get("transfer-encoding");
746
- if (te !== undefined && /\bchunked\b/i.test(String(te[1])) && !noBody) this.chunkedEncoding = true;
747
- 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) {
748
925
  if (this._contentLength !== null && this._contentLength !== undefined) length = this._contentLength;
749
- if (length !== undefined) {
750
- head += "Content-Length: " + length + "\r\n";
751
- } else {
752
- 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";
753
930
  this.chunkedEncoding = true;
931
+ } else {
932
+ this._keepAlive = false; /* neither length nor chunks: the body ends with the connection */
754
933
  }
755
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;
756
946
  /* as Node's _storeHeader: only a chunked body can have trailers (the head isn't sent) */
757
- if (this.chunkedEncoding !== true && this._headers.has("trailer")) {
947
+ if (this.chunkedEncoding !== true && hasTrailer) {
758
948
  throw codeError(Error, "ERR_HTTP_TRAILER_INVALID", "Trailers are invalid with this transfer encoding");
759
949
  }
760
950
  this.headersSent = true;
761
- 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;
762
955
  }
763
956
 
764
957
  write(chunk, encoding, callback) {
@@ -771,9 +964,11 @@ export class ServerResponse extends OutgoingMessage {
771
964
  const e = new Error(ended ? "write after end" : "Cannot call write after a stream was destroyed");
772
965
  e.code = ended ? "ERR_STREAM_WRITE_AFTER_END" : "ERR_STREAM_DESTROYED";
773
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) */
774
969
  process.nextTick(() => {
775
970
  if (typeof callback === "function") callback(e);
776
- if (!this.destroyed) this.emit("error", e);
971
+ this.emit("error", e);
777
972
  });
778
973
  } else if (typeof callback === "function") {
779
974
  process.nextTick(callback, e);
@@ -781,11 +976,13 @@ export class ServerResponse extends OutgoingMessage {
781
976
  return false;
782
977
  }
783
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);
784
980
  /* a writeHead of the response's own (a wrapper) sees the implicit head, as in Node */
785
981
  if (!this.headersSent && Object.hasOwn(this, "writeHead")) this._implicitHeader(undefined);
786
982
  const head = this._takeHead(undefined);
787
983
  if (head) this._send(head, true);
788
- 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) {
789
986
  if (this.chunkedEncoding) {
790
987
  this._send(chunkLength(chunk).toString(16) + "\r\n");
791
988
  this._send(chunk);
@@ -802,6 +999,22 @@ export class ServerResponse extends OutgoingMessage {
802
999
  return this._pendingBytes();
803
1000
  }
804
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
+
805
1018
  /* bytes written that haven't gone out: corked in C and in libuv's queue, or a TLS socket's */
806
1019
  _pendingBytes() {
807
1020
  if (this._conn === undefined || this._queued) return 0;
@@ -841,31 +1054,40 @@ export class ServerResponse extends OutgoingMessage {
841
1054
  if (chunk !== undefined && chunk !== null) validateChunk(chunk);
842
1055
  if (this.writableEnded) return this;
843
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);
844
1058
  if (!this.headersSent && Object.hasOwn(this, "writeHead")) this._implicitHeader(chunk !== undefined && chunk !== null ? chunkLength(chunk) : 0);
845
1059
  if (!this.headersSent) {
846
1060
  const hasChunk = chunk !== undefined && chunk !== null;
847
1061
  const length = hasChunk ? chunkLength(chunk) : 0;
848
- 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) {
849
1072
  /* the head's bytes are latin1, the body's UTF-8: corked into one write by the parser's
850
1073
  * connection; a response made by hand writes them as one string, as Node's _send */
851
1074
  if (this._conn === undefined) {
852
- this._send(this._renderHead(length) + chunk);
1075
+ this._send(head + chunk);
853
1076
  } else {
854
- this._send(this._renderHead(length), true);
1077
+ this._send(head, true);
855
1078
  this._send(chunk);
856
1079
  }
857
1080
  } else {
858
- this._send(this._renderHead(length), true);
859
- if (hasChunk && length && !this._head) this._send(chunk);
1081
+ this._send(head, true);
1082
+ if (hasChunk && length && this._hasBody) this._send(chunk);
860
1083
  }
861
1084
  } else {
862
1085
  if (chunk !== undefined && chunk !== null) this.write(chunk);
863
1086
  const head = this._takeHead(undefined);
864
1087
  if (head) this._send(head, true);
865
- 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);
866
1089
  }
867
- this.writableEnded = this.finished = true;
868
- this.writable = false;
1090
+ this.writableEnded = this.finished = true; /* writable stays true, as Node's (nodejs/node#15029) */
869
1091
  if (this._queued) { /* finished once it has gone out, after the responses before it */
870
1092
  this._endCallback = callback;
871
1093
  return this;
@@ -877,6 +1099,7 @@ export class ServerResponse extends OutgoingMessage {
877
1099
  /* a response that waited behind the connection's earlier ones: its turn (Node's assignSocket) */
878
1100
  _activate() {
879
1101
  this._queued = false;
1102
+ this.outputSize = 0;
880
1103
  const unsent = this._unsent;
881
1104
  this._unsent = undefined;
882
1105
  if (unsent) for (const [data, latin1] of unsent) httpWrite(this._conn, data, latin1);
@@ -886,6 +1109,7 @@ export class ServerResponse extends OutgoingMessage {
886
1109
  /* the response is all written: the socket ends after the last one (not kept alive, or the client
887
1110
  * ended its side), then 'finish' and 'close' */
888
1111
  _afterEnd(callback) {
1112
+ if (this.destroyed) return this; /* its connection closed: no 'finish', as Node's (the socket's write never completes) */
889
1113
  /* the response is all written: sent now, so that by 'finish' it has gone to the socket, as Node's */
890
1114
  if (this._conn !== undefined) httpFlush(this._conn);
891
1115
  if ((!this._keepAlive || this._last) && this.socket) { /* the response goes out, then the socket ends */
@@ -954,6 +1178,16 @@ class ServerClass extends NetServer {
954
1178
  this.insecureHTTPParser = boolean("insecureHTTPParser", undefined);
955
1179
  this.requireHostHeader = boolean("requireHostHeader", true);
956
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
+ }
957
1191
  /* the classes requests and responses are made of (subclasses of node:http's), as Node's */
958
1192
  this._IncomingMessage = options.IncomingMessage || IncomingMessage;
959
1193
  this._ServerResponse = options.ServerResponse || ServerResponse;
@@ -964,6 +1198,7 @@ class ServerClass extends NetServer {
964
1198
  throw outOfRange("headersTimeout", "<= requestTimeout", this.headersTimeout);
965
1199
  }
966
1200
  this.keepAliveTimeout = integer("keepAliveTimeout", 5000);
1201
+ this.keepAliveTimeoutBuffer = integer("keepAliveTimeoutBuffer", 1000);
967
1202
  this.connectionsCheckingInterval = integer("connectionsCheckingInterval", 30_000);
968
1203
  this._parsers = -1;
969
1204
  this._states = new Map(); /* connection → { socket, req, res, keepAliveTimeoutSet } */
@@ -1086,10 +1321,12 @@ class ServerClass extends NetServer {
1086
1321
  const conn = connOf(socket);
1087
1322
  if (conn === null) return;
1088
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);
1089
1326
  /* as Node's connection state: the request being parsed (req); those whose responses aren't
1090
1327
  * finished (incoming); the response writing to the connection (res), and those waiting behind it
1091
1328
  * (outgoing: pipelined requests are answered in order) */
1092
- 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 };
1093
1330
  this._states.set(conn, state);
1094
1331
  if (this.timeout && typeof socket.setTimeout === "function") socket.setTimeout(this.timeout);
1095
1332
  const onTimeout = () => this._onTimeout(state);
@@ -1159,8 +1396,11 @@ class ServerClass extends NetServer {
1159
1396
  req.httpVersionMinor = minor;
1160
1397
  req.httpVersion = major + "." + minor;
1161
1398
  req.rawHeaders = raw;
1162
- for (let i = 0; i < raw.length; i += 2) addHeader(req.headers, raw[i], raw[i + 1], this.joinDuplicateHeaders);
1163
- 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));
1164
1404
  if (req.upgrade) {
1165
1405
  state.upgradeReq = req;
1166
1406
  return 2;
@@ -1175,19 +1415,46 @@ class ServerClass extends NetServer {
1175
1415
  } else {
1176
1416
  state.res = res;
1177
1417
  }
1178
- res.on("finish", () => this._resOnFinish(state, req, res)); /* Node's resOnFinish, an `on` listener too */
1179
- 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 */
1180
1421
  res.writeHead(400, ["Connection", "close"]);
1181
1422
  res.end();
1182
1423
  return 0;
1183
1424
  }
1184
- 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
+ }
1185
1451
  return 0;
1186
1452
  }
1187
1453
 
1188
1454
  /* Node's resOnFinish: the request is done with; the next response's turn, or the idle keep-alive
1189
1455
  * timeout */
1190
- _resOnFinish(state, req, res) {
1456
+ _resOnFinish(state, req, res, socket) {
1457
+ if (onResponseFinishChannel.hasSubscribers) onResponseFinishChannel.publish({ request: req, response: res, socket, server: this });
1191
1458
  if (state.incoming[0] === req) state.incoming.shift();
1192
1459
  else {
1193
1460
  const i = state.incoming.indexOf(req);
@@ -1195,13 +1462,22 @@ class ServerClass extends NetServer {
1195
1462
  }
1196
1463
  if (state.req === req && req.complete) state.req = null;
1197
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();
1198
1471
  if (res._last || !res._keepAlive) return; /* the socket is ending */
1199
1472
  const next = state.outgoing.shift();
1200
1473
  if (next) {
1201
1474
  state.res = next;
1202
1475
  next._activate();
1203
1476
  } else if (this.keepAliveTimeout && state.socket.writable) {
1204
- 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);
1205
1481
  state.keepAliveTimeoutSet = true;
1206
1482
  }
1207
1483
  }
@@ -1265,6 +1541,10 @@ function unfeed(socket) {
1265
1541
  httpFedClose(fed.conn);
1266
1542
  }
1267
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
+
1268
1548
  /* Node's socketOnError: 'clientError', or a 400 (when nothing was written yet) and the end */
1269
1549
  function socketOnError(e) {
1270
1550
  this.removeListener("error", socketOnError);
@@ -1273,8 +1553,8 @@ function socketOnError(e) {
1273
1553
  if (!server.emit("clientError", e, this)) {
1274
1554
  const state = server._states.get(connKey(this));
1275
1555
  const res = state?.res;
1556
+ if (state) httpFlush(state.conn); /* what the responses wrote goes out first, as Node's socket had it */
1276
1557
  if (this.writable && (!res || !res.headersSent)) {
1277
- if (state) httpFlush(state.conn);
1278
1558
  this.write(e.code === "HPE_HEADER_OVERFLOW" ? headersTooLargeResponse : e.code === "ERR_HTTP_REQUEST_TIMEOUT" ? requestTimeoutResponse : badRequestResponse);
1279
1559
  }
1280
1560
  this.destroy(e);
@@ -1325,7 +1605,9 @@ function clientParsersId() {
1325
1605
  res.httpVersionMinor = minor;
1326
1606
  res.httpVersion = major + "." + minor;
1327
1607
  res.rawHeaders = raw;
1328
- 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);
1329
1611
  res.upgrade = upgrade;
1330
1612
  return parserOnIncomingClient(req, socket, res, keepAlive);
1331
1613
  },
@@ -1364,7 +1646,7 @@ function clientParsersId() {
1364
1646
  }
1365
1647
  if (req) {
1366
1648
  socket._hadError = true;
1367
- req.emit("error", e);
1649
+ emitErrorEvent(req, e);
1368
1650
  }
1369
1651
  },
1370
1652
  ].map(inRequestContext));
@@ -1470,8 +1752,14 @@ function clientUpgrade(req, socket, head) {
1470
1752
  }
1471
1753
  }
1472
1754
 
1755
+ function emitErrorEvent(request, error) {
1756
+ if (onClientRequestErrorChannel.hasSubscribers) onClientRequestErrorChannel.publish({ request, error });
1757
+ request.emit("error", error);
1758
+ }
1759
+
1473
1760
  function responseOnEnd() {
1474
1761
  const req = this.req;
1762
+ if (onClientResponseFinishChannel.hasSubscribers) onClientResponseFinishChannel.publish({ request: req, response: this });
1475
1763
  const socket = req.socket;
1476
1764
  if (socket) {
1477
1765
  if (req.timeoutCb) socket.removeListener("timeout", emitRequestTimeout);
@@ -1537,7 +1825,7 @@ function socketCloseListener() {
1537
1825
  } else {
1538
1826
  if (!this._hadError) { /* closed before a response: the request's error */
1539
1827
  this._hadError = true;
1540
- req.emit("error", connReset("socket hang up"));
1828
+ emitErrorEvent(req, connReset("socket hang up"));
1541
1829
  }
1542
1830
  req._closed = true;
1543
1831
  req.emit("close");
@@ -1550,7 +1838,7 @@ function socketErrorListener(err) {
1550
1838
  const req = this._httpMessage;
1551
1839
  if (req) {
1552
1840
  this._hadError = true;
1553
- req.emit("error", err);
1841
+ emitErrorEvent(req, err);
1554
1842
  }
1555
1843
  freeParser(this);
1556
1844
  this.removeListener("end", socketOnEnd);
@@ -1561,7 +1849,7 @@ function socketOnEnd() {
1561
1849
  const req = this._httpMessage;
1562
1850
  if (!req.res && !this._hadError) {
1563
1851
  this._hadError = true;
1564
- req.emit("error", connReset("socket hang up"));
1852
+ emitErrorEvent(req, connReset("socket hang up"));
1565
1853
  }
1566
1854
  freeParser(this);
1567
1855
  this.destroy();
@@ -1614,6 +1902,7 @@ export class ClientRequest extends OutgoingMessage {
1614
1902
  };
1615
1903
  const host = optsWithoutSignal.host = validHost(options.hostname, "hostname") || validHost(options.host, "host") || "localhost";
1616
1904
  const setHost = options.setHost !== undefined ? Boolean(options.setHost) : options.setDefaultHeaders !== false;
1905
+ this._removedConnection = this._removedContLen = this._removedTE = options.setDefaultHeaders === false;
1617
1906
  this.socketPath = options.socketPath;
1618
1907
  if (options.timeout !== undefined) {
1619
1908
  if (typeof options.timeout !== "number") throw invalidArgType("timeout", "of type number", options.timeout);
@@ -1655,6 +1944,7 @@ export class ClientRequest extends OutgoingMessage {
1655
1944
  throw invalidArgType("options.joinDuplicateHeaders", "of type boolean", options.joinDuplicateHeaders);
1656
1945
  }
1657
1946
  this.joinDuplicateHeaders = options.joinDuplicateHeaders;
1947
+ this._uniqueHeaders = uniqueHeadersOption(options.uniqueHeaders);
1658
1948
  this[kPath] = options.path || "/";
1659
1949
  if (cb) this.once("response", cb);
1660
1950
  this.useChunkedEncodingByDefault = !(method === "GET" || method === "HEAD" || method === "DELETE" || method === "OPTIONS" ||
@@ -1714,7 +2004,7 @@ export class ClientRequest extends OutgoingMessage {
1714
2004
  const oncreate = (err, socket) => {
1715
2005
  if (done) return;
1716
2006
  done = true;
1717
- if (err) process.nextTick(() => this.emit("error", err));
2007
+ if (err) process.nextTick(() => emitErrorEvent(this, err));
1718
2008
  else this.onSocket(socket);
1719
2009
  };
1720
2010
  try {
@@ -1727,6 +2017,25 @@ export class ClientRequest extends OutgoingMessage {
1727
2017
  this.onSocket(createConnection(opts));
1728
2018
  }
1729
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 });
1730
2039
  }
1731
2040
 
1732
2041
  _implicitHeader() {
@@ -1747,7 +2056,7 @@ export class ClientRequest extends OutgoingMessage {
1747
2056
  this.destroyed = true;
1748
2057
  const done = (er) => {
1749
2058
  if (!this.aborted && !er) er = connReset("socket hang up");
1750
- if (er) this.emit("error", er);
2059
+ if (er) emitErrorEvent(this, er);
1751
2060
  this._closed = true;
1752
2061
  this.emit("close");
1753
2062
  };