@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.
- package/LICENSE +202 -0
- package/package.json +16 -6
- package/src/scr_array.c +488 -0
- package/src/scr_assert.c +1393 -0
- package/src/scr_async.c +2593 -0
- package/src/scr_async_dyn.c +607 -0
- package/src/scr_bytes.c +1425 -0
- package/src/scr_bytes_io.c +161 -0
- package/src/scr_child.c +3587 -0
- package/src/scr_closure.c +214 -0
- package/src/scr_console.c +143 -0
- package/src/scr_cycle.c +218 -0
- package/src/scr_dc.c +805 -0
- package/src/scr_dgram.c +1007 -0
- package/src/scr_dyn_handle.c +154 -0
- package/src/scr_dyn_invoke.c +644 -0
- package/src/scr_error.c +332 -0
- package/src/scr_events.c +714 -0
- package/src/scr_events_emitter.c +699 -0
- package/src/scr_exception.c +242 -0
- package/src/scr_fetch.c +1130 -0
- package/src/scr_fetch_curl.c +654 -0
- package/src/scr_http.c +3482 -0
- package/src/scr_http2.c +3030 -0
- package/src/scr_inspect.c +803 -0
- package/src/scr_inspect_island.c +51 -0
- package/src/scr_island.c +9363 -0
- package/src/scr_json.c +2151 -0
- package/src/scr_lib.c +3612 -0
- package/src/scr_loop_epoll.c +241 -0
- package/src/scr_loop_kqueue.c +121 -0
- package/src/scr_loop_wsapoll.c +208 -0
- package/src/scr_map.c +591 -0
- package/src/scr_net.c +3017 -0
- package/src/scr_net_island.c +458 -0
- package/src/scr_number.c +115 -0
- package/src/scr_object.c +25 -0
- package/src/scr_path.c +1266 -0
- package/src/scr_platform.h +119 -0
- package/src/scr_readline.c +287 -0
- package/src/scr_regex.c +1080 -0
- package/src/scr_runtime.h +5046 -0
- package/src/scr_stream.c +2877 -0
- package/src/scr_string.c +1264 -0
- package/src/scr_symbol.c +132 -0
- package/src/scr_test.c +662 -0
- package/src/scr_tls.c +1520 -0
- package/src/scr_union.c +105 -0
- package/src/scr_url.c +911 -0
- package/src/scr_url_internal.h +29 -0
- package/src/scr_url_params.c +561 -0
- package/src/scr_watch.c +568 -0
- package/src/scr_web.c +1778 -0
- package/src/scr_win.c +76 -0
- package/src/scr_zlib.c +197 -0
- package/src/scr_zlib_island.c +15 -0
- package/vendor/README.md +52 -0
- package/vendor/curl/COPYRIGHT +534 -0
- package/vendor/curl/include/curl/curl.h +3214 -0
- package/vendor/curl/include/curl/curlver.h +79 -0
- package/vendor/curl/include/curl/easy.h +125 -0
- package/vendor/curl/include/curl/header.h +74 -0
- package/vendor/curl/include/curl/mprintf.h +52 -0
- package/vendor/curl/include/curl/multi.h +460 -0
- package/vendor/curl/include/curl/options.h +70 -0
- package/vendor/curl/include/curl/stdcheaders.h +35 -0
- package/vendor/curl/include/curl/system.h +508 -0
- package/vendor/curl/include/curl/typecheck-gcc.h +716 -0
- package/vendor/curl/include/curl/urlapi.h +149 -0
- package/vendor/curl/include/curl/websockets.h +84 -0
- package/vendor/mbedtls/LICENSE +553 -0
- package/vendor/mbedtls/include/CMakeLists.txt +22 -0
- package/vendor/mbedtls/include/mbedtls/aes.h +631 -0
- package/vendor/mbedtls/include/mbedtls/aria.h +343 -0
- package/vendor/mbedtls/include/mbedtls/asn1.h +642 -0
- package/vendor/mbedtls/include/mbedtls/asn1write.h +390 -0
- package/vendor/mbedtls/include/mbedtls/base64.h +82 -0
- package/vendor/mbedtls/include/mbedtls/bignum.h +1088 -0
- package/vendor/mbedtls/include/mbedtls/block_cipher.h +76 -0
- package/vendor/mbedtls/include/mbedtls/build_info.h +194 -0
- package/vendor/mbedtls/include/mbedtls/camellia.h +305 -0
- package/vendor/mbedtls/include/mbedtls/ccm.h +526 -0
- package/vendor/mbedtls/include/mbedtls/chacha20.h +209 -0
- package/vendor/mbedtls/include/mbedtls/chachapoly.h +351 -0
- package/vendor/mbedtls/include/mbedtls/check_config.h +1149 -0
- package/vendor/mbedtls/include/mbedtls/cipher.h +1250 -0
- package/vendor/mbedtls/include/mbedtls/cmac.h +246 -0
- package/vendor/mbedtls/include/mbedtls/compat-2.x.h +46 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h +578 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h +873 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h +359 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h +145 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_ssl.h +91 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_x509.h +35 -0
- package/vendor/mbedtls/include/mbedtls/config_psa.h +61 -0
- package/vendor/mbedtls/include/mbedtls/constant_time.h +36 -0
- package/vendor/mbedtls/include/mbedtls/ctr_drbg.h +596 -0
- package/vendor/mbedtls/include/mbedtls/debug.h +156 -0
- package/vendor/mbedtls/include/mbedtls/des.h +385 -0
- package/vendor/mbedtls/include/mbedtls/dhm.h +972 -0
- package/vendor/mbedtls/include/mbedtls/ecdh.h +455 -0
- package/vendor/mbedtls/include/mbedtls/ecdsa.h +674 -0
- package/vendor/mbedtls/include/mbedtls/ecjpake.h +298 -0
- package/vendor/mbedtls/include/mbedtls/ecp.h +1517 -0
- package/vendor/mbedtls/include/mbedtls/entropy.h +274 -0
- package/vendor/mbedtls/include/mbedtls/error.h +201 -0
- package/vendor/mbedtls/include/mbedtls/gcm.h +387 -0
- package/vendor/mbedtls/include/mbedtls/hkdf.h +124 -0
- package/vendor/mbedtls/include/mbedtls/hmac_drbg.h +434 -0
- package/vendor/mbedtls/include/mbedtls/lms.h +440 -0
- package/vendor/mbedtls/include/mbedtls/mbedtls_config.h +4446 -0
- package/vendor/mbedtls/include/mbedtls/md.h +526 -0
- package/vendor/mbedtls/include/mbedtls/md5.h +190 -0
- package/vendor/mbedtls/include/mbedtls/memory_buffer_alloc.h +142 -0
- package/vendor/mbedtls/include/mbedtls/net_sockets.h +299 -0
- package/vendor/mbedtls/include/mbedtls/nist_kw.h +166 -0
- package/vendor/mbedtls/include/mbedtls/oid.h +727 -0
- package/vendor/mbedtls/include/mbedtls/pem.h +160 -0
- package/vendor/mbedtls/include/mbedtls/pk.h +1303 -0
- package/vendor/mbedtls/include/mbedtls/pkcs12.h +186 -0
- package/vendor/mbedtls/include/mbedtls/pkcs5.h +198 -0
- package/vendor/mbedtls/include/mbedtls/pkcs7.h +252 -0
- package/vendor/mbedtls/include/mbedtls/platform.h +516 -0
- package/vendor/mbedtls/include/mbedtls/platform_time.h +79 -0
- package/vendor/mbedtls/include/mbedtls/platform_util.h +247 -0
- package/vendor/mbedtls/include/mbedtls/poly1305.h +168 -0
- package/vendor/mbedtls/include/mbedtls/private_access.h +20 -0
- package/vendor/mbedtls/include/mbedtls/psa_util.h +207 -0
- package/vendor/mbedtls/include/mbedtls/ripemd160.h +136 -0
- package/vendor/mbedtls/include/mbedtls/rsa.h +1170 -0
- package/vendor/mbedtls/include/mbedtls/sha1.h +219 -0
- package/vendor/mbedtls/include/mbedtls/sha256.h +200 -0
- package/vendor/mbedtls/include/mbedtls/sha3.h +172 -0
- package/vendor/mbedtls/include/mbedtls/sha512.h +208 -0
- package/vendor/mbedtls/include/mbedtls/ssl.h +5887 -0
- package/vendor/mbedtls/include/mbedtls/ssl_cache.h +187 -0
- package/vendor/mbedtls/include/mbedtls/ssl_ciphersuites.h +482 -0
- package/vendor/mbedtls/include/mbedtls/ssl_cookie.h +106 -0
- package/vendor/mbedtls/include/mbedtls/ssl_ticket.h +199 -0
- package/vendor/mbedtls/include/mbedtls/threading.h +167 -0
- package/vendor/mbedtls/include/mbedtls/timing.h +94 -0
- package/vendor/mbedtls/include/mbedtls/version.h +78 -0
- package/vendor/mbedtls/include/mbedtls/x509.h +500 -0
- package/vendor/mbedtls/include/mbedtls/x509_crl.h +184 -0
- package/vendor/mbedtls/include/mbedtls/x509_crt.h +1208 -0
- package/vendor/mbedtls/include/mbedtls/x509_csr.h +382 -0
- package/vendor/mbedtls/include/psa/build_info.h +20 -0
- package/vendor/mbedtls/include/psa/crypto.h +4998 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_auto_enabled.h +31 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_dependencies.h +51 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h +101 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_synonyms.h +49 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_composites.h +214 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_key_derivation.h +118 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_primitives.h +114 -0
- package/vendor/mbedtls/include/psa/crypto_compat.h +230 -0
- package/vendor/mbedtls/include/psa/crypto_config.h +145 -0
- package/vendor/mbedtls/include/psa/crypto_driver_common.h +44 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_composites.h +151 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_key_derivation.h +52 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_primitives.h +105 -0
- package/vendor/mbedtls/include/psa/crypto_extra.h +2145 -0
- package/vendor/mbedtls/include/psa/crypto_legacy.h +88 -0
- package/vendor/mbedtls/include/psa/crypto_platform.h +102 -0
- package/vendor/mbedtls/include/psa/crypto_se_driver.h +1383 -0
- package/vendor/mbedtls/include/psa/crypto_sizes.h +1319 -0
- package/vendor/mbedtls/include/psa/crypto_struct.h +527 -0
- package/vendor/mbedtls/include/psa/crypto_types.h +508 -0
- package/vendor/mbedtls/include/psa/crypto_values.h +2782 -0
- package/vendor/mbedtls/library/aes.c +2294 -0
- package/vendor/mbedtls/library/aesce.c +624 -0
- package/vendor/mbedtls/library/aesce.h +136 -0
- package/vendor/mbedtls/library/aesni.c +846 -0
- package/vendor/mbedtls/library/aesni.h +162 -0
- package/vendor/mbedtls/library/alignment.h +704 -0
- package/vendor/mbedtls/library/aria.c +969 -0
- package/vendor/mbedtls/library/asn1parse.c +468 -0
- package/vendor/mbedtls/library/asn1write.c +440 -0
- package/vendor/mbedtls/library/base64.c +322 -0
- package/vendor/mbedtls/library/base64_internal.h +45 -0
- package/vendor/mbedtls/library/bignum.c +2583 -0
- package/vendor/mbedtls/library/bignum_core.c +1240 -0
- package/vendor/mbedtls/library/bignum_core.h +872 -0
- package/vendor/mbedtls/library/bignum_core_invasive.h +38 -0
- package/vendor/mbedtls/library/bignum_internal.h +122 -0
- package/vendor/mbedtls/library/bignum_mod.c +394 -0
- package/vendor/mbedtls/library/bignum_mod.h +452 -0
- package/vendor/mbedtls/library/bignum_mod_raw.c +276 -0
- package/vendor/mbedtls/library/bignum_mod_raw.h +416 -0
- package/vendor/mbedtls/library/bignum_mod_raw_invasive.h +34 -0
- package/vendor/mbedtls/library/block_cipher.c +207 -0
- package/vendor/mbedtls/library/block_cipher_internal.h +99 -0
- package/vendor/mbedtls/library/bn_mul.h +1094 -0
- package/vendor/mbedtls/library/camellia.c +1058 -0
- package/vendor/mbedtls/library/ccm.c +777 -0
- package/vendor/mbedtls/library/chacha20.c +563 -0
- package/vendor/mbedtls/library/chacha20_internal.h +23 -0
- package/vendor/mbedtls/library/chachapoly.c +487 -0
- package/vendor/mbedtls/library/check_crypto_config.h +136 -0
- package/vendor/mbedtls/library/cipher.c +1712 -0
- package/vendor/mbedtls/library/cipher_invasive.h +28 -0
- package/vendor/mbedtls/library/cipher_wrap.c +2482 -0
- package/vendor/mbedtls/library/cipher_wrap.h +178 -0
- package/vendor/mbedtls/library/cmac.c +1067 -0
- package/vendor/mbedtls/library/common.h +487 -0
- package/vendor/mbedtls/library/constant_time.c +247 -0
- package/vendor/mbedtls/library/constant_time_impl.h +541 -0
- package/vendor/mbedtls/library/constant_time_internal.h +579 -0
- package/vendor/mbedtls/library/ctr.h +35 -0
- package/vendor/mbedtls/library/ctr_drbg.c +1016 -0
- package/vendor/mbedtls/library/debug.c +475 -0
- package/vendor/mbedtls/library/debug_internal.h +185 -0
- package/vendor/mbedtls/library/des.c +1042 -0
- package/vendor/mbedtls/library/dhm.c +700 -0
- package/vendor/mbedtls/library/ecdh.c +696 -0
- package/vendor/mbedtls/library/ecdsa.c +858 -0
- package/vendor/mbedtls/library/ecjpake.c +1216 -0
- package/vendor/mbedtls/library/ecp.c +3674 -0
- package/vendor/mbedtls/library/ecp_curves.c +6125 -0
- package/vendor/mbedtls/library/ecp_curves_new.c +9 -0
- package/vendor/mbedtls/library/ecp_internal_alt.h +287 -0
- package/vendor/mbedtls/library/ecp_invasive.h +305 -0
- package/vendor/mbedtls/library/entropy.c +680 -0
- package/vendor/mbedtls/library/entropy_poll.c +233 -0
- package/vendor/mbedtls/library/entropy_poll.h +64 -0
- package/vendor/mbedtls/library/error.c +878 -0
- package/vendor/mbedtls/library/gcm.c +1330 -0
- package/vendor/mbedtls/library/hkdf.c +161 -0
- package/vendor/mbedtls/library/hmac_drbg.c +633 -0
- package/vendor/mbedtls/library/lmots.c +789 -0
- package/vendor/mbedtls/library/lmots.h +288 -0
- package/vendor/mbedtls/library/lms.c +778 -0
- package/vendor/mbedtls/library/md.c +1108 -0
- package/vendor/mbedtls/library/md5.c +426 -0
- package/vendor/mbedtls/library/md_psa.h +26 -0
- package/vendor/mbedtls/library/md_wrap.h +46 -0
- package/vendor/mbedtls/library/memory_buffer_alloc.c +751 -0
- package/vendor/mbedtls/library/mps_common.h +181 -0
- package/vendor/mbedtls/library/mps_error.h +89 -0
- package/vendor/mbedtls/library/mps_reader.c +538 -0
- package/vendor/mbedtls/library/mps_reader.h +366 -0
- package/vendor/mbedtls/library/mps_trace.c +112 -0
- package/vendor/mbedtls/library/mps_trace.h +154 -0
- package/vendor/mbedtls/library/net_sockets.c +694 -0
- package/vendor/mbedtls/library/nist_kw.c +729 -0
- package/vendor/mbedtls/library/oid.c +1166 -0
- package/vendor/mbedtls/library/padlock.c +157 -0
- package/vendor/mbedtls/library/padlock.h +111 -0
- package/vendor/mbedtls/library/pem.c +554 -0
- package/vendor/mbedtls/library/pk.c +1602 -0
- package/vendor/mbedtls/library/pk_ecc.c +261 -0
- package/vendor/mbedtls/library/pk_internal.h +241 -0
- package/vendor/mbedtls/library/pk_wrap.c +1618 -0
- package/vendor/mbedtls/library/pk_wrap.h +138 -0
- package/vendor/mbedtls/library/pkcs12.c +437 -0
- package/vendor/mbedtls/library/pkcs5.c +500 -0
- package/vendor/mbedtls/library/pkcs7.c +787 -0
- package/vendor/mbedtls/library/pkparse.c +1392 -0
- package/vendor/mbedtls/library/pkwrite.c +631 -0
- package/vendor/mbedtls/library/pkwrite.h +121 -0
- package/vendor/mbedtls/library/platform.c +402 -0
- package/vendor/mbedtls/library/platform_util.c +258 -0
- package/vendor/mbedtls/library/poly1305.c +492 -0
- package/vendor/mbedtls/library/psa_crypto.c +9532 -0
- package/vendor/mbedtls/library/psa_crypto_aead.c +646 -0
- package/vendor/mbedtls/library/psa_crypto_aead.h +499 -0
- package/vendor/mbedtls/library/psa_crypto_cipher.c +747 -0
- package/vendor/mbedtls/library/psa_crypto_cipher.h +316 -0
- package/vendor/mbedtls/library/psa_crypto_client.c +22 -0
- package/vendor/mbedtls/library/psa_crypto_core.h +983 -0
- package/vendor/mbedtls/library/psa_crypto_core_common.h +52 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers.h +2896 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.c +256 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.h +31 -0
- package/vendor/mbedtls/library/psa_crypto_ecp.c +594 -0
- package/vendor/mbedtls/library/psa_crypto_ecp.h +267 -0
- package/vendor/mbedtls/library/psa_crypto_ffdh.c +361 -0
- package/vendor/mbedtls/library/psa_crypto_ffdh.h +131 -0
- package/vendor/mbedtls/library/psa_crypto_hash.c +470 -0
- package/vendor/mbedtls/library/psa_crypto_hash.h +211 -0
- package/vendor/mbedtls/library/psa_crypto_invasive.h +92 -0
- package/vendor/mbedtls/library/psa_crypto_its.h +131 -0
- package/vendor/mbedtls/library/psa_crypto_mac.c +505 -0
- package/vendor/mbedtls/library/psa_crypto_mac.h +264 -0
- package/vendor/mbedtls/library/psa_crypto_pake.c +571 -0
- package/vendor/mbedtls/library/psa_crypto_pake.h +159 -0
- package/vendor/mbedtls/library/psa_crypto_random.c +181 -0
- package/vendor/mbedtls/library/psa_crypto_random.h +72 -0
- package/vendor/mbedtls/library/psa_crypto_random_impl.h +200 -0
- package/vendor/mbedtls/library/psa_crypto_rsa.c +714 -0
- package/vendor/mbedtls/library/psa_crypto_rsa.h +321 -0
- package/vendor/mbedtls/library/psa_crypto_se.c +373 -0
- package/vendor/mbedtls/library/psa_crypto_se.h +192 -0
- package/vendor/mbedtls/library/psa_crypto_slot_management.c +1137 -0
- package/vendor/mbedtls/library/psa_crypto_slot_management.h +344 -0
- package/vendor/mbedtls/library/psa_crypto_storage.c +481 -0
- package/vendor/mbedtls/library/psa_crypto_storage.h +392 -0
- package/vendor/mbedtls/library/psa_its_file.c +254 -0
- package/vendor/mbedtls/library/psa_util.c +614 -0
- package/vendor/mbedtls/library/psa_util_internal.h +100 -0
- package/vendor/mbedtls/library/ripemd160.c +490 -0
- package/vendor/mbedtls/library/rsa.c +3121 -0
- package/vendor/mbedtls/library/rsa_alt_helpers.c +455 -0
- package/vendor/mbedtls/library/rsa_alt_helpers.h +209 -0
- package/vendor/mbedtls/library/rsa_internal.h +188 -0
- package/vendor/mbedtls/library/sha1.c +480 -0
- package/vendor/mbedtls/library/sha256.c +983 -0
- package/vendor/mbedtls/library/sha3.c +721 -0
- package/vendor/mbedtls/library/sha512.c +1115 -0
- package/vendor/mbedtls/library/ssl_cache.c +410 -0
- package/vendor/mbedtls/library/ssl_ciphersuites.c +2050 -0
- package/vendor/mbedtls/library/ssl_ciphersuites_internal.h +154 -0
- package/vendor/mbedtls/library/ssl_client.c +1023 -0
- package/vendor/mbedtls/library/ssl_client.h +22 -0
- package/vendor/mbedtls/library/ssl_cookie.c +384 -0
- package/vendor/mbedtls/library/ssl_debug_helpers.h +85 -0
- package/vendor/mbedtls/library/ssl_debug_helpers_generated.c +251 -0
- package/vendor/mbedtls/library/ssl_misc.h +3198 -0
- package/vendor/mbedtls/library/ssl_msg.c +6619 -0
- package/vendor/mbedtls/library/ssl_ticket.c +556 -0
- package/vendor/mbedtls/library/ssl_tls.c +10234 -0
- package/vendor/mbedtls/library/ssl_tls12_client.c +3688 -0
- package/vendor/mbedtls/library/ssl_tls12_server.c +4311 -0
- package/vendor/mbedtls/library/ssl_tls13_client.c +3208 -0
- package/vendor/mbedtls/library/ssl_tls13_generic.c +1793 -0
- package/vendor/mbedtls/library/ssl_tls13_invasive.h +23 -0
- package/vendor/mbedtls/library/ssl_tls13_keys.c +1918 -0
- package/vendor/mbedtls/library/ssl_tls13_keys.h +668 -0
- package/vendor/mbedtls/library/ssl_tls13_server.c +3624 -0
- package/vendor/mbedtls/library/threading.c +193 -0
- package/vendor/mbedtls/library/threading_internal.h +28 -0
- package/vendor/mbedtls/library/timing.c +152 -0
- package/vendor/mbedtls/library/version.c +32 -0
- package/vendor/mbedtls/library/version_features.c +856 -0
- package/vendor/mbedtls/library/x509.c +1776 -0
- package/vendor/mbedtls/library/x509_create.c +570 -0
- package/vendor/mbedtls/library/x509_crl.c +708 -0
- package/vendor/mbedtls/library/x509_crt.c +3311 -0
- package/vendor/mbedtls/library/x509_csr.c +648 -0
- package/vendor/mbedtls/library/x509_internal.h +101 -0
- package/vendor/mbedtls/library/x509write.c +174 -0
- package/vendor/mbedtls/library/x509write_crt.c +688 -0
- package/vendor/mbedtls/library/x509write_csr.c +336 -0
- package/vendor/quickjs-ng/CMakeLists.txt +566 -0
- package/vendor/quickjs-ng/LICENSE +24 -0
- package/vendor/quickjs-ng/README.md +24 -0
- package/vendor/quickjs-ng/api-test.c +1195 -0
- package/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
- package/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
- package/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
- package/vendor/quickjs-ng/cutils.h +1998 -0
- package/vendor/quickjs-ng/dtoa.c +1619 -0
- package/vendor/quickjs-ng/dtoa.h +87 -0
- package/vendor/quickjs-ng/gen/function_source.c +81 -0
- package/vendor/quickjs-ng/gen/hello.c +53 -0
- package/vendor/quickjs-ng/gen/hello_module.c +106 -0
- package/vendor/quickjs-ng/gen/repl.c +3036 -0
- package/vendor/quickjs-ng/gen/standalone.c +323 -0
- package/vendor/quickjs-ng/gen/test_fib.c +81 -0
- package/vendor/quickjs-ng/libregexp-opcode.h +73 -0
- package/vendor/quickjs-ng/libregexp.c +3474 -0
- package/vendor/quickjs-ng/libregexp.h +101 -0
- package/vendor/quickjs-ng/libunicode-table.h +5173 -0
- package/vendor/quickjs-ng/libunicode.c +2069 -0
- package/vendor/quickjs-ng/libunicode.h +172 -0
- package/vendor/quickjs-ng/list.h +107 -0
- package/vendor/quickjs-ng/lre-test.c +52 -0
- package/vendor/quickjs-ng/qjs.c +762 -0
- package/vendor/quickjs-ng/qjsc.c +673 -0
- package/vendor/quickjs-ng/quickjs-atom.h +280 -0
- package/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
- package/vendor/quickjs-ng/quickjs-libc.c +5037 -0
- package/vendor/quickjs-ng/quickjs-libc.h +87 -0
- package/vendor/quickjs-ng/quickjs-opcode.h +377 -0
- package/vendor/quickjs-ng/quickjs.c +64041 -0
- package/vendor/quickjs-ng/quickjs.h +1447 -0
- package/vendor/quickjs-ng/run-test262.c +2374 -0
- package/vendor/quickjs-ng/unicode_gen.c +3121 -0
- package/vendor/quickjs-ng/unicode_gen_def.h +310 -0
- package/vendor/ryu/LICENSE-Boost +23 -0
- package/vendor/ryu/common.h +114 -0
- package/vendor/ryu/d2s.c +509 -0
- package/vendor/ryu/d2s_full_table.h +367 -0
- package/vendor/ryu/d2s_intrinsics.h +357 -0
- package/vendor/ryu/d2s_small_table.h +186 -0
- package/vendor/ryu/digit_table.h +35 -0
- package/vendor/ryu/ryu.h +46 -0
- package/vendor/zlib/LICENSE +22 -0
- package/vendor/zlib/adler32.c +164 -0
- package/vendor/zlib/compress.c +75 -0
- package/vendor/zlib/crc32.c +1049 -0
- package/vendor/zlib/crc32.h +9446 -0
- package/vendor/zlib/deflate.c +2139 -0
- package/vendor/zlib/deflate.h +377 -0
- package/vendor/zlib/gzclose.c +23 -0
- package/vendor/zlib/gzguts.h +214 -0
- package/vendor/zlib/gzlib.c +582 -0
- package/vendor/zlib/gzread.c +602 -0
- package/vendor/zlib/gzwrite.c +631 -0
- package/vendor/zlib/infback.c +628 -0
- package/vendor/zlib/inffast.c +320 -0
- package/vendor/zlib/inffast.h +11 -0
- package/vendor/zlib/inffixed.h +94 -0
- package/vendor/zlib/inflate.c +1526 -0
- package/vendor/zlib/inflate.h +126 -0
- package/vendor/zlib/inftrees.c +299 -0
- package/vendor/zlib/inftrees.h +62 -0
- package/vendor/zlib/trees.c +1117 -0
- package/vendor/zlib/trees.h +128 -0
- package/vendor/zlib/uncompr.c +85 -0
- package/vendor/zlib/zconf.h +543 -0
- package/vendor/zlib/zlib.h +1938 -0
- package/vendor/zlib/zutil.c +299 -0
- package/vendor/zlib/zutil.h +254 -0
- package/README.md +0 -3
- 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
|
+
}
|