@scriptc/runtime 0.0.0 → 0.0.2

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 (416) hide show
  1. package/LICENSE +202 -0
  2. package/package.json +16 -6
  3. package/src/scr_array.c +488 -0
  4. package/src/scr_assert.c +1393 -0
  5. package/src/scr_async.c +2593 -0
  6. package/src/scr_async_dyn.c +607 -0
  7. package/src/scr_bytes.c +1425 -0
  8. package/src/scr_bytes_io.c +161 -0
  9. package/src/scr_child.c +3587 -0
  10. package/src/scr_closure.c +214 -0
  11. package/src/scr_console.c +143 -0
  12. package/src/scr_cycle.c +218 -0
  13. package/src/scr_dc.c +805 -0
  14. package/src/scr_dgram.c +1007 -0
  15. package/src/scr_dyn_handle.c +154 -0
  16. package/src/scr_dyn_invoke.c +644 -0
  17. package/src/scr_error.c +332 -0
  18. package/src/scr_events.c +714 -0
  19. package/src/scr_events_emitter.c +699 -0
  20. package/src/scr_exception.c +242 -0
  21. package/src/scr_fetch.c +1130 -0
  22. package/src/scr_fetch_curl.c +654 -0
  23. package/src/scr_http.c +3482 -0
  24. package/src/scr_http2.c +3030 -0
  25. package/src/scr_inspect.c +803 -0
  26. package/src/scr_inspect_island.c +51 -0
  27. package/src/scr_island.c +9363 -0
  28. package/src/scr_json.c +2151 -0
  29. package/src/scr_lib.c +3612 -0
  30. package/src/scr_loop_epoll.c +241 -0
  31. package/src/scr_loop_kqueue.c +121 -0
  32. package/src/scr_loop_wsapoll.c +208 -0
  33. package/src/scr_map.c +591 -0
  34. package/src/scr_net.c +3017 -0
  35. package/src/scr_net_island.c +458 -0
  36. package/src/scr_number.c +115 -0
  37. package/src/scr_object.c +25 -0
  38. package/src/scr_path.c +1266 -0
  39. package/src/scr_platform.h +119 -0
  40. package/src/scr_readline.c +287 -0
  41. package/src/scr_regex.c +1080 -0
  42. package/src/scr_runtime.h +5046 -0
  43. package/src/scr_stream.c +2877 -0
  44. package/src/scr_string.c +1264 -0
  45. package/src/scr_symbol.c +132 -0
  46. package/src/scr_test.c +662 -0
  47. package/src/scr_tls.c +1520 -0
  48. package/src/scr_union.c +105 -0
  49. package/src/scr_url.c +911 -0
  50. package/src/scr_url_internal.h +29 -0
  51. package/src/scr_url_params.c +561 -0
  52. package/src/scr_watch.c +568 -0
  53. package/src/scr_web.c +1778 -0
  54. package/src/scr_win.c +76 -0
  55. package/src/scr_zlib.c +197 -0
  56. package/src/scr_zlib_island.c +15 -0
  57. package/vendor/README.md +52 -0
  58. package/vendor/curl/COPYRIGHT +534 -0
  59. package/vendor/curl/include/curl/curl.h +3214 -0
  60. package/vendor/curl/include/curl/curlver.h +79 -0
  61. package/vendor/curl/include/curl/easy.h +125 -0
  62. package/vendor/curl/include/curl/header.h +74 -0
  63. package/vendor/curl/include/curl/mprintf.h +52 -0
  64. package/vendor/curl/include/curl/multi.h +460 -0
  65. package/vendor/curl/include/curl/options.h +70 -0
  66. package/vendor/curl/include/curl/stdcheaders.h +35 -0
  67. package/vendor/curl/include/curl/system.h +508 -0
  68. package/vendor/curl/include/curl/typecheck-gcc.h +716 -0
  69. package/vendor/curl/include/curl/urlapi.h +149 -0
  70. package/vendor/curl/include/curl/websockets.h +84 -0
  71. package/vendor/mbedtls/LICENSE +553 -0
  72. package/vendor/mbedtls/include/CMakeLists.txt +22 -0
  73. package/vendor/mbedtls/include/mbedtls/aes.h +631 -0
  74. package/vendor/mbedtls/include/mbedtls/aria.h +343 -0
  75. package/vendor/mbedtls/include/mbedtls/asn1.h +642 -0
  76. package/vendor/mbedtls/include/mbedtls/asn1write.h +390 -0
  77. package/vendor/mbedtls/include/mbedtls/base64.h +82 -0
  78. package/vendor/mbedtls/include/mbedtls/bignum.h +1088 -0
  79. package/vendor/mbedtls/include/mbedtls/block_cipher.h +76 -0
  80. package/vendor/mbedtls/include/mbedtls/build_info.h +194 -0
  81. package/vendor/mbedtls/include/mbedtls/camellia.h +305 -0
  82. package/vendor/mbedtls/include/mbedtls/ccm.h +526 -0
  83. package/vendor/mbedtls/include/mbedtls/chacha20.h +209 -0
  84. package/vendor/mbedtls/include/mbedtls/chachapoly.h +351 -0
  85. package/vendor/mbedtls/include/mbedtls/check_config.h +1149 -0
  86. package/vendor/mbedtls/include/mbedtls/cipher.h +1250 -0
  87. package/vendor/mbedtls/include/mbedtls/cmac.h +246 -0
  88. package/vendor/mbedtls/include/mbedtls/compat-2.x.h +46 -0
  89. package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h +578 -0
  90. package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h +873 -0
  91. package/vendor/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h +359 -0
  92. package/vendor/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h +145 -0
  93. package/vendor/mbedtls/include/mbedtls/config_adjust_ssl.h +91 -0
  94. package/vendor/mbedtls/include/mbedtls/config_adjust_x509.h +35 -0
  95. package/vendor/mbedtls/include/mbedtls/config_psa.h +61 -0
  96. package/vendor/mbedtls/include/mbedtls/constant_time.h +36 -0
  97. package/vendor/mbedtls/include/mbedtls/ctr_drbg.h +596 -0
  98. package/vendor/mbedtls/include/mbedtls/debug.h +156 -0
  99. package/vendor/mbedtls/include/mbedtls/des.h +385 -0
  100. package/vendor/mbedtls/include/mbedtls/dhm.h +972 -0
  101. package/vendor/mbedtls/include/mbedtls/ecdh.h +455 -0
  102. package/vendor/mbedtls/include/mbedtls/ecdsa.h +674 -0
  103. package/vendor/mbedtls/include/mbedtls/ecjpake.h +298 -0
  104. package/vendor/mbedtls/include/mbedtls/ecp.h +1517 -0
  105. package/vendor/mbedtls/include/mbedtls/entropy.h +274 -0
  106. package/vendor/mbedtls/include/mbedtls/error.h +201 -0
  107. package/vendor/mbedtls/include/mbedtls/gcm.h +387 -0
  108. package/vendor/mbedtls/include/mbedtls/hkdf.h +124 -0
  109. package/vendor/mbedtls/include/mbedtls/hmac_drbg.h +434 -0
  110. package/vendor/mbedtls/include/mbedtls/lms.h +440 -0
  111. package/vendor/mbedtls/include/mbedtls/mbedtls_config.h +4446 -0
  112. package/vendor/mbedtls/include/mbedtls/md.h +526 -0
  113. package/vendor/mbedtls/include/mbedtls/md5.h +190 -0
  114. package/vendor/mbedtls/include/mbedtls/memory_buffer_alloc.h +142 -0
  115. package/vendor/mbedtls/include/mbedtls/net_sockets.h +299 -0
  116. package/vendor/mbedtls/include/mbedtls/nist_kw.h +166 -0
  117. package/vendor/mbedtls/include/mbedtls/oid.h +727 -0
  118. package/vendor/mbedtls/include/mbedtls/pem.h +160 -0
  119. package/vendor/mbedtls/include/mbedtls/pk.h +1303 -0
  120. package/vendor/mbedtls/include/mbedtls/pkcs12.h +186 -0
  121. package/vendor/mbedtls/include/mbedtls/pkcs5.h +198 -0
  122. package/vendor/mbedtls/include/mbedtls/pkcs7.h +252 -0
  123. package/vendor/mbedtls/include/mbedtls/platform.h +516 -0
  124. package/vendor/mbedtls/include/mbedtls/platform_time.h +79 -0
  125. package/vendor/mbedtls/include/mbedtls/platform_util.h +247 -0
  126. package/vendor/mbedtls/include/mbedtls/poly1305.h +168 -0
  127. package/vendor/mbedtls/include/mbedtls/private_access.h +20 -0
  128. package/vendor/mbedtls/include/mbedtls/psa_util.h +207 -0
  129. package/vendor/mbedtls/include/mbedtls/ripemd160.h +136 -0
  130. package/vendor/mbedtls/include/mbedtls/rsa.h +1170 -0
  131. package/vendor/mbedtls/include/mbedtls/sha1.h +219 -0
  132. package/vendor/mbedtls/include/mbedtls/sha256.h +200 -0
  133. package/vendor/mbedtls/include/mbedtls/sha3.h +172 -0
  134. package/vendor/mbedtls/include/mbedtls/sha512.h +208 -0
  135. package/vendor/mbedtls/include/mbedtls/ssl.h +5887 -0
  136. package/vendor/mbedtls/include/mbedtls/ssl_cache.h +187 -0
  137. package/vendor/mbedtls/include/mbedtls/ssl_ciphersuites.h +482 -0
  138. package/vendor/mbedtls/include/mbedtls/ssl_cookie.h +106 -0
  139. package/vendor/mbedtls/include/mbedtls/ssl_ticket.h +199 -0
  140. package/vendor/mbedtls/include/mbedtls/threading.h +167 -0
  141. package/vendor/mbedtls/include/mbedtls/timing.h +94 -0
  142. package/vendor/mbedtls/include/mbedtls/version.h +78 -0
  143. package/vendor/mbedtls/include/mbedtls/x509.h +500 -0
  144. package/vendor/mbedtls/include/mbedtls/x509_crl.h +184 -0
  145. package/vendor/mbedtls/include/mbedtls/x509_crt.h +1208 -0
  146. package/vendor/mbedtls/include/mbedtls/x509_csr.h +382 -0
  147. package/vendor/mbedtls/include/psa/build_info.h +20 -0
  148. package/vendor/mbedtls/include/psa/crypto.h +4998 -0
  149. package/vendor/mbedtls/include/psa/crypto_adjust_auto_enabled.h +31 -0
  150. package/vendor/mbedtls/include/psa/crypto_adjust_config_dependencies.h +51 -0
  151. package/vendor/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h +101 -0
  152. package/vendor/mbedtls/include/psa/crypto_adjust_config_synonyms.h +49 -0
  153. package/vendor/mbedtls/include/psa/crypto_builtin_composites.h +214 -0
  154. package/vendor/mbedtls/include/psa/crypto_builtin_key_derivation.h +118 -0
  155. package/vendor/mbedtls/include/psa/crypto_builtin_primitives.h +114 -0
  156. package/vendor/mbedtls/include/psa/crypto_compat.h +230 -0
  157. package/vendor/mbedtls/include/psa/crypto_config.h +145 -0
  158. package/vendor/mbedtls/include/psa/crypto_driver_common.h +44 -0
  159. package/vendor/mbedtls/include/psa/crypto_driver_contexts_composites.h +151 -0
  160. package/vendor/mbedtls/include/psa/crypto_driver_contexts_key_derivation.h +52 -0
  161. package/vendor/mbedtls/include/psa/crypto_driver_contexts_primitives.h +105 -0
  162. package/vendor/mbedtls/include/psa/crypto_extra.h +2145 -0
  163. package/vendor/mbedtls/include/psa/crypto_legacy.h +88 -0
  164. package/vendor/mbedtls/include/psa/crypto_platform.h +102 -0
  165. package/vendor/mbedtls/include/psa/crypto_se_driver.h +1383 -0
  166. package/vendor/mbedtls/include/psa/crypto_sizes.h +1319 -0
  167. package/vendor/mbedtls/include/psa/crypto_struct.h +527 -0
  168. package/vendor/mbedtls/include/psa/crypto_types.h +508 -0
  169. package/vendor/mbedtls/include/psa/crypto_values.h +2782 -0
  170. package/vendor/mbedtls/library/aes.c +2294 -0
  171. package/vendor/mbedtls/library/aesce.c +624 -0
  172. package/vendor/mbedtls/library/aesce.h +136 -0
  173. package/vendor/mbedtls/library/aesni.c +846 -0
  174. package/vendor/mbedtls/library/aesni.h +162 -0
  175. package/vendor/mbedtls/library/alignment.h +704 -0
  176. package/vendor/mbedtls/library/aria.c +969 -0
  177. package/vendor/mbedtls/library/asn1parse.c +468 -0
  178. package/vendor/mbedtls/library/asn1write.c +440 -0
  179. package/vendor/mbedtls/library/base64.c +322 -0
  180. package/vendor/mbedtls/library/base64_internal.h +45 -0
  181. package/vendor/mbedtls/library/bignum.c +2583 -0
  182. package/vendor/mbedtls/library/bignum_core.c +1240 -0
  183. package/vendor/mbedtls/library/bignum_core.h +872 -0
  184. package/vendor/mbedtls/library/bignum_core_invasive.h +38 -0
  185. package/vendor/mbedtls/library/bignum_internal.h +122 -0
  186. package/vendor/mbedtls/library/bignum_mod.c +394 -0
  187. package/vendor/mbedtls/library/bignum_mod.h +452 -0
  188. package/vendor/mbedtls/library/bignum_mod_raw.c +276 -0
  189. package/vendor/mbedtls/library/bignum_mod_raw.h +416 -0
  190. package/vendor/mbedtls/library/bignum_mod_raw_invasive.h +34 -0
  191. package/vendor/mbedtls/library/block_cipher.c +207 -0
  192. package/vendor/mbedtls/library/block_cipher_internal.h +99 -0
  193. package/vendor/mbedtls/library/bn_mul.h +1094 -0
  194. package/vendor/mbedtls/library/camellia.c +1058 -0
  195. package/vendor/mbedtls/library/ccm.c +777 -0
  196. package/vendor/mbedtls/library/chacha20.c +563 -0
  197. package/vendor/mbedtls/library/chacha20_internal.h +23 -0
  198. package/vendor/mbedtls/library/chachapoly.c +487 -0
  199. package/vendor/mbedtls/library/check_crypto_config.h +136 -0
  200. package/vendor/mbedtls/library/cipher.c +1712 -0
  201. package/vendor/mbedtls/library/cipher_invasive.h +28 -0
  202. package/vendor/mbedtls/library/cipher_wrap.c +2482 -0
  203. package/vendor/mbedtls/library/cipher_wrap.h +178 -0
  204. package/vendor/mbedtls/library/cmac.c +1067 -0
  205. package/vendor/mbedtls/library/common.h +487 -0
  206. package/vendor/mbedtls/library/constant_time.c +247 -0
  207. package/vendor/mbedtls/library/constant_time_impl.h +541 -0
  208. package/vendor/mbedtls/library/constant_time_internal.h +579 -0
  209. package/vendor/mbedtls/library/ctr.h +35 -0
  210. package/vendor/mbedtls/library/ctr_drbg.c +1016 -0
  211. package/vendor/mbedtls/library/debug.c +475 -0
  212. package/vendor/mbedtls/library/debug_internal.h +185 -0
  213. package/vendor/mbedtls/library/des.c +1042 -0
  214. package/vendor/mbedtls/library/dhm.c +700 -0
  215. package/vendor/mbedtls/library/ecdh.c +696 -0
  216. package/vendor/mbedtls/library/ecdsa.c +858 -0
  217. package/vendor/mbedtls/library/ecjpake.c +1216 -0
  218. package/vendor/mbedtls/library/ecp.c +3674 -0
  219. package/vendor/mbedtls/library/ecp_curves.c +6125 -0
  220. package/vendor/mbedtls/library/ecp_curves_new.c +9 -0
  221. package/vendor/mbedtls/library/ecp_internal_alt.h +287 -0
  222. package/vendor/mbedtls/library/ecp_invasive.h +305 -0
  223. package/vendor/mbedtls/library/entropy.c +680 -0
  224. package/vendor/mbedtls/library/entropy_poll.c +233 -0
  225. package/vendor/mbedtls/library/entropy_poll.h +64 -0
  226. package/vendor/mbedtls/library/error.c +878 -0
  227. package/vendor/mbedtls/library/gcm.c +1330 -0
  228. package/vendor/mbedtls/library/hkdf.c +161 -0
  229. package/vendor/mbedtls/library/hmac_drbg.c +633 -0
  230. package/vendor/mbedtls/library/lmots.c +789 -0
  231. package/vendor/mbedtls/library/lmots.h +288 -0
  232. package/vendor/mbedtls/library/lms.c +778 -0
  233. package/vendor/mbedtls/library/md.c +1108 -0
  234. package/vendor/mbedtls/library/md5.c +426 -0
  235. package/vendor/mbedtls/library/md_psa.h +26 -0
  236. package/vendor/mbedtls/library/md_wrap.h +46 -0
  237. package/vendor/mbedtls/library/memory_buffer_alloc.c +751 -0
  238. package/vendor/mbedtls/library/mps_common.h +181 -0
  239. package/vendor/mbedtls/library/mps_error.h +89 -0
  240. package/vendor/mbedtls/library/mps_reader.c +538 -0
  241. package/vendor/mbedtls/library/mps_reader.h +366 -0
  242. package/vendor/mbedtls/library/mps_trace.c +112 -0
  243. package/vendor/mbedtls/library/mps_trace.h +154 -0
  244. package/vendor/mbedtls/library/net_sockets.c +694 -0
  245. package/vendor/mbedtls/library/nist_kw.c +729 -0
  246. package/vendor/mbedtls/library/oid.c +1166 -0
  247. package/vendor/mbedtls/library/padlock.c +157 -0
  248. package/vendor/mbedtls/library/padlock.h +111 -0
  249. package/vendor/mbedtls/library/pem.c +554 -0
  250. package/vendor/mbedtls/library/pk.c +1602 -0
  251. package/vendor/mbedtls/library/pk_ecc.c +261 -0
  252. package/vendor/mbedtls/library/pk_internal.h +241 -0
  253. package/vendor/mbedtls/library/pk_wrap.c +1618 -0
  254. package/vendor/mbedtls/library/pk_wrap.h +138 -0
  255. package/vendor/mbedtls/library/pkcs12.c +437 -0
  256. package/vendor/mbedtls/library/pkcs5.c +500 -0
  257. package/vendor/mbedtls/library/pkcs7.c +787 -0
  258. package/vendor/mbedtls/library/pkparse.c +1392 -0
  259. package/vendor/mbedtls/library/pkwrite.c +631 -0
  260. package/vendor/mbedtls/library/pkwrite.h +121 -0
  261. package/vendor/mbedtls/library/platform.c +402 -0
  262. package/vendor/mbedtls/library/platform_util.c +258 -0
  263. package/vendor/mbedtls/library/poly1305.c +492 -0
  264. package/vendor/mbedtls/library/psa_crypto.c +9532 -0
  265. package/vendor/mbedtls/library/psa_crypto_aead.c +646 -0
  266. package/vendor/mbedtls/library/psa_crypto_aead.h +499 -0
  267. package/vendor/mbedtls/library/psa_crypto_cipher.c +747 -0
  268. package/vendor/mbedtls/library/psa_crypto_cipher.h +316 -0
  269. package/vendor/mbedtls/library/psa_crypto_client.c +22 -0
  270. package/vendor/mbedtls/library/psa_crypto_core.h +983 -0
  271. package/vendor/mbedtls/library/psa_crypto_core_common.h +52 -0
  272. package/vendor/mbedtls/library/psa_crypto_driver_wrappers.h +2896 -0
  273. package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.c +256 -0
  274. package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.h +31 -0
  275. package/vendor/mbedtls/library/psa_crypto_ecp.c +594 -0
  276. package/vendor/mbedtls/library/psa_crypto_ecp.h +267 -0
  277. package/vendor/mbedtls/library/psa_crypto_ffdh.c +361 -0
  278. package/vendor/mbedtls/library/psa_crypto_ffdh.h +131 -0
  279. package/vendor/mbedtls/library/psa_crypto_hash.c +470 -0
  280. package/vendor/mbedtls/library/psa_crypto_hash.h +211 -0
  281. package/vendor/mbedtls/library/psa_crypto_invasive.h +92 -0
  282. package/vendor/mbedtls/library/psa_crypto_its.h +131 -0
  283. package/vendor/mbedtls/library/psa_crypto_mac.c +505 -0
  284. package/vendor/mbedtls/library/psa_crypto_mac.h +264 -0
  285. package/vendor/mbedtls/library/psa_crypto_pake.c +571 -0
  286. package/vendor/mbedtls/library/psa_crypto_pake.h +159 -0
  287. package/vendor/mbedtls/library/psa_crypto_random.c +181 -0
  288. package/vendor/mbedtls/library/psa_crypto_random.h +72 -0
  289. package/vendor/mbedtls/library/psa_crypto_random_impl.h +200 -0
  290. package/vendor/mbedtls/library/psa_crypto_rsa.c +714 -0
  291. package/vendor/mbedtls/library/psa_crypto_rsa.h +321 -0
  292. package/vendor/mbedtls/library/psa_crypto_se.c +373 -0
  293. package/vendor/mbedtls/library/psa_crypto_se.h +192 -0
  294. package/vendor/mbedtls/library/psa_crypto_slot_management.c +1137 -0
  295. package/vendor/mbedtls/library/psa_crypto_slot_management.h +344 -0
  296. package/vendor/mbedtls/library/psa_crypto_storage.c +481 -0
  297. package/vendor/mbedtls/library/psa_crypto_storage.h +392 -0
  298. package/vendor/mbedtls/library/psa_its_file.c +254 -0
  299. package/vendor/mbedtls/library/psa_util.c +614 -0
  300. package/vendor/mbedtls/library/psa_util_internal.h +100 -0
  301. package/vendor/mbedtls/library/ripemd160.c +490 -0
  302. package/vendor/mbedtls/library/rsa.c +3121 -0
  303. package/vendor/mbedtls/library/rsa_alt_helpers.c +455 -0
  304. package/vendor/mbedtls/library/rsa_alt_helpers.h +209 -0
  305. package/vendor/mbedtls/library/rsa_internal.h +188 -0
  306. package/vendor/mbedtls/library/sha1.c +480 -0
  307. package/vendor/mbedtls/library/sha256.c +983 -0
  308. package/vendor/mbedtls/library/sha3.c +721 -0
  309. package/vendor/mbedtls/library/sha512.c +1115 -0
  310. package/vendor/mbedtls/library/ssl_cache.c +410 -0
  311. package/vendor/mbedtls/library/ssl_ciphersuites.c +2050 -0
  312. package/vendor/mbedtls/library/ssl_ciphersuites_internal.h +154 -0
  313. package/vendor/mbedtls/library/ssl_client.c +1023 -0
  314. package/vendor/mbedtls/library/ssl_client.h +22 -0
  315. package/vendor/mbedtls/library/ssl_cookie.c +384 -0
  316. package/vendor/mbedtls/library/ssl_debug_helpers.h +85 -0
  317. package/vendor/mbedtls/library/ssl_debug_helpers_generated.c +251 -0
  318. package/vendor/mbedtls/library/ssl_misc.h +3198 -0
  319. package/vendor/mbedtls/library/ssl_msg.c +6619 -0
  320. package/vendor/mbedtls/library/ssl_ticket.c +556 -0
  321. package/vendor/mbedtls/library/ssl_tls.c +10234 -0
  322. package/vendor/mbedtls/library/ssl_tls12_client.c +3688 -0
  323. package/vendor/mbedtls/library/ssl_tls12_server.c +4311 -0
  324. package/vendor/mbedtls/library/ssl_tls13_client.c +3208 -0
  325. package/vendor/mbedtls/library/ssl_tls13_generic.c +1793 -0
  326. package/vendor/mbedtls/library/ssl_tls13_invasive.h +23 -0
  327. package/vendor/mbedtls/library/ssl_tls13_keys.c +1918 -0
  328. package/vendor/mbedtls/library/ssl_tls13_keys.h +668 -0
  329. package/vendor/mbedtls/library/ssl_tls13_server.c +3624 -0
  330. package/vendor/mbedtls/library/threading.c +193 -0
  331. package/vendor/mbedtls/library/threading_internal.h +28 -0
  332. package/vendor/mbedtls/library/timing.c +152 -0
  333. package/vendor/mbedtls/library/version.c +32 -0
  334. package/vendor/mbedtls/library/version_features.c +856 -0
  335. package/vendor/mbedtls/library/x509.c +1776 -0
  336. package/vendor/mbedtls/library/x509_create.c +570 -0
  337. package/vendor/mbedtls/library/x509_crl.c +708 -0
  338. package/vendor/mbedtls/library/x509_crt.c +3311 -0
  339. package/vendor/mbedtls/library/x509_csr.c +648 -0
  340. package/vendor/mbedtls/library/x509_internal.h +101 -0
  341. package/vendor/mbedtls/library/x509write.c +174 -0
  342. package/vendor/mbedtls/library/x509write_crt.c +688 -0
  343. package/vendor/mbedtls/library/x509write_csr.c +336 -0
  344. package/vendor/quickjs-ng/CMakeLists.txt +566 -0
  345. package/vendor/quickjs-ng/LICENSE +24 -0
  346. package/vendor/quickjs-ng/README.md +24 -0
  347. package/vendor/quickjs-ng/api-test.c +1195 -0
  348. package/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
  349. package/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
  350. package/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
  351. package/vendor/quickjs-ng/cutils.h +1998 -0
  352. package/vendor/quickjs-ng/dtoa.c +1619 -0
  353. package/vendor/quickjs-ng/dtoa.h +87 -0
  354. package/vendor/quickjs-ng/gen/function_source.c +81 -0
  355. package/vendor/quickjs-ng/gen/hello.c +53 -0
  356. package/vendor/quickjs-ng/gen/hello_module.c +106 -0
  357. package/vendor/quickjs-ng/gen/repl.c +3036 -0
  358. package/vendor/quickjs-ng/gen/standalone.c +323 -0
  359. package/vendor/quickjs-ng/gen/test_fib.c +81 -0
  360. package/vendor/quickjs-ng/libregexp-opcode.h +73 -0
  361. package/vendor/quickjs-ng/libregexp.c +3474 -0
  362. package/vendor/quickjs-ng/libregexp.h +101 -0
  363. package/vendor/quickjs-ng/libunicode-table.h +5173 -0
  364. package/vendor/quickjs-ng/libunicode.c +2069 -0
  365. package/vendor/quickjs-ng/libunicode.h +172 -0
  366. package/vendor/quickjs-ng/list.h +107 -0
  367. package/vendor/quickjs-ng/lre-test.c +52 -0
  368. package/vendor/quickjs-ng/qjs.c +762 -0
  369. package/vendor/quickjs-ng/qjsc.c +673 -0
  370. package/vendor/quickjs-ng/quickjs-atom.h +280 -0
  371. package/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
  372. package/vendor/quickjs-ng/quickjs-libc.c +5037 -0
  373. package/vendor/quickjs-ng/quickjs-libc.h +87 -0
  374. package/vendor/quickjs-ng/quickjs-opcode.h +377 -0
  375. package/vendor/quickjs-ng/quickjs.c +64041 -0
  376. package/vendor/quickjs-ng/quickjs.h +1447 -0
  377. package/vendor/quickjs-ng/run-test262.c +2374 -0
  378. package/vendor/quickjs-ng/unicode_gen.c +3121 -0
  379. package/vendor/quickjs-ng/unicode_gen_def.h +310 -0
  380. package/vendor/ryu/LICENSE-Boost +23 -0
  381. package/vendor/ryu/common.h +114 -0
  382. package/vendor/ryu/d2s.c +509 -0
  383. package/vendor/ryu/d2s_full_table.h +367 -0
  384. package/vendor/ryu/d2s_intrinsics.h +357 -0
  385. package/vendor/ryu/d2s_small_table.h +186 -0
  386. package/vendor/ryu/digit_table.h +35 -0
  387. package/vendor/ryu/ryu.h +46 -0
  388. package/vendor/zlib/LICENSE +22 -0
  389. package/vendor/zlib/adler32.c +164 -0
  390. package/vendor/zlib/compress.c +75 -0
  391. package/vendor/zlib/crc32.c +1049 -0
  392. package/vendor/zlib/crc32.h +9446 -0
  393. package/vendor/zlib/deflate.c +2139 -0
  394. package/vendor/zlib/deflate.h +377 -0
  395. package/vendor/zlib/gzclose.c +23 -0
  396. package/vendor/zlib/gzguts.h +214 -0
  397. package/vendor/zlib/gzlib.c +582 -0
  398. package/vendor/zlib/gzread.c +602 -0
  399. package/vendor/zlib/gzwrite.c +631 -0
  400. package/vendor/zlib/infback.c +628 -0
  401. package/vendor/zlib/inffast.c +320 -0
  402. package/vendor/zlib/inffast.h +11 -0
  403. package/vendor/zlib/inffixed.h +94 -0
  404. package/vendor/zlib/inflate.c +1526 -0
  405. package/vendor/zlib/inflate.h +126 -0
  406. package/vendor/zlib/inftrees.c +299 -0
  407. package/vendor/zlib/inftrees.h +62 -0
  408. package/vendor/zlib/trees.c +1117 -0
  409. package/vendor/zlib/trees.h +128 -0
  410. package/vendor/zlib/uncompr.c +85 -0
  411. package/vendor/zlib/zconf.h +543 -0
  412. package/vendor/zlib/zlib.h +1938 -0
  413. package/vendor/zlib/zutil.c +299 -0
  414. package/vendor/zlib/zutil.h +254 -0
  415. package/README.md +0 -3
  416. package/index.js +0 -2
package/src/scr_http.c ADDED
@@ -0,0 +1,3482 @@
1
+ /* node:http — the SERVER and CLIENT slices, layered on scr_net.c's
2
+ * native hooks: a hand-written HTTP/1.1 parser (request OR status line,
3
+ * headers, Content-Length / chunked / read-to-EOF bodies, keep-alive)
4
+ * and Node-shaped serializers (the response's writeHead/setHeader/end
5
+ * with Content-Length or chunked framing, the Date header, keep-alive
6
+ * Connection headers; the client's request head with Node's exact
7
+ * header order and framing decisions).
8
+ *
9
+ * The CLIENT (scr_http_request — http.request/http.get): one dialed
10
+ * connection per request, NO agent pooling (the wire still carries
11
+ * Node's Connection: keep-alive; the socket closes when the response
12
+ * completes — SEMANTICS.md). The response is an ordinary ScrHttpReq
13
+ * (IncomingMessage) parsed in RESPONSE mode: status line instead of
14
+ * request line, and a body that may be EOF-delimited; HEAD/204/304
15
+ * responses have none. Error shapes are Node's: the net layer's
16
+ * 'connect ECONNREFUSED ip:port' via the socket's native error hook, a
17
+ * premature close before any response is 'socket hang up' on the
18
+ * request, and mid-body death is 'aborted' on the RESPONSE (the request
19
+ * just closes) — all pinned by the client differential fixtures.
20
+ * Deferred 'close' emits ride the emit QUEUE drained from scr_net.c's
21
+ * proto sweep, so req/res 'close' fire a pass after the work that
22
+ * flagged them, Node's later-than-the-handler ordering (client order:
23
+ * res 'end', req 'close', res 'close').
24
+ * NO external dependencies — the parser implements exactly what the
25
+ * differential fixtures and portless-shaped handlers exercise, and
26
+ * SEMANTICS.md states its bounds honestly.
27
+ *
28
+ * Object model: http.createServer returns an ORDINARY ScrNetServer (the
29
+ * frontend maps http.Server to the same netServer kind — listen/close/
30
+ * address()/error all reuse the net lowering) whose native-connection
31
+ * hook installs one ScrHttpConn parser per accepted socket. ScrHttpReq /
32
+ * ScrHttpRes are lean refcounted handles (the ScrChild story): the req
33
+ * holds the parsed method/url/headers and the body listener lists
34
+ * (dropped when the body completes or the connection dies); the res
35
+ * holds the socket (+1), the pending header list, and the framing state.
36
+ *
37
+ * Keep-alive: HTTP/1.1 requests keep the connection open (1.0 opts in
38
+ * with Connection: keep-alive) and the parser resets for the next
39
+ * request on the same socket, pipelining included; Connection: close —
40
+ * either side — ends the socket after the response finishes. There is NO
41
+ * idle keep-alive timeout in this slice (Node reaps idle connections
42
+ * after server.keepAliveTimeout, 5s — SEMANTICS.md); real clients close
43
+ * their idle sockets and the fixtures' drivers do too.
44
+ *
45
+ * Event dispatch rides scr_net.c wholesale: the request handler and the
46
+ * req 'data'/'end' listeners fire from the socket read path (macrotasks,
47
+ * snapshot semantics, once-before-run), and errors/teardown follow the
48
+ * socket's own story. */
49
+ #include "scr_runtime.h"
50
+
51
+ #include <ctype.h>
52
+ #include <stdio.h>
53
+ #include <stdlib.h>
54
+ #include <string.h>
55
+ #include <time.h>
56
+
57
+ static void scr_http_oom(void) {
58
+ fputs("scriptc: out of memory\n", stderr);
59
+ abort();
60
+ }
61
+
62
+ /* Node's STATUS_CODES reason phrases (the slice's subset; everything the
63
+ * fixtures and portless's handlers send, plus the common neighbors). */
64
+ static const char *scr_http_reason(int code) {
65
+ switch (code) {
66
+ case 100: return "Continue";
67
+ case 101: return "Switching Protocols";
68
+ case 200: return "OK";
69
+ case 201: return "Created";
70
+ case 202: return "Accepted";
71
+ case 204: return "No Content";
72
+ case 206: return "Partial Content";
73
+ case 301: return "Moved Permanently";
74
+ case 302: return "Found";
75
+ case 303: return "See Other";
76
+ case 304: return "Not Modified";
77
+ case 307: return "Temporary Redirect";
78
+ case 308: return "Permanent Redirect";
79
+ case 400: return "Bad Request";
80
+ case 401: return "Unauthorized";
81
+ case 403: return "Forbidden";
82
+ case 404: return "Not Found";
83
+ case 405: return "Method Not Allowed";
84
+ case 408: return "Request Timeout";
85
+ case 409: return "Conflict";
86
+ case 410: return "Gone";
87
+ case 413: return "Payload Too Large";
88
+ case 414: return "URI Too Long";
89
+ case 415: return "Unsupported Media Type";
90
+ case 429: return "Too Many Requests";
91
+ case 431: return "Request Header Fields Too Large";
92
+ case 500: return "Internal Server Error";
93
+ case 501: return "Not Implemented";
94
+ case 502: return "Bad Gateway";
95
+ case 503: return "Service Unavailable";
96
+ case 504: return "Gateway Timeout";
97
+ case 508: return "Loop Detected";
98
+ default: return "unknown";
99
+ }
100
+ }
101
+
102
+ /* ── the h2 compat transport seam ────────────────────────────────────────
103
+ *
104
+ * Http2ServerRequest/Http2ServerResponse ARE these req/res handles: the
105
+ * http/1 surface is the API template, the h2 stream machinery is the
106
+ * transport. scr_http2.c (linked exactly when the program uses the real
107
+ * h2 surface — this unit can link WITHOUT it) installs a vtable for the
108
+ * response write paths (HEADERS/DATA frames instead of HTTP/1 bytes) and
109
+ * a request-registration hook so server.on("request", ...) on an
110
+ * h2-tagged server ctx routes to the h2 request list. The vtable's
111
+ * typedef lives in scr_runtime.h (both units name it). */
112
+ static const ScrHttpH2Ops *scr_http_h2_ops = NULL;
113
+ static void (*scr_http_h2_request_hook)(void *h2ctx, ScrClosure *cb /*moves*/,
114
+ void *fn, bool once) = NULL;
115
+
116
+ void scr_http_set_h2_ops(const ScrHttpH2Ops *ops) { scr_http_h2_ops = ops; }
117
+
118
+ void scr_http_set_h2_request_hook(void (*hook)(void *h2ctx, ScrClosure *cb, void *fn, bool once)) {
119
+ scr_http_h2_request_hook = hook;
120
+ }
121
+
122
+ /* ── the request handle ──────────────────────────────────────────────── */
123
+
124
+ struct ScrHttpReq {
125
+ size_t rc;
126
+ int status; /* -1 on server requests (Node's undefined statusCode) */
127
+ ScrStr *method;
128
+ ScrStr *url;
129
+ ScrNetSocket *sock; /* +1 — req.socket, may be NULL defensively */
130
+ void *h2_stream; /* +1 through scr_http_h2_ops — the h2 compat request's
131
+ * stream (destroy() RSTs it, never the shared socket) */
132
+ ScrStr **hnames; /* lowercased */
133
+ ScrStr **hnames_raw; /* arrival case — rawHeaders */
134
+ ScrStr **hvalues;
135
+ size_t nheaders;
136
+ ScrStr *status_msg; /* client responses' reason phrase; NULL on server requests */
137
+ ScrNetLs data_ls, end_ls, err_ls, close_ls;
138
+ /* pipe destinations (req.pipe(...) — one of each kind, +1; released at
139
+ * finish, so a piped destination never outlives the body) */
140
+ ScrHttpRes *pipe_res;
141
+ struct ScrHttpClientReq *pipe_client;
142
+ ScrNetSocket *pipe_sock;
143
+ bool ended; /* body complete (or connection dead): data/end drop */
144
+ bool enc_utf8; /* setEncoding('utf8'): 'data' delivers strings */
145
+ bool http10; /* the parsed request/status line's version (httpVersion) */
146
+ bool http2; /* an h2 compat request (httpVersion "2.0") */
147
+ bool aborted; /* h2: the stream died with our writable side open */
148
+ bool close_queued;
149
+ bool close_emitted; /* settled: err/close listeners dropped */
150
+ bool join_dup; /* joinDuplicateHeaders: repeated names read joined ", " */
151
+ ScrNetLs aborted_ls; /* req.on('aborted') — the h2 compat event */
152
+ };
153
+
154
+ #ifdef SCR_RC_AUDIT
155
+ static long scr_http_live = 0;
156
+ long scr_http_live_count(void) { return scr_http_live; }
157
+ #endif
158
+
159
+ ScrHttpReq *scr_http_req_retain(ScrHttpReq *r) {
160
+ if (r->rc != SIZE_MAX) r->rc++;
161
+ return r;
162
+ }
163
+
164
+ /* req.setEncoding(enc) — IncomingMessage's readable setEncoding (server
165
+ * requests and client responses alike): utf8 flips 'data' delivery to
166
+ * strings (the chunk-encoding window); other REAL Node encodings meet
167
+ * the loud not-supported ladder; unknown names throw Node's
168
+ * ERR_UNKNOWN_ENCODING TypeError. */
169
+ void scr_http_req_set_encoding(ScrHttpReq *r, ScrStr *enc /*borrowed*/) {
170
+ if ((enc->len == 4 && memcmp(enc->data, "utf8", 4) == 0) ||
171
+ (enc->len == 5 && memcmp(enc->data, "utf-8", 5) == 0)) {
172
+ r->enc_utf8 = true;
173
+ return;
174
+ }
175
+ static const char *const known[] = { "ascii", "latin1", "binary", "base64",
176
+ "base64url", "hex", "ucs2", "ucs-2", "utf16le", "utf-16le", NULL };
177
+ for (size_t i = 0; known[i]; i++) {
178
+ if (enc->len == strlen(known[i]) && memcmp(enc->data, known[i], enc->len) == 0) {
179
+ char msg[128];
180
+ int n = snprintf(msg, sizeof msg, "setEncoding('%s') is not supported yet (only 'utf8' here)",
181
+ known[i]);
182
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, (size_t)n);
183
+ return;
184
+ }
185
+ }
186
+ char msg[128];
187
+ int n = snprintf(msg, sizeof msg, "Unknown encoding: %.*s",
188
+ (int)(enc->len < 64 ? enc->len : 64), enc->data);
189
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)n, "ERR_UNKNOWN_ENCODING");
190
+ }
191
+
192
+ void scr_http_req_release(ScrHttpReq *r) {
193
+ if (!r || r->rc == SIZE_MAX) return;
194
+ if (--r->rc == 0) {
195
+ scr_str_release(r->method);
196
+ scr_str_release(r->url);
197
+ for (size_t i = 0; i < r->nheaders; i++) {
198
+ scr_str_release(r->hnames[i]);
199
+ scr_str_release(r->hnames_raw[i]);
200
+ scr_str_release(r->hvalues[i]);
201
+ }
202
+ free(r->hnames);
203
+ free(r->hnames_raw);
204
+ free(r->hvalues);
205
+ scr_str_release(r->status_msg);
206
+ scr_net_ls_drop(&r->data_ls);
207
+ scr_net_ls_drop(&r->end_ls);
208
+ scr_net_ls_drop(&r->err_ls);
209
+ scr_net_ls_drop(&r->close_ls);
210
+ scr_net_ls_drop(&r->aborted_ls);
211
+ scr_http_res_release(r->pipe_res);
212
+ scr_http_client_release(r->pipe_client);
213
+ scr_net_sock_release(r->pipe_sock);
214
+ if (r->sock) scr_net_sock_release(r->sock);
215
+ if (r->h2_stream) scr_http_h2_ops->release(r->h2_stream);
216
+ #ifdef SCR_RC_AUDIT
217
+ scr_http_live--;
218
+ #endif
219
+ free(r);
220
+ }
221
+ }
222
+
223
+ void *scr_http_req_retain_v(void *p) { return scr_http_req_retain((ScrHttpReq *)p); }
224
+ void scr_http_req_release_v(void *p) { scr_http_req_release((ScrHttpReq *)p); }
225
+
226
+ ScrStr *scr_http_req_url(ScrHttpReq *r) { return scr_str_retain(r->url); }
227
+ ScrStr *scr_http_req_method(ScrHttpReq *r) { return scr_str_retain(r->method); }
228
+
229
+ /* Case-insensitive equality of two lowercased-vs-any names. */
230
+ static bool scr_http_name_eq(const ScrStr *lower, const ScrStr *name) {
231
+ if (lower->len != name->len) return false;
232
+ for (size_t j = 0; j < name->len; j++) {
233
+ if (lower->data[j] != (char)tolower((unsigned char)name->data[j])) return false;
234
+ }
235
+ return true;
236
+ }
237
+
238
+ /* The joined ", " value of every occurrence of hnames[i]'s name — the
239
+ * joinDuplicateHeaders read (createServer option): Node joins repeats
240
+ * where the default keeps the first. Always +1. */
241
+ static ScrStr *scr_http_req_joined_value(ScrHttpReq *r, size_t i) {
242
+ size_t total = 0, count = 0;
243
+ for (size_t k = 0; k < r->nheaders; k++) {
244
+ if (scr_http_name_eq(r->hnames[k], r->hnames[i])) {
245
+ total += r->hvalues[k]->len;
246
+ count++;
247
+ }
248
+ }
249
+ if (count == 1) return scr_str_retain(r->hvalues[i]);
250
+ char *buf = malloc(total + (count - 1) * 2 + 1);
251
+ if (!buf) scr_http_oom();
252
+ size_t off = 0;
253
+ for (size_t k = 0; k < r->nheaders; k++) {
254
+ if (!scr_http_name_eq(r->hnames[k], r->hnames[i])) continue;
255
+ if (off > 0) {
256
+ memcpy(buf + off, ", ", 2);
257
+ off += 2;
258
+ }
259
+ memcpy(buf + off, r->hvalues[k]->data, r->hvalues[k]->len);
260
+ off += r->hvalues[k]->len;
261
+ }
262
+ ScrStr *out = scr_str_new(buf, off);
263
+ free(buf);
264
+ return out;
265
+ }
266
+
267
+ /* The snapshot pairs behind `{ ...req.headers }` (the record-building
268
+ * helper's feed): [lowercased name, value, ...] in arrival order — the
269
+ * same keys the per-name reads answer (under joinDuplicateHeaders a
270
+ * repeated name appears once, at its first position, joined). Always a
271
+ * fresh +1 array. */
272
+ ScrArr *scr_http_req_header_pairs(ScrHttpReq *r) {
273
+ ScrArr *out = scr_arr_new(SCR_ELEM_STR, r->nheaders * 2);
274
+ for (size_t i = 0; i < r->nheaders; i++) {
275
+ if (r->join_dup) {
276
+ bool seen = false;
277
+ for (size_t k = 0; k < i && !seen; k++) {
278
+ seen = scr_http_name_eq(r->hnames[k], r->hnames[i]);
279
+ }
280
+ if (seen) continue;
281
+ scr_arr_push_ref(out, scr_str_retain(r->hnames[i]));
282
+ scr_arr_push_ref(out, scr_http_req_joined_value(r, i));
283
+ continue;
284
+ }
285
+ scr_arr_push_ref(out, scr_str_retain(r->hnames[i]));
286
+ scr_arr_push_ref(out, scr_str_retain(r->hvalues[i]));
287
+ }
288
+ return out;
289
+ }
290
+
291
+ /* req.rawHeaders: [name, value, name, value, ...] in arrival order, names
292
+ * in their ORIGINAL case (Node's shape). Always a fresh +1 array. */
293
+ ScrArr *scr_http_req_raw_headers(ScrHttpReq *r) {
294
+ ScrArr *out = scr_arr_new(SCR_ELEM_STR, r->nheaders * 2);
295
+ for (size_t i = 0; i < r->nheaders; i++) {
296
+ scr_arr_push_ref(out, scr_str_retain(r->hnames_raw[i]));
297
+ scr_arr_push_ref(out, scr_str_retain(r->hvalues[i]));
298
+ }
299
+ return out;
300
+ }
301
+
302
+ /* res.statusMessage: the reason phrase on client responses (+1, "" when
303
+ * the status line carried none); NULL on server requests — the
304
+ * compiler's undefined arm, the statusCode split. */
305
+ ScrStr *scr_http_req_status_message(ScrHttpReq *r) {
306
+ return r->status_msg ? scr_str_retain(r->status_msg) : NULL;
307
+ }
308
+
309
+ /* Header lookup by (case-insensitively matched) name: +1 value, or NULL —
310
+ * the compiler's undefined arm, exactly process.envGet's contract. */
311
+ ScrStr *scr_http_req_header(ScrHttpReq *r, ScrStr *name) {
312
+ for (size_t i = 0; i < r->nheaders; i++) {
313
+ if (scr_http_name_eq(r->hnames[i], name)) {
314
+ /* joinDuplicateHeaders: every occurrence joins ", " (Node's option);
315
+ * the default answers the first, Node's keep-first rule. */
316
+ return r->join_dup ? scr_http_req_joined_value(r, i) : scr_str_retain(r->hvalues[i]);
317
+ }
318
+ }
319
+ return NULL;
320
+ }
321
+
322
+ /* req.pipe(dest) — the proxy legs: an IncomingMessage body streams into
323
+ * a ServerResponse, a ClientRequest, or a raw socket (chunk-for-chunk, no
324
+ * backpressure — divergence 54's stream model); the body's natural end
325
+ * ends the destination, Node's pipe default. A body that already ended
326
+ * ends the destination NOW (nothing more will flow). */
327
+ void scr_http_req_pipe_res(ScrHttpReq *r, ScrHttpRes *dst /*borrowed*/) {
328
+ if (r->ended) {
329
+ scr_http_res_end(dst);
330
+ return;
331
+ }
332
+ if (r->pipe_res) scr_http_res_release(r->pipe_res);
333
+ r->pipe_res = scr_http_res_retain(dst);
334
+ }
335
+
336
+ /* socket.pipe(res) — the extended-CONNECT bridge leg: a native reader on
337
+ * the SOURCE socket turns raw chunks into response body writes (the
338
+ * response's own framing applies) and EOF into res.end(), pipe's
339
+ * default. The ctx owns the response (+1, released with the socket's
340
+ * native-reader teardown); errors/closes need no handling here — the
341
+ * caller registers its own socket listeners (the portless cleanup). */
342
+ typedef struct {
343
+ ScrHttpRes *res; /* +1 */
344
+ } ScrSockResPipe;
345
+
346
+ static void scr_http_sock_res_data(void *ctx, const char *buf, size_t n) {
347
+ ScrSockResPipe *p = (ScrSockResPipe *)ctx;
348
+ ScrBytes *chunk = scr_bytes_new(SCR_BYTES_U8, (double)n);
349
+ if (n > 0) memcpy(chunk->data, buf, n);
350
+ scr_http_res_write_bytes(p->res, chunk);
351
+ scr_bytes_release(chunk);
352
+ }
353
+
354
+ static void scr_http_sock_res_eof(void *ctx) {
355
+ ScrSockResPipe *p = (ScrSockResPipe *)ctx;
356
+ scr_http_res_end(p->res);
357
+ }
358
+
359
+ static void scr_http_sock_res_closed(void *ctx) { (void)ctx; }
360
+
361
+ static void scr_http_sock_res_free(void *ctx) {
362
+ ScrSockResPipe *p = (ScrSockResPipe *)ctx;
363
+ scr_http_res_release(p->res);
364
+ free(p);
365
+ }
366
+
367
+ void scr_http_sock_pipe_res(ScrNetSocket *src, ScrHttpRes *dst /*borrowed*/) {
368
+ ScrSockResPipe *p = calloc(1, sizeof *p);
369
+ if (!p) scr_http_oom();
370
+ p->res = scr_http_res_retain(dst);
371
+ scr_net_sock_set_native_reader(src, &scr_http_sock_res_data, &scr_http_sock_res_eof,
372
+ &scr_http_sock_res_closed, p, &scr_http_sock_res_free);
373
+ }
374
+
375
+ void scr_http_req_pipe_client(ScrHttpReq *r, ScrHttpClientReq *dst /*borrowed*/) {
376
+ if (r->ended) {
377
+ scr_http_client_end(dst);
378
+ return;
379
+ }
380
+ if (r->pipe_client) scr_http_client_release(r->pipe_client);
381
+ r->pipe_client = scr_http_client_retain(dst);
382
+ }
383
+
384
+ void scr_http_req_pipe_sock(ScrHttpReq *r, ScrNetSocket *dst /*borrowed*/) {
385
+ if (r->ended) {
386
+ scr_net_sock_end(dst);
387
+ return;
388
+ }
389
+ if (r->pipe_sock) scr_net_sock_release(r->pipe_sock);
390
+ r->pipe_sock = scr_net_sock_retain(dst);
391
+ }
392
+
393
+ void scr_http_req_on_data(ScrHttpReq *r, ScrClosure *cb /*moves*/, ScrNetDataFn fn, bool once) {
394
+ if (r->ended) {
395
+ scr_closure_release(cb);
396
+ return;
397
+ }
398
+ scr_net_ls_add(&r->data_ls, cb, (void *)fn, once);
399
+ }
400
+
401
+ void scr_http_req_on_end(ScrHttpReq *r, ScrClosure *cb /*moves*/, bool once) {
402
+ if (r->ended) {
403
+ scr_closure_release(cb);
404
+ return;
405
+ }
406
+ scr_net_ls_add(&r->end_ls, cb, NULL, once);
407
+ }
408
+
409
+ double scr_http_req_status(ScrHttpReq *r) { return (double)r->status; }
410
+
411
+ ScrNetSocket *scr_http_req_socket(ScrHttpReq *r) {
412
+ /* +1; a NULL socket cannot reach the program (every req is built over
413
+ * a live connection), but stay defensive for the emitter's contract */
414
+ return r->sock ? scr_net_sock_retain(r->sock) : NULL;
415
+ }
416
+
417
+ /* resume(): a flow-control no-op — this parser always consumes body
418
+ * bytes and fires 'end' regardless of consumers (SEMANTICS.md; Node
419
+ * requires the stream to flow). */
420
+ void scr_http_req_resume(ScrHttpReq *r) { (void)r; }
421
+
422
+ /* destroy(): tears the underlying connection down NOW; the teardown
423
+ * events flow through the socket's native hooks like a peer close. */
424
+ void scr_http_req_destroy(ScrHttpReq *r) {
425
+ if (r->h2_stream != NULL) {
426
+ scr_http_h2_ops->destroy(r->h2_stream);
427
+ return;
428
+ }
429
+ if (r->sock) scr_net_sock_destroy(r->sock);
430
+ }
431
+
432
+ void scr_http_req_on_error(ScrHttpReq *r, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once) {
433
+ if (r->close_emitted) {
434
+ scr_closure_release(cb);
435
+ return;
436
+ }
437
+ scr_net_ls_add(&r->err_ls, cb, (void *)fn, once);
438
+ }
439
+
440
+ void scr_http_req_on_close(ScrHttpReq *r, ScrClosure *cb /*moves*/, bool once) {
441
+ if (r->close_emitted) {
442
+ scr_closure_release(cb);
443
+ return;
444
+ }
445
+ scr_net_ls_add(&r->close_ls, cb, NULL, once);
446
+ }
447
+
448
+ /* req.on('aborted') — the h2 compat event (an http/1 request registers
449
+ * too; the parser lane never fires it, matching its 'error'-only story). */
450
+ void scr_http_req_on_aborted(ScrHttpReq *r, ScrClosure *cb /*moves*/, bool once) {
451
+ if (r->close_emitted || r->aborted) {
452
+ scr_closure_release(cb);
453
+ return;
454
+ }
455
+ scr_net_ls_add(&r->aborted_ls, cb, NULL, once);
456
+ }
457
+
458
+ /* Body completion: 'end' fires, then both listener lists drop (the
459
+ * settle-releases-listeners story — a handler closure capturing its own
460
+ * req cannot cycle past the body). */
461
+ static void scr_http_req_finish(ScrHttpReq *r, bool fire) {
462
+ if (r->ended) return;
463
+ r->ended = true;
464
+ if (fire) scr_net_fire0_this(&r->end_ls, r, SCR_DYNH_HTTP_REQ);
465
+ scr_net_ls_drop(&r->data_ls);
466
+ scr_net_ls_drop(&r->end_ls);
467
+ /* pipes: a NATURAL end ends the destination (Node's pipe end:true
468
+ * default); an aborted body just drops the edge — the destination's
469
+ * own error/close story is already running */
470
+ if (r->pipe_res) {
471
+ if (fire) scr_http_res_end(r->pipe_res);
472
+ scr_http_res_release(r->pipe_res);
473
+ r->pipe_res = NULL;
474
+ }
475
+ if (r->pipe_client) {
476
+ if (fire) scr_http_client_end(r->pipe_client);
477
+ scr_http_client_release(r->pipe_client);
478
+ r->pipe_client = NULL;
479
+ }
480
+ if (r->pipe_sock) {
481
+ if (fire) scr_net_sock_end(r->pipe_sock);
482
+ scr_net_sock_release(r->pipe_sock);
483
+ r->pipe_sock = NULL;
484
+ }
485
+ }
486
+
487
+ /* ── the response handle ─────────────────────────────────────────────── */
488
+
489
+ struct ScrHttpRes {
490
+ size_t rc;
491
+ ScrNetSocket *sock; /* +1; writes after the connection died are no-ops */
492
+ void *h2_stream; /* +1 through scr_http_h2_ops — set on h2 compat
493
+ * responses; every write path routes to frames */
494
+ struct ScrHttpConn *conn; /* backref, no ref (conn outlives via socket ctx) */
495
+ int status;
496
+ ScrStr *status_msg; /* res.statusMessage / writeHead's reason phrase;
497
+ * NULL = the status code's default phrase */
498
+ ScrStr **hnames; /* as-set (serialized verbatim, Node keeps the case) */
499
+ ScrStr **hvalues;
500
+ size_t nheaders, cap_headers;
501
+ bool head_sent;
502
+ bool chunked;
503
+ bool finished;
504
+ bool no_date; /* res.sendDate = false: suppress the implicit Date header */
505
+ bool keep_alive; /* the REQUEST's verdict; Connection: close overrides */
506
+ ScrNetLs close_ls;
507
+ ScrNetLs finish_ls; /* res.end(cb) — fires deferred once the body went out */
508
+ bool finish_queued;
509
+ bool close_queued;
510
+ bool close_emitted;
511
+ };
512
+
513
+ ScrHttpRes *scr_http_res_retain(ScrHttpRes *r) {
514
+ if (r->rc != SIZE_MAX) r->rc++;
515
+ return r;
516
+ }
517
+
518
+ void scr_http_res_release(ScrHttpRes *r) {
519
+ if (!r || r->rc == SIZE_MAX) return;
520
+ if (--r->rc == 0) {
521
+ for (size_t i = 0; i < r->nheaders; i++) {
522
+ scr_str_release(r->hnames[i]);
523
+ scr_str_release(r->hvalues[i]);
524
+ }
525
+ free(r->hnames);
526
+ free(r->hvalues);
527
+ scr_str_release(r->status_msg);
528
+ scr_net_ls_drop(&r->close_ls);
529
+ scr_net_ls_drop(&r->finish_ls);
530
+ if (r->sock) scr_net_sock_release(r->sock);
531
+ if (r->h2_stream) scr_http_h2_ops->release(r->h2_stream);
532
+ #ifdef SCR_RC_AUDIT
533
+ scr_http_live--;
534
+ #endif
535
+ free(r);
536
+ }
537
+ }
538
+
539
+ void *scr_http_res_retain_v(void *p) { return scr_http_res_retain((ScrHttpRes *)p); }
540
+ void scr_http_res_release_v(void *p) { scr_http_res_release((ScrHttpRes *)p); }
541
+
542
+ bool scr_http_res_headers_sent(ScrHttpRes *r) { return r->head_sent; }
543
+
544
+ /* res.statusCode: 200 until assigned (Node's fresh-response default);
545
+ * assignment after the head went out is inert (Node throws on the WRITE
546
+ * paths, not the property — the sent head simply no longer changes). */
547
+ double scr_http_res_status_get(ScrHttpRes *r) { return r->status > 0 ? (double)r->status : 200; }
548
+
549
+ void scr_http_res_status_set(ScrHttpRes *r, double status) {
550
+ if (r->head_sent) return;
551
+ r->status = (int)status;
552
+ }
553
+
554
+ /* res.statusMessage: the assigned reason phrase, or the current status
555
+ * code's default once none was set (Node answers undefined before the
556
+ * head goes out — divergence: this surface is string-typed). */
557
+ ScrStr *scr_http_res_status_msg_get(ScrHttpRes *r) {
558
+ if (r->status_msg) return scr_str_retain(r->status_msg);
559
+ const char *reason = scr_http_reason(r->status > 0 ? r->status : 200);
560
+ return scr_str_new(reason, strlen(reason));
561
+ }
562
+
563
+ void scr_http_res_status_msg_set(ScrHttpRes *r, ScrStr *msg /*borrowed*/) {
564
+ if (r->head_sent) return;
565
+ scr_str_release(r->status_msg);
566
+ r->status_msg = scr_str_retain(msg);
567
+ }
568
+
569
+ /* getHeader(name): the value as SET (case-insensitive lookup), +1, or
570
+ * NULL — the compiler's undefined arm. */
571
+ ScrStr *scr_http_res_get_header(ScrHttpRes *r, ScrStr *name) {
572
+ for (size_t i = 0; i < r->nheaders; i++) {
573
+ if (r->hnames[i]->len == name->len) {
574
+ bool eq = true;
575
+ for (size_t j = 0; j < name->len && eq; j++) {
576
+ if (tolower((unsigned char)r->hnames[i]->data[j]) !=
577
+ tolower((unsigned char)name->data[j])) eq = false;
578
+ }
579
+ if (eq) return scr_str_retain(r->hvalues[i]);
580
+ }
581
+ }
582
+ return NULL;
583
+ }
584
+
585
+ bool scr_http_res_has_header_named(ScrHttpRes *r, ScrStr *name) {
586
+ ScrStr *v = scr_http_res_get_header(r, name);
587
+ if (v) scr_str_release(v);
588
+ return v != NULL;
589
+ }
590
+
591
+ /* removeHeader(name): drops every case-insensitive match (setHeader keeps
592
+ * one entry per name, but the array-valued writeHead appends repeats). */
593
+ void scr_http_res_remove_header(ScrHttpRes *r, ScrStr *name) {
594
+ if (r->head_sent) return; /* Node throws ERR_HTTP_HEADERS_SENT; this slice drops */
595
+ size_t w = 0;
596
+ for (size_t i = 0; i < r->nheaders; i++) {
597
+ bool eq = r->hnames[i]->len == name->len;
598
+ for (size_t j = 0; j < name->len && eq; j++) {
599
+ if (tolower((unsigned char)r->hnames[i]->data[j]) !=
600
+ tolower((unsigned char)name->data[j])) eq = false;
601
+ }
602
+ if (eq) {
603
+ scr_str_release(r->hnames[i]);
604
+ scr_str_release(r->hvalues[i]);
605
+ } else {
606
+ r->hnames[w] = r->hnames[i];
607
+ r->hvalues[w] = r->hvalues[i];
608
+ w++;
609
+ }
610
+ }
611
+ r->nheaders = w;
612
+ }
613
+
614
+ static bool scr_http_res_has_header(ScrHttpRes *r, const char *name) {
615
+ size_t len = strlen(name);
616
+ for (size_t i = 0; i < r->nheaders; i++) {
617
+ if (r->hnames[i]->len == len) {
618
+ bool eq = true;
619
+ for (size_t j = 0; j < len && eq; j++) {
620
+ if (tolower((unsigned char)r->hnames[i]->data[j]) != tolower((unsigned char)name[j])) eq = false;
621
+ }
622
+ if (eq) return true;
623
+ }
624
+ }
625
+ return false;
626
+ }
627
+
628
+ /* Append one header line verbatim (the array-valued writeHead expansion:
629
+ * repeated names each get their own line). */
630
+ static void scr_http_res_append_header(ScrHttpRes *r, ScrStr *name /*borrowed*/,
631
+ ScrStr *value /*borrowed*/) {
632
+ if (r->head_sent) return;
633
+ if (r->nheaders == r->cap_headers) {
634
+ r->cap_headers = r->cap_headers ? r->cap_headers * 2 : 8;
635
+ r->hnames = realloc(r->hnames, r->cap_headers * sizeof *r->hnames);
636
+ r->hvalues = realloc(r->hvalues, r->cap_headers * sizeof *r->hvalues);
637
+ if (!r->hnames || !r->hvalues) scr_http_oom();
638
+ }
639
+ r->hnames[r->nheaders] = scr_str_retain(name);
640
+ r->hvalues[r->nheaders] = scr_str_retain(value);
641
+ r->nheaders++;
642
+ }
643
+
644
+ /* setHeader: replace-by-name (case-insensitive), Node's semantics. */
645
+ void scr_http_res_set_header(ScrHttpRes *r, ScrStr *name /*borrowed*/, ScrStr *value /*borrowed*/) {
646
+ if (r->head_sent) return; /* Node throws ERR_HTTP_HEADERS_SENT; slice drops (SEMANTICS) */
647
+ for (size_t i = 0; i < r->nheaders; i++) {
648
+ if (r->hnames[i]->len == name->len) {
649
+ bool eq = true;
650
+ for (size_t j = 0; j < name->len && eq; j++) {
651
+ if (tolower((unsigned char)r->hnames[i]->data[j]) != tolower((unsigned char)name->data[j])) eq = false;
652
+ }
653
+ if (eq) {
654
+ scr_str_release(r->hvalues[i]);
655
+ r->hvalues[i] = scr_str_retain(value);
656
+ return;
657
+ }
658
+ }
659
+ }
660
+ if (r->nheaders == r->cap_headers) {
661
+ r->cap_headers = r->cap_headers ? r->cap_headers * 2 : 8;
662
+ r->hnames = realloc(r->hnames, r->cap_headers * sizeof *r->hnames);
663
+ r->hvalues = realloc(r->hvalues, r->cap_headers * sizeof *r->hvalues);
664
+ if (!r->hnames || !r->hvalues) scr_http_oom();
665
+ }
666
+ r->hnames[r->nheaders] = scr_str_retain(name);
667
+ r->hvalues[r->nheaders] = scr_str_retain(value);
668
+ r->nheaders++;
669
+ }
670
+
671
+ /* Grow-and-append head builder. */
672
+ typedef struct {
673
+ char *data;
674
+ size_t len, cap;
675
+ } ScrHttpBuf;
676
+
677
+ static void scr_http_buf_append(ScrHttpBuf *b, const char *s, size_t n) {
678
+ if (b->len + n > b->cap) {
679
+ size_t cap = b->cap ? b->cap : 512;
680
+ while (cap < b->len + n) cap *= 2;
681
+ b->data = realloc(b->data, cap);
682
+ if (!b->data) scr_http_oom();
683
+ b->cap = cap;
684
+ }
685
+ memcpy(b->data + b->len, s, n);
686
+ b->len += n;
687
+ }
688
+
689
+ static void scr_http_buf_str(ScrHttpBuf *b, const char *s) { scr_http_buf_append(b, s, strlen(s)); }
690
+
691
+ /* Serializes and sends the head. `body_len` >= 0 fixes Content-Length
692
+ * (the end(data)-before-head path); -1 means chunked unless the user set
693
+ * Content-Length or Transfer-Encoding (Node's useChunkedEncodingByDefault
694
+ * for 1.1). Header order matches Node's storeHeader: user headers as set,
695
+ * then the injected Date / Connection (+Keep-Alive) / framing header. */
696
+ static void scr_http_res_send_head(ScrHttpRes *r, long long body_len) {
697
+ if (r->head_sent) return;
698
+ r->head_sent = true;
699
+ int status = r->status > 0 ? r->status : 200;
700
+ if (r->h2_stream != NULL) {
701
+ /* the h2 transport: a HEADERS frame — no HTTP/1 framing headers */
702
+ (void)body_len;
703
+ scr_http_h2_ops->respond(r->h2_stream, (double)status, r->hnames, r->hvalues,
704
+ r->nheaders, !r->no_date);
705
+ return;
706
+ }
707
+ ScrHttpBuf b = {NULL, 0, 0};
708
+ char line[96];
709
+ snprintf(line, sizeof line, "HTTP/1.1 %d ", status);
710
+ scr_http_buf_str(&b, line);
711
+ /* the assigned reason phrase wins (writeHead's statusMessage argument /
712
+ * the res.statusMessage assignment — "" serializes as an empty phrase,
713
+ * Node's "HTTP/1.1 200 " line) */
714
+ if (r->status_msg) scr_http_buf_append(&b, r->status_msg->data, r->status_msg->len);
715
+ else scr_http_buf_str(&b, scr_http_reason(status));
716
+ scr_http_buf_str(&b, "\r\n");
717
+ for (size_t i = 0; i < r->nheaders; i++) {
718
+ scr_http_buf_append(&b, r->hnames[i]->data, r->hnames[i]->len);
719
+ scr_http_buf_str(&b, ": ");
720
+ scr_http_buf_append(&b, r->hvalues[i]->data, r->hvalues[i]->len);
721
+ scr_http_buf_str(&b, "\r\n");
722
+ }
723
+ if (!r->no_date && !scr_http_res_has_header(r, "date")) {
724
+ /* Node's utcDate: "Date: Wed, 16 Jul 2026 04:20:00 GMT" */
725
+ time_t now = time(NULL);
726
+ struct tm tm;
727
+ gmtime_r(&now, &tm);
728
+ static const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
729
+ static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
730
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
731
+ char date[64];
732
+ snprintf(date, sizeof date, "Date: %s, %02d %s %04d %02d:%02d:%02d GMT\r\n",
733
+ days[tm.tm_wday], tm.tm_mday, months[tm.tm_mon], tm.tm_year + 1900,
734
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
735
+ scr_http_buf_str(&b, date);
736
+ }
737
+ bool user_close = false;
738
+ if (scr_http_res_has_header(r, "connection")) {
739
+ /* the user's own Connection header decides (already serialized) */
740
+ user_close = true; /* trust their framing intent; keep-alive logic off */
741
+ } else if (r->keep_alive) {
742
+ scr_http_buf_str(&b, "Connection: keep-alive\r\nKeep-Alive: timeout=5\r\n");
743
+ } else {
744
+ scr_http_buf_str(&b, "Connection: close\r\n");
745
+ }
746
+ (void)user_close;
747
+ bool have_len = scr_http_res_has_header(r, "content-length") ||
748
+ scr_http_res_has_header(r, "transfer-encoding");
749
+ if (!have_len) {
750
+ if (body_len >= 0) {
751
+ char cl[48];
752
+ snprintf(cl, sizeof cl, "Content-Length: %lld\r\n", body_len);
753
+ scr_http_buf_str(&b, cl);
754
+ } else {
755
+ scr_http_buf_str(&b, "Transfer-Encoding: chunked\r\n");
756
+ r->chunked = true;
757
+ }
758
+ }
759
+ scr_http_buf_str(&b, "\r\n");
760
+ if (r->sock) scr_net_sock_write_native(r->sock, b.data, b.len);
761
+ free(b.data);
762
+ }
763
+
764
+ static void scr_http_res_write_raw(ScrHttpRes *r, const char *data, size_t len) {
765
+ if (r->finished) return;
766
+ if (!r->head_sent) scr_http_res_send_head(r, -1); /* streaming: chunked */
767
+ if (r->h2_stream != NULL) {
768
+ scr_http_h2_ops->write(r->h2_stream, data, len);
769
+ return;
770
+ }
771
+ if (!r->sock) return;
772
+ if (r->chunked) {
773
+ char size[32];
774
+ snprintf(size, sizeof size, "%zx\r\n", len);
775
+ if (len > 0) {
776
+ scr_net_sock_write_native(r->sock, size, strlen(size));
777
+ scr_net_sock_write_native(r->sock, data, len);
778
+ scr_net_sock_write_native(r->sock, "\r\n", 2);
779
+ }
780
+ } else {
781
+ scr_net_sock_write_native(r->sock, data, len);
782
+ }
783
+ }
784
+
785
+ void scr_http_res_write_str(ScrHttpRes *r, ScrStr *data /*borrowed*/) {
786
+ scr_http_res_write_raw(r, data->data, data->len);
787
+ }
788
+
789
+ void scr_http_res_write_bytes(ScrHttpRes *r, ScrBytes *data /*borrowed*/) {
790
+ scr_http_res_write_raw(r, (const char *)data->data, data->len);
791
+ }
792
+
793
+ static void scr_http_conn_response_finished(struct ScrHttpConn *conn, bool keep_alive);
794
+ static void scr_http_queue_res_finish(ScrHttpRes *res);
795
+
796
+ static void scr_http_res_end_raw(ScrHttpRes *r, const char *data, size_t len) {
797
+ if (r->finished) return;
798
+ if (r->h2_stream != NULL) {
799
+ if (!r->head_sent) scr_http_res_send_head(r, (long long)len);
800
+ scr_http_h2_ops->end(r->h2_stream, data, len);
801
+ r->finished = true;
802
+ if (r->finish_ls.n > 0) scr_http_queue_res_finish(r);
803
+ return;
804
+ }
805
+ if (!r->head_sent) {
806
+ /* whole body known NOW: Content-Length framing, Node's implicit head */
807
+ scr_http_res_send_head(r, (long long)len);
808
+ if (r->sock && len > 0) scr_net_sock_write_native(r->sock, data, len);
809
+ } else {
810
+ scr_http_res_write_raw(r, data, len);
811
+ if (r->chunked && r->sock) scr_net_sock_write_native(r->sock, "0\r\n\r\n", 5);
812
+ }
813
+ r->finished = true;
814
+ if (r->finish_ls.n > 0) scr_http_queue_res_finish(r);
815
+ if (r->conn) scr_http_conn_response_finished(r->conn, r->keep_alive);
816
+ }
817
+
818
+ void scr_http_res_end(ScrHttpRes *r) { scr_http_res_end_raw(r, "", 0); }
819
+
820
+ void scr_http_res_end_str(ScrHttpRes *r, ScrStr *data /*borrowed*/) {
821
+ scr_http_res_end_raw(r, data->data, data->len);
822
+ }
823
+
824
+ void scr_http_res_end_bytes(ScrHttpRes *r, ScrBytes *data /*borrowed*/) {
825
+ scr_http_res_end_raw(r, (const char *)data->data, data->len);
826
+ }
827
+
828
+ void scr_http_res_write_head(ScrHttpRes *r, double status) {
829
+ if (r->head_sent) return;
830
+ r->status = (int)status;
831
+ scr_http_res_send_head(r, -1); /* Node: an explicit writeHead means chunked unless CL was set */
832
+ }
833
+
834
+ /* writeHead(status, { ...literal headers... }): the packed form — names
835
+ * and values arrive as parallel string arrays (evaluation order was the
836
+ * literal's), each setHeader'd, then the head goes out. */
837
+ /* writeHead(status, headers) with a CHECKED-DYNAMIC headers value (an
838
+ * untyped JS helper's parameter — the suite's test(headers) idiom): OBJ
839
+ * entries setHeader in insertion order (string values verbatim, numbers
840
+ * via ToString, string arrays one line per element via the append path),
841
+ * then the head goes out. undefined/null headers are the plain head;
842
+ * other kinds throw the ERR_INVALID_ARG_TYPE-flavored fence. */
843
+ void scr_http_res_write_head_dyn(ScrHttpRes *r, double status, const ScrDyn *headers) {
844
+ if (r->head_sent) return;
845
+ if (headers->kind == SCR_DYN_UNDEF || headers->kind == SCR_DYN_NULL) {
846
+ scr_http_res_write_head(r, status);
847
+ return;
848
+ }
849
+ if (headers->kind != SCR_DYN_OBJ) {
850
+ scr_dyn_arg_type_fail("headers", "an instance of Object", headers);
851
+ return;
852
+ }
853
+ for (size_t i = 0; i < headers->v.obj.len; i++) {
854
+ const ScrDynEntry *e = &headers->v.obj.entries[i];
855
+ const ScrDyn *v = e->value;
856
+ ScrStr *name = scr_str_new(e->key, e->key_len);
857
+ if (v->kind == SCR_DYN_STR) {
858
+ scr_http_res_set_header(r, name, v->v.str);
859
+ } else if (v->kind == SCR_DYN_NUM) {
860
+ char num[32];
861
+ size_t n = scr_f64_to_str(v->v.num, num);
862
+ ScrStr *value = scr_str_new(num, n);
863
+ scr_http_res_set_header(r, name, value);
864
+ scr_str_release(value);
865
+ } else {
866
+ scr_str_release(name);
867
+ scr_dyn_arg_type_fail("value", "of type string or number", v);
868
+ return;
869
+ }
870
+ scr_str_release(name);
871
+ }
872
+ scr_http_res_write_head(r, status);
873
+ }
874
+
875
+ void scr_http_res_write_head_n(ScrHttpRes *r, double status, ScrArr *names, ScrArr *values) {
876
+ if (r->head_sent) return;
877
+ size_t n = (size_t)scr_arr_len(names);
878
+ for (size_t i = 0; i < n; i++) {
879
+ ScrStr *name = (ScrStr *)scr_arr_get_ref(names, (double)i);
880
+ ScrStr *value = (ScrStr *)scr_arr_get_ref(values, (double)i);
881
+ scr_http_res_set_header(r, name, value);
882
+ scr_str_release(name);
883
+ scr_str_release(value);
884
+ }
885
+ scr_http_res_write_head(r, status);
886
+ }
887
+
888
+ /* writeHead(status, record): the flat [k0, v0, k1, v1, ...] pairs the
889
+ * env.pairs helper produces — each entry setHeader'd, then the head goes
890
+ * out (undefined-valued entries were dropped by the helper, the Node-
891
+ * drops-undefined env rule; Node's writeHead would throw on them). */
892
+ void scr_http_res_write_head_pairs(ScrHttpRes *r, double status, ScrArr *pairs /*borrowed*/) {
893
+ if (r->head_sent) return;
894
+ size_t n = (size_t)scr_arr_len(pairs);
895
+ for (size_t i = 0; i + 1 < n; i += 2) {
896
+ ScrStr *name = (ScrStr *)scr_arr_get_ref(pairs, (double)i);
897
+ ScrStr *value = (ScrStr *)scr_arr_get_ref(pairs, (double)(i + 1));
898
+ /* A REPEATED name within this pairs array is an ARRAY-VALUED header
899
+ * (["a", "b"] expands to consecutive same-name pairs): the first
900
+ * occurrence replaces any earlier setHeader (Node's writeHead-wins
901
+ * merge), the repeats APPEND — Node writes one line per element. */
902
+ bool repeat = false;
903
+ for (size_t j = 0; j < i && !repeat; j += 2) {
904
+ ScrStr *prev = (ScrStr *)scr_arr_get_ref(pairs, (double)j);
905
+ if (prev->len == name->len) {
906
+ bool eq = true;
907
+ for (size_t k = 0; k < name->len && eq; k++) {
908
+ if (tolower((unsigned char)prev->data[k]) != tolower((unsigned char)name->data[k])) eq = false;
909
+ }
910
+ repeat = eq;
911
+ }
912
+ scr_str_release(prev);
913
+ }
914
+ if (repeat) scr_http_res_append_header(r, name, value);
915
+ else scr_http_res_set_header(r, name, value);
916
+ scr_str_release(name);
917
+ scr_str_release(value);
918
+ }
919
+ scr_http_res_write_head(r, status);
920
+ }
921
+
922
+ /* res.destroy(): tears the connection down NOW (mid-stream RST is the
923
+ * point — portless aborts a proxied response with it); 'close' follows
924
+ * through the teardown path. Node-matched: no 'error' from a destroy. */
925
+ void scr_http_res_destroy(ScrHttpRes *r) {
926
+ if (r->h2_stream != NULL) {
927
+ /* h2 compat: destroy the STREAM (RST), never the shared session */
928
+ scr_http_h2_ops->destroy(r->h2_stream);
929
+ return;
930
+ }
931
+ if (r->sock) scr_net_sock_destroy(r->sock);
932
+ }
933
+
934
+ void scr_http_res_on_close(ScrHttpRes *r, ScrClosure *cb /*moves*/, bool once) {
935
+ if (r->close_emitted) {
936
+ scr_closure_release(cb);
937
+ return;
938
+ }
939
+ scr_net_ls_add(&r->close_ls, cb, NULL, once);
940
+ }
941
+
942
+ /* ── the deferred-emit queue (scr_net.c's proto sweep drains it) ───────
943
+ *
944
+ * 'close' on requests/responses and the client's own 'close' fire a
945
+ * sweep pass AFTER the work that flagged them — Node's ordering (the
946
+ * handler's synchronous tail runs first; res 'close' precedes req
947
+ * 'close'; the client's res 'end' precedes req 'close' precedes res
948
+ * 'close'). RES_ABORTED is the mid-body premature-close 'error' on a
949
+ * client response ("aborted", Node's message) — queued between the
950
+ * client close and the response close, the oracle-pinned order. Each
951
+ * entry holds +1 on its handle; firing settles the handle (listener
952
+ * lists drop — the cycle story). */
953
+
954
+ enum {
955
+ SCR_HTTP_EMIT_RES_CLOSE = 1, /* ScrHttpRes */
956
+ SCR_HTTP_EMIT_REQ_CLOSE = 2, /* ScrHttpReq */
957
+ SCR_HTTP_EMIT_REQ_ABORTED = 3, /* ScrHttpReq: 'error' ("aborted") */
958
+ SCR_HTTP_EMIT_CLIENT_CLOSE = 4, /* ScrHttpClientReq */
959
+ SCR_HTTP_EMIT_RES_FINISH = 5 /* ScrHttpRes: the end(cb) callbacks */
960
+ };
961
+
962
+ typedef struct {
963
+ int kind;
964
+ void *h; /* +1 */
965
+ } ScrHttpEmit;
966
+
967
+ static ScrHttpEmit *scr_http_emits = NULL;
968
+ static size_t scr_http_emits_head = 0, scr_http_emits_len = 0, scr_http_emits_cap = 0;
969
+
970
+ static void scr_http_client_release_internal(struct ScrHttpClientReq *c);
971
+ static void scr_http_client_settle(struct ScrHttpClientReq *c);
972
+
973
+ /* Exit-time flag (set by the atexit cleanup, which runs BEFORE the net
974
+ * unit's — atexit LIFO, http installs after net): once the emits queue
975
+ * has drained for the last time, teardown paths that would queue close
976
+ * emits (scr_net_cleanup_atexit freeing in-flight parsers) discard the
977
+ * payload instead — nothing will ever sweep again. */
978
+ static bool scr_http_exiting = false;
979
+
980
+ static void scr_http_emit_release(ScrHttpEmit *e);
981
+
982
+ static void scr_http_emit_push(int kind, void *h /*moves +1*/) {
983
+ if (scr_http_exiting) {
984
+ ScrHttpEmit e = { kind, h };
985
+ scr_http_emit_release(&e);
986
+ return;
987
+ }
988
+ if (scr_http_emits_len == scr_http_emits_cap) {
989
+ /* compact the consumed head first */
990
+ if (scr_http_emits_head > 0) {
991
+ memmove(scr_http_emits, scr_http_emits + scr_http_emits_head,
992
+ (scr_http_emits_len - scr_http_emits_head) * sizeof *scr_http_emits);
993
+ scr_http_emits_len -= scr_http_emits_head;
994
+ scr_http_emits_head = 0;
995
+ }
996
+ if (scr_http_emits_len == scr_http_emits_cap) {
997
+ scr_http_emits_cap = scr_http_emits_cap ? scr_http_emits_cap * 2 : 8;
998
+ scr_http_emits = realloc(scr_http_emits, scr_http_emits_cap * sizeof *scr_http_emits);
999
+ if (!scr_http_emits) scr_http_oom();
1000
+ }
1001
+ }
1002
+ scr_http_emits[scr_http_emits_len].kind = kind;
1003
+ scr_http_emits[scr_http_emits_len].h = h;
1004
+ scr_http_emits_len++;
1005
+ }
1006
+
1007
+ static void scr_http_emit_release(ScrHttpEmit *e) {
1008
+ switch (e->kind) {
1009
+ case SCR_HTTP_EMIT_RES_CLOSE:
1010
+ case SCR_HTTP_EMIT_RES_FINISH: scr_http_res_release((ScrHttpRes *)e->h); break;
1011
+ case SCR_HTTP_EMIT_REQ_CLOSE:
1012
+ case SCR_HTTP_EMIT_REQ_ABORTED: scr_http_req_release((ScrHttpReq *)e->h); break;
1013
+ case SCR_HTTP_EMIT_CLIENT_CLOSE:
1014
+ scr_http_client_release_internal((struct ScrHttpClientReq *)e->h);
1015
+ break;
1016
+ }
1017
+ }
1018
+
1019
+ /* Enqueue helpers: idempotent per handle (the close_queued flags). */
1020
+ static void scr_http_queue_res_close(ScrHttpRes *res) {
1021
+ if (res->close_queued || res->close_emitted) return;
1022
+ res->close_queued = true;
1023
+ scr_http_emit_push(SCR_HTTP_EMIT_RES_CLOSE, scr_http_res_retain(res));
1024
+ }
1025
+
1026
+ static void scr_http_queue_req_close(ScrHttpReq *req) {
1027
+ if (req->close_queued || req->close_emitted) return;
1028
+ req->close_queued = true;
1029
+ scr_http_emit_push(SCR_HTTP_EMIT_REQ_CLOSE, scr_http_req_retain(req));
1030
+ }
1031
+
1032
+ static void scr_http_queue_req_aborted(ScrHttpReq *req) {
1033
+ scr_http_emit_push(SCR_HTTP_EMIT_REQ_ABORTED, scr_http_req_retain(req));
1034
+ }
1035
+
1036
+ /* The end(cb) deferral: 'finish'-time callbacks fire one sweep after the
1037
+ * body went out (the handler's synchronous tail runs first, Node's
1038
+ * deferred emit — the res 'close' precedent). */
1039
+ static void scr_http_queue_res_finish(ScrHttpRes *res) {
1040
+ if (res->finish_queued) return;
1041
+ res->finish_queued = true;
1042
+ scr_http_emit_push(SCR_HTTP_EMIT_RES_FINISH, scr_http_res_retain(res));
1043
+ }
1044
+
1045
+ /* res.end(cb)'s callback slot: registered just before the end call; a
1046
+ * response that already finished (a second end) still fires it deferred
1047
+ * — Node errbacks ERR_STREAM_ALREADY_FINISHED there (divergence: this
1048
+ * surface's callbacks take no arguments, so it fires plainly). */
1049
+ void scr_http_res_on_finish(ScrHttpRes *r, ScrClosure *cb /*moves*/) {
1050
+ scr_net_ls_add(&r->finish_ls, cb, NULL, true);
1051
+ if (r->finished) scr_http_queue_res_finish(r);
1052
+ }
1053
+
1054
+ static bool scr_http_proto_pending(void) { return scr_http_emits_head < scr_http_emits_len; }
1055
+
1056
+ static void scr_http_proto_sweep(void) {
1057
+ while (scr_http_emits_head < scr_http_emits_len) {
1058
+ ScrHttpEmit e = scr_http_emits[scr_http_emits_head++];
1059
+ switch (e.kind) {
1060
+ case SCR_HTTP_EMIT_RES_CLOSE: {
1061
+ ScrHttpRes *res = (ScrHttpRes *)e.h;
1062
+ if (!res->close_emitted) {
1063
+ res->close_emitted = true;
1064
+ scr_net_fire0_this(&res->close_ls, res, SCR_DYNH_HTTP_RES);
1065
+ scr_net_ls_drop(&res->close_ls);
1066
+ }
1067
+ break;
1068
+ }
1069
+ case SCR_HTTP_EMIT_REQ_CLOSE: {
1070
+ ScrHttpReq *req = (ScrHttpReq *)e.h;
1071
+ if (!req->close_emitted) {
1072
+ req->close_emitted = true;
1073
+ scr_net_fire0_this(&req->close_ls, req, SCR_DYNH_HTTP_REQ);
1074
+ scr_net_ls_drop(&req->err_ls);
1075
+ scr_net_ls_drop(&req->close_ls);
1076
+ scr_net_ls_drop(&req->aborted_ls);
1077
+ }
1078
+ break;
1079
+ }
1080
+ case SCR_HTTP_EMIT_REQ_ABORTED: {
1081
+ ScrHttpReq *req = (ScrHttpReq *)e.h;
1082
+ if (!req->close_emitted) {
1083
+ ScrStr *msg = scr_str_new("aborted", 7);
1084
+ scr_net_fire_err_this(&req->err_ls, msg, req, SCR_DYNH_HTTP_REQ);
1085
+ scr_str_release(msg);
1086
+ }
1087
+ break;
1088
+ }
1089
+ case SCR_HTTP_EMIT_CLIENT_CLOSE:
1090
+ scr_http_client_settle((struct ScrHttpClientReq *)e.h);
1091
+ break;
1092
+ case SCR_HTTP_EMIT_RES_FINISH: {
1093
+ ScrHttpRes *res = (ScrHttpRes *)e.h;
1094
+ res->finish_queued = false; /* a later end(cb) on the finished res re-queues */
1095
+ scr_net_fire0_this(&res->finish_ls, res, SCR_DYNH_HTTP_RES);
1096
+ break;
1097
+ }
1098
+ }
1099
+ scr_http_emit_release(&e);
1100
+ if (scr_exc_pending()) return;
1101
+ }
1102
+ scr_http_emits_head = scr_http_emits_len = 0;
1103
+ }
1104
+
1105
+ static void scr_http_clients_cleanup(void);
1106
+
1107
+ static void scr_http_cleanup_atexit(void) {
1108
+ scr_http_exiting = true;
1109
+ while (scr_http_emits_head < scr_http_emits_len) {
1110
+ ScrHttpEmit e = scr_http_emits[scr_http_emits_head++];
1111
+ scr_http_emit_release(&e);
1112
+ }
1113
+ free(scr_http_emits);
1114
+ scr_http_emits = NULL;
1115
+ scr_http_emits_head = scr_http_emits_len = scr_http_emits_cap = 0;
1116
+ scr_http_clients_cleanup();
1117
+ }
1118
+
1119
+ /* One-time wiring into the net sweep (both createServer and request call
1120
+ * through here). */
1121
+ static void scr_http_install(void) {
1122
+ static bool installed = false;
1123
+ if (installed) return;
1124
+ installed = true;
1125
+ atexit(scr_http_cleanup_atexit);
1126
+ scr_net_set_proto_sweep(&scr_http_proto_pending, &scr_http_proto_sweep);
1127
+ }
1128
+
1129
+ /* ── the per-connection parser ───────────────────────────────────────── */
1130
+
1131
+ typedef enum {
1132
+ SCR_HTTP_HEAD = 0,
1133
+ SCR_HTTP_BODY_CL,
1134
+ SCR_HTTP_CHUNK_SIZE,
1135
+ SCR_HTTP_CHUNK_DATA,
1136
+ SCR_HTTP_CHUNK_CRLF,
1137
+ SCR_HTTP_CHUNK_TRAILER,
1138
+ SCR_HTTP_BODY_EOF, /* RESPONSE mode: no framing header — body until EOF */
1139
+ } ScrHttpParseState;
1140
+
1141
+ typedef struct ScrHttpConn {
1142
+ ScrNetSocket *sock; /* BORROWED: the ctx lives and dies with the socket
1143
+ * (a retained ref here would be a cycle the RC
1144
+ * could never break) */
1145
+ struct ScrHttpSrvCtx *srv; /* +1; NULL in client mode — the server ctx
1146
+ * whose 'request' listeners fire per parsed
1147
+ * request (EMIT-time resolution, Node's
1148
+ * dispatch: a listener installed after the
1149
+ * accept but before the request still runs) */
1150
+ char *buf;
1151
+ size_t len, cap;
1152
+ ScrHttpParseState state;
1153
+ size_t body_remaining;
1154
+ ScrHttpReq *req; /* the in-flight request (server) or response (client),
1155
+ * +1; NULL between requests / before the head */
1156
+ ScrHttpRes *res; /* +1; server mode only */
1157
+ bool close_after; /* the response said Connection: close */
1158
+ bool client_mode; /* RESPONSE parsing: status line, EOF bodies */
1159
+ bool client_resp_started; /* the status line parsed (conn-level twin of
1160
+ * the client's response_started — the pump
1161
+ * runs below the client struct's definition) */
1162
+ struct ScrHttpClientReq *client; /* +1 in client mode; released at close */
1163
+ } ScrHttpConn;
1164
+
1165
+ /* Client-mode forward declarations (implementations live with the client
1166
+ * below the server parser). */
1167
+ static bool scr_http_client_parse_head(ScrHttpConn *conn, size_t head_len);
1168
+ static void scr_http_client_head_overflow(ScrHttpConn *conn);
1169
+
1170
+ /* The server-side ctx: the 'request' listener list, shared by every
1171
+ * connection. REFCOUNTED: the server's native-conn chain holds one ref
1172
+ * (released through scr_http_srv_ctx_free) and each live connection
1173
+ * holds one — a socket can outlive its server handle. createServer's
1174
+ * eager handler is just the first listener; server.on("request", ...)
1175
+ * appends (scr_http_server_on_request), and requests fire the list
1176
+ * snapshot-at-emit like every other event here. */
1177
+ typedef struct ScrHttpSrvCtx {
1178
+ int proto; /* SCR_NET_PROTO_HTTP1 — FIRST: the http_ctx alias slot is
1179
+ * shared with scr_http2.c's ScrH2SrvCtx; each family checks
1180
+ * the tag before treating the ctx as its own */
1181
+ size_t rc;
1182
+ ScrNetLs request_ls;
1183
+ ScrNetLs upgrade_ls;
1184
+ ScrNetLs connect_ls; /* HTTP CONNECT — the upgrade machinery's twin */
1185
+ bool join_dup; /* createServer({ joinDuplicateHeaders: true }) */
1186
+ } ScrHttpSrvCtx;
1187
+
1188
+ static ScrHttpSrvCtx *scr_http_srv_ctx_retain(ScrHttpSrvCtx *ctx) {
1189
+ ctx->rc++;
1190
+ return ctx;
1191
+ }
1192
+
1193
+ static void scr_http_srv_ctx_release(ScrHttpSrvCtx *ctx) {
1194
+ if (--ctx->rc > 0) return;
1195
+ scr_net_ls_drop(&ctx->request_ls);
1196
+ scr_net_ls_drop(&ctx->upgrade_ls);
1197
+ scr_net_ls_drop(&ctx->connect_ls);
1198
+ free(ctx);
1199
+ }
1200
+
1201
+ static void scr_http_srv_ctx_free(void *p) { scr_http_srv_ctx_release((ScrHttpSrvCtx *)p); }
1202
+
1203
+ /* The server-settle hook (scr_net_server_set_proto_settle): the ctx's own
1204
+ * listener lists drop when the server settles — no request can fire past
1205
+ * the settle (the fd is closed and every connection drained), and a
1206
+ * handler closure capturing its own server through a dyn binding box
1207
+ * would otherwise cycle through collector-invisible edges. */
1208
+ static void scr_http_srv_ctx_settle(void *p) {
1209
+ ScrHttpSrvCtx *ctx = (ScrHttpSrvCtx *)p;
1210
+ scr_net_ls_drop(&ctx->request_ls);
1211
+ scr_net_ls_drop(&ctx->upgrade_ls);
1212
+ scr_net_ls_drop(&ctx->connect_ls);
1213
+ }
1214
+
1215
+ static void scr_http_conn_drop_request(ScrHttpConn *conn, bool fire_end) {
1216
+ /* the deferred 'close' emits: res before req, Node's order */
1217
+ if (conn->res) scr_http_queue_res_close(conn->res);
1218
+ if (conn->req) scr_http_queue_req_close(conn->req);
1219
+ if (conn->req) {
1220
+ scr_http_req_finish(conn->req, fire_end);
1221
+ scr_http_req_release(conn->req);
1222
+ conn->req = NULL;
1223
+ }
1224
+ if (conn->res) {
1225
+ conn->res->conn = NULL;
1226
+ scr_http_res_release(conn->res);
1227
+ conn->res = NULL;
1228
+ }
1229
+ }
1230
+
1231
+ /* The response finished: with keep-alive the parser waits for the next
1232
+ * request (pipelined bytes may already sit in the buffer — the read path
1233
+ * keeps parsing); without it the socket half-closes, Node's teardown. */
1234
+ static void scr_http_conn_response_finished(ScrHttpConn *conn, bool keep_alive) {
1235
+ bool fire_end = conn->req != NULL && conn->state == SCR_HTTP_HEAD;
1236
+ /* the handler answered before consuming the whole body: the request
1237
+ * settles quietly (Node destroys unconsumed bodies on 'close') */
1238
+ scr_http_conn_drop_request(conn, fire_end && false);
1239
+ if (!keep_alive || conn->close_after) {
1240
+ scr_net_sock_end(conn->sock);
1241
+ }
1242
+ }
1243
+
1244
+ /* One parsed header line (name lowercased into the req). */
1245
+ static void scr_http_req_add_header(ScrHttpReq *r, const char *name, size_t nlen,
1246
+ const char *value, size_t vlen) {
1247
+ char *lower = malloc(nlen);
1248
+ if (!lower && nlen > 0) scr_http_oom();
1249
+ for (size_t i = 0; i < nlen; i++) lower[i] = (char)tolower((unsigned char)name[i]);
1250
+ r->hnames = realloc(r->hnames, (r->nheaders + 1) * sizeof *r->hnames);
1251
+ r->hnames_raw = realloc(r->hnames_raw, (r->nheaders + 1) * sizeof *r->hnames_raw);
1252
+ r->hvalues = realloc(r->hvalues, (r->nheaders + 1) * sizeof *r->hvalues);
1253
+ if (!r->hnames || !r->hnames_raw || !r->hvalues) scr_http_oom();
1254
+ r->hnames[r->nheaders] = scr_str_new(lower, nlen);
1255
+ r->hnames_raw[r->nheaders] = scr_str_new(name, nlen);
1256
+ r->hvalues[r->nheaders] = scr_str_new(value, vlen);
1257
+ r->nheaders++;
1258
+ free(lower);
1259
+ }
1260
+
1261
+ /* Malformed input: this slice answers 400 and closes — Node's lenient
1262
+ * spots (bare LF line endings, obsolete folding) are NOT accepted;
1263
+ * SEMANTICS.md states the bound. */
1264
+ static void scr_http_conn_bad_request(ScrHttpConn *conn) {
1265
+ static const char bad[] =
1266
+ "HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 0\r\n\r\n";
1267
+ scr_net_sock_write_native(conn->sock, bad, sizeof bad - 1);
1268
+ scr_net_sock_end(conn->sock);
1269
+ conn->len = 0;
1270
+ scr_http_conn_drop_request(conn, false);
1271
+ }
1272
+
1273
+ /* Delivers a body slice to a request's data listeners (the parser's
1274
+ * feed, and the h2 compat DATA-frame feed). */
1275
+ static void scr_http_req_deliver(ScrHttpReq *r, const char *data, size_t n) {
1276
+ if (!r || n == 0 || r->ended) return;
1277
+ bool piped = r->pipe_res != NULL || r->pipe_client != NULL || r->pipe_sock != NULL;
1278
+ if (r->data_ls.n == 0 && !piped) return;
1279
+ ScrBytes *chunk = scr_bytes_new(SCR_BYTES_U8, (double)n);
1280
+ memcpy(chunk->data, data, n);
1281
+ if (r->data_ls.n > 0) {
1282
+ ScrNetL *snap;
1283
+ size_t nl = scr_net_ls_snapshot(&r->data_ls, &snap);
1284
+ scr_dyn_this_push(r, SCR_DYNH_HTTP_REQ);
1285
+ scr_dyn_chunk_enc(r->enc_utf8);
1286
+ for (size_t i = 0; i < nl; i++) {
1287
+ if (!scr_exc_pending()) ((ScrNetDataFn)snap[i].fn)(snap[i].cb, chunk);
1288
+ scr_closure_release(snap[i].cb);
1289
+ }
1290
+ scr_dyn_chunk_enc(false);
1291
+ scr_dyn_this_pop();
1292
+ free(snap);
1293
+ }
1294
+ if (!scr_exc_pending()) {
1295
+ if (r->pipe_res) scr_http_res_write_bytes(r->pipe_res, chunk);
1296
+ if (r->pipe_client) scr_http_client_write_bytes(r->pipe_client, chunk);
1297
+ if (r->pipe_sock) scr_net_sock_write_bytes(r->pipe_sock, chunk);
1298
+ }
1299
+ scr_bytes_release(chunk);
1300
+ }
1301
+
1302
+ static void scr_http_conn_body_data(ScrHttpConn *conn, const char *data, size_t n) {
1303
+ scr_http_req_deliver(conn->req, data, n);
1304
+ }
1305
+
1306
+ static void scr_http_client_response_done(ScrHttpConn *conn);
1307
+
1308
+ /* Body complete: 'end' fires on the request; the response may already be
1309
+ * finished (handler answered from the head alone) — then the connection
1310
+ * resets here instead. Client mode: 'end' on the response, then the
1311
+ * whole exchange tears down (one request per connection). */
1312
+ static void scr_http_conn_body_done(ScrHttpConn *conn) {
1313
+ conn->state = SCR_HTTP_HEAD;
1314
+ conn->body_remaining = 0;
1315
+ if (conn->client_mode) {
1316
+ scr_http_client_response_done(conn);
1317
+ return;
1318
+ }
1319
+ ScrHttpReq *r = conn->req;
1320
+ if (r) {
1321
+ scr_http_req_retain(r);
1322
+ scr_http_req_finish(r, true);
1323
+ scr_http_req_release(r);
1324
+ }
1325
+ if (conn->res && conn->res->finished) {
1326
+ bool keep = conn->res->keep_alive && !conn->close_after;
1327
+ scr_http_conn_drop_request(conn, false);
1328
+ if (!keep) scr_net_sock_end(conn->sock);
1329
+ }
1330
+ }
1331
+
1332
+ /* Parses one complete request HEAD sitting at buf[0..head_len). Returns
1333
+ * false on malformed input (already answered). */
1334
+ static bool scr_http_conn_parse_head(ScrHttpConn *conn, size_t head_len) {
1335
+ const char *buf = conn->buf;
1336
+ const char *line_end = memchr(buf, '\r', head_len);
1337
+ if (!line_end || line_end + 1 >= buf + head_len || line_end[1] != '\n') return false;
1338
+ /* METHOD SP request-target SP HTTP/1.x */
1339
+ const char *sp1 = memchr(buf, ' ', (size_t)(line_end - buf));
1340
+ if (!sp1) return false;
1341
+ const char *sp2 = memchr(sp1 + 1, ' ', (size_t)(line_end - sp1 - 1));
1342
+ if (!sp2) return false;
1343
+ const char *ver = sp2 + 1;
1344
+ size_t verlen = (size_t)(line_end - ver);
1345
+ bool http10;
1346
+ if (verlen == 8 && memcmp(ver, "HTTP/1.1", 8) == 0) http10 = false;
1347
+ else if (verlen == 8 && memcmp(ver, "HTTP/1.0", 8) == 0) http10 = true;
1348
+ else return false;
1349
+
1350
+ ScrHttpReq *req = calloc(1, sizeof *req);
1351
+ if (!req) scr_http_oom();
1352
+ req->rc = 1;
1353
+ req->status = -1; /* Node: statusCode is undefined on server requests */
1354
+ req->join_dup = conn->srv != NULL && conn->srv->join_dup;
1355
+ req->sock = scr_net_sock_retain(conn->sock);
1356
+ #ifdef SCR_RC_AUDIT
1357
+ scr_http_live++;
1358
+ #endif
1359
+ req->method = scr_str_new(buf, (size_t)(sp1 - buf));
1360
+ req->url = scr_str_new(sp1 + 1, (size_t)(sp2 - sp1 - 1));
1361
+ req->http10 = http10;
1362
+
1363
+ /* header lines until the blank line */
1364
+ const char *p = line_end + 2;
1365
+ const char *end = buf + head_len;
1366
+ bool ok = true;
1367
+ while (p < end) {
1368
+ const char *eol = memchr(p, '\r', (size_t)(end - p));
1369
+ if (!eol || eol + 1 >= end || eol[1] != '\n') {
1370
+ ok = false;
1371
+ break;
1372
+ }
1373
+ if (eol == p) {
1374
+ p += 2; /* the blank line */
1375
+ break;
1376
+ }
1377
+ const char *colon = memchr(p, ':', (size_t)(eol - p));
1378
+ if (!colon || colon == p) {
1379
+ ok = false;
1380
+ break;
1381
+ }
1382
+ const char *v = colon + 1;
1383
+ while (v < eol && (*v == ' ' || *v == '\t')) v++;
1384
+ const char *ve = eol;
1385
+ while (ve > v && (ve[-1] == ' ' || ve[-1] == '\t')) ve--;
1386
+ scr_http_req_add_header(req, p, (size_t)(colon - p), v, (size_t)(ve - v));
1387
+ p = eol + 2;
1388
+ }
1389
+ if (!ok) {
1390
+ scr_http_req_release(req);
1391
+ return false;
1392
+ }
1393
+
1394
+ /* framing: chunked wins over Content-Length (RFC 9112) */
1395
+ bool chunked = false;
1396
+ long long content_length = 0;
1397
+ bool req_keep_alive = !http10;
1398
+ bool has_upgrade_hdr = false;
1399
+ bool conn_upgrade_token = false;
1400
+ for (size_t i = 0; i < req->nheaders; i++) {
1401
+ const ScrStr *n = req->hnames[i];
1402
+ const ScrStr *v = req->hvalues[i];
1403
+ if (n->len == 17 && memcmp(n->data, "transfer-encoding", 17) == 0) {
1404
+ if (v->len >= 7 && strstr(v->data, "chunked") != NULL) chunked = true;
1405
+ } else if (n->len == 14 && memcmp(n->data, "content-length", 14) == 0) {
1406
+ content_length = atoll(v->data);
1407
+ if (content_length < 0) content_length = 0;
1408
+ } else if (n->len == 7 && memcmp(n->data, "upgrade", 7) == 0) {
1409
+ has_upgrade_hdr = true;
1410
+ } else if (n->len == 10 && memcmp(n->data, "connection", 10) == 0) {
1411
+ /* token scan, case-insensitive enough for close/keep-alive */
1412
+ if (strcasestr(v->data, "upgrade") != NULL) conn_upgrade_token = true;
1413
+ if (strcasestr(v->data, "close") != NULL) req_keep_alive = false;
1414
+ else if (strcasestr(v->data, "keep-alive") != NULL) req_keep_alive = true;
1415
+ }
1416
+ }
1417
+
1418
+ /* A CONNECT request: 'connect' fires INSTEAD of 'request' — the
1419
+ * upgrade machinery's exact shape ((req, socket, head); the parser
1420
+ * steps aside; no listener destroys the socket, Node's default). The
1421
+ * h2 compat server's HTTP/1.1 arm lands here too — under the
1422
+ * allowHTTP1 lowering it is the ONLY arm (SEMANTICS.md divergence
1423
+ * 57), so a portless-style listener always takes the raw socket. */
1424
+ if (req->method->len == 7 && memcmp(req->method->data, "CONNECT", 7) == 0) {
1425
+ memmove(conn->buf, conn->buf + head_len, conn->len - head_len);
1426
+ conn->len -= head_len;
1427
+ if (conn->srv->connect_ls.n == 0) {
1428
+ scr_http_req_release(req);
1429
+ conn->len = 0;
1430
+ scr_net_sock_destroy(conn->sock);
1431
+ return true;
1432
+ }
1433
+ ScrBytes *head = scr_bytes_new(SCR_BYTES_U8, (double)conn->len);
1434
+ if (conn->len > 0) memcpy(head->data, conn->buf, conn->len);
1435
+ conn->len = 0;
1436
+ scr_net_sock_clear_native_reader(conn->sock);
1437
+ ScrNetL *snap;
1438
+ size_t n = scr_net_ls_snapshot(&conn->srv->connect_ls, &snap);
1439
+ scr_dyn_this_push(scr_net_sock_server(conn->sock), SCR_DYNH_NET_SERVER);
1440
+ for (size_t i = 0; i < n; i++) {
1441
+ if (!scr_exc_pending()) {
1442
+ ((ScrHttpUpgradeFn)snap[i].fn)(snap[i].cb, scr_http_req_retain(req),
1443
+ scr_net_sock_retain(conn->sock), scr_bytes_retain(head));
1444
+ }
1445
+ scr_closure_release(snap[i].cb);
1446
+ }
1447
+ scr_dyn_this_pop();
1448
+ free(snap);
1449
+ scr_bytes_release(head);
1450
+ scr_http_req_release(req);
1451
+ return true;
1452
+ }
1453
+
1454
+ /* An Upgrade request (Connection: upgrade + an Upgrade header): the
1455
+ * 'upgrade' listeners take the socket RAW — the parser steps aside
1456
+ * (fn pointers clear; the conn ctx stays until the socket dies), bytes
1457
+ * already read past the head travel as `head`, and no 'request' fires.
1458
+ * With no listener Node destroys the socket; so does this slice. */
1459
+ if (has_upgrade_hdr && conn_upgrade_token) {
1460
+ memmove(conn->buf, conn->buf + head_len, conn->len - head_len);
1461
+ conn->len -= head_len;
1462
+ if (conn->srv->upgrade_ls.n == 0) {
1463
+ scr_http_req_release(req);
1464
+ conn->len = 0;
1465
+ scr_net_sock_destroy(conn->sock);
1466
+ return true;
1467
+ }
1468
+ ScrBytes *head = scr_bytes_new(SCR_BYTES_U8, (double)conn->len);
1469
+ if (conn->len > 0) memcpy(head->data, conn->buf, conn->len);
1470
+ conn->len = 0;
1471
+ scr_net_sock_clear_native_reader(conn->sock);
1472
+ ScrNetL *snap;
1473
+ size_t n = scr_net_ls_snapshot(&conn->srv->upgrade_ls, &snap);
1474
+ scr_dyn_this_push(scr_net_sock_server(conn->sock), SCR_DYNH_NET_SERVER);
1475
+ for (size_t i = 0; i < n; i++) {
1476
+ if (!scr_exc_pending()) {
1477
+ ((ScrHttpUpgradeFn)snap[i].fn)(snap[i].cb, scr_http_req_retain(req),
1478
+ scr_net_sock_retain(conn->sock), scr_bytes_retain(head));
1479
+ }
1480
+ scr_closure_release(snap[i].cb);
1481
+ }
1482
+ scr_dyn_this_pop();
1483
+ free(snap);
1484
+ scr_bytes_release(head);
1485
+ scr_http_req_release(req);
1486
+ return true;
1487
+ }
1488
+
1489
+ ScrHttpRes *res = calloc(1, sizeof *res);
1490
+ if (!res) scr_http_oom();
1491
+ res->rc = 1;
1492
+ #ifdef SCR_RC_AUDIT
1493
+ scr_http_live++;
1494
+ #endif
1495
+ res->sock = scr_net_sock_retain(conn->sock);
1496
+ res->conn = conn;
1497
+ res->keep_alive = req_keep_alive;
1498
+ conn->req = req; /* conn's +1 */
1499
+ conn->res = res;
1500
+ conn->close_after = !req_keep_alive;
1501
+
1502
+ if (chunked) {
1503
+ conn->state = SCR_HTTP_CHUNK_SIZE;
1504
+ } else if (content_length > 0) {
1505
+ conn->state = SCR_HTTP_BODY_CL;
1506
+ conn->body_remaining = (size_t)content_length;
1507
+ } else {
1508
+ conn->state = SCR_HTTP_HEAD; /* no body */
1509
+ }
1510
+
1511
+ /* consume the head bytes */
1512
+ memmove(conn->buf, conn->buf + head_len, conn->len - head_len);
1513
+ conn->len -= head_len;
1514
+
1515
+ /* fire the 'request' listeners (macrotask, main stack) — snapshot at
1516
+ * emit time, the scr_net_fire0 discipline; each listener gets its own
1517
+ * req/res refs (the adapters release what their shape drops) */
1518
+ {
1519
+ ScrNetL *snap;
1520
+ size_t n = scr_net_ls_snapshot(&conn->srv->request_ls, &snap);
1521
+ scr_dyn_this_push(scr_net_sock_server(conn->sock), SCR_DYNH_NET_SERVER);
1522
+ for (size_t i = 0; i < n; i++) {
1523
+ if (!scr_exc_pending()) {
1524
+ ((ScrHttpReqFn)snap[i].fn)(snap[i].cb, scr_http_req_retain(req), scr_http_res_retain(res));
1525
+ }
1526
+ scr_closure_release(snap[i].cb);
1527
+ }
1528
+ scr_dyn_this_pop();
1529
+ free(snap);
1530
+ }
1531
+ if (scr_exc_pending()) return true; /* the loop surfaces it */
1532
+ if (!chunked && content_length == 0) {
1533
+ /* headless body: 'end' delivers to the listeners the handler attached */
1534
+ scr_http_conn_body_done(conn);
1535
+ }
1536
+ return true;
1537
+ }
1538
+
1539
+ /* The parse pump: consume as much of conn->buf as the state machine can. */
1540
+ static void scr_http_conn_pump(ScrHttpConn *conn) {
1541
+ for (;;) {
1542
+ if (scr_exc_pending()) return;
1543
+ if (conn->state == SCR_HTTP_HEAD) {
1544
+ if (conn->client_mode) {
1545
+ if (conn->client == NULL || conn->client_resp_started) {
1546
+ /* response already delivered (or torn down): stray bytes after
1547
+ * the exchange are discarded — the socket is closing anyway */
1548
+ conn->len = 0;
1549
+ return;
1550
+ }
1551
+ } else if (conn->req != NULL && conn->res == NULL) {
1552
+ return; /* torn down */
1553
+ }
1554
+ /* between requests, or awaiting a head */
1555
+ if (conn->len == 0) return;
1556
+ /* find CRLFCRLF */
1557
+ char *hit = NULL;
1558
+ if (conn->len >= 4) {
1559
+ for (size_t i = 0; i + 3 < conn->len; i++) {
1560
+ if (conn->buf[i] == '\r' && conn->buf[i + 1] == '\n' && conn->buf[i + 2] == '\r' &&
1561
+ conn->buf[i + 3] == '\n') {
1562
+ hit = conn->buf + i;
1563
+ break;
1564
+ }
1565
+ }
1566
+ }
1567
+ if (!hit) {
1568
+ if (conn->len > 65536) {
1569
+ if (conn->client_mode) scr_http_client_head_overflow(conn);
1570
+ else scr_http_conn_bad_request(conn); /* header cap */
1571
+ }
1572
+ return;
1573
+ }
1574
+ size_t head_len = (size_t)(hit - conn->buf) + 4;
1575
+ if (conn->client_mode) {
1576
+ if (!scr_http_client_parse_head(conn, head_len)) {
1577
+ scr_http_client_head_overflow(conn); /* malformed: hang up */
1578
+ }
1579
+ if (scr_exc_pending()) return;
1580
+ continue;
1581
+ }
1582
+ /* a previous exchange must be COMPLETE before the next parses: the
1583
+ * response may still be streaming (keep-alive pipelining waits) */
1584
+ if (conn->res != NULL && !conn->res->finished) return;
1585
+ if (conn->res != NULL) scr_http_conn_drop_request(conn, false);
1586
+ if (!scr_http_conn_parse_head(conn, head_len)) {
1587
+ scr_http_conn_bad_request(conn);
1588
+ return;
1589
+ }
1590
+ continue;
1591
+ }
1592
+ if (conn->state == SCR_HTTP_BODY_CL) {
1593
+ if (conn->len == 0) return;
1594
+ size_t take = conn->len < conn->body_remaining ? conn->len : conn->body_remaining;
1595
+ scr_http_conn_body_data(conn, conn->buf, take);
1596
+ memmove(conn->buf, conn->buf + take, conn->len - take);
1597
+ conn->len -= take;
1598
+ conn->body_remaining -= take;
1599
+ if (conn->body_remaining == 0) scr_http_conn_body_done(conn);
1600
+ else return;
1601
+ continue;
1602
+ }
1603
+ if (conn->state == SCR_HTTP_CHUNK_SIZE) {
1604
+ char *eol = NULL;
1605
+ for (size_t i = 0; i + 1 < conn->len; i++) {
1606
+ if (conn->buf[i] == '\r' && conn->buf[i + 1] == '\n') {
1607
+ eol = conn->buf + i;
1608
+ break;
1609
+ }
1610
+ }
1611
+ if (!eol) return;
1612
+ size_t size = 0;
1613
+ bool any = false;
1614
+ for (char *p = conn->buf; p < eol; p++) {
1615
+ char c = *p;
1616
+ if (c == ';') break; /* chunk extensions: ignored */
1617
+ int digit;
1618
+ if (c >= '0' && c <= '9') digit = c - '0';
1619
+ else if (c >= 'a' && c <= 'f') digit = c - 'a' + 10;
1620
+ else if (c >= 'A' && c <= 'F') digit = c - 'A' + 10;
1621
+ else {
1622
+ scr_http_conn_bad_request(conn);
1623
+ return;
1624
+ }
1625
+ size = size * 16 + (size_t)digit;
1626
+ any = true;
1627
+ }
1628
+ if (!any) {
1629
+ scr_http_conn_bad_request(conn);
1630
+ return;
1631
+ }
1632
+ size_t consumed = (size_t)(eol - conn->buf) + 2;
1633
+ memmove(conn->buf, conn->buf + consumed, conn->len - consumed);
1634
+ conn->len -= consumed;
1635
+ if (size == 0) {
1636
+ conn->state = SCR_HTTP_CHUNK_TRAILER;
1637
+ } else {
1638
+ conn->state = SCR_HTTP_CHUNK_DATA;
1639
+ conn->body_remaining = size;
1640
+ }
1641
+ continue;
1642
+ }
1643
+ if (conn->state == SCR_HTTP_CHUNK_DATA) {
1644
+ if (conn->len == 0) return;
1645
+ size_t take = conn->len < conn->body_remaining ? conn->len : conn->body_remaining;
1646
+ scr_http_conn_body_data(conn, conn->buf, take);
1647
+ memmove(conn->buf, conn->buf + take, conn->len - take);
1648
+ conn->len -= take;
1649
+ conn->body_remaining -= take;
1650
+ if (conn->body_remaining == 0) conn->state = SCR_HTTP_CHUNK_CRLF;
1651
+ else return;
1652
+ continue;
1653
+ }
1654
+ if (conn->state == SCR_HTTP_CHUNK_CRLF) {
1655
+ if (conn->len < 2) return;
1656
+ if (conn->buf[0] != '\r' || conn->buf[1] != '\n') {
1657
+ scr_http_conn_bad_request(conn);
1658
+ return;
1659
+ }
1660
+ memmove(conn->buf, conn->buf + 2, conn->len - 2);
1661
+ conn->len -= 2;
1662
+ conn->state = SCR_HTTP_CHUNK_SIZE;
1663
+ continue;
1664
+ }
1665
+ if (conn->state == SCR_HTTP_BODY_EOF) {
1666
+ /* RESPONSE mode, no framing header: every arrived byte is body;
1667
+ * EOF completes it (the eof hook calls body_done). */
1668
+ if (conn->len == 0) return;
1669
+ scr_http_conn_body_data(conn, conn->buf, conn->len);
1670
+ conn->len = 0;
1671
+ return;
1672
+ }
1673
+ if (conn->state == SCR_HTTP_CHUNK_TRAILER) {
1674
+ /* trailers until the blank line; all discarded */
1675
+ if (conn->len < 2) return;
1676
+ if (conn->buf[0] == '\r' && conn->buf[1] == '\n') {
1677
+ memmove(conn->buf, conn->buf + 2, conn->len - 2);
1678
+ conn->len -= 2;
1679
+ scr_http_conn_body_done(conn);
1680
+ continue;
1681
+ }
1682
+ char *eol = NULL;
1683
+ for (size_t i = 0; i + 1 < conn->len; i++) {
1684
+ if (conn->buf[i] == '\r' && conn->buf[i + 1] == '\n') {
1685
+ eol = conn->buf + i;
1686
+ break;
1687
+ }
1688
+ }
1689
+ if (!eol) return;
1690
+ size_t consumed = (size_t)(eol - conn->buf) + 2;
1691
+ memmove(conn->buf, conn->buf + consumed, conn->len - consumed);
1692
+ conn->len -= consumed;
1693
+ continue;
1694
+ }
1695
+ return;
1696
+ }
1697
+ }
1698
+
1699
+ /* ── the native socket hooks ─────────────────────────────────────────── */
1700
+
1701
+ static void scr_http_conn_data(void *ctx, const char *buf, size_t n) {
1702
+ ScrHttpConn *conn = (ScrHttpConn *)ctx;
1703
+ if (conn->len + n > conn->cap) {
1704
+ size_t cap = conn->cap ? conn->cap : 8192;
1705
+ while (cap < conn->len + n) cap *= 2;
1706
+ conn->buf = realloc(conn->buf, cap);
1707
+ if (!conn->buf) scr_http_oom();
1708
+ conn->cap = cap;
1709
+ }
1710
+ memcpy(conn->buf + conn->len, buf, n);
1711
+ conn->len += n;
1712
+ scr_http_conn_pump(conn);
1713
+ }
1714
+
1715
+ static void scr_http_client_eof(ScrHttpConn *conn);
1716
+ static void scr_http_client_closed(ScrHttpConn *conn);
1717
+ static void scr_http_client_conn_free(ScrHttpConn *conn);
1718
+
1719
+ static void scr_http_conn_eof(void *ctx) {
1720
+ ScrHttpConn *conn = (ScrHttpConn *)ctx;
1721
+ if (conn->client_mode) {
1722
+ scr_http_client_eof(conn);
1723
+ return;
1724
+ }
1725
+ /* client FIN between requests: our half closes too (keep-alive over);
1726
+ * mid-request it is an aborted request — everything settles quietly */
1727
+ scr_http_conn_drop_request(conn, false);
1728
+ conn->len = 0;
1729
+ }
1730
+
1731
+ static void scr_http_conn_closed(void *ctx) {
1732
+ ScrHttpConn *conn = (ScrHttpConn *)ctx;
1733
+ if (conn->client_mode) {
1734
+ scr_http_client_closed(conn);
1735
+ return;
1736
+ }
1737
+ scr_http_conn_drop_request(conn, false);
1738
+ }
1739
+
1740
+ /* Socket errors under a parser: the protocol layer owns the story. On
1741
+ * the SERVER an abrupt client death (ECONNRESET mid-request) must never
1742
+ * crash the process — Node's http server swallows these into request
1743
+ * teardown ('error' listeners on the req get "aborted"); the CLIENT
1744
+ * routes them to the request handle's 'error'. Always consumed: the
1745
+ * underlying socket's own listeners never see a parser-owned error. */
1746
+ static bool scr_http_client_sock_err(ScrHttpConn *conn, ScrStr *msg);
1747
+
1748
+ static bool scr_http_conn_err(void *ctx, ScrStr *msg) {
1749
+ ScrHttpConn *conn = (ScrHttpConn *)ctx;
1750
+ if (conn->client_mode) return scr_http_client_sock_err(conn, msg);
1751
+ if (conn->req && conn->req->err_ls.n > 0) {
1752
+ ScrStr *aborted = scr_str_new("aborted", 7);
1753
+ scr_net_fire_err_this(&conn->req->err_ls, aborted, conn->req, SCR_DYNH_HTTP_REQ);
1754
+ scr_str_release(aborted);
1755
+ }
1756
+ scr_http_conn_drop_request(conn, false);
1757
+ return true;
1758
+ }
1759
+
1760
+ static void scr_http_conn_free(void *ctx) {
1761
+ ScrHttpConn *conn = (ScrHttpConn *)ctx;
1762
+ if (conn->client_mode) {
1763
+ scr_http_client_conn_free(conn);
1764
+ return;
1765
+ }
1766
+ scr_http_conn_drop_request(conn, false);
1767
+ if (conn->srv) scr_http_srv_ctx_release(conn->srv);
1768
+ free(conn->buf);
1769
+ free(conn);
1770
+ }
1771
+
1772
+ /* The server's native connection hook: one parser per accepted socket. */
1773
+ static void scr_http_on_connection(void *ctx, ScrNetSocket *sock) {
1774
+ ScrHttpSrvCtx *srv = (ScrHttpSrvCtx *)ctx;
1775
+ ScrHttpConn *conn = calloc(1, sizeof *conn);
1776
+ if (!conn) scr_http_oom();
1777
+ conn->sock = sock; /* borrowed — see the struct comment */
1778
+ conn->srv = scr_http_srv_ctx_retain(srv);
1779
+ scr_net_sock_set_native_reader(sock, &scr_http_conn_data, &scr_http_conn_eof,
1780
+ &scr_http_conn_closed, conn, &scr_http_conn_free);
1781
+ scr_net_sock_set_native_events(sock, NULL, &scr_http_conn_err);
1782
+ }
1783
+
1784
+ /* The unguarded h2-only stream call (`req.stream.on(...)`): stream IS
1785
+ * undefined on every connection this lowering accepts — and on every
1786
+ * HTTP/1.1 connection of Node's own allowHTTP1 server — so calling
1787
+ * through it is Node's member read on undefined: the exact catchable
1788
+ * TypeError, member name interpolated. Borrows the name; never returns. */
1789
+ void scr_http2_stream_undef_call(ScrStr *member) {
1790
+ char msg[128];
1791
+ int n = snprintf(msg, sizeof msg, "Cannot read properties of undefined (reading '%.*s')",
1792
+ (int)member->len, member->data);
1793
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)n);
1794
+ }
1795
+
1796
+ /* http.createServer(handler): an ordinary net server whose connections
1797
+ * feed the parser; listen/close/address/'error' all ride the net surface.
1798
+ * A NULL handler (http2.createSecureServer's route) creates the server
1799
+ * with an empty 'request' list — server.on("request", ...) fills it. */
1800
+ ScrNetServer *scr_http_create_server(ScrClosure *handler /*moves, nullable*/, ScrHttpReqFn fn) {
1801
+ scr_http_install();
1802
+ ScrNetServer *s = scr_net_create_server(NULL, NULL);
1803
+ ScrHttpSrvCtx *ctx = calloc(1, sizeof *ctx);
1804
+ if (!ctx) scr_http_oom();
1805
+ ctx->proto = SCR_NET_PROTO_HTTP1;
1806
+ ctx->rc = 1;
1807
+ if (handler != NULL) scr_net_ls_add(&ctx->request_ls, handler, (void *)fn, false);
1808
+ scr_net_server_set_native_conn(s, &scr_http_on_connection, ctx, &scr_http_srv_ctx_free);
1809
+ scr_net_server_set_http_ctx(s, ctx);
1810
+ scr_net_server_set_proto_settle(s, &scr_http_srv_ctx_settle);
1811
+ return s;
1812
+ }
1813
+
1814
+ /* createServer({ joinDuplicateHeaders: true, ... }): repeated request
1815
+ * header names read back joined with ", " where Node's default (and this
1816
+ * parser's) keeps the first occurrence. A no-op on parserless servers. */
1817
+ void scr_http_server_join_duplicate_headers(ScrNetServer *s) {
1818
+ ScrHttpSrvCtx *ctx = (ScrHttpSrvCtx *)scr_net_server_get_http_ctx(s);
1819
+ if (ctx != NULL && ctx->proto == SCR_NET_PROTO_HTTP1) ctx->join_dup = true;
1820
+ }
1821
+
1822
+ /* Late 'request' listener installs (server.on/once("request", ...)): the
1823
+ * http2 allowHTTP1 server's handler route, and http.Server's 'request'
1824
+ * event. On a server with no HTTP parser the closure releases
1825
+ * unregistered — 'request' never fires there, like Node. */
1826
+ void scr_http_server_on_request(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrHttpReqFn fn,
1827
+ bool once) {
1828
+ ScrHttpSrvCtx *ctx = (ScrHttpSrvCtx *)scr_net_server_get_http_ctx(s);
1829
+ if (ctx != NULL && ctx->proto != SCR_NET_PROTO_HTTP1) {
1830
+ /* an h2 server's ctx: the compat seam — scr_http2.c's hook owns the
1831
+ * registration (Http2ServerRequest/Response over h2 streams) */
1832
+ if (scr_http_h2_request_hook != NULL && !scr_net_server_settled(s)) {
1833
+ scr_http_h2_request_hook(ctx, cb, (void *)fn, once);
1834
+ return;
1835
+ }
1836
+ ctx = NULL;
1837
+ }
1838
+ if (ctx == NULL || scr_net_server_settled(s)) {
1839
+ /* no parser, or the server already settled ('request' can never fire
1840
+ * again — an install would only re-arm the cycle the settle broke) */
1841
+ scr_closure_release(cb);
1842
+ return;
1843
+ }
1844
+ scr_net_ls_add(&ctx->request_ls, cb, (void *)fn, once);
1845
+ }
1846
+
1847
+ /* ── the h2 compat pair (built by scr_http2.c per server stream) ───────
1848
+ *
1849
+ * The request materializes from the decoded header list (:method/:path
1850
+ * feed the line; EVERY pair — pseudo-headers included — lands in
1851
+ * req.headers, Node's compat shape); the response routes its write paths
1852
+ * through the h2 ops vtable. Body DATA frames and END_STREAM feed the
1853
+ * req exactly like the parser's body states; 'close' rides this unit's
1854
+ * deferred-emit queue so ordering matches the http/1 story (res before
1855
+ * req). */
1856
+
1857
+ ScrHttpReq *scr_http_h2_req_new(ScrNetSocket *sock /*borrowed, nullable*/,
1858
+ void *stream /*borrowed, nullable*/) {
1859
+ scr_http_install();
1860
+ ScrHttpReq *req = calloc(1, sizeof *req);
1861
+ if (!req) scr_http_oom();
1862
+ req->rc = 1;
1863
+ req->status = -1; /* server requests: statusCode is Node's undefined */
1864
+ req->http2 = true;
1865
+ req->sock = sock ? scr_net_sock_retain(sock) : NULL;
1866
+ req->h2_stream = stream != NULL ? scr_http_h2_ops->retain(stream) : NULL;
1867
+ req->method = scr_str_new("GET", 3); /* :method replaces it */
1868
+ req->url = scr_str_new("/", 1); /* :path replaces it */
1869
+ #ifdef SCR_RC_AUDIT
1870
+ scr_http_live++;
1871
+ #endif
1872
+ return req;
1873
+ }
1874
+
1875
+ void scr_http_h2_req_line(ScrHttpReq *r, ScrStr *method /*borrowed, nullable*/,
1876
+ ScrStr *url /*borrowed, nullable*/) {
1877
+ if (method != NULL) {
1878
+ scr_str_release(r->method);
1879
+ r->method = scr_str_retain(method);
1880
+ }
1881
+ if (url != NULL) {
1882
+ scr_str_release(r->url);
1883
+ r->url = scr_str_retain(url);
1884
+ }
1885
+ }
1886
+
1887
+ void scr_http_h2_req_header(ScrHttpReq *r, ScrStr *name /*borrowed*/, ScrStr *value /*borrowed*/) {
1888
+ scr_http_req_add_header(r, name->data, name->len, value->data, value->len);
1889
+ }
1890
+
1891
+ void scr_http_h2_req_data(ScrHttpReq *r, const char *data, size_t n) {
1892
+ scr_http_req_deliver(r, data, n);
1893
+ }
1894
+
1895
+ void scr_http_h2_req_end(ScrHttpReq *r) { scr_http_req_finish(r, true); }
1896
+
1897
+ /* Typed member reads shared by both parser lanes: the version triple and
1898
+ * the compat pair's aborted/complete flags. */
1899
+ ScrStr *scr_http_req_http_version(ScrHttpReq *r) {
1900
+ return scr_str_new(r->http2 ? "2.0" : r->http10 ? "1.0" : "1.1", 3);
1901
+ }
1902
+ double scr_http_req_http_version_major(ScrHttpReq *r) { return r->http2 ? 2 : 1; }
1903
+ double scr_http_req_http_version_minor(ScrHttpReq *r) { return r->http2 || r->http10 ? 0 : 1; }
1904
+ bool scr_http_req_aborted_flag(ScrHttpReq *r) { return r->aborted; }
1905
+ bool scr_http_req_complete(ScrHttpReq *r) { return r->ended; }
1906
+
1907
+ /* The stream died with the response side open: 'aborted' fires NOW (the
1908
+ * teardown macrotask, Node's position), then the close rides the sweep. */
1909
+ void scr_http_h2_req_aborted(ScrHttpReq *r) {
1910
+ if (r->close_emitted || r->aborted) return;
1911
+ r->aborted = true;
1912
+ scr_net_fire0_this(&r->aborted_ls, r, SCR_DYNH_HTTP_REQ);
1913
+ scr_net_ls_drop(&r->aborted_ls);
1914
+ }
1915
+
1916
+ void scr_http_h2_req_close(ScrHttpReq *r) { scr_http_queue_req_close(r); }
1917
+
1918
+ ScrHttpRes *scr_http_h2_res_new(ScrNetSocket *sock /*borrowed, nullable*/,
1919
+ void *stream /*borrowed*/) {
1920
+ scr_http_install();
1921
+ ScrHttpRes *res = calloc(1, sizeof *res);
1922
+ if (!res) scr_http_oom();
1923
+ res->rc = 1;
1924
+ res->sock = sock ? scr_net_sock_retain(sock) : NULL;
1925
+ res->h2_stream = scr_http_h2_ops->retain(stream);
1926
+ res->keep_alive = true; /* inert on h2 — no Connection header exists */
1927
+ #ifdef SCR_RC_AUDIT
1928
+ scr_http_live++;
1929
+ #endif
1930
+ return res;
1931
+ }
1932
+
1933
+ void scr_http_h2_res_close(ScrHttpRes *r) { scr_http_queue_res_close(r); }
1934
+
1935
+ bool scr_http_h2_res_finished(ScrHttpRes *r) { return r->finished; }
1936
+
1937
+ /* server.on("connect", (req, socket, head) => ...) — HTTP CONNECT, and
1938
+ * server.on("upgrade", ...) below: the registration twins of on_request.
1939
+ * On a server with no HTTP parser neither list ever fires — honest dead
1940
+ * weight, Node's shape. */
1941
+ void scr_http_server_on_connect(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrHttpUpgradeFn fn,
1942
+ bool once) {
1943
+ ScrHttpSrvCtx *ctx = (ScrHttpSrvCtx *)scr_net_server_get_http_ctx(s);
1944
+ if (ctx != NULL && ctx->proto != SCR_NET_PROTO_HTTP1) ctx = NULL; /* an h2 server's ctx */
1945
+ if (ctx == NULL) {
1946
+ /* no HTTP parser on this server: the event can never fire (Node's
1947
+ * net server never emits 'connect' either) — honest dead weight */
1948
+ scr_closure_release(cb);
1949
+ return;
1950
+ }
1951
+ scr_net_ls_add(&ctx->connect_ls, cb, (void *)fn, once);
1952
+ }
1953
+
1954
+ void scr_http_server_on_upgrade(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrHttpUpgradeFn fn,
1955
+ bool once) {
1956
+ ScrHttpSrvCtx *ctx = (ScrHttpSrvCtx *)scr_net_server_get_http_ctx(s);
1957
+ if (ctx != NULL && ctx->proto != SCR_NET_PROTO_HTTP1) ctx = NULL; /* an h2 server's ctx */
1958
+ if (ctx == NULL) {
1959
+ scr_closure_release(cb);
1960
+ return;
1961
+ }
1962
+ scr_net_ls_add(&ctx->upgrade_ls, cb, (void *)fn, once);
1963
+ }
1964
+
1965
+ /* The upgrade adapters: (req, socket, head), and every shorter prefix.
1966
+ * All three arrive +1; unused ones release here. */
1967
+ void scr_http_upgrade_thunk3(ScrClosure *cb, ScrHttpReq *req, ScrNetSocket *sock, ScrBytes *head) {
1968
+ ((void (*)(ScrClosure *, ScrHttpReq *, ScrNetSocket *, ScrBytes *))cb->fn)(cb, req, sock, head);
1969
+ }
1970
+
1971
+ void scr_http_upgrade_thunk2(ScrClosure *cb, ScrHttpReq *req, ScrNetSocket *sock, ScrBytes *head) {
1972
+ scr_bytes_release(head);
1973
+ ((void (*)(ScrClosure *, ScrHttpReq *, ScrNetSocket *))cb->fn)(cb, req, sock);
1974
+ }
1975
+
1976
+ void scr_http_upgrade_thunk1(ScrClosure *cb, ScrHttpReq *req, ScrNetSocket *sock, ScrBytes *head) {
1977
+ scr_bytes_release(head);
1978
+ scr_net_sock_release(sock);
1979
+ ((void (*)(ScrClosure *, ScrHttpReq *))cb->fn)(cb, req);
1980
+ }
1981
+
1982
+ void scr_http_upgrade_thunk0(ScrClosure *cb, ScrHttpReq *req, ScrNetSocket *sock, ScrBytes *head) {
1983
+ scr_bytes_release(head);
1984
+ scr_net_sock_release(sock);
1985
+ scr_http_req_release(req);
1986
+ ((void (*)(ScrClosure *))cb->fn)(cb);
1987
+ }
1988
+
1989
+ /* The handler adapters: (req, res), (req), and (). Both handles arrive
1990
+ * +1; unused ones release here. */
1991
+ void scr_http_handler_thunk2(ScrClosure *cb, ScrHttpReq *req, ScrHttpRes *res) {
1992
+ ((void (*)(ScrClosure *, ScrHttpReq *, ScrHttpRes *))cb->fn)(cb, req, res);
1993
+ }
1994
+
1995
+ void scr_http_handler_thunk1(ScrClosure *cb, ScrHttpReq *req, ScrHttpRes *res) {
1996
+ scr_http_res_release(res);
1997
+ ((void (*)(ScrClosure *, ScrHttpReq *))cb->fn)(cb, req);
1998
+ }
1999
+
2000
+ void scr_http_handler_thunk0(ScrClosure *cb, ScrHttpReq *req, ScrHttpRes *res) {
2001
+ scr_http_req_release(req);
2002
+ scr_http_res_release(res);
2003
+ ((void (*)(ScrClosure *))cb->fn)(cb);
2004
+ }
2005
+
2006
+ /* ── the CLIENT (http.request / http.get) ────────────────────────────────
2007
+ *
2008
+ * One handle per request, one dialed connection per handle (no agent
2009
+ * pooling — SEMANTICS.md; the wire is Node's regardless, Connection:
2010
+ * keep-alive included, and the socket closes when the response
2011
+ * completes). Ownership: the handle holds the socket (+1) and, once
2012
+ * parsed, the response req (+1); the socket's parser ctx holds the
2013
+ * handle (+1) until the connection dies — the registry-shaped cycle
2014
+ * breaks at teardown exactly like the server's conn→req edge. A live
2015
+ * client is registered (+1, atexit-cleaned) so abandoned handles behave
2016
+ * like abandoned sockets. */
2017
+
2018
+ struct ScrHttpClientReq {
2019
+ size_t rc;
2020
+ ScrNetSocket *sock; /* +1 */
2021
+ ScrHttpConn *conn; /* borrowed backref (the socket's ctx); NULL after free */
2022
+ ScrStr *host, *path, *method;
2023
+ int port;
2024
+ int default_port; /* the Host header omits it (80 http, 443 https) */
2025
+ ScrStr **hnames; /* user headers, verbatim case */
2026
+ ScrStr **hvalues;
2027
+ size_t nheaders;
2028
+ bool user_cl; /* caller set content-length/transfer-encoding */
2029
+ bool head_sent;
2030
+ bool chunked; /* streaming framing committed */
2031
+ bool ended;
2032
+ bool destroyed;
2033
+ bool response_started; /* head parsed, res exists */
2034
+ bool response_done;
2035
+ bool had_error;
2036
+ bool close_queued;
2037
+ bool close_emitted; /* settled: listeners dropped, off the registry */
2038
+ ScrHttpReq *res; /* +1 once the head parses */
2039
+ ScrNetLs resp_ls, err_ls, timeout_ls, close_ls, upgrade_ls;
2040
+ bool in_registry;
2041
+ struct ScrHttpClientReq *next;
2042
+ };
2043
+
2044
+ static ScrHttpClientReq *scr_http_clients = NULL; /* registry: +1 each */
2045
+
2046
+ ScrHttpClientReq *scr_http_client_retain(ScrHttpClientReq *c) {
2047
+ if (c->rc != SIZE_MAX) c->rc++;
2048
+ return c;
2049
+ }
2050
+
2051
+ void scr_http_client_release(ScrHttpClientReq *c) {
2052
+ if (!c || c->rc == SIZE_MAX) return;
2053
+ if (--c->rc == 0) {
2054
+ scr_net_ls_drop(&c->resp_ls);
2055
+ scr_net_ls_drop(&c->err_ls);
2056
+ scr_net_ls_drop(&c->timeout_ls);
2057
+ scr_net_ls_drop(&c->close_ls);
2058
+ scr_net_ls_drop(&c->upgrade_ls);
2059
+ scr_str_release(c->host);
2060
+ scr_str_release(c->path);
2061
+ scr_str_release(c->method);
2062
+ for (size_t i = 0; i < c->nheaders; i++) {
2063
+ scr_str_release(c->hnames[i]);
2064
+ scr_str_release(c->hvalues[i]);
2065
+ }
2066
+ free(c->hnames);
2067
+ free(c->hvalues);
2068
+ if (c->res) scr_http_req_release(c->res);
2069
+ if (c->sock) scr_net_sock_release(c->sock);
2070
+ #ifdef SCR_RC_AUDIT
2071
+ scr_http_live--;
2072
+ #endif
2073
+ free(c);
2074
+ }
2075
+ }
2076
+
2077
+ static void scr_http_client_release_internal(struct ScrHttpClientReq *c) {
2078
+ scr_http_client_release(c);
2079
+ }
2080
+
2081
+ void *scr_http_client_retain_v(void *p) { return scr_http_client_retain((ScrHttpClientReq *)p); }
2082
+ void scr_http_client_release_v(void *p) { scr_http_client_release((ScrHttpClientReq *)p); }
2083
+
2084
+ static void scr_http_client_register(ScrHttpClientReq *c) {
2085
+ if (c->in_registry) return;
2086
+ c->in_registry = true;
2087
+ c->next = NULL;
2088
+ ScrHttpClientReq **link = &scr_http_clients;
2089
+ while (*link) link = &(*link)->next;
2090
+ *link = scr_http_client_retain(c);
2091
+ }
2092
+
2093
+ static void scr_http_client_unregister(ScrHttpClientReq *c) {
2094
+ if (!c->in_registry) return;
2095
+ ScrHttpClientReq **link = &scr_http_clients;
2096
+ while (*link && *link != c) link = &(*link)->next;
2097
+ if (*link) {
2098
+ *link = c->next;
2099
+ c->next = NULL;
2100
+ c->in_registry = false;
2101
+ scr_http_client_release(c);
2102
+ }
2103
+ }
2104
+
2105
+ /* The queue's CLIENT_CLOSE emit: 'close' fires, the handle settles
2106
+ * (listeners drop — the cycle story) and leaves the registry. */
2107
+ static void scr_http_client_settle(struct ScrHttpClientReq *c) {
2108
+ if (c->close_emitted) return;
2109
+ c->close_emitted = true;
2110
+ c->destroyed = true;
2111
+ scr_net_fire0(&c->close_ls);
2112
+ scr_net_ls_drop(&c->resp_ls);
2113
+ scr_net_ls_drop(&c->err_ls);
2114
+ scr_net_ls_drop(&c->timeout_ls);
2115
+ scr_net_ls_drop(&c->close_ls);
2116
+ scr_http_client_unregister(c);
2117
+ }
2118
+
2119
+ static void scr_http_client_queue_close(ScrHttpClientReq *c) {
2120
+ if (c->close_queued || c->close_emitted) return;
2121
+ c->close_queued = true;
2122
+ scr_http_emit_push(SCR_HTTP_EMIT_CLIENT_CLOSE, scr_http_client_retain(c));
2123
+ }
2124
+
2125
+ /* 'error' on the request handle: fires NOW (the callers sit in the net
2126
+ * sweep / dispatch, Node's own emit spots); no listener exits 1, the
2127
+ * unhandled-'error' story. The close follows through the queue. */
2128
+ static void scr_http_client_error(ScrHttpClientReq *c, ScrStr *msg /*borrowed*/) {
2129
+ if (c->close_emitted) return;
2130
+ c->had_error = true;
2131
+ scr_net_fire_err(&c->err_ls, msg);
2132
+ }
2133
+
2134
+ /* ── the wire: Node's exact request head ─────────────────────────────── */
2135
+
2136
+ static bool scr_http_client_has_header(ScrHttpClientReq *c, const char *name) {
2137
+ size_t len = strlen(name);
2138
+ for (size_t i = 0; i < c->nheaders; i++) {
2139
+ if (c->hnames[i]->len == len) {
2140
+ bool eq = true;
2141
+ for (size_t j = 0; j < len && eq; j++) {
2142
+ if (tolower((unsigned char)c->hnames[i]->data[j]) != tolower((unsigned char)name[j])) eq = false;
2143
+ }
2144
+ if (eq) return true;
2145
+ }
2146
+ }
2147
+ return false;
2148
+ }
2149
+
2150
+ /* Serializes and sends the head. `body_len` >= 0 fixes Content-Length
2151
+ * (the end(data) path — and Node's Content-Length: 0 for empty POST/PUT/
2152
+ * PATCH bodies); -1 means STREAMING: chunked unless the caller set the
2153
+ * framing. Node's order: user headers verbatim, then Host (unless user-
2154
+ * set), Connection: keep-alive, and the framing header. */
2155
+ static void scr_http_client_send_head(ScrHttpClientReq *c, long long body_len) {
2156
+ if (c->head_sent) return;
2157
+ c->head_sent = true;
2158
+ ScrHttpBuf b = {NULL, 0, 0};
2159
+ scr_http_buf_append(&b, c->method->data, c->method->len);
2160
+ scr_http_buf_str(&b, " ");
2161
+ scr_http_buf_append(&b, c->path->data, c->path->len);
2162
+ scr_http_buf_str(&b, " HTTP/1.1\r\n");
2163
+ for (size_t i = 0; i < c->nheaders; i++) {
2164
+ scr_http_buf_append(&b, c->hnames[i]->data, c->hnames[i]->len);
2165
+ scr_http_buf_str(&b, ": ");
2166
+ scr_http_buf_append(&b, c->hvalues[i]->data, c->hvalues[i]->len);
2167
+ scr_http_buf_str(&b, "\r\n");
2168
+ }
2169
+ if (!scr_http_client_has_header(c, "host")) {
2170
+ /* Host: name[:port] — the scheme's default port omitted (80 http,
2171
+ * 443 https), IPv6 literals bracketed (Node) */
2172
+ bool v6 = memchr(c->host->data, ':', c->host->len) != NULL;
2173
+ scr_http_buf_str(&b, "Host: ");
2174
+ if (v6) scr_http_buf_str(&b, "[");
2175
+ scr_http_buf_append(&b, c->host->data, c->host->len);
2176
+ if (v6) scr_http_buf_str(&b, "]");
2177
+ if (c->port != c->default_port) {
2178
+ char pbuf[16];
2179
+ snprintf(pbuf, sizeof pbuf, ":%d", c->port);
2180
+ scr_http_buf_str(&b, pbuf);
2181
+ }
2182
+ scr_http_buf_str(&b, "\r\n");
2183
+ }
2184
+ if (!scr_http_client_has_header(c, "connection")) {
2185
+ scr_http_buf_str(&b, "Connection: keep-alive\r\n");
2186
+ }
2187
+ if (!c->user_cl) {
2188
+ if (body_len < 0) {
2189
+ scr_http_buf_str(&b, "Transfer-Encoding: chunked\r\n");
2190
+ c->chunked = true;
2191
+ } else if (body_len > 0) {
2192
+ char cl[48];
2193
+ snprintf(cl, sizeof cl, "Content-Length: %lld\r\n", body_len);
2194
+ scr_http_buf_str(&b, cl);
2195
+ } else {
2196
+ /* empty body: POST/PUT/PATCH send Content-Length: 0, everything
2197
+ * else sends no framing header at all — Node's method split */
2198
+ const ScrStr *m = c->method;
2199
+ bool bodied = (m->len == 4 && memcmp(m->data, "POST", 4) == 0) ||
2200
+ (m->len == 3 && memcmp(m->data, "PUT", 3) == 0) ||
2201
+ (m->len == 5 && memcmp(m->data, "PATCH", 5) == 0);
2202
+ if (bodied) scr_http_buf_str(&b, "Content-Length: 0\r\n");
2203
+ }
2204
+ }
2205
+ scr_http_buf_str(&b, "\r\n");
2206
+ if (c->sock) scr_net_sock_write_native(c->sock, b.data, b.len);
2207
+ free(b.data);
2208
+ }
2209
+
2210
+ static void scr_http_client_write_raw(ScrHttpClientReq *c, const char *data, size_t len) {
2211
+ if (c->ended || c->destroyed || c->close_emitted) return; /* write-after-end drops (divergence 48's stance) */
2212
+ if (!c->head_sent) scr_http_client_send_head(c, -1); /* streaming: chunked */
2213
+ if (!c->sock || len == 0) return;
2214
+ if (c->chunked) {
2215
+ char size[32];
2216
+ snprintf(size, sizeof size, "%zx\r\n", len);
2217
+ scr_net_sock_write_native(c->sock, size, strlen(size));
2218
+ scr_net_sock_write_native(c->sock, data, len);
2219
+ scr_net_sock_write_native(c->sock, "\r\n", 2);
2220
+ } else {
2221
+ scr_net_sock_write_native(c->sock, data, len);
2222
+ }
2223
+ }
2224
+
2225
+ void scr_http_client_write_str(ScrHttpClientReq *c, ScrStr *data /*borrowed*/) {
2226
+ scr_http_client_write_raw(c, data->data, data->len);
2227
+ }
2228
+
2229
+ void scr_http_client_write_bytes(ScrHttpClientReq *c, ScrBytes *data /*borrowed*/) {
2230
+ scr_http_client_write_raw(c, (const char *)data->data, data->len);
2231
+ }
2232
+
2233
+ static void scr_http_client_end_raw(ScrHttpClientReq *c, const char *data, size_t len) {
2234
+ if (c->ended || c->destroyed || c->close_emitted) return;
2235
+ if (!c->head_sent) {
2236
+ /* whole body known NOW: Content-Length framing (or none — the head
2237
+ * serializer's empty-body method split) */
2238
+ scr_http_client_send_head(c, (long long)len);
2239
+ if (c->sock && len > 0) scr_net_sock_write_native(c->sock, data, len);
2240
+ } else {
2241
+ scr_http_client_write_raw(c, data, len);
2242
+ if (c->chunked && c->sock) scr_net_sock_write_native(c->sock, "0\r\n\r\n", 5);
2243
+ }
2244
+ c->ended = true;
2245
+ }
2246
+
2247
+ void scr_http_client_end(ScrHttpClientReq *c) { scr_http_client_end_raw(c, "", 0); }
2248
+
2249
+ void scr_http_client_end_str(ScrHttpClientReq *c, ScrStr *data /*borrowed*/) {
2250
+ scr_http_client_end_raw(c, data->data, data->len);
2251
+ }
2252
+
2253
+ void scr_http_client_end_bytes(ScrHttpClientReq *c, ScrBytes *data /*borrowed*/) {
2254
+ scr_http_client_end_raw(c, (const char *)data->data, data->len);
2255
+ }
2256
+
2257
+ /* req.setTimeout(ms) after construction (the island bridge's late arm —
2258
+ * the constructor's timeout_ms is the static lane's route). */
2259
+ void scr_http_client_set_timeout(ScrHttpClientReq *c, double ms) {
2260
+ if (c->sock) scr_net_sock_set_timeout(c->sock, ms);
2261
+ }
2262
+
2263
+ /* destroy(): tears the connection down NOW. A destroy after the response
2264
+ * completed is a quiet no-op past the socket close (Node: just 'close');
2265
+ * before it, the teardown surfaces as 'socket hang up' — both flow
2266
+ * through the closed hook below. */
2267
+ void scr_http_client_destroy(ScrHttpClientReq *c) {
2268
+ if (c->destroyed) return;
2269
+ c->destroyed = true;
2270
+ if (c->sock) scr_net_sock_destroy(c->sock);
2271
+ }
2272
+
2273
+ bool scr_http_client_destroyed(ScrHttpClientReq *c) {
2274
+ return c->destroyed || c->close_emitted;
2275
+ }
2276
+
2277
+ void scr_http_client_on_response(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, ScrHttpRespFn fn, bool once) {
2278
+ if (c->close_emitted || c->response_started) {
2279
+ /* the event is past (or the handle settled): never fires */
2280
+ scr_closure_release(cb);
2281
+ return;
2282
+ }
2283
+ scr_net_ls_add(&c->resp_ls, cb, (void *)fn, once);
2284
+ }
2285
+
2286
+ void scr_http_client_on_error(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once) {
2287
+ if (c->close_emitted) {
2288
+ scr_closure_release(cb);
2289
+ return;
2290
+ }
2291
+ scr_net_ls_add(&c->err_ls, cb, (void *)fn, once);
2292
+ }
2293
+
2294
+ void scr_http_client_on_upgrade(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, ScrHttpUpgradeFn fn, bool once) {
2295
+ if (c->close_emitted || c->response_started) {
2296
+ /* the exchange already resolved (a 101 fired or a response landed) */
2297
+ scr_closure_release(cb);
2298
+ return;
2299
+ }
2300
+ scr_net_ls_add(&c->upgrade_ls, cb, (void *)fn, once);
2301
+ }
2302
+
2303
+ void scr_http_client_on_timeout(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, bool once) {
2304
+ if (c->close_emitted) {
2305
+ scr_closure_release(cb);
2306
+ return;
2307
+ }
2308
+ scr_net_ls_add(&c->timeout_ls, cb, NULL, once);
2309
+ }
2310
+
2311
+ void scr_http_client_on_close(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, bool once) {
2312
+ if (c->close_emitted) {
2313
+ scr_closure_release(cb);
2314
+ return;
2315
+ }
2316
+ scr_net_ls_add(&c->close_ls, cb, NULL, once);
2317
+ }
2318
+
2319
+ /* ── response parsing (the conn's client mode) ───────────────────────── */
2320
+
2321
+ /* Parses one complete response HEAD sitting at buf[0..head_len):
2322
+ * "HTTP/1.x SP status SP reason CRLF" then headers. Returns false on
2323
+ * malformed input (the caller hangs up). */
2324
+ static bool scr_http_client_parse_head(ScrHttpConn *conn, size_t head_len) {
2325
+ ScrHttpClientReq *c = conn->client;
2326
+ if (!c) return false;
2327
+ const char *buf = conn->buf;
2328
+ const char *line_end = memchr(buf, '\r', head_len);
2329
+ if (!line_end || line_end + 1 >= buf + head_len || line_end[1] != '\n') return false;
2330
+ const char *sp1 = memchr(buf, ' ', (size_t)(line_end - buf));
2331
+ if (!sp1) return false;
2332
+ size_t verlen = (size_t)(sp1 - buf);
2333
+ if (!(verlen == 8 && (memcmp(buf, "HTTP/1.1", 8) == 0 || memcmp(buf, "HTTP/1.0", 8) == 0))) {
2334
+ return false;
2335
+ }
2336
+ const char *st = sp1 + 1;
2337
+ int status = 0;
2338
+ int digits = 0;
2339
+ while (st < line_end && *st >= '0' && *st <= '9' && digits < 3) {
2340
+ status = status * 10 + (*st - '0');
2341
+ st++;
2342
+ digits++;
2343
+ }
2344
+ if (digits != 3) return false;
2345
+
2346
+ ScrHttpReq *res = calloc(1, sizeof *res);
2347
+ if (!res) scr_http_oom();
2348
+ res->rc = 1;
2349
+ res->status = status;
2350
+ res->sock = scr_net_sock_retain(conn->sock);
2351
+ #ifdef SCR_RC_AUDIT
2352
+ scr_http_live++;
2353
+ #endif
2354
+ res->method = scr_str_new("", 0);
2355
+ res->url = scr_str_new("", 0); /* Node: res.url is "" on client responses */
2356
+ res->http10 = memcmp(buf, "HTTP/1.0", 8) == 0;
2357
+ {
2358
+ /* the reason phrase — res.statusMessage ("" when absent, Node) */
2359
+ const char *reason = st;
2360
+ if (reason < line_end && *reason == ' ') reason++;
2361
+ res->status_msg = scr_str_new(reason, (size_t)(line_end - reason));
2362
+ }
2363
+
2364
+ /* header lines until the blank line */
2365
+ const char *p = line_end + 2;
2366
+ const char *end = buf + head_len;
2367
+ bool ok = true;
2368
+ while (p < end) {
2369
+ const char *eol = memchr(p, '\r', (size_t)(end - p));
2370
+ if (!eol || eol + 1 >= end || eol[1] != '\n') {
2371
+ ok = false;
2372
+ break;
2373
+ }
2374
+ if (eol == p) break; /* the blank line */
2375
+ const char *colon = memchr(p, ':', (size_t)(eol - p));
2376
+ if (!colon || colon == p) {
2377
+ ok = false;
2378
+ break;
2379
+ }
2380
+ const char *v = colon + 1;
2381
+ while (v < eol && (*v == ' ' || *v == '\t')) v++;
2382
+ const char *ve = eol;
2383
+ while (ve > v && (ve[-1] == ' ' || ve[-1] == '\t')) ve--;
2384
+ scr_http_req_add_header(res, p, (size_t)(colon - p), v, (size_t)(ve - v));
2385
+ p = eol + 2;
2386
+ }
2387
+ if (!ok) {
2388
+ scr_http_req_release(res);
2389
+ return false;
2390
+ }
2391
+
2392
+ /* framing: HEAD requests and 204/304 responses have NO body; chunked
2393
+ * wins over Content-Length; neither means EOF-delimited */
2394
+ bool chunked = false;
2395
+ long long content_length = -1;
2396
+ for (size_t i = 0; i < res->nheaders; i++) {
2397
+ const ScrStr *n = res->hnames[i];
2398
+ const ScrStr *v = res->hvalues[i];
2399
+ if (n->len == 17 && memcmp(n->data, "transfer-encoding", 17) == 0) {
2400
+ if (strstr(v->data, "chunked") != NULL) chunked = true;
2401
+ } else if (n->len == 14 && memcmp(n->data, "content-length", 14) == 0) {
2402
+ content_length = atoll(v->data);
2403
+ if (content_length < 0) content_length = 0;
2404
+ }
2405
+ }
2406
+ bool head_req = c->method->len == 4 && memcmp(c->method->data, "HEAD", 4) == 0;
2407
+ bool no_body = head_req || status == 204 || status == 304;
2408
+
2409
+ /* 101 Switching Protocols: 'upgrade' fires INSTEAD of 'response' with
2410
+ * (res, socket, head) — the parser steps aside and the raw socket is
2411
+ * the listener's (the server-side handover, mirrored). With no
2412
+ * listener Node destroys the connection; so does this slice. */
2413
+ if (status == 101) {
2414
+ conn->client_resp_started = true;
2415
+ c->response_started = true;
2416
+ c->response_done = true; /* the exchange settled: no timeout/hang-up paths */
2417
+ c->res = scr_http_req_retain(res);
2418
+ memmove(conn->buf, conn->buf + head_len, conn->len - head_len);
2419
+ conn->len -= head_len;
2420
+ if (c->upgrade_ls.n == 0) {
2421
+ scr_http_req_release(res);
2422
+ conn->len = 0;
2423
+ scr_net_sock_destroy(conn->sock);
2424
+ return true;
2425
+ }
2426
+ ScrBytes *head = scr_bytes_new(SCR_BYTES_U8, (double)conn->len);
2427
+ if (conn->len > 0) memcpy(head->data, conn->buf, conn->len);
2428
+ conn->len = 0;
2429
+ scr_net_sock_clear_native_reader(conn->sock);
2430
+ ScrNetL *snap;
2431
+ size_t n = scr_net_ls_snapshot(&c->upgrade_ls, &snap);
2432
+ for (size_t i = 0; i < n; i++) {
2433
+ if (!scr_exc_pending()) {
2434
+ ((ScrHttpUpgradeFn)snap[i].fn)(snap[i].cb, scr_http_req_retain(res),
2435
+ scr_net_sock_retain(conn->sock), scr_bytes_retain(head));
2436
+ }
2437
+ scr_closure_release(snap[i].cb);
2438
+ }
2439
+ free(snap);
2440
+ scr_bytes_release(head);
2441
+ scr_http_req_release(res);
2442
+ /* The REQUEST handle's exchange is over: break the ctx→client edge
2443
+ * (the parser's native hooks are cleared, so the socket's 'closed'
2444
+ * would never break it — the cycle story) and settle 'close' through
2445
+ * the queue, Node's req 'close' after an upgrade. The handed-over
2446
+ * SOCKET lives on through its own refs. */
2447
+ conn->client = NULL;
2448
+ scr_http_client_queue_close(c);
2449
+ scr_http_client_release(c);
2450
+ return true;
2451
+ }
2452
+
2453
+ conn->req = res; /* conn's +1 */
2454
+ conn->client_resp_started = true;
2455
+ c->response_started = true;
2456
+ c->res = scr_http_req_retain(res);
2457
+
2458
+ /* consume the head bytes */
2459
+ memmove(conn->buf, conn->buf + head_len, conn->len - head_len);
2460
+ conn->len -= head_len;
2461
+
2462
+ if (no_body) {
2463
+ conn->state = SCR_HTTP_HEAD;
2464
+ } else if (chunked) {
2465
+ conn->state = SCR_HTTP_CHUNK_SIZE;
2466
+ } else if (content_length > 0) {
2467
+ conn->state = SCR_HTTP_BODY_CL;
2468
+ conn->body_remaining = (size_t)content_length;
2469
+ } else if (content_length == 0) {
2470
+ conn->state = SCR_HTTP_HEAD;
2471
+ } else {
2472
+ conn->state = SCR_HTTP_BODY_EOF;
2473
+ }
2474
+
2475
+ /* 'response' fires NOW (macrotask, main stack — Node emits it from the
2476
+ * socket's data path too) */
2477
+ ScrNetL *snap;
2478
+ size_t n = scr_net_ls_snapshot(&c->resp_ls, &snap);
2479
+ for (size_t i = 0; i < n; i++) {
2480
+ if (!scr_exc_pending()) ((ScrHttpRespFn)snap[i].fn)(snap[i].cb, scr_http_req_retain(res));
2481
+ scr_closure_release(snap[i].cb);
2482
+ }
2483
+ free(snap);
2484
+ if (scr_exc_pending()) return true;
2485
+
2486
+ if (conn->state == SCR_HTTP_HEAD) {
2487
+ /* bodiless response: 'end' + teardown immediately */
2488
+ scr_http_conn_body_done(conn);
2489
+ }
2490
+ return true;
2491
+ }
2492
+
2493
+ /* The response completed: 'end' on the response, then the exchange tears
2494
+ * down — the socket closes (no pooling) and the deferred closes queue in
2495
+ * Node's order (req 'close' before res 'close'). */
2496
+ static void scr_http_client_response_done(ScrHttpConn *conn) {
2497
+ ScrHttpClientReq *c = conn->client;
2498
+ ScrHttpReq *res = conn->req;
2499
+ if (!c || c->response_done) return;
2500
+ c->response_done = true;
2501
+ if (res) {
2502
+ scr_http_req_retain(res);
2503
+ scr_http_req_finish(res, true); /* fires 'end' */
2504
+ }
2505
+ /* Node's order: the REQUEST's 'close' precedes the response's */
2506
+ scr_http_client_queue_close(c);
2507
+ if (res) {
2508
+ scr_http_queue_req_close(res);
2509
+ scr_http_req_release(res);
2510
+ /* drop the conn's ref NOW: the response outlives the exchange only
2511
+ * through user refs / c->res — a retained conn->req would cycle
2512
+ * (res→sock→ctx→res) past every settle */
2513
+ conn->req = NULL;
2514
+ scr_http_req_release(res);
2515
+ }
2516
+ if (c->sock) scr_net_sock_destroy(c->sock); /* quiet close, no pooling */
2517
+ }
2518
+
2519
+ /* Premature teardown classification, shared by eof/closed/error:
2520
+ * before any response head → 'socket hang up' on the REQUEST; mid-body →
2521
+ * 'aborted' on the RESPONSE (the request just closes) — both orders
2522
+ * oracle-pinned. */
2523
+ static void scr_http_client_premature(ScrHttpConn *conn) {
2524
+ ScrHttpClientReq *c = conn->client;
2525
+ if (!c || c->response_done || c->close_emitted) return;
2526
+ c->response_done = true; /* the exchange is over, one way or another */
2527
+ if (!c->response_started) {
2528
+ if (!c->had_error) {
2529
+ ScrStr *msg = scr_str_new("socket hang up", 14);
2530
+ scr_http_client_error(c, msg);
2531
+ scr_str_release(msg);
2532
+ if (scr_exc_pending()) return;
2533
+ }
2534
+ scr_http_client_queue_close(c);
2535
+ } else {
2536
+ /* mid-head-to-body death: req close, then 'aborted' on the res, then
2537
+ * res close (the queue preserves push order) */
2538
+ scr_http_client_queue_close(c);
2539
+ if (conn->req) {
2540
+ ScrHttpReq *res = conn->req;
2541
+ scr_http_queue_req_aborted(res);
2542
+ scr_http_queue_req_close(res);
2543
+ scr_http_req_finish(res, false); /* body never completes */
2544
+ conn->req = NULL; /* break the res→sock→ctx→res cycle */
2545
+ scr_http_req_release(res);
2546
+ }
2547
+ }
2548
+ }
2549
+
2550
+ static void scr_http_client_eof(ScrHttpConn *conn) {
2551
+ ScrHttpClientReq *c = conn->client;
2552
+ if (!c) return;
2553
+ if (conn->state == SCR_HTTP_BODY_EOF && c->response_started && !c->response_done) {
2554
+ /* EOF-delimited body: this IS completion */
2555
+ scr_http_conn_body_done(conn);
2556
+ return;
2557
+ }
2558
+ scr_http_client_premature(conn);
2559
+ }
2560
+
2561
+ static void scr_http_client_closed(ScrHttpConn *conn) {
2562
+ ScrHttpClientReq *c = conn->client;
2563
+ if (!c) return;
2564
+ scr_http_client_premature(conn);
2565
+ /* the connection is gone: break the ctx→client edge (the client stays
2566
+ * alive through user refs / the queue until its 'close' settles) */
2567
+ conn->client = NULL;
2568
+ scr_http_client_release(c);
2569
+ }
2570
+
2571
+ static bool scr_http_client_sock_err(ScrHttpConn *conn, ScrStr *msg) {
2572
+ ScrHttpClientReq *c = conn->client;
2573
+ if (!c) return true;
2574
+ if (c->response_done || c->close_emitted) return true; /* teardown noise */
2575
+ if (!c->response_started) {
2576
+ /* connect/read failure before any response: the socket's message IS
2577
+ * Node's ('connect ECONNREFUSED ip:port') — fire it on the request */
2578
+ c->response_done = true;
2579
+ scr_http_client_error(c, msg);
2580
+ if (!scr_exc_pending()) scr_http_client_queue_close(c);
2581
+ } else {
2582
+ scr_http_client_premature(conn);
2583
+ }
2584
+ return true;
2585
+ }
2586
+
2587
+ /* Header-cap overflow / malformed response head: Node surfaces a parse
2588
+ * error on the request; this slice hangs up with the same teardown shape
2589
+ * ('socket hang up' when nothing parsed yet). */
2590
+ static void scr_http_client_head_overflow(ScrHttpConn *conn) {
2591
+ conn->len = 0;
2592
+ scr_http_client_premature(conn);
2593
+ if (conn->client && conn->client->sock) scr_net_sock_destroy(conn->client->sock);
2594
+ }
2595
+
2596
+ /* The client socket's idle timeout: 'timeout' on the request handle
2597
+ * (never destroys anything — Node; the caller destroys). */
2598
+ static void scr_http_client_sock_timeout(void *ctx) {
2599
+ ScrHttpConn *conn = (ScrHttpConn *)ctx;
2600
+ ScrHttpClientReq *c = conn->client;
2601
+ if (!c || c->response_done || c->close_emitted) return;
2602
+ scr_net_fire0(&c->timeout_ls);
2603
+ }
2604
+
2605
+ static void scr_http_client_conn_free(ScrHttpConn *conn) {
2606
+ if (conn->req) {
2607
+ scr_http_req_finish(conn->req, false);
2608
+ scr_http_req_release(conn->req);
2609
+ conn->req = NULL;
2610
+ }
2611
+ if (conn->client) {
2612
+ ScrHttpClientReq *c = conn->client;
2613
+ conn->client = NULL;
2614
+ scr_http_client_release(c);
2615
+ }
2616
+ free(conn->buf);
2617
+ free(conn);
2618
+ }
2619
+
2620
+ /* ── http.request / http.get ─────────────────────────────────────────── */
2621
+
2622
+ static ScrHttpClientReq *scr_http_request_impl(ScrStr *host /*borrowed*/, double port,
2623
+ ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/,
2624
+ double timeout_ms, ScrArr *header_pairs /*borrowed*/,
2625
+ bool auto_end, ScrClosure *cb /*moves, nullable*/,
2626
+ ScrHttpRespFn fn, int default_port,
2627
+ void (*wrap)(ScrNetSocket *, void *), void *wrap_ctx,
2628
+ ScrNetSocket *presock /*moves, nullable*/) {
2629
+ /* Node's ClientRequest constructor validates the method token
2630
+ * SYNCHRONOUSLY (RFC 9110 tchar, at least one): the catchable
2631
+ * ERR_INVALID_HTTP_TOKEN TypeError with Node's exact message, the raw
2632
+ * method text inside the quotes. Moved-in arguments release — the
2633
+ * request never exists. */
2634
+ bool method_ok = method->len > 0;
2635
+ for (size_t i = 0; i < method->len && method_ok; i++) {
2636
+ unsigned char ch = (unsigned char)method->data[i];
2637
+ method_ok = (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
2638
+ (ch >= '0' && ch <= '9') ||
2639
+ (ch != 0 && strchr("!#$%&'*+-.^_`|~", ch) != NULL);
2640
+ }
2641
+ if (!method_ok) {
2642
+ if (cb) scr_closure_release(cb);
2643
+ if (presock) scr_net_sock_release(presock);
2644
+ ScrJsonBuf b;
2645
+ scr_jb_init(&b);
2646
+ scr_jb_puts(&b, "Method must be a valid HTTP token [\"");
2647
+ for (size_t i = 0; i < method->len; i++) scr_jb_putc(&b, method->data[i]);
2648
+ scr_jb_puts(&b, "\"]");
2649
+ ScrStr *msg = scr_jb_finish(&b);
2650
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len, "ERR_INVALID_HTTP_TOKEN");
2651
+ scr_str_release(msg);
2652
+ return NULL;
2653
+ }
2654
+ scr_http_install();
2655
+ ScrHttpClientReq *c = calloc(1, sizeof *c);
2656
+ if (!c) scr_http_oom();
2657
+ c->rc = 1;
2658
+ #ifdef SCR_RC_AUDIT
2659
+ scr_http_live++;
2660
+ #endif
2661
+ c->host = scr_str_retain(host);
2662
+ c->path = scr_str_retain(path);
2663
+ c->method = scr_str_retain(method);
2664
+ c->port = (int)port;
2665
+ c->default_port = default_port;
2666
+ size_t npairs = (size_t)scr_arr_len(header_pairs);
2667
+ if (npairs >= 2) {
2668
+ c->hnames = malloc((npairs / 2) * sizeof *c->hnames);
2669
+ c->hvalues = malloc((npairs / 2) * sizeof *c->hvalues);
2670
+ if (!c->hnames || !c->hvalues) scr_http_oom();
2671
+ for (size_t i = 0; i + 1 < npairs; i += 2) {
2672
+ c->hnames[c->nheaders] = (ScrStr *)scr_arr_get_ref(header_pairs, (double)i);
2673
+ c->hvalues[c->nheaders] = (ScrStr *)scr_arr_get_ref(header_pairs, (double)(i + 1));
2674
+ c->nheaders++;
2675
+ }
2676
+ }
2677
+ c->user_cl = scr_http_client_has_header(c, "content-length") ||
2678
+ scr_http_client_has_header(c, "transfer-encoding");
2679
+ if (cb) scr_net_ls_add(&c->resp_ls, cb, (void *)fn, true);
2680
+
2681
+ /* dial — or take the caller's pre-made socket (createConnection); dial
2682
+ * failures are the async 'error' either way, the net story */
2683
+ c->sock = presock != NULL ? presock : scr_net_connect(port, host, NULL);
2684
+ ScrHttpConn *conn = calloc(1, sizeof *conn);
2685
+ if (!conn) scr_http_oom();
2686
+ conn->sock = c->sock; /* borrowed */
2687
+ conn->client_mode = true;
2688
+ conn->client = scr_http_client_retain(c);
2689
+ c->conn = conn;
2690
+ scr_net_sock_set_native_reader(c->sock, &scr_http_conn_data, &scr_http_conn_eof,
2691
+ &scr_http_conn_closed, conn, &scr_http_conn_free);
2692
+ scr_net_sock_set_native_events(c->sock, &scr_http_client_sock_timeout, &scr_http_conn_err);
2693
+ /* the transport (scr_tls.c's https) wraps the dialed socket before any
2694
+ * bytes go out — everything below buffers until its handshake ends */
2695
+ if (wrap) wrap(c->sock, wrap_ctx);
2696
+ if (timeout_ms > 0) scr_net_sock_set_timeout(c->sock, timeout_ms);
2697
+ scr_http_client_register(c);
2698
+ if (auto_end) scr_http_client_end(c);
2699
+ return c;
2700
+ }
2701
+
2702
+ ScrHttpClientReq *scr_http_request_ex(ScrStr *host /*borrowed*/, double port,
2703
+ ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/,
2704
+ double timeout_ms, ScrArr *header_pairs /*borrowed*/,
2705
+ bool auto_end, ScrClosure *cb /*moves, nullable*/,
2706
+ ScrHttpRespFn fn, int default_port,
2707
+ void (*wrap)(ScrNetSocket *, void *), void *wrap_ctx) {
2708
+ return scr_http_request_impl(host, port, path, method, timeout_ms, header_pairs, auto_end,
2709
+ cb, fn, default_port, wrap, wrap_ctx, NULL);
2710
+ }
2711
+
2712
+ /* http.request({ createConnection, ... }): the caller's DIALER supplies
2713
+ * the socket — conn_cb runs ONCE, synchronously (Node invokes it from
2714
+ * the request constructor's onSocket path), and its socket carries the
2715
+ * exchange exactly like a dialed one (head bytes buffer through connect
2716
+ * and flush on establishment). The Host header defaults to "localhost"
2717
+ * — the proxy shape sets headers.host itself, which wins verbatim. */
2718
+ ScrHttpClientReq *scr_http_request_conn(ScrClosure *conn_cb /*moves*/, ScrStr *path /*borrowed*/,
2719
+ ScrStr *method /*borrowed*/, double timeout_ms,
2720
+ ScrArr *header_pairs /*borrowed*/, bool auto_end,
2721
+ ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn) {
2722
+ ScrNetSocket *sock = ((ScrNetSocket *(*)(ScrClosure *))conn_cb->fn)(conn_cb); /* +1 result */
2723
+ scr_closure_release(conn_cb);
2724
+ if (scr_exc_pending() || sock == NULL) {
2725
+ /* the dialer threw: NULL result, dummy-released past the emitted
2726
+ * pending check (the libCall is in the may-throw seed set) */
2727
+ if (cb != NULL) scr_closure_release(cb);
2728
+ scr_net_sock_release(sock);
2729
+ return NULL;
2730
+ }
2731
+ ScrStr *host = scr_str_new("localhost", 9);
2732
+ ScrHttpClientReq *c = scr_http_request_impl(host, 80, path, method, timeout_ms, header_pairs,
2733
+ auto_end, cb, fn, 80, NULL, NULL, sock);
2734
+ scr_str_release(host);
2735
+ return c;
2736
+ }
2737
+
2738
+ ScrHttpClientReq *scr_http_request(ScrStr *host /*borrowed*/, double port,
2739
+ ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/,
2740
+ double timeout_ms, ScrArr *header_pairs /*borrowed*/,
2741
+ bool auto_end, ScrClosure *cb /*moves, nullable*/,
2742
+ ScrHttpRespFn fn) {
2743
+ return scr_http_request_ex(host, port, path, method, timeout_ms, header_pairs, auto_end, cb,
2744
+ fn, 80, NULL, NULL);
2745
+ }
2746
+
2747
+ /* Exit-time registry cleanup (the net-unit precedent): clients a program
2748
+ * legitimately leaves in flight at exit release their listeners and
2749
+ * registry references so the RC audit sees a clean heap. */
2750
+ static void scr_http_clients_cleanup(void) {
2751
+ while (scr_http_clients) {
2752
+ ScrHttpClientReq *c = scr_http_clients;
2753
+ scr_net_ls_drop(&c->resp_ls);
2754
+ scr_net_ls_drop(&c->err_ls);
2755
+ scr_net_ls_drop(&c->timeout_ls);
2756
+ scr_net_ls_drop(&c->close_ls);
2757
+ scr_net_ls_drop(&c->upgrade_ls);
2758
+ scr_http_client_unregister(c);
2759
+ }
2760
+ }
2761
+
2762
+ /* The response-callback adapters: res arrives +1; the zero-param shape
2763
+ * releases it. */
2764
+ void scr_http_resp_thunk0(ScrClosure *cb, ScrHttpReq *res) {
2765
+ scr_http_req_release(res);
2766
+ ((void (*)(ScrClosure *))cb->fn)(cb);
2767
+ }
2768
+
2769
+ void scr_http_resp_thunk_res(ScrClosure *cb, ScrHttpReq *res) {
2770
+ ((void (*)(ScrClosure *, ScrHttpReq *))cb->fn)(cb, res);
2771
+ }
2772
+
2773
+ /* ── checked-dynamic handle dispatch (SCR_DYN_HANDLE ops) ──────────────
2774
+ *
2775
+ * The receiver surface behind `server.on('request', mustCall((req, res)
2776
+ * => ...))`: the mustCall wrapper makes the listener dyn, req/res box
2777
+ * into the DOM as HANDLE values, and member uses inside the listener
2778
+ * body dispatch HERE — onto the very entry points the static lowerings
2779
+ * use, with per-argument gates shaped like Node's (the libCall signature
2780
+ * table in validate.ts is the modeled surface; this dispatcher mirrors
2781
+ * it name for name).
2782
+ *
2783
+ * Honesty ladder (the scr_dyn_invoke stance):
2784
+ * - modeled member: the static entry point's exact semantics;
2785
+ * - a member the class HAS but this table does not model: a LOUD
2786
+ * "not supported yet" Error — never a silent wrong answer;
2787
+ * - anything else: Node's "<spelling> is not a function" for calls,
2788
+ * the undefined singleton for reads (the DOM's own-property answer;
2789
+ * SEMANTICS.md documents the remainder).
2790
+ *
2791
+ * Ownership: invoke/get/set receive BORROWED handles/args and answer
2792
+ * owned (+1) results; listener registrations retain the dyn callback
2793
+ * into a runtime-built adapter closure (scr_dyn_listener_closure*). */
2794
+
2795
+ static void scr_http_dynh_unsupported(const char *cls, const char *member, const char *what) {
2796
+ char msg[160];
2797
+ int n = snprintf(msg, sizeof msg, "'%s.prototype.%s' on a dynamic value is not supported yet%s%s",
2798
+ cls, member, what ? " — " : "", what ? what : "");
2799
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, (size_t)n);
2800
+ }
2801
+
2802
+ static void scr_http_dynh_not_fn(const char *what) {
2803
+ char msg[160];
2804
+ int n = snprintf(msg, sizeof msg, "%s is not a function", what);
2805
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)n);
2806
+ }
2807
+
2808
+ static void scr_http_dynh_event_unsupported(const char *cls, const ScrDyn *name) {
2809
+ ScrJsonBuf b;
2810
+ scr_jb_init(&b);
2811
+ scr_jb_puts(&b, "listening for '");
2812
+ if (name->kind == SCR_DYN_STR) {
2813
+ for (size_t i = 0; i < name->v.str->len; i++) scr_jb_putc(&b, name->v.str->data[i]);
2814
+ }
2815
+ scr_jb_puts(&b, "' on a dynamic ");
2816
+ scr_jb_puts(&b, cls);
2817
+ scr_jb_puts(&b, " is not supported yet");
2818
+ scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b));
2819
+ }
2820
+
2821
+ static bool scr_http_dynh_name_is(const ScrDyn *name, const char *lit) {
2822
+ size_t n = strlen(lit);
2823
+ return name->kind == SCR_DYN_STR && name->v.str->len == n &&
2824
+ memcmp(name->v.str->data, lit, n) == 0;
2825
+ }
2826
+
2827
+ static bool scr_http_dynh_in(const char *m, const char *const *names) {
2828
+ for (size_t i = 0; names[i]; i++) {
2829
+ if (strcmp(m, names[i]) == 0) return true;
2830
+ }
2831
+ return false;
2832
+ }
2833
+
2834
+ /* The registration-family gate shared by every handle class: on/once/
2835
+ * addListener (prepend* is unmodeled — these per-event lists know no
2836
+ * front). Answers the once flag; leaves reg=false for other names. */
2837
+ static bool scr_http_dynh_reg(const char *method, bool *once) {
2838
+ if (strcmp(method, "on") == 0 || strcmp(method, "addListener") == 0) {
2839
+ *once = false;
2840
+ return true;
2841
+ }
2842
+ if (strcmp(method, "once") == 0) {
2843
+ *once = true;
2844
+ return true;
2845
+ }
2846
+ return false;
2847
+ }
2848
+
2849
+ /* String/Buffer chunk gate (res.end/res.write/socket.write): answers
2850
+ * which flavor, throws Node's ERR_INVALID_ARG_TYPE otherwise. */
2851
+ static bool scr_http_dynh_chunk_ok(const ScrDyn *chunk) {
2852
+ if (chunk->kind == SCR_DYN_STR || chunk->kind == SCR_DYN_BYTES) return true;
2853
+ scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", chunk);
2854
+ return false;
2855
+ }
2856
+
2857
+ /* An optional trailing encoding argument: utf8 spellings pass (chunks
2858
+ * already carry utf8 bytes), anything else fences loudly. */
2859
+ static bool scr_http_dynh_enc_ok(const ScrDyn *enc, const char *cls, const char *member) {
2860
+ if (scr_http_dynh_name_is(enc, "utf8") || scr_http_dynh_name_is(enc, "utf-8")) return true;
2861
+ scr_http_dynh_unsupported(cls, member, "only the 'utf8' encoding is modeled");
2862
+ return false;
2863
+ }
2864
+
2865
+ /* ── IncomingMessage (SCR_DYNH_HTTP_REQ) ─────────────────────────────── */
2866
+
2867
+ static ScrDyn *scr_http_dynh_req_invoke(void *h, ScrDyn *self, const char *method,
2868
+ ScrDyn *const *args, size_t argc, const char *what) {
2869
+ ScrHttpReq *r = (ScrHttpReq *)h;
2870
+ bool once = false;
2871
+ if (scr_http_dynh_reg(method, &once)) {
2872
+ const ScrDyn *name = argc > 0 ? args[0] : scr_dyn_undefined();
2873
+ const ScrDyn *cb = argc > 1 ? args[1] : scr_dyn_undefined();
2874
+ scr_dyn_check_listener(cb, "listener");
2875
+ if (scr_exc_pending()) return NULL;
2876
+ if (scr_http_dynh_name_is(name, "data")) {
2877
+ scr_http_req_on_data(r, scr_dyn_listener_closure_data(cb), (ScrNetDataFn)&scr_dyn_listener_fire_data, once);
2878
+ } else if (scr_http_dynh_name_is(name, "end")) {
2879
+ scr_http_req_on_end(r, scr_dyn_listener_closure0(cb), once);
2880
+ } else if (scr_http_dynh_name_is(name, "error")) {
2881
+ scr_http_req_on_error(r, scr_dyn_listener_closure_err(cb), (ScrChildErrFn)&scr_dyn_listener_fire_err, once);
2882
+ } else if (scr_http_dynh_name_is(name, "close")) {
2883
+ scr_http_req_on_close(r, scr_dyn_listener_closure0(cb), once);
2884
+ } else if (scr_http_dynh_name_is(name, "aborted")) {
2885
+ scr_http_req_on_aborted(r, scr_dyn_listener_closure0(cb), once);
2886
+ } else {
2887
+ scr_http_dynh_event_unsupported("IncomingMessage", name);
2888
+ return NULL;
2889
+ }
2890
+ return scr_dyn_retain(self); /* chaining, like Node */
2891
+ }
2892
+ if (strcmp(method, "resume") == 0) {
2893
+ scr_http_req_resume(r);
2894
+ return scr_dyn_retain(self);
2895
+ }
2896
+ if (strcmp(method, "setEncoding") == 0) {
2897
+ const ScrDyn *enc = argc > 0 ? args[0] : scr_dyn_undefined();
2898
+ if (enc->kind != SCR_DYN_STR) {
2899
+ scr_dyn_arg_type_fail("encoding", "of type string", enc);
2900
+ return NULL;
2901
+ }
2902
+ scr_http_req_set_encoding(r, enc->v.str);
2903
+ if (scr_exc_pending()) return NULL;
2904
+ return scr_dyn_retain(self);
2905
+ }
2906
+ if (strcmp(method, "destroy") == 0) {
2907
+ if (argc > 0 && args[0]->kind != SCR_DYN_UNDEF) {
2908
+ scr_http_dynh_unsupported("IncomingMessage", "destroy", "destroy(error) carries a payload this surface does not model");
2909
+ return NULL;
2910
+ }
2911
+ scr_http_req_destroy(r);
2912
+ return scr_dyn_retain(self);
2913
+ }
2914
+ if (strcmp(method, "pipe") == 0) {
2915
+ const ScrDyn *dst = argc > 0 ? args[0] : scr_dyn_undefined();
2916
+ if (dst->kind == SCR_DYN_HANDLE && dst->v.handle.tag == SCR_DYNH_HTTP_RES) {
2917
+ scr_http_req_pipe_res(r, (ScrHttpRes *)dst->v.handle.ptr);
2918
+ return scr_dyn_retain((ScrDyn *)dst); /* pipe answers the destination */
2919
+ }
2920
+ if (dst->kind == SCR_DYN_HANDLE && dst->v.handle.tag == SCR_DYNH_NET_SOCKET) {
2921
+ scr_http_req_pipe_sock(r, (ScrNetSocket *)dst->v.handle.ptr);
2922
+ return scr_dyn_retain((ScrDyn *)dst);
2923
+ }
2924
+ scr_http_dynh_unsupported("IncomingMessage", "pipe", "only ServerResponse and Socket destinations are modeled");
2925
+ return NULL;
2926
+ }
2927
+ if (strcmp(method, "toString") == 0) {
2928
+ ScrStr *s = scr_str_new("[object Object]", 15);
2929
+ ScrDyn *d = scr_dyn_new_str(s);
2930
+ scr_str_release(s);
2931
+ return d;
2932
+ }
2933
+ {
2934
+ /* Real prototype members without a modeled dispatch: loud. */
2935
+ static const char *const known[] = { "pause", "unpipe", "setTimeout",
2936
+ "read", "push", "off", "removeListener", "removeAllListeners", "emit",
2937
+ "prependListener", "prependOnceListener", "listenerCount", "listeners",
2938
+ "isPaused", "wrap", "cork", "uncork", NULL };
2939
+ if (scr_http_dynh_in(method, known)) {
2940
+ scr_http_dynh_unsupported("IncomingMessage", method, NULL);
2941
+ return NULL;
2942
+ }
2943
+ }
2944
+ scr_http_dynh_not_fn(what);
2945
+ return NULL;
2946
+ }
2947
+
2948
+ static ScrDyn *scr_http_dynh_req_get(void *h, const char *key, size_t key_len) {
2949
+ ScrHttpReq *r = (ScrHttpReq *)h;
2950
+ (void)key_len;
2951
+ if (strcmp(key, "url") == 0) {
2952
+ ScrStr *s = scr_http_req_url(r);
2953
+ ScrDyn *d = scr_dyn_new_str(s);
2954
+ scr_str_release(s);
2955
+ return d;
2956
+ }
2957
+ if (strcmp(key, "httpVersion") == 0) {
2958
+ /* The parsed request/status line's version — the 1.0 tests read it;
2959
+ * the h2 compat request answers Node's "2.0". */
2960
+ ScrStr *v = scr_str_new(r->http2 ? "2.0" : r->http10 ? "1.0" : "1.1", 3);
2961
+ ScrDyn *d = scr_dyn_new_str(v);
2962
+ scr_str_release(v);
2963
+ return d;
2964
+ }
2965
+ if (strcmp(key, "httpVersionMajor") == 0) return scr_dyn_new_num(r->http2 ? 2 : 1);
2966
+ if (strcmp(key, "httpVersionMinor") == 0) return scr_dyn_new_num(r->http2 ? 0 : r->http10 ? 0 : 1);
2967
+ if (strcmp(key, "method") == 0) {
2968
+ ScrStr *s = scr_http_req_method(r);
2969
+ ScrDyn *d = scr_dyn_new_str(s);
2970
+ scr_str_release(s);
2971
+ return d;
2972
+ }
2973
+ if (strcmp(key, "headers") == 0) {
2974
+ /* The snapshot object `{ ...req.headers }` builds — lowercased
2975
+ * names, arrival order, keep-first duplicates (joined under the
2976
+ * joinDuplicateHeaders option), the static lane's exact feed. */
2977
+ ScrArr *pairs = scr_http_req_header_pairs(r);
2978
+ ScrDyn *obj = scr_dyn_new_obj();
2979
+ size_t n = (size_t)scr_arr_len(pairs);
2980
+ for (size_t i = 0; i + 1 < n; i += 2) {
2981
+ ScrStr *name = (ScrStr *)scr_arr_get_ref(pairs, (double)i);
2982
+ ScrStr *value = (ScrStr *)scr_arr_get_ref(pairs, (double)(i + 1));
2983
+ /* Keep-FIRST on repeated names (scr_dyn_obj_set is later-wins) —
2984
+ * the per-name read's rule (scr_http_req_header), so h.headers.x
2985
+ * and h.headers['x'] agree; joinDuplicateHeaders pairs arrive
2986
+ * pre-joined and unique. */
2987
+ if (!scr_dyn_obj_get(obj, name->data, name->len)) {
2988
+ scr_dyn_obj_set(obj, name->data, name->len, scr_dyn_new_str(value));
2989
+ }
2990
+ scr_str_release(name);
2991
+ scr_str_release(value);
2992
+ }
2993
+ scr_arr_release(pairs);
2994
+ return obj;
2995
+ }
2996
+ if (strcmp(key, "rawHeaders") == 0) {
2997
+ ScrArr *raw = scr_http_req_raw_headers(r);
2998
+ ScrDyn *arr = scr_dyn_new_arr();
2999
+ size_t n = (size_t)scr_arr_len(raw);
3000
+ for (size_t i = 0; i < n; i++) {
3001
+ ScrStr *s = (ScrStr *)scr_arr_get_ref(raw, (double)i);
3002
+ scr_dyn_arr_push(arr, scr_dyn_new_str(s));
3003
+ scr_str_release(s);
3004
+ }
3005
+ scr_arr_release(raw);
3006
+ return arr;
3007
+ }
3008
+ if (strcmp(key, "statusCode") == 0) {
3009
+ double st = scr_http_req_status(r);
3010
+ /* Node's IncomingMessage constructor sets statusCode = null; only a
3011
+ * client response assigns a number (the static lane's typed surface
3012
+ * spells this number|undefined — the DOM can afford Node's null). */
3013
+ return st < 0 ? scr_dyn_new_null() : scr_dyn_new_num(st);
3014
+ }
3015
+ if (strcmp(key, "statusMessage") == 0) {
3016
+ ScrStr *s = scr_http_req_status_message(r);
3017
+ if (!s) return scr_dyn_new_null(); /* null on server requests, like statusCode */
3018
+ ScrDyn *d = scr_dyn_new_str(s);
3019
+ scr_str_release(s);
3020
+ return d;
3021
+ }
3022
+ if (strcmp(key, "socket") == 0 || strcmp(key, "connection") == 0) {
3023
+ ScrNetSocket *s = scr_http_req_socket(r);
3024
+ if (!s) return NULL;
3025
+ ScrDyn *d = scr_dyn_new_handle(s, SCR_DYNH_NET_SOCKET);
3026
+ scr_net_sock_release(s);
3027
+ return d;
3028
+ }
3029
+ if (strcmp(key, "aborted") == 0) return scr_dyn_new_bool(r->aborted);
3030
+ if (strcmp(key, "complete") == 0) return scr_dyn_new_bool(r->ended);
3031
+ {
3032
+ /* Real instance properties without a modeled read: loud, never a
3033
+ * silent undefined where Node has a value. */
3034
+ static const char *const known[] = {
3035
+ "trailers", "rawTrailers", "readable", "readableEnded",
3036
+ "closed", "destroyed", NULL };
3037
+ if (scr_http_dynh_in(key, known)) {
3038
+ scr_http_dynh_unsupported("IncomingMessage", key, NULL);
3039
+ return NULL;
3040
+ }
3041
+ }
3042
+ return NULL; /* unknown key: the undefined singleton (SEMANTICS.md) */
3043
+ }
3044
+
3045
+ static bool scr_http_dynh_req_set(void *h, const char *key, size_t key_len, const ScrDyn *value) {
3046
+ (void)h; (void)key; (void)key_len; (void)value;
3047
+ return false; /* no modeled writable properties — the caller's loud fence */
3048
+ }
3049
+
3050
+ /* ── ServerResponse (SCR_DYNH_HTTP_RES) ──────────────────────────────── */
3051
+
3052
+ /* writeHead's header-object form over a DOM OBJ: flatten into the
3053
+ * [k0, v0, k1, v1, ...] pairs the static lowering feeds
3054
+ * scr_http_res_write_head_pairs (repeats append — array values expand to
3055
+ * consecutive same-name pairs). Numbers format through String(n). NULL =
3056
+ * a value this surface cannot honestly carry (exception pending). */
3057
+ static ScrArr *scr_http_dynh_pairs(const ScrDyn *headers) {
3058
+ ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, headers->v.obj.len * 2);
3059
+ for (size_t i = 0; i < headers->v.obj.len; i++) {
3060
+ const ScrDynEntry *e = &headers->v.obj.entries[i];
3061
+ const ScrDyn *v = e->value;
3062
+ size_t reps = v->kind == SCR_DYN_ARR ? v->v.arr.len : 1;
3063
+ for (size_t k = 0; k < reps; k++) {
3064
+ const ScrDyn *one = v->kind == SCR_DYN_ARR ? v->v.arr.items[k] : v;
3065
+ ScrStr *vs;
3066
+ if (one->kind == SCR_DYN_STR) {
3067
+ vs = scr_str_retain(one->v.str);
3068
+ } else if (one->kind == SCR_DYN_NUM) {
3069
+ vs = scr_f64_to_scrstr(one->v.num);
3070
+ } else {
3071
+ ScrJsonBuf b;
3072
+ scr_jb_init(&b);
3073
+ scr_jb_puts(&b, "Invalid value \"");
3074
+ scr_jb_puts(&b, one->kind == SCR_DYN_UNDEF ? "undefined" : "object");
3075
+ scr_jb_puts(&b, "\" for header \"");
3076
+ for (size_t c = 0; c < e->key_len; c++) scr_jb_putc(&b, e->key[c]);
3077
+ scr_jb_putc(&b, '"');
3078
+ ScrStr *msg = scr_jb_finish(&b);
3079
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len, "ERR_HTTP_INVALID_HEADER_VALUE");
3080
+ scr_str_release(msg);
3081
+ scr_arr_release(pairs);
3082
+ return NULL;
3083
+ }
3084
+ scr_arr_push_ref(pairs, scr_str_new(e->key, e->key_len));
3085
+ scr_arr_push_ref(pairs, vs);
3086
+ }
3087
+ }
3088
+ return pairs;
3089
+ }
3090
+
3091
+ static ScrDyn *scr_http_dynh_res_invoke(void *h, ScrDyn *self, const char *method,
3092
+ ScrDyn *const *args, size_t argc, const char *what) {
3093
+ ScrHttpRes *r = (ScrHttpRes *)h;
3094
+ bool once = false;
3095
+ if (scr_http_dynh_reg(method, &once)) {
3096
+ const ScrDyn *name = argc > 0 ? args[0] : scr_dyn_undefined();
3097
+ const ScrDyn *cb = argc > 1 ? args[1] : scr_dyn_undefined();
3098
+ scr_dyn_check_listener(cb, "listener");
3099
+ if (scr_exc_pending()) return NULL;
3100
+ if (scr_http_dynh_name_is(name, "close")) {
3101
+ scr_http_res_on_close(r, scr_dyn_listener_closure0(cb), once);
3102
+ } else if (scr_http_dynh_name_is(name, "finish")) {
3103
+ scr_http_res_on_finish(r, scr_dyn_listener_closure0(cb));
3104
+ } else if (scr_http_dynh_name_is(name, "drain")) {
3105
+ /* This surface never backpressures (write answers true), so Node's
3106
+ * contract says 'drain' never fires — an accepted, never-fired
3107
+ * registration is the consistent answer (SEMANTICS.md). */
3108
+ } else {
3109
+ scr_http_dynh_event_unsupported("ServerResponse", name);
3110
+ return NULL;
3111
+ }
3112
+ return scr_dyn_retain(self);
3113
+ }
3114
+ if (strcmp(method, "end") == 0) {
3115
+ /* end() / end(chunk) / end(chunk, enc) / end(cb) / end(chunk, cb) /
3116
+ * end(chunk, enc, cb) — Node's whole shape family. */
3117
+ size_t i = 0;
3118
+ const ScrDyn *chunk = NULL;
3119
+ const ScrDyn *cb = NULL;
3120
+ if (i < argc && args[i]->kind == SCR_DYN_FUNC) {
3121
+ cb = args[i++];
3122
+ } else if (i < argc && args[i]->kind != SCR_DYN_UNDEF && args[i]->kind != SCR_DYN_NULL) {
3123
+ if (!scr_http_dynh_chunk_ok(args[i])) return NULL;
3124
+ chunk = args[i++];
3125
+ if (i < argc && args[i]->kind == SCR_DYN_STR) { /* encoding */
3126
+ if (!scr_http_dynh_enc_ok(args[i], "ServerResponse", "end")) return NULL;
3127
+ i++;
3128
+ }
3129
+ if (i < argc && args[i]->kind == SCR_DYN_FUNC) cb = args[i++];
3130
+ }
3131
+ if (i < argc && args[i]->kind != SCR_DYN_UNDEF) {
3132
+ scr_http_dynh_unsupported("ServerResponse", "end", "this argument shape is not modeled");
3133
+ return NULL;
3134
+ }
3135
+ if (cb) scr_http_res_on_finish(r, scr_dyn_listener_closure0(cb));
3136
+ if (chunk == NULL) scr_http_res_end(r);
3137
+ else if (chunk->kind == SCR_DYN_STR) scr_http_res_end_str(r, chunk->v.str);
3138
+ else scr_http_res_end_bytes(r, chunk->v.bytes);
3139
+ return scr_dyn_retain(self);
3140
+ }
3141
+ if (strcmp(method, "write") == 0) {
3142
+ const ScrDyn *chunk = argc > 0 ? args[0] : scr_dyn_undefined();
3143
+ if (!scr_http_dynh_chunk_ok(chunk)) return NULL;
3144
+ if (argc > 1 && args[1]->kind == SCR_DYN_STR && !scr_http_dynh_enc_ok(args[1], "ServerResponse", "write")) return NULL;
3145
+ if (argc > 1 && args[1]->kind == SCR_DYN_FUNC) {
3146
+ scr_http_dynh_unsupported("ServerResponse", "write", "write(chunk, callback) is not modeled");
3147
+ return NULL;
3148
+ }
3149
+ if (chunk->kind == SCR_DYN_STR) scr_http_res_write_str(r, chunk->v.str);
3150
+ else scr_http_res_write_bytes(r, chunk->v.bytes);
3151
+ /* Always-true: this surface buffers without a highWaterMark verdict
3152
+ * (SEMANTICS.md — backpressure is not modeled). */
3153
+ return scr_dyn_new_bool(true);
3154
+ }
3155
+ if (strcmp(method, "writeHead") == 0) {
3156
+ const ScrDyn *st = argc > 0 ? args[0] : scr_dyn_undefined();
3157
+ if (st->kind != SCR_DYN_NUM) {
3158
+ scr_dyn_arg_type_fail("statusCode", "of type number", st);
3159
+ return NULL;
3160
+ }
3161
+ size_t i = 1;
3162
+ if (i < argc && args[i]->kind == SCR_DYN_STR) {
3163
+ scr_http_res_status_msg_set(r, args[i]->v.str);
3164
+ i++;
3165
+ }
3166
+ if (i < argc && args[i]->kind == SCR_DYN_OBJ) {
3167
+ ScrArr *pairs = scr_http_dynh_pairs(args[i]);
3168
+ if (!pairs) return NULL;
3169
+ scr_http_res_write_head_pairs(r, st->v.num, pairs);
3170
+ scr_arr_release(pairs);
3171
+ i++;
3172
+ } else if (i < argc && args[i]->kind != SCR_DYN_UNDEF) {
3173
+ scr_http_dynh_unsupported("ServerResponse", "writeHead", "only the header-object form is modeled");
3174
+ return NULL;
3175
+ } else {
3176
+ scr_http_res_write_head(r, st->v.num);
3177
+ }
3178
+ return scr_dyn_retain(self); /* chaining: writeHead(...).end(...) */
3179
+ }
3180
+ if (strcmp(method, "setHeader") == 0) {
3181
+ const ScrDyn *name = argc > 0 ? args[0] : scr_dyn_undefined();
3182
+ const ScrDyn *value = argc > 1 ? args[1] : scr_dyn_undefined();
3183
+ if (name->kind != SCR_DYN_STR) {
3184
+ scr_dyn_arg_type_fail("name", "of type string", name);
3185
+ return NULL;
3186
+ }
3187
+ if (value->kind == SCR_DYN_STR || value->kind == SCR_DYN_NUM) {
3188
+ ScrStr *vs = value->kind == SCR_DYN_STR ? scr_str_retain(value->v.str)
3189
+ : scr_f64_to_scrstr(value->v.num);
3190
+ scr_http_res_set_header(r, name->v.str, vs);
3191
+ scr_str_release(vs);
3192
+ return scr_dyn_retain(self);
3193
+ }
3194
+ if (value->kind == SCR_DYN_ARR) {
3195
+ for (size_t k = 0; k < value->v.arr.len; k++) {
3196
+ const ScrDyn *one = value->v.arr.items[k];
3197
+ ScrStr *vs;
3198
+ if (one->kind == SCR_DYN_STR) vs = scr_str_retain(one->v.str);
3199
+ else if (one->kind == SCR_DYN_NUM) vs = scr_f64_to_scrstr(one->v.num);
3200
+ else {
3201
+ scr_http_dynh_unsupported("ServerResponse", "setHeader", "array header values must be strings or numbers");
3202
+ return NULL;
3203
+ }
3204
+ if (k == 0) scr_http_res_set_header(r, name->v.str, vs);
3205
+ else scr_http_res_append_header(r, name->v.str, vs);
3206
+ scr_str_release(vs);
3207
+ }
3208
+ return scr_dyn_retain(self);
3209
+ }
3210
+ scr_http_dynh_unsupported("ServerResponse", "setHeader", "only string, number, and array values are modeled");
3211
+ return NULL;
3212
+ }
3213
+ if (strcmp(method, "getHeader") == 0) {
3214
+ const ScrDyn *name = argc > 0 ? args[0] : scr_dyn_undefined();
3215
+ if (name->kind != SCR_DYN_STR) {
3216
+ scr_dyn_arg_type_fail("name", "of type string", name);
3217
+ return NULL;
3218
+ }
3219
+ ScrStr *v = scr_http_res_get_header(r, name->v.str);
3220
+ if (!v) return scr_dyn_retain(scr_dyn_undefined());
3221
+ ScrDyn *d = scr_dyn_new_str(v);
3222
+ scr_str_release(v);
3223
+ return d;
3224
+ }
3225
+ if (strcmp(method, "hasHeader") == 0) {
3226
+ const ScrDyn *name = argc > 0 ? args[0] : scr_dyn_undefined();
3227
+ if (name->kind != SCR_DYN_STR) {
3228
+ scr_dyn_arg_type_fail("name", "of type string", name);
3229
+ return NULL;
3230
+ }
3231
+ return scr_dyn_new_bool(scr_http_res_has_header_named(r, name->v.str));
3232
+ }
3233
+ if (strcmp(method, "removeHeader") == 0) {
3234
+ const ScrDyn *name = argc > 0 ? args[0] : scr_dyn_undefined();
3235
+ if (name->kind != SCR_DYN_STR) {
3236
+ scr_dyn_arg_type_fail("name", "of type string", name);
3237
+ return NULL;
3238
+ }
3239
+ scr_http_res_remove_header(r, name->v.str);
3240
+ return scr_dyn_retain(scr_dyn_undefined());
3241
+ }
3242
+ if (strcmp(method, "destroy") == 0) {
3243
+ scr_http_res_destroy(r);
3244
+ return scr_dyn_retain(self);
3245
+ }
3246
+ if (strcmp(method, "toString") == 0) {
3247
+ ScrStr *s = scr_str_new("[object Object]", 15);
3248
+ ScrDyn *d = scr_dyn_new_str(s);
3249
+ scr_str_release(s);
3250
+ return d;
3251
+ }
3252
+ {
3253
+ static const char *const known[] = { "writeContinue", "writeEarlyHints", "cork", "uncork",
3254
+ "flushHeaders", "getHeaders", "getHeaderNames", "appendHeader", "setTimeout",
3255
+ "addTrailers", "off", "removeListener", "removeAllListeners", "emit",
3256
+ "prependListener", "prependOnceListener", "listenerCount", "listeners", "setDefaultEncoding", NULL };
3257
+ if (scr_http_dynh_in(method, known)) {
3258
+ scr_http_dynh_unsupported("ServerResponse", method, NULL);
3259
+ return NULL;
3260
+ }
3261
+ }
3262
+ scr_http_dynh_not_fn(what);
3263
+ return NULL;
3264
+ }
3265
+
3266
+ static ScrDyn *scr_http_dynh_res_get(void *h, const char *key, size_t key_len) {
3267
+ ScrHttpRes *r = (ScrHttpRes *)h;
3268
+ (void)key_len;
3269
+ if (strcmp(key, "statusCode") == 0) return scr_dyn_new_num(scr_http_res_status_get(r));
3270
+ if (strcmp(key, "statusMessage") == 0) {
3271
+ ScrStr *s = scr_http_res_status_msg_get(r);
3272
+ ScrDyn *d = scr_dyn_new_str(s);
3273
+ scr_str_release(s);
3274
+ return d;
3275
+ }
3276
+ if (strcmp(key, "headersSent") == 0) return scr_dyn_new_bool(scr_http_res_headers_sent(r));
3277
+ if (strcmp(key, "finished") == 0 || strcmp(key, "writableEnded") == 0) {
3278
+ /* One flag serves both (Node splits ended-vs-flushed; this surface
3279
+ * flushes synchronously into the socket buffer — SEMANTICS.md). */
3280
+ return scr_dyn_new_bool(r->finished);
3281
+ }
3282
+ if (strcmp(key, "socket") == 0 || strcmp(key, "connection") == 0) {
3283
+ if (!r->sock) return scr_dyn_new_null(); /* destroyed: Node nulls it */
3284
+ return scr_dyn_new_handle(r->sock, SCR_DYNH_NET_SOCKET);
3285
+ }
3286
+ {
3287
+ static const char *const known[] = { "req", "chunkedEncoding", "sendDate",
3288
+ "strictContentLength", "writableFinished", "writableLength", "writableHighWaterMark",
3289
+ "writableCorked", "writableObjectMode", "closed", "destroyed", "errored", NULL };
3290
+ if (scr_http_dynh_in(key, known)) {
3291
+ scr_http_dynh_unsupported("ServerResponse", key, NULL);
3292
+ return NULL;
3293
+ }
3294
+ }
3295
+ return NULL;
3296
+ }
3297
+
3298
+ static bool scr_http_dynh_res_set(void *h, const char *key, size_t key_len, const ScrDyn *value) {
3299
+ ScrHttpRes *r = (ScrHttpRes *)h;
3300
+ (void)key_len;
3301
+ if (strcmp(key, "statusCode") == 0) {
3302
+ if (value->kind != SCR_DYN_NUM) {
3303
+ scr_dyn_arg_type_fail("statusCode", "of type number", value);
3304
+ return true; /* handled: the exception is pending */
3305
+ }
3306
+ scr_http_res_status_set(r, value->v.num);
3307
+ return true;
3308
+ }
3309
+ if (strcmp(key, "statusMessage") == 0) {
3310
+ if (value->kind != SCR_DYN_STR) {
3311
+ scr_dyn_arg_type_fail("statusMessage", "of type string", value);
3312
+ return true;
3313
+ }
3314
+ scr_http_res_status_msg_set(r, value->v.str);
3315
+ return true;
3316
+ }
3317
+ if (strcmp(key, "sendDate") == 0) {
3318
+ /* Node's boolean flag over the implicit Date header (ToBoolean, like
3319
+ * the property's own coercion). */
3320
+ r->no_date = !scr_dyn_truthy(value);
3321
+ return true;
3322
+ }
3323
+ return false;
3324
+ }
3325
+
3326
+ /* Pipe DESTINATION hook: `req.pipe(res)` dispatches inside this unit,
3327
+ * but `socket.pipe(res)` arrives from scr_net.c, which cannot name this
3328
+ * unit's entry points — the destination accepts the source here. */
3329
+ static bool scr_http_dynh_res_pipe_from(void *dst, const ScrDyn *src) {
3330
+ ScrHttpRes *r = (ScrHttpRes *)dst;
3331
+ if (src->kind == SCR_DYN_HANDLE && src->v.handle.tag == SCR_DYNH_NET_SOCKET) {
3332
+ scr_http_sock_pipe_res((ScrNetSocket *)src->v.handle.ptr, r);
3333
+ return true;
3334
+ }
3335
+ if (src->kind == SCR_DYN_HANDLE && src->v.handle.tag == SCR_DYNH_HTTP_REQ) {
3336
+ scr_http_req_pipe_res((ScrHttpReq *)src->v.handle.ptr, r);
3337
+ return true;
3338
+ }
3339
+ return false;
3340
+ }
3341
+
3342
+ static const ScrDynHandleOps scr_http_dynh_req_ops = {
3343
+ "IncomingMessage",
3344
+ &scr_http_req_retain_v,
3345
+ &scr_http_req_release_v,
3346
+ &scr_http_dynh_req_invoke,
3347
+ &scr_http_dynh_req_get,
3348
+ &scr_http_dynh_req_set,
3349
+ NULL,
3350
+ };
3351
+
3352
+ static const ScrDynHandleOps scr_http_dynh_res_ops = {
3353
+ "ServerResponse",
3354
+ &scr_http_res_retain_v,
3355
+ &scr_http_res_release_v,
3356
+ &scr_http_dynh_res_invoke,
3357
+ &scr_http_dynh_res_get,
3358
+ &scr_http_dynh_res_set,
3359
+ &scr_http_dynh_res_pipe_from,
3360
+ };
3361
+
3362
+ /* The 'request' fire for a DYN listener registered through the netServer
3363
+ * handle dispatch (`server.on('request', wrapper)` where `server` lives
3364
+ * in a dyn binding): box req/res by reference and call through the
3365
+ * checked-dynamic machinery. The ScrHttpReqFn ABI — req/res arrive +1
3366
+ * and the adapter owns them, like the compiler-emitted adapters. */
3367
+ static void scr_http_dynh_fire_reqres(ScrClosure *cb, ScrHttpReq *req /* +1 */,
3368
+ ScrHttpRes *res /* +1 */) {
3369
+ ScrDyn *fn = scr_dyn_listener_fn(cb);
3370
+ ScrDyn *dreq = scr_dyn_new_handle(req, SCR_DYNH_HTTP_REQ);
3371
+ ScrDyn *dres = scr_dyn_new_handle(res, SCR_DYNH_HTTP_RES);
3372
+ ScrDyn *args[2] = { dreq, dres };
3373
+ ScrDyn *r = scr_dyn_call(fn, args, 2, "listener");
3374
+ scr_dyn_release(r);
3375
+ scr_dyn_release(dres);
3376
+ scr_dyn_release(dreq);
3377
+ scr_dyn_release(fn);
3378
+ scr_http_req_release(req);
3379
+ scr_http_res_release(res);
3380
+ }
3381
+
3382
+ /* The netServer dyn dispatch's http-event hook (scr_net_set_dynh_http_on):
3383
+ * 'request' registers; anything else answers false and the net side
3384
+ * fences loudly. */
3385
+ static bool scr_http_dynh_server_on(ScrNetServer *s, const char *event, const ScrDyn *cb,
3386
+ bool once) {
3387
+ if (strcmp(event, "request") == 0) {
3388
+ scr_http_server_on_request(s, scr_dyn_listener_closure_fn(cb, (void *)&scr_http_dynh_fire_reqres),
3389
+ (ScrHttpReqFn)&scr_http_dynh_fire_reqres, once);
3390
+ return true;
3391
+ }
3392
+ return false;
3393
+ }
3394
+
3395
+ void scr_http_dyn_install(void) {
3396
+ scr_dyn_handle_install(SCR_DYNH_HTTP_REQ, &scr_http_dynh_req_ops);
3397
+ scr_dyn_handle_install(SCR_DYNH_HTTP_RES, &scr_http_dynh_res_ops);
3398
+ scr_net_set_dynh_http_on(&scr_http_dynh_server_on);
3399
+ }
3400
+
3401
+ /* Checked-dynamic chunks into TYPED res/client handles — the
3402
+ * scr_net_sock_write_dynv story (the static lowering's dyn-data arm). */
3403
+ void scr_http_res_write_dynv(ScrHttpRes *r, const ScrDyn *d) {
3404
+ if (d->kind == SCR_DYN_STR) scr_http_res_write_str(r, d->v.str);
3405
+ else if (d->kind == SCR_DYN_BYTES) scr_http_res_write_bytes(r, d->v.bytes);
3406
+ else scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", d);
3407
+ }
3408
+
3409
+ void scr_http_res_end_dynv(ScrHttpRes *r, const ScrDyn *d) {
3410
+ if (d->kind == SCR_DYN_STR) scr_http_res_end_str(r, d->v.str);
3411
+ else if (d->kind == SCR_DYN_BYTES) scr_http_res_end_bytes(r, d->v.bytes);
3412
+ else if (d->kind == SCR_DYN_UNDEF || d->kind == SCR_DYN_NULL) scr_http_res_end(r);
3413
+ else scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", d);
3414
+ }
3415
+
3416
+ void scr_http_client_write_dynv(ScrHttpClientReq *c, const ScrDyn *d) {
3417
+ if (d->kind == SCR_DYN_STR) scr_http_client_write_str(c, d->v.str);
3418
+ else if (d->kind == SCR_DYN_BYTES) scr_http_client_write_bytes(c, d->v.bytes);
3419
+ else scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", d);
3420
+ }
3421
+
3422
+ void scr_http_client_end_dynv(ScrHttpClientReq *c, const ScrDyn *d) {
3423
+ if (d->kind == SCR_DYN_STR) scr_http_client_end_str(c, d->v.str);
3424
+ else if (d->kind == SCR_DYN_BYTES) scr_http_client_end_bytes(c, d->v.bytes);
3425
+ else if (d->kind == SCR_DYN_UNDEF || d->kind == SCR_DYN_NULL) scr_http_client_end(c);
3426
+ else scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", d);
3427
+ }
3428
+
3429
+ /* http.request/get with a URL-STRING first argument — the suite's
3430
+ * `http.get(`http://127.0.0.1:${port}/x`)` spelling. Parses through the
3431
+ * WHATWG unit (scr_url.c, always linked): an unparsable input throws
3432
+ * scr_url_new's catchable "Invalid URL" TypeError; a non-http scheme
3433
+ * throws Node's ERR_INVALID_PROTOCOL shape. Path is pathname + search
3434
+ * (fragment dropped, Node's client behavior). cb MOVES (nullable);
3435
+ * result +1 or NULL with the exception pending. */
3436
+ #include "scr_url_internal.h"
3437
+
3438
+ ScrHttpClientReq *scr_http_request_url(ScrStr *url /*borrowed*/, ScrStr *method /*borrowed*/,
3439
+ bool auto_end, ScrClosure *cb /*moves, nullable*/,
3440
+ ScrHttpRespFn fn) {
3441
+ ScrUrl *u = scr_url_new(url);
3442
+ if (!u) {
3443
+ if (cb) scr_closure_release(cb);
3444
+ return NULL; /* Invalid URL pending */
3445
+ }
3446
+ if (!(u->scheme->len == 4 && memcmp(u->scheme->data, "http", 4) == 0)) {
3447
+ char msg[128];
3448
+ int n = snprintf(msg, sizeof msg, "Protocol \"%.*s:\" not supported. Expected \"http:\"",
3449
+ (int)(u->scheme->len < 32 ? u->scheme->len : 32), u->scheme->data);
3450
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)n, "ERR_INVALID_PROTOCOL");
3451
+ scr_url_release(u);
3452
+ if (cb) scr_closure_release(cb);
3453
+ return NULL;
3454
+ }
3455
+ double port = 80;
3456
+ if (u->port->len > 0) {
3457
+ port = 0;
3458
+ for (size_t i = 0; i < u->port->len; i++) port = port * 10 + (u->port->data[i] - '0');
3459
+ }
3460
+ /* IPv6 authority arrives bracketed ("[::1]") — the dial wants the bare
3461
+ * address, like Node's client strips them. */
3462
+ ScrStr *host;
3463
+ if (u->host->len >= 2 && u->host->data[0] == '[' && u->host->data[u->host->len - 1] == ']') {
3464
+ host = scr_str_new(u->host->data + 1, u->host->len - 2);
3465
+ } else {
3466
+ host = scr_str_retain(u->host);
3467
+ }
3468
+ ScrStr *path;
3469
+ if (u->query->len > 0) {
3470
+ path = scr_str_concat(u->path, u->query);
3471
+ } else {
3472
+ path = u->path->len > 0 ? scr_str_retain(u->path) : scr_str_new("/", 1);
3473
+ }
3474
+ ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, 0); /* request_impl reads the pairs array */
3475
+ ScrHttpClientReq *c = scr_http_request_ex(host, port, path, method, 0, pairs, auto_end,
3476
+ cb, fn, 80, NULL, NULL);
3477
+ scr_arr_release(pairs);
3478
+ scr_str_release(host);
3479
+ scr_str_release(path);
3480
+ scr_url_release(u);
3481
+ return c;
3482
+ }