mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 00:25:34 +08:00
feat(http): expand static node:http parity (#403)
- Add typed HTTP header validation, outgoing header APIs, and lifecycle state. - Emit 100, 102, and string-valued 103 informational responses in C and LLVM. - Cover the behavior with differential fixtures and update compatibility data.
This commit is contained in:
@@ -2,6 +2,6 @@
|
||||
"schemaVersion": 3,
|
||||
"nodeVersion": "24.15.0",
|
||||
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
|
||||
"artifactVersion": "0ac7558fba31464469e7",
|
||||
"artifactVersion": "a7e38d1b2b0e92cf8ec4",
|
||||
"rowCount": 3662
|
||||
}
|
||||
|
||||
@@ -506,9 +506,9 @@
|
||||
"apiEntries": 174,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 40,
|
||||
"partial": 70,
|
||||
"refused": 0,
|
||||
"not-implemented": 134,
|
||||
"not-implemented": 104,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -45933,9 +45933,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static outgoing header reads and mutations support the tested string-value shapes.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -45961,9 +45961,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static outgoing header reads and mutations support the tested string-value shapes.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -45989,9 +45989,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static outgoing header reads and mutations support the tested string-value shapes.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -46017,9 +46017,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static outgoing header reads and mutations support the tested string-value shapes.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -46045,9 +46045,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static outgoing header reads and mutations support the tested string-value shapes.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -46073,9 +46073,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static outgoing header reads and mutations support the tested string-value shapes.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -46101,9 +46101,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static outgoing header reads and mutations support the tested string-value shapes.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -46381,9 +46381,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -46409,9 +46409,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -46437,9 +46437,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -46465,9 +46465,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -46549,9 +46549,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -47109,9 +47109,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -47725,9 +47725,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static outgoing header reads and mutations support the tested string-value shapes.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -47753,9 +47753,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static outgoing header reads and mutations support the tested string-value shapes.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -47949,9 +47949,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP/1.1 informational responses support the tested no-callback forms; Early Hints accepts string-valued fields.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -47977,9 +47977,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP/1.1 informational responses support the tested no-callback forms; Early Hints accepts string-valued fields.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -48033,9 +48033,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP/1.1 informational responses support the tested no-callback forms; Early Hints accepts string-valued fields.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -48117,9 +48117,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -48229,9 +48229,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -48313,9 +48313,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -48537,9 +48537,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -48649,9 +48649,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -48677,9 +48677,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -48789,9 +48789,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -48901,9 +48901,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -49517,9 +49517,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -49601,9 +49601,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static HTTP request metadata and message state support the tested lifecycle.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -49993,9 +49993,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static header validation is implemented for the tested call shapes.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -50021,9 +50021,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Static header validation is implemented for the tested call shapes.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"tiers": {
|
||||
"static": {
|
||||
"replace-refusal": 46,
|
||||
"verify-gap": 2672,
|
||||
"audit-partial": 494,
|
||||
"verify-gap": 2642,
|
||||
"audit-partial": 524,
|
||||
"classify": 2,
|
||||
"implement": 81
|
||||
},
|
||||
@@ -47149,12 +47149,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.request.getHeader",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -47184,12 +47186,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.request.getHeaderNames",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -47217,12 +47221,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.request.getHeaders",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -47250,12 +47256,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.request.getRawHeaderNames",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -47283,12 +47291,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.request.hasHeader",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -47316,12 +47326,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.request.removeHeader",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -47351,12 +47363,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.request.setHeader",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -47700,12 +47714,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.path",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -47733,12 +47749,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.method",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -47766,12 +47784,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.host",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -47799,12 +47819,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.protocol",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -47898,12 +47920,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.writableEnded",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -48568,12 +48592,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.listening",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -49333,12 +49359,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.response.getHeaderNames",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -49366,12 +49394,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.response.getHeaders",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -49618,12 +49648,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.response.writeContinue",
|
||||
"tests": [
|
||||
"tests/corpus/2956-http-static-informational.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -49651,12 +49683,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.response.writeEarlyHints",
|
||||
"tests": [
|
||||
"tests/corpus/2956-http-static-informational.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -49721,12 +49755,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.response.writeProcessing",
|
||||
"tests": [
|
||||
"tests/corpus/2956-http-static-informational.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -49820,12 +49856,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.headersSent",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -49952,12 +49990,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.statusCode",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -50051,12 +50091,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.writableEnded",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -50319,12 +50361,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.complete",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -50456,12 +50500,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.httpVersion",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -50489,12 +50535,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.method",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -50626,12 +50674,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.statusCode",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -50768,12 +50818,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.url",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -51514,12 +51566,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.headersSent",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -51618,12 +51672,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.writableEnded",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -52108,12 +52164,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.http.validateHeaderName",
|
||||
"tests": [
|
||||
"tests/corpus/2953-http-header-validation.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -52141,12 +52199,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:http",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:http.http.validateHeaderValue",
|
||||
"tests": [
|
||||
"tests/corpus/2953-http-header-validation.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
|
||||
@@ -555,9 +555,9 @@
|
||||
"apiEntries": 174,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 40,
|
||||
"partial": 70,
|
||||
"refused": 0,
|
||||
"not-implemented": 134,
|
||||
"not-implemented": 104,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -62161,6 +62161,10 @@
|
||||
"tests/corpus/2700-http-static-trailers.ts",
|
||||
"tests/corpus/2701-http-static-trailer-wire.ts",
|
||||
"tests/corpus/2702-http-static-trailer-end.ts",
|
||||
"tests/corpus/2953-http-header-validation.ts",
|
||||
"tests/corpus/2954-http-outgoing-headers.ts",
|
||||
"tests/corpus/2955-http-static-state.ts",
|
||||
"tests/corpus/2956-http-static-informational.ts",
|
||||
"tests/fixtures/server/cases/http-trailers/main.ts"
|
||||
]
|
||||
},
|
||||
@@ -62828,8 +62832,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.request.getHeader",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -62858,8 +62865,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.request.getHeaderNames",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -62885,8 +62895,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.request.getHeaders",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -62912,8 +62925,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.request.getRawHeaderNames",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -62939,8 +62955,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.request.hasHeader",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -62966,8 +62985,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.request.removeHeader",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -62996,8 +63018,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.request.setHeader",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -63291,8 +63316,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.path",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -63318,8 +63346,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.method",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -63345,8 +63376,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.host",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -63372,8 +63406,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.protocol",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -63453,8 +63490,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.writableEnded",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -64006,8 +64046,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.listening",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -64648,8 +64691,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.response.getHeaderNames",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -64675,8 +64721,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.response.getHeaders",
|
||||
"tests": [
|
||||
"tests/corpus/2954-http-outgoing-headers.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -64890,8 +64939,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.response.writeContinue",
|
||||
"tests": [
|
||||
"tests/corpus/2956-http-static-informational.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -64917,8 +64969,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.response.writeEarlyHints",
|
||||
"tests": [
|
||||
"tests/corpus/2956-http-static-informational.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -64976,8 +65031,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.response.writeProcessing",
|
||||
"tests": [
|
||||
"tests/corpus/2956-http-static-informational.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -65057,8 +65115,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.headersSent",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -65165,8 +65226,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.statusCode",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -65246,8 +65310,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.writableEnded",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -65468,8 +65535,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.complete",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -65582,8 +65652,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.httpVersion",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -65609,8 +65682,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.method",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -65723,8 +65799,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.statusCode",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -65843,8 +65922,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.url",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -66461,8 +66543,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.headersSent",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -66548,8 +66633,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.writableEnded",
|
||||
"tests": [
|
||||
"tests/corpus/2955-http-static-state.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -66963,8 +67051,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.http.validateHeaderName",
|
||||
"tests": [
|
||||
"tests/corpus/2953-http-header-validation.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -66990,8 +67081,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:http"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:http.http.validateHeaderValue",
|
||||
"tests": [
|
||||
"tests/corpus/2953-http-header-validation.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
|
||||
@@ -599,6 +599,23 @@ function publicDetail(tier) {
|
||||
if (source.startsWith("compiler-feature:stream.") || source.startsWith("compiler-feature:webstreams.")) {
|
||||
return "Implemented for the documented static async-iteration subset.";
|
||||
}
|
||||
if (source.startsWith("compiler-feature:http.http.validateHeader")) {
|
||||
return "Static header validation is implemented for the tested call shapes.";
|
||||
}
|
||||
if (source.startsWith("compiler-feature:http.request.getHeader") ||
|
||||
source.startsWith("compiler-feature:http.request.getRawHeaderNames") ||
|
||||
source.startsWith("compiler-feature:http.request.hasHeader") ||
|
||||
source.startsWith("compiler-feature:http.request.removeHeader") ||
|
||||
source.startsWith("compiler-feature:http.request.setHeader") ||
|
||||
source.startsWith("compiler-feature:http.response.getHeader")) {
|
||||
return "Static outgoing header reads and mutations support the tested string-value shapes.";
|
||||
}
|
||||
if (/^compiler-feature:http\.(?:path|method|host|protocol|listening|headersSent|writableEnded|complete|httpVersion|url|statusCode)$/.test(source)) {
|
||||
return "Static HTTP request metadata and message state support the tested lifecycle.";
|
||||
}
|
||||
if (/^compiler-feature:http\.response\.write(?:Continue|Processing|EarlyHints)$/.test(source)) {
|
||||
return "Static HTTP/1.1 informational responses support the tested no-callback forms; Early Hints accepts string-valued fields.";
|
||||
}
|
||||
return "Implemented for the documented scriptc module-loader subset.";
|
||||
}
|
||||
if (source.startsWith("compiler-chapter-policy:")) {
|
||||
|
||||
@@ -79,6 +79,10 @@
|
||||
"stdlib.abort": ["tests/harness/fetch-conformance.test.ts"]
|
||||
},
|
||||
"features": [
|
||||
{ "chapter": "http", "signatures": ["`http.validateHeaderName(name[, label])`", "`http.validateHeaderValue(name, value)`"], "status": "partial", "evidence": ["tests/corpus/2953-http-header-validation.ts"] },
|
||||
{ "chapter": "http", "signatures": ["`request.getHeader(name)`", "`request.getHeaderNames()`", "`request.getHeaders()`", "`request.getRawHeaderNames()`", "`request.hasHeader(name)`", "`request.removeHeader(name)`", "`request.setHeader(name, value)`", "`response.getHeaderNames()`", "`response.getHeaders()`"], "status": "partial", "evidence": ["tests/corpus/2954-http-outgoing-headers.ts"] },
|
||||
{ "chapter": "http", "signatures": ["`path` Type: {string} The request path.", "`method` Type: {string} The request method.", "`host` Type: {string} The request host.", "`protocol` Type: {string} The request protocol.", "`listening` Type: {boolean} Indicates whether or not the server is listening for connections.", "`headersSent` Type: {boolean}", "`writableEnded` Type: {boolean}", "`complete` Type: {boolean}", "`httpVersion` Type: {string}", "`method` Type: {string}", "`url` Type: {string}", "`statusCode` Type: {number}", "`statusCode` Type: {number} **Default:** `200`"], "status": "partial", "evidence": ["tests/corpus/2955-http-static-state.ts"] },
|
||||
{ "chapter": "http", "signatures": ["`response.writeContinue()`", "`response.writeProcessing()`", "`response.writeEarlyHints(hints[, callback])`"], "status": "partial", "evidence": ["tests/corpus/2956-http-static-informational.ts"] },
|
||||
{ "chapter": "http", "signatures": ["`request.cork()`", "`request.flushHeaders()`", "`request.uncork()`", "`response.addTrailers(headers)`", "`response.cork()`", "`response.flushHeaders()`", "`response.uncork()`", "`headersDistinct` Type: {Object}", "`rawTrailers` Type: {string\\[]}", "`trailers` Type: {Object}", "`trailersDistinct` Type: {Object}", "`outgoingMessage.addTrailers(headers)`", "`outgoingMessage.cork()`", "`outgoingMessage.flushHeaders()`", "`outgoingMessage.uncork()`", "`writableCorked` Type: {number}"], "status": "partial", "evidence": ["tests/corpus/2700-http-static-trailers.ts", "tests/corpus/2701-http-static-trailer-wire.ts", "tests/corpus/2702-http-static-trailer-end.ts", "tests/fixtures/server/cases/http-trailers/main.ts"] },
|
||||
{ "chapter": "buffer", "signatures": ["`buf.readBigInt64BE([offset])`", "`buf.readBigInt64LE([offset])`", "`buf.readBigUInt64BE([offset])`", "`buf.readBigUInt64LE([offset])`", "`buf.writeBigInt64BE(value[, offset])`", "`buf.writeBigInt64LE(value[, offset])`", "`buf.writeBigUInt64BE(value[, offset])`", "`buf.writeBigUInt64LE(value[, offset])`"], "status": "supported", "evidence": ["tests/corpus/2902-bigint-bytes.ts", "tests/corpus/2904-bigint-errors.ts"] },
|
||||
{ "chapter": "environment_variables", "symbols": ["export"], "status": "not-applicable" },
|
||||
|
||||
+32
-1
@@ -2228,6 +2228,7 @@ declare module "net" {
|
||||
once(event: "session", listener: (session: Buffer) => void): void;
|
||||
}
|
||||
export interface Server {
|
||||
readonly listening: boolean;
|
||||
/* Node answers the server itself (`return this` chaining). */
|
||||
listen(port: number, callback?: () => void): Server;
|
||||
/* The positional bind address (the options form's host in argument
|
||||
@@ -2356,6 +2357,8 @@ declare module "http" {
|
||||
export interface IncomingMessage {
|
||||
readonly url: string;
|
||||
readonly method: string;
|
||||
readonly httpVersion: string;
|
||||
readonly complete: boolean;
|
||||
readonly statusCode: number | undefined;
|
||||
readonly statusMessage: string | undefined;
|
||||
readonly socket: Socket;
|
||||
@@ -2372,7 +2375,7 @@ declare module "http" {
|
||||
setEncoding(encoding: string): void;
|
||||
/* The proxy legs: the body streams into a ServerResponse, a
|
||||
* ClientRequest, or a raw Socket; natural end ends the destination. */
|
||||
pipe(destination: ServerResponse | ClientRequest | Socket): void;
|
||||
pipe(destination: ServerResponse | import("http2").Http2ServerResponse | ClientRequest | Socket): void;
|
||||
on(event: "data", listener: (chunk: any) => void): void;
|
||||
on(event: "end" | "close", listener: () => void): void;
|
||||
on(event: "error", listener: (err: Error) => void): void;
|
||||
@@ -2386,18 +2389,25 @@ declare module "http" {
|
||||
}
|
||||
export interface ServerResponse {
|
||||
readonly headersSent: boolean;
|
||||
readonly writableEnded: boolean;
|
||||
readonly writableCorked: number;
|
||||
/* Node's writable head properties: the implicit head reads them. */
|
||||
statusCode: number;
|
||||
statusMessage: string;
|
||||
setHeader(name: string, value: string | number): void;
|
||||
getHeader(name: string): string | undefined;
|
||||
getHeaderNames(): string[];
|
||||
getRawHeaderNames(): string[];
|
||||
getHeaders(): OutgoingHttpHeaders;
|
||||
hasHeader(name: string): boolean;
|
||||
removeHeader(name: string): void;
|
||||
/* Both overloads answer the response (`return this` chaining); the
|
||||
* headers argument also takes Node's flat [name, value, ...] array. */
|
||||
writeHead(statusCode: number, headers?: OutgoingHttpHeaders | string[]): ServerResponse;
|
||||
writeHead(statusCode: number, statusMessage: string, headers?: OutgoingHttpHeaders | string[]): ServerResponse;
|
||||
writeContinue(callback?: () => void): void;
|
||||
writeProcessing(callback?: () => void): void;
|
||||
writeEarlyHints(hints: Record<string, string | string[]>, callback?: () => void): void;
|
||||
write(data: string | Uint8Array): void;
|
||||
flushHeaders(): void;
|
||||
cork(): void;
|
||||
@@ -2479,6 +2489,19 @@ declare module "http" {
|
||||
export interface ClientRequest {
|
||||
readonly destroyed: boolean;
|
||||
readonly writableCorked: number;
|
||||
readonly method: string;
|
||||
readonly path: string;
|
||||
readonly host: string;
|
||||
readonly protocol: string;
|
||||
readonly headersSent: boolean;
|
||||
readonly writableEnded: boolean;
|
||||
setHeader(name: string, value: string): void;
|
||||
getHeader(name: string): string | undefined;
|
||||
hasHeader(name: string): boolean;
|
||||
removeHeader(name: string): void;
|
||||
getHeaderNames(): string[];
|
||||
getRawHeaderNames(): string[];
|
||||
getHeaders(): OutgoingHttpHeaders;
|
||||
flushHeaders(): void;
|
||||
addTrailers(headers: OutgoingHttpHeaders | ReadonlyArray<[string, string]>): void;
|
||||
cork(): void;
|
||||
@@ -2528,6 +2551,8 @@ declare module "http" {
|
||||
target: RequestOptions | string | URL,
|
||||
callback?: (res: IncomingMessage) => void,
|
||||
): ClientRequest;
|
||||
export function validateHeaderName(name: string, label?: string): void;
|
||||
export function validateHeaderValue(name: string, value: unknown): void;
|
||||
}
|
||||
declare module "node:http" {
|
||||
export * from "http";
|
||||
@@ -2700,6 +2725,7 @@ declare module "http2" {
|
||||
}
|
||||
export interface Http2ServerResponse {
|
||||
readonly headersSent: boolean;
|
||||
readonly writableEnded: boolean;
|
||||
readonly writableCorked: number;
|
||||
/* The same lowered surface as http.ServerResponse — the allowHTTP1
|
||||
* lowering serves every connection as HTTP/1.1, where the compat
|
||||
@@ -2708,10 +2734,15 @@ declare module "http2" {
|
||||
statusMessage: string;
|
||||
setHeader(name: string, value: string | number): void;
|
||||
getHeader(name: string): string | undefined;
|
||||
getHeaderNames(): string[];
|
||||
getRawHeaderNames(): string[];
|
||||
getHeaders(): import("http").OutgoingHttpHeaders;
|
||||
hasHeader(name: string): boolean;
|
||||
removeHeader(name: string): void;
|
||||
writeHead(statusCode: number, headers?: import("http").OutgoingHttpHeaders | string[]): Http2ServerResponse;
|
||||
writeHead(statusCode: number, statusMessage: string, headers?: import("http").OutgoingHttpHeaders | string[]): Http2ServerResponse;
|
||||
writeContinue(): void;
|
||||
writeEarlyHints(hints: Record<string, string | string[]>): void;
|
||||
write(data: string | Uint8Array): void;
|
||||
flushHeaders(): void;
|
||||
cork(): void;
|
||||
|
||||
@@ -5928,6 +5928,8 @@ function emitNetworkLibCall(state: LibCallState): Temp {
|
||||
}
|
||||
case "net.serverPort":
|
||||
return finish(`scr_net_server_port(${arg(0)})`);
|
||||
case "net.serverListening":
|
||||
return finish(`scr_net_server_listening(${arg(0)})`);
|
||||
case "net.serverAddress": {
|
||||
// The AddressInfo record from the three runtime reads (the
|
||||
// dgram.address materialization; none of these throw).
|
||||
@@ -6458,10 +6460,22 @@ function emitHttpLibCall(state: LibCallState): Temp {
|
||||
case "http.resSetHeader":
|
||||
emitter.line(`scr_http_res_set_header(${arg(0)}, ${arg(1)}, ${arg(2)});${emitter.srcComment(e.loc)}`);
|
||||
return { name: "", type: e.type };
|
||||
case "http.validateHeaderName":
|
||||
emitter.line(`scr_http_validate_header_name(${arg(0)}, ${arg(1)});${emitter.srcComment(e.loc)}`);
|
||||
return { name: "", type: e.type };
|
||||
case "http.validateHeaderValue":
|
||||
emitter.line(`scr_http_validate_header_value(${arg(0)}, ${arg(1)});${emitter.srcComment(e.loc)}`);
|
||||
return { name: "", type: e.type };
|
||||
case "http.resWriteHead":
|
||||
return finish(`scr_http_res_write_head(${arg(0)}, ${arg(1)})`);
|
||||
case "http.resWriteHeadN":
|
||||
return finish(`scr_http_res_write_head_n(${arg(0)}, ${arg(1)}, ${arg(2)}, ${arg(3)})`);
|
||||
case "http.resWriteContinue":
|
||||
return finish(`scr_http_res_write_continue(${arg(0)})`);
|
||||
case "http.resWriteProcessing":
|
||||
return finish(`scr_http_res_write_processing(${arg(0)})`);
|
||||
case "http.resWriteEarlyHints":
|
||||
return finish(`scr_http_res_write_early_hints(${arg(0)}, ${arg(1)})`);
|
||||
case "http.resWrite":
|
||||
return finish(`scr_http_res_write_str(${arg(0)}, ${arg(1)})`);
|
||||
case "http.resWriteBytes":
|
||||
@@ -6478,6 +6492,8 @@ function emitHttpLibCall(state: LibCallState): Temp {
|
||||
return finish(`scr_http_res_end_dynv(${arg(0)}, ${arg(1)})`);
|
||||
case "http.resHeadersSent":
|
||||
return finish(`scr_http_res_headers_sent(${arg(0)})`);
|
||||
case "http.resWritableEnded":
|
||||
return finish(`scr_http_res_writable_ended(${arg(0)})`);
|
||||
case "http.resFlushHeaders":
|
||||
return finish(`scr_http_res_flush_headers(${arg(0)})`);
|
||||
case "http.resAddTrailers":
|
||||
@@ -6657,21 +6673,29 @@ function emitHttpLibCall(state: LibCallState): Temp {
|
||||
case "http.resStatusMsgSet":
|
||||
emitter.line(`scr_http_res_status_msg_set(${arg(0)}, ${arg(1)});${emitter.srcComment(e.loc)}`);
|
||||
return { name: "", type: e.type };
|
||||
case "http.resGetHeader": {
|
||||
case "http.resGetHeader":
|
||||
case "http.clientGetHeader": {
|
||||
// string|undefined, exactly the http.reqHeader emission.
|
||||
if (e.type.kind !== "union") throw new InternalCompilerError("emitter bug: http.resGetHeader result is not a union");
|
||||
if (e.type.kind !== "union") throw new InternalCompilerError(`emitter bug: ${e.fn} result is not a union`);
|
||||
const def = emitter.unionsById.get(e.type.unionId);
|
||||
const strTag = def ? def.arms.findIndex((a) => a.kind === "string") : -1;
|
||||
const undefTag = undefinedArmTag(e.type, emitter.unionsById);
|
||||
if (strTag < 0 || undefTag < 0) {
|
||||
throw new InternalCompilerError("emitter bug: http.resGetHeader union lacks its arms");
|
||||
}
|
||||
const s = emitter.newTemp(STRING, `scr_http_res_get_header(${arg(0)}, ${arg(1)})`);
|
||||
const entry = e.fn === "http.resGetHeader" ? "scr_http_res_get_header" : "scr_http_client_get_header";
|
||||
const s = emitter.newTemp(STRING, `${entry}(${arg(0)}, ${arg(1)})`);
|
||||
emitter.moveTemp(s); // moves into the box when present; NULL otherwise
|
||||
const present = `scr_union_new_ref(${strTag}, ${s.name}, &scr_str_retain_v, &scr_str_release_v, NULL)`;
|
||||
const absent = emitter.unitInstanceRef(e.type.unionId, undefTag);
|
||||
return emitter.newTemp(e.type, `${s.name} ? ${present} : ${absent}`);
|
||||
}
|
||||
case "http.resGetHeaderNames":
|
||||
return finish(`scr_http_res_get_header_names(${arg(0)})`);
|
||||
case "http.resGetRawHeaderNames":
|
||||
return finish(`scr_http_res_get_raw_header_names(${arg(0)})`);
|
||||
case "http.resGetHeaders":
|
||||
return finish(`scr_http_res_get_headers(${arg(0)})`);
|
||||
case "http.resHasHeader":
|
||||
return finish(`scr_http_res_has_header_named(${arg(0)}, ${arg(1)})`);
|
||||
case "http.resRemoveHeader":
|
||||
@@ -6869,6 +6893,18 @@ function emitHttpLibCall(state: LibCallState): Temp {
|
||||
}
|
||||
case "http.clientWrite":
|
||||
return finish(`scr_http_client_write_str(${arg(0)}, ${arg(1)})`);
|
||||
case "http.clientSetHeader":
|
||||
return finish(`scr_http_client_set_header(${arg(0)}, ${arg(1)}, ${arg(2)})`);
|
||||
case "http.clientHasHeader":
|
||||
return finish(`scr_http_client_has_header_named(${arg(0)}, ${arg(1)})`);
|
||||
case "http.clientRemoveHeader":
|
||||
return finish(`scr_http_client_remove_header(${arg(0)}, ${arg(1)})`);
|
||||
case "http.clientGetHeaderNames":
|
||||
return finish(`scr_http_client_get_header_names(${arg(0)})`);
|
||||
case "http.clientGetRawHeaderNames":
|
||||
return finish(`scr_http_client_get_raw_header_names(${arg(0)})`);
|
||||
case "http.clientGetHeaders":
|
||||
return finish(`scr_http_client_get_headers(${arg(0)})`);
|
||||
case "http.clientWriteBytes":
|
||||
return finish(`scr_http_client_write_bytes(${arg(0)}, ${arg(1)})`);
|
||||
case "http.clientEnd":
|
||||
@@ -6892,6 +6928,18 @@ function emitHttpLibCall(state: LibCallState): Temp {
|
||||
return finish(`scr_http_client_uncork(${arg(0)})`);
|
||||
case "http.clientWritableCorked":
|
||||
return finish(`scr_http_client_writable_corked(${arg(0)})`);
|
||||
case "http.clientMethod":
|
||||
return finish(`scr_http_client_method(${arg(0)})`);
|
||||
case "http.clientPath":
|
||||
return finish(`scr_http_client_path(${arg(0)})`);
|
||||
case "http.clientHost":
|
||||
return finish(`scr_http_client_host(${arg(0)})`);
|
||||
case "http.clientProtocol":
|
||||
return finish(`scr_http_client_protocol(${arg(0)})`);
|
||||
case "http.clientHeadersSent":
|
||||
return finish(`scr_http_client_headers_sent(${arg(0)})`);
|
||||
case "http.clientWritableEnded":
|
||||
return finish(`scr_http_client_writable_ended(${arg(0)})`);
|
||||
case "http.clientDestroy":
|
||||
emitter.line(`scr_http_client_destroy(${arg(0)});${emitter.srcComment(e.loc)}`);
|
||||
return { name: "", type: e.type };
|
||||
|
||||
@@ -242,7 +242,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
B.line(`${raw} = call ptr @scr_net_sock_read_bytes(ptr ${args[0]!.name}, double ${args[1]!.name}) ; +1 or NULL`);
|
||||
return host.wrapNullable(raw, raw, def!.arms[bytesTag]!, bytesTag, e.type, nullTag);
|
||||
}
|
||||
if (e.fn === "net.sockRemoteAddress" || e.fn === "http.reqHeader" || e.fn === "http.reqTrailer" || e.fn === "http.resGetHeader" || e.fn === "http.reqStatusMessage") {
|
||||
if (e.fn === "net.sockRemoteAddress" || e.fn === "http.reqHeader" || e.fn === "http.reqTrailer" || e.fn === "http.resGetHeader" || e.fn === "http.clientGetHeader" || e.fn === "http.reqStatusMessage") {
|
||||
// string | undefined: +1 or NULL, NULL takes the undefined arm.
|
||||
if (e.type.kind !== "union") throw new InternalCompilerError(`llvm emitter bug: ${e.fn} result is not a union`);
|
||||
const def = host.unionsById.get(e.type.unionId);
|
||||
@@ -254,6 +254,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
"http.reqHeader": "scr_http_req_header",
|
||||
"http.reqTrailer": "scr_http_req_trailer",
|
||||
"http.resGetHeader": "scr_http_res_get_header",
|
||||
"http.clientGetHeader": "scr_http_client_get_header",
|
||||
"http.reqStatusMessage": "scr_http_req_status_message",
|
||||
}[e.fn]!;
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
|
||||
@@ -579,6 +579,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
// are may-throw (generic pending check). Listener registrations and
|
||||
// the client request forms have non-generic shapes — special-cased.
|
||||
"net.serverPort": "scr_net_server_port",
|
||||
"net.serverListening": "scr_net_server_listening",
|
||||
"net.sockWrite": "scr_net_sock_write_str",
|
||||
"net.sockWriteBytes": "scr_net_sock_write_bytes",
|
||||
"net.sockEnd": "scr_net_sock_end",
|
||||
@@ -604,7 +605,14 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"net.getAutoSelTimeout": "scr_net_get_autosel_timeout",
|
||||
"net.setAutoSelTimeout": "scr_net_set_autosel_timeout",
|
||||
"http.reqUrl": "scr_http_req_url",
|
||||
"http.validateHeaderName": "scr_http_validate_header_name",
|
||||
"http.validateHeaderValue": "scr_http_validate_header_value",
|
||||
"http.reqMethod": "scr_http_req_method",
|
||||
"http.reqHttpVersion": "scr_http_req_http_version",
|
||||
"http.reqHttpVersionMajor": "scr_http_req_http_version_major",
|
||||
"http.reqHttpVersionMinor": "scr_http_req_http_version_minor",
|
||||
"http.reqAborted": "scr_http_req_aborted_flag",
|
||||
"http.reqComplete": "scr_http_req_complete",
|
||||
"http.reqSocket": "scr_http_req_socket",
|
||||
"http.reqRawHeaders": "scr_http_req_raw_headers",
|
||||
"http.reqRawTrailers": "scr_http_req_raw_trailers",
|
||||
@@ -617,8 +625,14 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"http.reqDestroy": "scr_http_req_destroy",
|
||||
"http.reqSetEncoding": "scr_http_req_set_encoding",
|
||||
"http.resSetHeader": "scr_http_res_set_header",
|
||||
"http.resGetHeaderNames": "scr_http_res_get_header_names",
|
||||
"http.resGetRawHeaderNames": "scr_http_res_get_raw_header_names",
|
||||
"http.resGetHeaders": "scr_http_res_get_headers",
|
||||
"http.resWriteHead": "scr_http_res_write_head",
|
||||
"http.resWriteHeadN": "scr_http_res_write_head_n",
|
||||
"http.resWriteContinue": "scr_http_res_write_continue",
|
||||
"http.resWriteProcessing": "scr_http_res_write_processing",
|
||||
"http.resWriteEarlyHints": "scr_http_res_write_early_hints",
|
||||
"http.resWrite": "scr_http_res_write_str",
|
||||
"http.resWriteBytes": "scr_http_res_write_bytes",
|
||||
"http.resEnd": "scr_http_res_end",
|
||||
@@ -627,6 +641,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"http.resWriteDyn": "scr_http_res_write_dynv",
|
||||
"http.resEndDyn": "scr_http_res_end_dynv",
|
||||
"http.resHeadersSent": "scr_http_res_headers_sent",
|
||||
"http.resWritableEnded": "scr_http_res_writable_ended",
|
||||
"http.resFlushHeaders": "scr_http_res_flush_headers",
|
||||
"http.resAddTrailers": "scr_http_res_add_trailers",
|
||||
"http.resCork": "scr_http_res_cork",
|
||||
@@ -647,6 +662,12 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"http.serverTimeoutSet": "scr_net_server_timeout_set",
|
||||
"http.serverTimeoutOptionSet": "scr_net_server_timeout_option_set",
|
||||
"http.clientWrite": "scr_http_client_write_str",
|
||||
"http.clientSetHeader": "scr_http_client_set_header",
|
||||
"http.clientHasHeader": "scr_http_client_has_header_named",
|
||||
"http.clientRemoveHeader": "scr_http_client_remove_header",
|
||||
"http.clientGetHeaderNames": "scr_http_client_get_header_names",
|
||||
"http.clientGetRawHeaderNames": "scr_http_client_get_raw_header_names",
|
||||
"http.clientGetHeaders": "scr_http_client_get_headers",
|
||||
"http.clientWriteBytes": "scr_http_client_write_bytes",
|
||||
"http.clientEnd": "scr_http_client_end",
|
||||
"http.clientEndStr": "scr_http_client_end_str",
|
||||
@@ -658,6 +679,12 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"http.clientCork": "scr_http_client_cork",
|
||||
"http.clientUncork": "scr_http_client_uncork",
|
||||
"http.clientWritableCorked": "scr_http_client_writable_corked",
|
||||
"http.clientMethod": "scr_http_client_method",
|
||||
"http.clientPath": "scr_http_client_path",
|
||||
"http.clientHost": "scr_http_client_host",
|
||||
"http.clientProtocol": "scr_http_client_protocol",
|
||||
"http.clientHeadersSent": "scr_http_client_headers_sent",
|
||||
"http.clientWritableEnded": "scr_http_client_writable_ended",
|
||||
"http.clientDestroy": "scr_http_client_destroy",
|
||||
"http.clientDestroyed": "scr_http_client_destroyed",
|
||||
"http2.streamUndefCall": "scr_http2_stream_undef_call",
|
||||
|
||||
@@ -2057,6 +2057,10 @@ export function lowerServerProperty(lowerer: Lowerer, expr: ts.PropertyAccessExp
|
||||
// req.headers.NAME — the `string | undefined` union (envGet's type).
|
||||
const recvKind = lowerer.mapTypeOf(lowerer.typeOf(expr.expression))?.kind;
|
||||
if (recvKind === "netServer" && lowerer.isStdlibMember(expr)) {
|
||||
if (expr.name.text === "listening") {
|
||||
const receiver = coerceToHandle(lowerer, expr.expression, NETSERVER_T);
|
||||
return { kind: "libCall", fn: "net.serverListening", args: [receiver], type: BOOL, loc };
|
||||
}
|
||||
const field = httpServerTimeoutField(expr.name.text);
|
||||
if (field !== null) {
|
||||
const receiver = coerceToHandle(lowerer, expr.expression, NETSERVER_T);
|
||||
@@ -2245,6 +2249,23 @@ export function lowerServerProperty(lowerer: Lowerer, expr: ts.PropertyAccessExp
|
||||
const receiver = coerceToHandle(lowerer, expr.expression, HTTPCLIENTREQ_T);
|
||||
return { kind: "libCall", fn: "http.clientWritableCorked", args: [receiver], type: F64, loc };
|
||||
}
|
||||
if (recvKind === "httpClientReq" && lowerer.isStdlibMember(expr)) {
|
||||
const name = expr.name.text;
|
||||
const strFn: IrLibFn | null = name === "method" ? "http.clientMethod"
|
||||
: name === "path" ? "http.clientPath"
|
||||
: name === "host" ? "http.clientHost"
|
||||
: name === "protocol" ? "http.clientProtocol" : null;
|
||||
if (strFn !== null) {
|
||||
const receiver = coerceToHandle(lowerer, expr.expression, HTTPCLIENTREQ_T);
|
||||
return { kind: "libCall", fn: strFn, args: [receiver], type: STRING, loc };
|
||||
}
|
||||
const boolFn: IrLibFn | null = name === "headersSent" ? "http.clientHeadersSent"
|
||||
: name === "writableEnded" ? "http.clientWritableEnded" : null;
|
||||
if (boolFn !== null) {
|
||||
const receiver = coerceToHandle(lowerer, expr.expression, HTTPCLIENTREQ_T);
|
||||
return { kind: "libCall", fn: boolFn, args: [receiver], type: BOOL, loc };
|
||||
}
|
||||
}
|
||||
if (httpReqFieldCollection(lowerer, expr.expression) !== null) {
|
||||
const collection = httpReqFieldCollection(lowerer, expr.expression)!;
|
||||
const receiver = coerceToHandle(lowerer, (expr.expression as ts.PropertyAccessExpression).expression, HTTPREQ_T);
|
||||
@@ -2259,6 +2280,10 @@ export function lowerServerProperty(lowerer: Lowerer, expr: ts.PropertyAccessExp
|
||||
const receiver = coerceToHandle(lowerer, expr.expression, HTTPRES_T);
|
||||
return { kind: "libCall", fn: "http.resHeadersSent", args: [receiver], type: BOOL, loc };
|
||||
}
|
||||
if (recvKind === "httpRes" && lowerer.isStdlibMember(expr) && expr.name.text === "writableEnded") {
|
||||
const receiver = coerceToHandle(lowerer, expr.expression, HTTPRES_T);
|
||||
return { kind: "libCall", fn: "http.resWritableEnded", args: [receiver], type: BOOL, loc };
|
||||
}
|
||||
if (recvKind === "httpRes" && lowerer.isStdlibMember(expr) && expr.name.text === "writableCorked") {
|
||||
const receiver = coerceToHandle(lowerer, expr.expression, HTTPRES_T);
|
||||
return { kind: "libCall", fn: "http.resWritableCorked", args: [receiver], type: F64, loc };
|
||||
@@ -2706,6 +2731,26 @@ export function lowerHttpServerNew(lowerer: Lowerer, expr: ts.NewExpression): Ir
|
||||
function lowerHttpModuleCall(lowerer: Lowerer, expr: ts.CallExpression,
|
||||
bi: { module: string; member: string },
|
||||
loc: SrcLoc,): IrExpr {
|
||||
if (bi.member === "validateHeaderName") {
|
||||
requireStatementPosition(lowerer, expr, "http.validateHeaderName(...)");
|
||||
if (expr.arguments.length < 1 || expr.arguments.length > 2 || expr.arguments.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering("http.validateHeaderName arguments", expr, "pass a header name and an optional label");
|
||||
}
|
||||
const name = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
|
||||
const label = expr.arguments.length === 2
|
||||
? lowerer.lowerExprExpecting(expr.arguments[1]!, STRING)
|
||||
: strLit("Header name", loc);
|
||||
return { kind: "libCall", fn: "http.validateHeaderName", args: [name, label], type: VOID, loc };
|
||||
}
|
||||
if (bi.member === "validateHeaderValue") {
|
||||
requireStatementPosition(lowerer, expr, "http.validateHeaderValue(...)");
|
||||
if (expr.arguments.length !== 2 || expr.arguments.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering("http.validateHeaderValue arguments", expr, "pass a header name and a value");
|
||||
}
|
||||
const name = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
|
||||
const value = lowerer.lowerExprExpecting(expr.arguments[1]!, DYN);
|
||||
return { kind: "libCall", fn: "http.validateHeaderValue", args: [name, value], type: VOID, loc };
|
||||
}
|
||||
if (bi.member === "createServer" || bi.member === "Server") {
|
||||
return lowerHttpCreateServerForms(lowerer, expr, `http.${bi.member}`, loc);
|
||||
}
|
||||
@@ -2715,7 +2760,7 @@ function lowerHttpModuleCall(lowerer: Lowerer, expr: ts.CallExpression,
|
||||
lowerer.noLowering(
|
||||
`http.${bi.member}`,
|
||||
expr,
|
||||
"createServer, Server, request, and get are the lowered http module functions",
|
||||
"createServer, Server, request, get, validateHeaderName, and validateHeaderValue are the lowered http module functions",
|
||||
lowerer.resolveValueSymbol(expr.expression as ts.Identifier),
|
||||
);
|
||||
}
|
||||
@@ -4235,6 +4280,31 @@ function lowerHttpClientMethodCall(lowerer: Lowerer, call: ts.CallExpression,
|
||||
const name = access.name.text;
|
||||
const loc = locOf(call);
|
||||
const args = call.arguments;
|
||||
if (name === "setHeader") {
|
||||
requireStatementPosition(lowerer, call, "request.setHeader(...)");
|
||||
if (args.length !== 2) lowerer.noLowering("request.setHeader arguments", call, "pass a name and string value");
|
||||
const receiver = coerceToHandle(lowerer, access.expression, HTTPCLIENTREQ_T);
|
||||
const header = lowerer.lowerExprExpecting(args[0]!, STRING);
|
||||
const value = lowerer.lowerExprExpecting(args[1]!, STRING);
|
||||
return { kind: "libCall", fn: "http.clientSetHeader", args: [receiver, header, value], type: VOID, loc };
|
||||
}
|
||||
if (name === "getHeader" || name === "hasHeader" || name === "removeHeader") {
|
||||
if (name === "removeHeader") requireStatementPosition(lowerer, call, "request.removeHeader(...)");
|
||||
if (args.length !== 1) lowerer.noLowering(`request.${name} arguments`, call, `pass one header name`);
|
||||
const receiver = coerceToHandle(lowerer, access.expression, HTTPCLIENTREQ_T);
|
||||
const header = lowerer.lowerExprExpecting(args[0]!, STRING);
|
||||
const fn: IrLibFn = name === "getHeader" ? "http.clientGetHeader"
|
||||
: name === "hasHeader" ? "http.clientHasHeader" : "http.clientRemoveHeader";
|
||||
const type = name === "getHeader" ? lowerer.envValueType() : name === "hasHeader" ? BOOL : VOID;
|
||||
return { kind: "libCall", fn, args: [receiver, header], type, loc };
|
||||
}
|
||||
if (name === "getHeaderNames" || name === "getRawHeaderNames" || name === "getHeaders") {
|
||||
if (args.length !== 0) lowerer.noLowering(`request.${name} arguments`, call, `${name}() takes no arguments`);
|
||||
const receiver = coerceToHandle(lowerer, access.expression, HTTPCLIENTREQ_T);
|
||||
const fn: IrLibFn = name === "getHeaderNames" ? "http.clientGetHeaderNames"
|
||||
: name === "getRawHeaderNames" ? "http.clientGetRawHeaderNames" : "http.clientGetHeaders";
|
||||
return { kind: "libCall", fn, args: [receiver], type: name === "getHeaders" ? DYN : arrayOf(STRING), loc };
|
||||
}
|
||||
if (name === "flushHeaders" || name === "cork" || name === "uncork") {
|
||||
requireStatementPosition(lowerer, call, `request.${name}()`);
|
||||
if (args.length !== 0) lowerer.noLowering(`request.${name} with arguments`, call, `${name}() takes no arguments`);
|
||||
@@ -4359,7 +4429,7 @@ function lowerHttpClientMethodCall(lowerer: Lowerer, call: ts.CallExpression,
|
||||
lowerer.noLowering(
|
||||
`ClientRequest.${name}`,
|
||||
call,
|
||||
"write, end, destroy, destroyed, and on/once of response/error/timeout/close are the supported ClientRequest members",
|
||||
"header reads/mutations, write, end, destroy, and on/once of response/error/timeout/close are the supported ClientRequest members",
|
||||
lowerer.checker.getSymbolAtLocation(access.name),
|
||||
);
|
||||
}
|
||||
@@ -4564,6 +4634,53 @@ function lowerHttpResMethodCall(lowerer: Lowerer, call: ts.CallExpression,
|
||||
const name = access.name.text;
|
||||
const loc = locOf(call);
|
||||
const args = call.arguments;
|
||||
if (name === "writeContinue" || name === "writeProcessing" || name === "writeEarlyHints") {
|
||||
requireStatementPosition(lowerer, call, `res.${name}(...)`);
|
||||
if (lowerer.typeOf(access.expression).getSymbol()?.name === "Http2ServerResponse") {
|
||||
lowerer.noLowering(`Http2ServerResponse.${name}`, call, "HTTP/2 informational responses are not supported by the static runtime yet");
|
||||
}
|
||||
const receiver = coerceToHandle(lowerer, access.expression, HTTPRES_T);
|
||||
if (name !== "writeEarlyHints") {
|
||||
if (args.length !== 0) lowerer.noLowering(`res.${name} callback`, call, `${name}() supports no callback here`);
|
||||
return { kind: "libCall", fn: name === "writeContinue" ? "http.resWriteContinue" : "http.resWriteProcessing", args: [receiver], type: VOID, loc };
|
||||
}
|
||||
if (args.length !== 1) lowerer.noLowering("res.writeEarlyHints arguments", call, "pass one header object or Record<string, string> without a callback");
|
||||
const node = args[0]!;
|
||||
let pairs: IrExpr;
|
||||
if (ts.isObjectLiteralExpression(node)) {
|
||||
const elems: IrExpr[] = [];
|
||||
for (const prop of node.properties) {
|
||||
const key = ts.isPropertyAssignment(prop) ? staticHeaderKeyOf(lowerer, prop) : null;
|
||||
if (!ts.isPropertyAssignment(prop) || key === null) {
|
||||
lowerer.noLowering("writeEarlyHints headers with dynamic keys, spreads, or shorthand entries", prop, "use literal header keys and string values");
|
||||
}
|
||||
const value = lowerer.lowerExpr(prop.initializer);
|
||||
if (value.type.kind !== "string") {
|
||||
lowerer.noLowering(`writeEarlyHints value of type '${lowerer.fmt(value.type)}'`, prop.initializer, "header values must be strings here");
|
||||
}
|
||||
elems.push({ kind: "strLit", value: key, type: STRING, loc }, value);
|
||||
}
|
||||
pairs = { kind: "arrayLit", elems, type: arrayOf(STRING), loc };
|
||||
} else {
|
||||
const value = lowerer.lowerExpr(node);
|
||||
if (value.type.kind !== "record") {
|
||||
lowerer.noLowering(`writeEarlyHints headers of '${lowerer.fmt(value.type)}'`, node, "pass an object literal or a Record<string, string>");
|
||||
}
|
||||
const helper = lowerer.envToPairsHelper(value.type.shapeId, loc);
|
||||
if (helper === null) {
|
||||
lowerer.noLowering("writeEarlyHints header values", node, "use a Record<string, string> with string values");
|
||||
}
|
||||
pairs = { kind: "call", callee: helper, args: [value], type: arrayOf(STRING), loc };
|
||||
}
|
||||
return { kind: "libCall", fn: "http.resWriteEarlyHints", args: [receiver, pairs], type: VOID, loc };
|
||||
}
|
||||
if (name === "getHeaderNames" || name === "getRawHeaderNames" || name === "getHeaders") {
|
||||
if (args.length !== 0) lowerer.noLowering(`res.${name} arguments`, call, `${name}() takes no arguments`);
|
||||
const receiver = coerceToHandle(lowerer, access.expression, HTTPRES_T);
|
||||
const fn: IrLibFn = name === "getHeaderNames" ? "http.resGetHeaderNames"
|
||||
: name === "getRawHeaderNames" ? "http.resGetRawHeaderNames" : "http.resGetHeaders";
|
||||
return { kind: "libCall", fn, args: [receiver], type: name === "getHeaders" ? DYN : arrayOf(STRING), loc };
|
||||
}
|
||||
if (name === "flushHeaders" || name === "cork" || name === "uncork") {
|
||||
requireStatementPosition(lowerer, call, `res.${name}()`);
|
||||
if (args.length !== 0) lowerer.noLowering(`res.${name} with arguments`, call, `${name}() takes no arguments`);
|
||||
@@ -4857,7 +4974,7 @@ function lowerHttpResMethodCall(lowerer: Lowerer, call: ts.CallExpression,
|
||||
lowerer.noLowering(
|
||||
`ServerResponse.${name}`,
|
||||
call,
|
||||
"setHeader, getHeader, hasHeader, removeHeader, writeHead, write, end, destroy, headersSent, statusCode, statusMessage, and on/once of close are the supported ServerResponse members",
|
||||
"setHeader, getHeader, hasHeader, removeHeader, writeHead, writeContinue, writeProcessing, writeEarlyHints, write, end, destroy, headersSent, statusCode, statusMessage, and on/once of close are the supported ServerResponse members",
|
||||
lowerer.checker.getSymbolAtLocation(access.name),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2582,6 +2582,7 @@ export type IrLibFn =
|
||||
| "net.listenOptsReusePort"
|
||||
| "net.listenOptsReusePortCb"
|
||||
| "net.serverPort"
|
||||
| "net.serverListening"
|
||||
/** server.address() as the full AddressInfo record (the dgram.address
|
||||
* materialization pattern: the emitter builds the record from the three
|
||||
* runtime reads; the frontend pinned the shape). Never throws — before
|
||||
@@ -2740,6 +2741,8 @@ export type IrLibFn =
|
||||
* on("request") route; option helpers set parser behavior on a fresh
|
||||
* server before the constructor/factory result is returned. */
|
||||
| "http.createServerEmpty"
|
||||
| "http.validateHeaderName"
|
||||
| "http.validateHeaderValue"
|
||||
| "http.serverJoinDupHeaders"
|
||||
| "http.serverAllowMissingHostHeader"
|
||||
/** The five writable numeric http.Server timeout fields use one
|
||||
@@ -2765,6 +2768,9 @@ export type IrLibFn =
|
||||
| "http.resStatusMsgGet"
|
||||
| "http.resStatusMsgSet"
|
||||
| "http.resGetHeader"
|
||||
| "http.resGetHeaderNames"
|
||||
| "http.resGetRawHeaderNames"
|
||||
| "http.resGetHeaders"
|
||||
| "http.resHasHeader"
|
||||
| "http.resRemoveHeader"
|
||||
| "http.resOnFinish"
|
||||
@@ -2779,6 +2785,11 @@ export type IrLibFn =
|
||||
| "http.resSetHeader"
|
||||
| "http.resWriteHead"
|
||||
| "http.resWriteHeadN"
|
||||
/** HTTP/1.1 informational heads; early hints takes flat string pairs,
|
||||
* with a lowercase `link` key required to send anything. */
|
||||
| "http.resWriteContinue"
|
||||
| "http.resWriteProcessing"
|
||||
| "http.resWriteEarlyHints"
|
||||
| "http.resWrite"
|
||||
| "http.resWriteBytes"
|
||||
| "http.resEnd"
|
||||
@@ -2788,6 +2799,7 @@ export type IrLibFn =
|
||||
| "http.resWriteDyn"
|
||||
| "http.resEndDyn"
|
||||
| "http.resHeadersSent"
|
||||
| "http.resWritableEnded"
|
||||
| "http.resFlushHeaders"
|
||||
| "http.resAddTrailers"
|
||||
| "http.resCork"
|
||||
@@ -3166,6 +3178,13 @@ export type IrLibFn =
|
||||
* idiom reads it through a cast). */
|
||||
| "net.sockEncrypted"
|
||||
| "http.clientWrite"
|
||||
| "http.clientSetHeader"
|
||||
| "http.clientGetHeader"
|
||||
| "http.clientHasHeader"
|
||||
| "http.clientRemoveHeader"
|
||||
| "http.clientGetHeaderNames"
|
||||
| "http.clientGetRawHeaderNames"
|
||||
| "http.clientGetHeaders"
|
||||
| "http.clientWriteBytes"
|
||||
| "http.clientEnd"
|
||||
| "http.clientEndStr"
|
||||
@@ -3178,6 +3197,12 @@ export type IrLibFn =
|
||||
| "http.clientCork"
|
||||
| "http.clientUncork"
|
||||
| "http.clientWritableCorked"
|
||||
| "http.clientMethod"
|
||||
| "http.clientPath"
|
||||
| "http.clientHost"
|
||||
| "http.clientProtocol"
|
||||
| "http.clientHeadersSent"
|
||||
| "http.clientWritableEnded"
|
||||
/** request/get with a URL-STRING first argument: the runtime parses it
|
||||
* (WHATWG) and dials — throws catchably on an unparsable input or a
|
||||
* non-http scheme. */
|
||||
@@ -7358,6 +7383,11 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"dc.chanBindStore",
|
||||
"dc.chanRunStores",
|
||||
"http.resWriteHeadDyn",
|
||||
"http.resWriteContinue",
|
||||
"http.resWriteProcessing",
|
||||
"http.resWriteEarlyHints",
|
||||
"http.validateHeaderName",
|
||||
"http.validateHeaderValue",
|
||||
"http.serverTimeoutGet",
|
||||
"http.serverTimeoutOptionSet",
|
||||
"net.sockSetEncoding",
|
||||
|
||||
@@ -428,6 +428,7 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
|
||||
// the callback follows the reusePort BOOL in this additive ABI.
|
||||
"net.listenOptsReusePortCb": { argTypes: [NETSERVER_T, F64, STRING, BOOL, BOOL, null], result: VOID },
|
||||
"net.serverPort": { argTypes: [NETSERVER_T], result: F64 },
|
||||
"net.serverListening": { argTypes: [NETSERVER_T], result: BOOL },
|
||||
// net.serverAddress's record result is shape-checked in the libCall case
|
||||
// (the dgram.address sentinel pattern).
|
||||
"net.serverAddress": { argTypes: [NETSERVER_T], result: VOID },
|
||||
@@ -503,6 +504,8 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
|
||||
// union, checked like process.envGet.
|
||||
"http.createServer": { argTypes: [null], result: NETSERVER_T },
|
||||
"http.createServerEmpty": { argTypes: [], result: NETSERVER_T },
|
||||
"http.validateHeaderName": { argTypes: [STRING, STRING], result: VOID },
|
||||
"http.validateHeaderValue": { argTypes: [STRING, DYN], result: VOID },
|
||||
"http.serverJoinDupHeaders": { argTypes: [NETSERVER_T], result: VOID },
|
||||
"http.serverAllowMissingHostHeader": { argTypes: [NETSERVER_T], result: VOID },
|
||||
"http.serverTimeoutGet": { argTypes: [NETSERVER_T, F64], result: F64 },
|
||||
@@ -516,6 +519,9 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
|
||||
// resGetHeader answers the interned `string | undefined` union — the
|
||||
// reqHeader/envGet sentinel pattern (VOID here, checked specially).
|
||||
"http.resGetHeader": { argTypes: [HTTPRES_T, STRING], result: VOID },
|
||||
"http.resGetHeaderNames": { argTypes: [HTTPRES_T], result: arrayOf(STRING) },
|
||||
"http.resGetRawHeaderNames": { argTypes: [HTTPRES_T], result: arrayOf(STRING) },
|
||||
"http.resGetHeaders": { argTypes: [HTTPRES_T], result: DYN },
|
||||
"http.resHasHeader": { argTypes: [HTTPRES_T, STRING], result: BOOL },
|
||||
"http.resRemoveHeader": { argTypes: [HTTPRES_T, STRING], result: VOID },
|
||||
"http.resOnFinish": { argTypes: [HTTPRES_T, { kind: "func", params: [], ret: VOID }], result: VOID },
|
||||
@@ -530,6 +536,9 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
|
||||
"http.resSetHeader": { argTypes: [HTTPRES_T, STRING, STRING], result: VOID },
|
||||
"http.resWriteHead": { argTypes: [HTTPRES_T, F64], result: VOID },
|
||||
"http.resWriteHeadN": { argTypes: [HTTPRES_T, F64, arrayOf(STRING), arrayOf(STRING)], result: VOID },
|
||||
"http.resWriteContinue": { argTypes: [HTTPRES_T], result: VOID },
|
||||
"http.resWriteProcessing": { argTypes: [HTTPRES_T], result: VOID },
|
||||
"http.resWriteEarlyHints": { argTypes: [HTTPRES_T, arrayOf(STRING)], result: VOID },
|
||||
"http.resWrite": { argTypes: [HTTPRES_T, STRING], result: VOID },
|
||||
"http.resWriteBytes": { argTypes: [HTTPRES_T, BYTES_U8], result: VOID },
|
||||
"http.resEnd": { argTypes: [HTTPRES_T], result: VOID },
|
||||
@@ -538,6 +547,7 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
|
||||
"http.resWriteDyn": { argTypes: [HTTPRES_T, DYN], result: VOID },
|
||||
"http.resEndDyn": { argTypes: [HTTPRES_T, DYN], result: VOID },
|
||||
"http.resHeadersSent": { argTypes: [HTTPRES_T], result: BOOL },
|
||||
"http.resWritableEnded": { argTypes: [HTTPRES_T], result: BOOL },
|
||||
"http.resFlushHeaders": { argTypes: [HTTPRES_T], result: VOID },
|
||||
"http.resAddTrailers": { argTypes: [HTTPRES_T, arrayOf(STRING)], result: VOID },
|
||||
"http.resCork": { argTypes: [HTTPRES_T], result: VOID },
|
||||
@@ -742,6 +752,13 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
|
||||
"https.requestFn": { argTypes: [BOOL, STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null], result: HTTPCLIENTREQ_T },
|
||||
"https.requestFnCb": { argTypes: [BOOL, STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, null], result: HTTPCLIENTREQ_T },
|
||||
"http.clientWrite": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID },
|
||||
"http.clientSetHeader": { argTypes: [HTTPCLIENTREQ_T, STRING, STRING], result: VOID },
|
||||
"http.clientGetHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID },
|
||||
"http.clientHasHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: BOOL },
|
||||
"http.clientRemoveHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID },
|
||||
"http.clientGetHeaderNames": { argTypes: [HTTPCLIENTREQ_T], result: arrayOf(STRING) },
|
||||
"http.clientGetRawHeaderNames": { argTypes: [HTTPCLIENTREQ_T], result: arrayOf(STRING) },
|
||||
"http.clientGetHeaders": { argTypes: [HTTPCLIENTREQ_T], result: DYN },
|
||||
"http.clientWriteBytes": { argTypes: [HTTPCLIENTREQ_T, BYTES_U8], result: VOID },
|
||||
"http.clientEnd": { argTypes: [HTTPCLIENTREQ_T], result: VOID },
|
||||
"http.clientEndStr": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID },
|
||||
@@ -753,6 +770,12 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
|
||||
"http.clientCork": { argTypes: [HTTPCLIENTREQ_T], result: VOID },
|
||||
"http.clientUncork": { argTypes: [HTTPCLIENTREQ_T], result: VOID },
|
||||
"http.clientWritableCorked": { argTypes: [HTTPCLIENTREQ_T], result: F64 },
|
||||
"http.clientMethod": { argTypes: [HTTPCLIENTREQ_T], result: STRING },
|
||||
"http.clientPath": { argTypes: [HTTPCLIENTREQ_T], result: STRING },
|
||||
"http.clientHost": { argTypes: [HTTPCLIENTREQ_T], result: STRING },
|
||||
"http.clientProtocol": { argTypes: [HTTPCLIENTREQ_T], result: STRING },
|
||||
"http.clientHeadersSent": { argTypes: [HTTPCLIENTREQ_T], result: BOOL },
|
||||
"http.clientWritableEnded": { argTypes: [HTTPCLIENTREQ_T], result: BOOL },
|
||||
"http.clientDestroy": { argTypes: [HTTPCLIENTREQ_T], result: VOID },
|
||||
"http.clientDestroyed": { argTypes: [HTTPCLIENTREQ_T], result: BOOL },
|
||||
"http.clientOnResponse": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID },
|
||||
@@ -4238,7 +4261,7 @@ function validateFunction(
|
||||
if (!ok) err(`libCall http.reqH2Stream must return the 'Http2Stream | undefined' union`, e.loc);
|
||||
break;
|
||||
}
|
||||
if (e.fn === "http.reqHeader" || e.fn === "http.reqTrailer" || e.fn === "http.resGetHeader") {
|
||||
if (e.fn === "http.reqHeader" || e.fn === "http.reqTrailer" || e.fn === "http.resGetHeader" || e.fn === "http.clientGetHeader") {
|
||||
// Result is the interned `string | undefined` union (envGet's).
|
||||
const def = e.type.kind === "union" ? unions.get(e.type.unionId) : undefined;
|
||||
const ok =
|
||||
|
||||
@@ -267,11 +267,12 @@ void scr_http_req_release_v(void *p) { scr_http_req_release((ScrHttpReq *)p); }
|
||||
ScrStr *scr_http_req_url(ScrHttpReq *r) { return scr_str_retain(r->url); }
|
||||
ScrStr *scr_http_req_method(ScrHttpReq *r) { return scr_str_retain(r->method); }
|
||||
|
||||
/* Case-insensitive equality of two lowercased-vs-any names. */
|
||||
/* Case-insensitive equality of HTTP field names. Incoming names happen to
|
||||
* be lowercased already; outgoing names retain the spelling of the setter. */
|
||||
static bool scr_http_name_eq(const ScrStr *lower, const ScrStr *name) {
|
||||
if (lower->len != name->len) return false;
|
||||
for (size_t j = 0; j < name->len; j++) {
|
||||
if (lower->data[j] != (char)tolower((unsigned char)name->data[j])) return false;
|
||||
if (tolower((unsigned char)lower->data[j]) != tolower((unsigned char)name->data[j])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -682,6 +683,72 @@ static bool scr_http_token_char(unsigned char ch) {
|
||||
(ch >= 'a' && ch <= 'z') || (ch != 0 && strchr("!#$%&'*+-.^_`|~", ch) != NULL);
|
||||
}
|
||||
|
||||
/* node:http's public validators use the same token and field-vchar rules
|
||||
* as OutgoingMessage. Strings are UTF-8 here, so the Latin-1 obs-text
|
||||
* range accepted by Node's JS regex is C2/C3 plus one continuation byte. */
|
||||
void scr_http_validate_header_name(ScrStr *name /*borrowed*/, ScrStr *label /*borrowed*/) {
|
||||
bool valid = name->len > 0;
|
||||
for (size_t i = 0; i < name->len && valid; i++)
|
||||
valid = scr_http_token_char((unsigned char)name->data[i]);
|
||||
if (valid) return;
|
||||
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
ScrStr *shown = label->len ? label : NULL;
|
||||
if (shown) {
|
||||
for (size_t i = 0; i < shown->len; i++) scr_jb_putc(&b, shown->data[i]);
|
||||
} else {
|
||||
scr_jb_puts(&b, "Header name");
|
||||
}
|
||||
scr_jb_puts(&b, " must be a valid HTTP token [\"");
|
||||
for (size_t i = 0; i < name->len; i++) scr_jb_putc(&b, name->data[i]);
|
||||
scr_jb_puts(&b, "\"]");
|
||||
ScrStr *msg = scr_jb_finish(&b);
|
||||
scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len, "ERR_INVALID_HTTP_TOKEN");
|
||||
scr_str_release(msg);
|
||||
}
|
||||
|
||||
void scr_http_validate_header_value(ScrStr *name /*borrowed*/, const ScrDyn *value /*borrowed*/) {
|
||||
if (value->kind == SCR_DYN_UNDEF) {
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, "Invalid value \"undefined\" for header \"");
|
||||
for (size_t i = 0; i < name->len; i++) scr_jb_putc(&b, name->data[i]);
|
||||
scr_jb_putc(&b, '"');
|
||||
ScrStr *msg = scr_jb_finish(&b);
|
||||
scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len, "ERR_HTTP_INVALID_HEADER_VALUE");
|
||||
scr_str_release(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
ScrStr *s = scr_dyn_string_coerce_js(value);
|
||||
if (!s) return; /* object coercion threw */
|
||||
bool invalid = false;
|
||||
for (size_t i = 0; i < s->len; i++) {
|
||||
unsigned char ch = (unsigned char)s->data[i];
|
||||
if (ch == '\t' || (ch >= 0x20 && ch <= 0x7e)) continue;
|
||||
if ((ch == 0xc2 || ch == 0xc3) && i + 1 < s->len &&
|
||||
(unsigned char)s->data[i + 1] >= 0x80 &&
|
||||
(unsigned char)s->data[i + 1] <= 0xbf) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
scr_str_release(s);
|
||||
if (!invalid) return;
|
||||
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, "Invalid character in header content [\"");
|
||||
for (size_t i = 0; i < name->len; i++) scr_jb_putc(&b, name->data[i]);
|
||||
scr_jb_puts(&b, "\"]");
|
||||
ScrStr *msg = scr_jb_finish(&b);
|
||||
scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len, "ERR_INVALID_CHAR");
|
||||
scr_str_release(msg);
|
||||
}
|
||||
|
||||
static bool scr_http_trailers_replace(ScrHttpTrailers *t, ScrArr *pairs /*borrowed*/) {
|
||||
scr_http_trailers_clear(t);
|
||||
size_t n = (size_t)scr_arr_len(pairs);
|
||||
@@ -803,6 +870,7 @@ void *scr_http_res_retain_v(void *p) { return scr_http_res_retain((ScrHttpRes *)
|
||||
void scr_http_res_release_v(void *p) { scr_http_res_release((ScrHttpRes *)p); }
|
||||
|
||||
bool scr_http_res_headers_sent(ScrHttpRes *r) { return r->head_sent; }
|
||||
bool scr_http_res_writable_ended(ScrHttpRes *r) { return r->finished; }
|
||||
|
||||
/* res.statusCode: 200 until assigned (Node's fresh-response default);
|
||||
* assignment after the head went out is inert (Node throws on the WRITE
|
||||
@@ -845,6 +913,50 @@ ScrStr *scr_http_res_get_header(ScrHttpRes *r, ScrStr *name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Outgoing header snapshots omit the automatically serialized fields. The
|
||||
* names API lowercases keys while the raw variant preserves their casing. */
|
||||
static ScrArr *scr_http_out_header_names(ScrStr **names, size_t count, bool raw) {
|
||||
ScrArr *out = scr_arr_new(SCR_ELEM_STR, count);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
bool seen = false;
|
||||
for (size_t j = 0; j < i && !seen; j++) seen = scr_http_name_eq(names[j], names[i]);
|
||||
if (seen) continue;
|
||||
if (raw) {
|
||||
scr_arr_push_ref(out, scr_str_retain(names[i]));
|
||||
} else {
|
||||
ScrStr *lower = scr_str_new(names[i]->data, names[i]->len);
|
||||
for (size_t j = 0; j < lower->len; j++)
|
||||
lower->data[j] = (char)tolower((unsigned char)lower->data[j]);
|
||||
scr_arr_push_ref(out, lower);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_http_out_headers(ScrStr **names, ScrStr **values, size_t count) {
|
||||
ScrDyn *out = scr_dyn_new_obj();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
ScrStr *lower = scr_str_new(names[i]->data, names[i]->len);
|
||||
for (size_t j = 0; j < lower->len; j++)
|
||||
lower->data[j] = (char)tolower((unsigned char)lower->data[j]);
|
||||
scr_dyn_obj_set(out, lower->data, lower->len, scr_dyn_new_str(values[i]));
|
||||
scr_str_release(lower);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
ScrArr *scr_http_res_get_header_names(ScrHttpRes *r) {
|
||||
return scr_http_out_header_names(r->hnames, r->nheaders, false);
|
||||
}
|
||||
|
||||
ScrArr *scr_http_res_get_raw_header_names(ScrHttpRes *r) {
|
||||
return scr_http_out_header_names(r->hnames, r->nheaders, true);
|
||||
}
|
||||
|
||||
ScrDyn *scr_http_res_get_headers(ScrHttpRes *r) {
|
||||
return scr_http_out_headers(r->hnames, r->hvalues, r->nheaders);
|
||||
}
|
||||
|
||||
bool scr_http_res_has_header_named(ScrHttpRes *r, ScrStr *name) {
|
||||
ScrStr *v = scr_http_res_get_header(r, name);
|
||||
if (v) scr_str_release(v);
|
||||
@@ -914,6 +1026,8 @@ void scr_http_res_set_header(ScrHttpRes *r, ScrStr *name /*borrowed*/, ScrStr *v
|
||||
if (tolower((unsigned char)r->hnames[i]->data[j]) != tolower((unsigned char)name->data[j])) eq = false;
|
||||
}
|
||||
if (eq) {
|
||||
scr_str_release(r->hnames[i]);
|
||||
r->hnames[i] = scr_str_retain(name);
|
||||
scr_str_release(r->hvalues[i]);
|
||||
r->hvalues[i] = scr_str_retain(value);
|
||||
return;
|
||||
@@ -951,6 +1065,144 @@ static void scr_http_buf_append(ScrHttpBuf *b, const char *s, size_t n) {
|
||||
|
||||
static void scr_http_buf_str(ScrHttpBuf *b, const char *s) { scr_http_buf_append(b, s, strlen(s)); }
|
||||
|
||||
/* Informational response heads are written with Node's `ascii` encoding.
|
||||
* The header validator allows Latin-1 obs-text; collapse its UTF-8 C2/C3
|
||||
* spelling back to one wire byte. */
|
||||
static void scr_http_buf_header_value(ScrHttpBuf *b, const ScrStr *s) {
|
||||
for (size_t i = 0; i < s->len; i++) {
|
||||
unsigned char ch = (unsigned char)s->data[i];
|
||||
if ((ch == 0xc2 || ch == 0xc3) && i + 1 < s->len) {
|
||||
unsigned char next = (unsigned char)s->data[++i];
|
||||
char byte = (char)(((ch & 1) << 6) | (next & 0x3f));
|
||||
scr_http_buf_append(b, &byte, 1);
|
||||
} else {
|
||||
scr_http_buf_append(b, s->data + i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool scr_http_res_informational_h1(ScrHttpRes *r, const char *method) {
|
||||
if (r->h2_stream == NULL) return true;
|
||||
char msg[128];
|
||||
int n = snprintf(msg, sizeof msg, "Http2ServerResponse.%s is not supported by the static runtime yet", method);
|
||||
scr_throw_error_msg(SCR_ERR_ERROR, msg, (size_t)n);
|
||||
return false;
|
||||
}
|
||||
|
||||
void scr_http_res_write_continue(ScrHttpRes *r) {
|
||||
if (!scr_http_res_informational_h1(r, "writeContinue")) return;
|
||||
if (r->sock) scr_net_sock_write_native(r->sock, "HTTP/1.1 100 Continue\r\n\r\n", 25);
|
||||
}
|
||||
|
||||
void scr_http_res_write_processing(ScrHttpRes *r) {
|
||||
if (!scr_http_res_informational_h1(r, "writeProcessing")) return;
|
||||
if (r->sock) scr_net_sock_write_native(r->sock, "HTTP/1.1 102 Processing\r\n\r\n", 27);
|
||||
}
|
||||
|
||||
/* Node's linkValueRegExp from internal/validators: `<...>` followed by
|
||||
* optional semicolon parameters. Its string arm refuses an empty or
|
||||
* malformed link before the general header-content check runs. */
|
||||
static bool scr_http_early_link_format(const ScrStr *link) {
|
||||
if (link->len < 2 || link->data[0] != '<') return false;
|
||||
size_t i = 1;
|
||||
while (i < link->len && link->data[i] != '>') {
|
||||
if (link->data[i] == '\r' || link->data[i] == '\n') return false;
|
||||
i++;
|
||||
}
|
||||
if (i == link->len) return false;
|
||||
i++;
|
||||
while (i < link->len) {
|
||||
while (i < link->len && isspace((unsigned char)link->data[i])) i++;
|
||||
if (i == link->len || link->data[i++] != ';') return false;
|
||||
while (i < link->len && isspace((unsigned char)link->data[i])) i++;
|
||||
size_t start = i;
|
||||
while (i < link->len && link->data[i] != ';' && link->data[i] != '"' &&
|
||||
!isspace((unsigned char)link->data[i])) i++;
|
||||
if (i == start) return false;
|
||||
if (i < link->len && link->data[i] == '"') {
|
||||
if (link->data[i - 1] != '=') return false;
|
||||
i++;
|
||||
while (i < link->len && link->data[i] != ';' && link->data[i] != '"' &&
|
||||
!isspace((unsigned char)link->data[i])) i++;
|
||||
if (i == link->len || link->data[i++] != '"') return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* A lowercase `link` controls whether Node sends a 103 at all; when
|
||||
* present it is always the first field, followed by the other object
|
||||
* properties in insertion order. This static entry accepts string-valued
|
||||
* fields. */
|
||||
void scr_http_res_write_early_hints(ScrHttpRes *r, ScrArr *pairs /*borrowed*/) {
|
||||
if (!scr_http_res_informational_h1(r, "writeEarlyHints")) return;
|
||||
size_t count = (size_t)scr_arr_len(pairs);
|
||||
ScrStr *link = NULL;
|
||||
for (size_t i = 0; i + 1 < count; i += 2) {
|
||||
ScrStr *name = (ScrStr *)scr_arr_get_ref(pairs, (double)i);
|
||||
if (name->len == 4 && memcmp(name->data, "link", 4) == 0) {
|
||||
scr_str_release(link);
|
||||
link = (ScrStr *)scr_arr_get_ref(pairs, (double)(i + 1));
|
||||
}
|
||||
scr_str_release(name);
|
||||
}
|
||||
if (!link) return;
|
||||
if (!scr_http_early_link_format(link)) {
|
||||
ScrDyn *received = scr_dyn_new_str(link);
|
||||
scr_dyn_arg_value_fail("hints", "must be an array or string of format \"</styles.css>; rel=preload; as=style\"", received);
|
||||
scr_dyn_release(received);
|
||||
scr_str_release(link);
|
||||
return;
|
||||
}
|
||||
ScrStr *link_name = scr_str_new("Link", 4);
|
||||
ScrDyn *link_value = scr_dyn_new_str(link);
|
||||
scr_http_validate_header_value(link_name, link_value);
|
||||
scr_dyn_release(link_value);
|
||||
scr_str_release(link_name);
|
||||
if (scr_exc_pending()) {
|
||||
scr_str_release(link);
|
||||
return;
|
||||
}
|
||||
ScrHttpBuf b = {NULL, 0, 0};
|
||||
scr_http_buf_str(&b, "HTTP/1.1 103 Early Hints\r\nLink: ");
|
||||
scr_http_buf_header_value(&b, link);
|
||||
scr_http_buf_str(&b, "\r\n");
|
||||
scr_str_release(link);
|
||||
ScrStr *empty_label = scr_str_new("", 0);
|
||||
for (size_t i = 0; i + 1 < count; i += 2) {
|
||||
ScrStr *name = (ScrStr *)scr_arr_get_ref(pairs, (double)i);
|
||||
ScrStr *value = (ScrStr *)scr_arr_get_ref(pairs, (double)(i + 1));
|
||||
if (name->len == 4 && memcmp(name->data, "link", 4) == 0) {
|
||||
scr_str_release(name);
|
||||
scr_str_release(value);
|
||||
continue;
|
||||
}
|
||||
scr_http_validate_header_name(name, empty_label);
|
||||
if (!scr_exc_pending()) {
|
||||
ScrDyn *wrapped = scr_dyn_new_str(value);
|
||||
scr_http_validate_header_value(name, wrapped);
|
||||
scr_dyn_release(wrapped);
|
||||
}
|
||||
if (scr_exc_pending()) {
|
||||
scr_str_release(name);
|
||||
scr_str_release(value);
|
||||
scr_str_release(empty_label);
|
||||
free(b.data);
|
||||
return;
|
||||
}
|
||||
scr_http_buf_append(&b, name->data, name->len);
|
||||
scr_http_buf_str(&b, ": ");
|
||||
scr_http_buf_header_value(&b, value);
|
||||
scr_http_buf_str(&b, "\r\n");
|
||||
scr_str_release(name);
|
||||
scr_str_release(value);
|
||||
}
|
||||
scr_str_release(empty_label);
|
||||
scr_http_buf_str(&b, "\r\n");
|
||||
if (r->sock) scr_net_sock_write_native(r->sock, b.data, b.len);
|
||||
free(b.data);
|
||||
}
|
||||
|
||||
static void scr_http_send_chunk_end(ScrNetSocket *sock, const ScrHttpTrailers *t) {
|
||||
ScrHttpBuf b = {NULL, 0, 0};
|
||||
scr_http_buf_str(&b, "0\r\n");
|
||||
@@ -2684,6 +2936,8 @@ struct ScrHttpClientReq {
|
||||
ScrStr **hnames; /* user headers, verbatim case */
|
||||
ScrStr **hvalues;
|
||||
size_t nheaders;
|
||||
bool removed_host;
|
||||
bool removed_connection;
|
||||
ScrHttpTrailers trailers;
|
||||
bool user_cl; /* caller set content-length/transfer-encoding */
|
||||
bool head_sent;
|
||||
@@ -2822,6 +3076,106 @@ static bool scr_http_client_has_header(ScrHttpClientReq *c, const char *name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool scr_http_header_name_is(ScrStr *name, const char *text) {
|
||||
size_t len = strlen(text);
|
||||
if (name->len != len) return false;
|
||||
for (size_t i = 0; i < len; i++)
|
||||
if (tolower((unsigned char)name->data[i]) != text[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
ScrStr *scr_http_client_get_header(ScrHttpClientReq *c, ScrStr *name) {
|
||||
for (size_t i = 0; i < c->nheaders; i++)
|
||||
if (scr_http_name_eq(c->hnames[i], name)) return scr_str_retain(c->hvalues[i]);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool scr_http_client_has_header_named(ScrHttpClientReq *c, ScrStr *name) {
|
||||
for (size_t i = 0; i < c->nheaders; i++)
|
||||
if (scr_http_name_eq(c->hnames[i], name)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
ScrArr *scr_http_client_get_header_names(ScrHttpClientReq *c) {
|
||||
return scr_http_out_header_names(c->hnames, c->nheaders, false);
|
||||
}
|
||||
|
||||
ScrArr *scr_http_client_get_raw_header_names(ScrHttpClientReq *c) {
|
||||
return scr_http_out_header_names(c->hnames, c->nheaders, true);
|
||||
}
|
||||
|
||||
ScrDyn *scr_http_client_get_headers(ScrHttpClientReq *c) {
|
||||
return scr_http_out_headers(c->hnames, c->hvalues, c->nheaders);
|
||||
}
|
||||
|
||||
ScrStr *scr_http_client_method(ScrHttpClientReq *c) { return scr_str_retain(c->method); }
|
||||
ScrStr *scr_http_client_path(ScrHttpClientReq *c) { return scr_str_retain(c->path); }
|
||||
ScrStr *scr_http_client_host(ScrHttpClientReq *c) { return scr_str_retain(c->host); }
|
||||
ScrStr *scr_http_client_protocol(ScrHttpClientReq *c) {
|
||||
return c->default_port == 443 ? scr_str_new("https:", 6) : scr_str_new("http:", 5);
|
||||
}
|
||||
bool scr_http_client_headers_sent(ScrHttpClientReq *c) { return c->head_sent; }
|
||||
bool scr_http_client_writable_ended(ScrHttpClientReq *c) { return c->ended; }
|
||||
|
||||
static bool scr_http_client_header_mutable(ScrHttpClientReq *c, const char *op) {
|
||||
if (!c->head_sent) return true;
|
||||
char msg[80];
|
||||
int len = snprintf(msg, sizeof msg, "Cannot %s headers after they are sent to the client", op);
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, "ERR_HTTP_HEADERS_SENT");
|
||||
return false;
|
||||
}
|
||||
|
||||
void scr_http_client_set_header(ScrHttpClientReq *c, ScrStr *name, ScrStr *value) {
|
||||
if (!scr_http_client_header_mutable(c, "set")) return;
|
||||
ScrStr *label = scr_str_new("Header name", 11);
|
||||
scr_http_validate_header_name(name, label);
|
||||
scr_str_release(label);
|
||||
if (scr_exc_pending()) return;
|
||||
ScrDyn *wrapped = scr_dyn_new_str(value);
|
||||
scr_http_validate_header_value(name, wrapped);
|
||||
scr_dyn_release(wrapped);
|
||||
if (scr_exc_pending()) return;
|
||||
for (size_t i = 0; i < c->nheaders; i++) {
|
||||
if (!scr_http_name_eq(c->hnames[i], name)) continue;
|
||||
scr_str_release(c->hnames[i]);
|
||||
scr_str_release(c->hvalues[i]);
|
||||
c->hnames[i] = scr_str_retain(name);
|
||||
c->hvalues[i] = scr_str_retain(value);
|
||||
goto updated;
|
||||
}
|
||||
c->hnames = realloc(c->hnames, (c->nheaders + 1) * sizeof *c->hnames);
|
||||
c->hvalues = realloc(c->hvalues, (c->nheaders + 1) * sizeof *c->hvalues);
|
||||
if (!c->hnames || !c->hvalues) scr_http_oom();
|
||||
c->hnames[c->nheaders] = scr_str_retain(name);
|
||||
c->hvalues[c->nheaders] = scr_str_retain(value);
|
||||
c->nheaders++;
|
||||
updated:
|
||||
if (scr_http_header_name_is(name, "host")) c->removed_host = false;
|
||||
if (scr_http_header_name_is(name, "connection")) c->removed_connection = false;
|
||||
c->user_cl = scr_http_client_has_header(c, "content-length") ||
|
||||
scr_http_client_has_header(c, "transfer-encoding");
|
||||
}
|
||||
|
||||
void scr_http_client_remove_header(ScrHttpClientReq *c, ScrStr *name) {
|
||||
if (!scr_http_client_header_mutable(c, "remove")) return;
|
||||
size_t w = 0;
|
||||
for (size_t i = 0; i < c->nheaders; i++) {
|
||||
if (scr_http_name_eq(c->hnames[i], name)) {
|
||||
scr_str_release(c->hnames[i]);
|
||||
scr_str_release(c->hvalues[i]);
|
||||
} else {
|
||||
c->hnames[w] = c->hnames[i];
|
||||
c->hvalues[w] = c->hvalues[i];
|
||||
w++;
|
||||
}
|
||||
}
|
||||
c->nheaders = w;
|
||||
if (scr_http_header_name_is(name, "host")) c->removed_host = true;
|
||||
if (scr_http_header_name_is(name, "connection")) c->removed_connection = true;
|
||||
c->user_cl = scr_http_client_has_header(c, "content-length") ||
|
||||
scr_http_client_has_header(c, "transfer-encoding");
|
||||
}
|
||||
|
||||
/* Serializes and sends the head. `body_len` >= 0 fixes Content-Length
|
||||
* (the end(data) path — and Node's Content-Length: 0 for empty POST/PUT/
|
||||
* PATCH bodies); -1 means STREAMING: chunked unless the caller set the
|
||||
@@ -2845,7 +3199,7 @@ static void scr_http_client_send_head(ScrHttpClientReq *c, long long body_len) {
|
||||
}
|
||||
if (transfer && scr_http_header_has_token(c->hvalues[i], "chunked")) c->chunked = true;
|
||||
}
|
||||
if (!scr_http_client_has_header(c, "host")) {
|
||||
if (!c->removed_host && !scr_http_client_has_header(c, "host")) {
|
||||
/* Host: name[:port] — the scheme's default port omitted (80 http,
|
||||
* 443 https), IPv6 literals bracketed (Node) */
|
||||
bool v6 = memchr(c->host->data, ':', c->host->len) != NULL;
|
||||
@@ -2860,7 +3214,7 @@ static void scr_http_client_send_head(ScrHttpClientReq *c, long long body_len) {
|
||||
}
|
||||
scr_http_buf_str(&b, "\r\n");
|
||||
}
|
||||
if (!scr_http_client_has_header(c, "connection")) {
|
||||
if (!c->removed_connection && !scr_http_client_has_header(c, "connection")) {
|
||||
scr_http_buf_str(&b, "Connection: keep-alive\r\n");
|
||||
}
|
||||
if (!c->user_cl) {
|
||||
@@ -3437,6 +3791,26 @@ static ScrHttpClientReq *scr_http_request_impl(ScrStr *host /*borrowed*/, double
|
||||
}
|
||||
c->user_cl = scr_http_client_has_header(c, "content-length") ||
|
||||
scr_http_client_has_header(c, "transfer-encoding");
|
||||
if (!scr_http_client_has_header(c, "host")) {
|
||||
/* Node queues the implicit Host header at construction, so all three
|
||||
* outgoing header readers see it before end()/flushHeaders(). */
|
||||
ScrHttpBuf hostbuf = {NULL, 0, 0};
|
||||
bool v6 = memchr(c->host->data, ':', c->host->len) != NULL;
|
||||
if (v6) scr_http_buf_str(&hostbuf, "[");
|
||||
scr_http_buf_append(&hostbuf, c->host->data, c->host->len);
|
||||
if (v6) scr_http_buf_str(&hostbuf, "]");
|
||||
if (c->port != c->default_port) {
|
||||
char portbuf[16];
|
||||
snprintf(portbuf, sizeof portbuf, ":%d", c->port);
|
||||
scr_http_buf_str(&hostbuf, portbuf);
|
||||
}
|
||||
ScrStr *host_name = scr_str_new("Host", 4);
|
||||
ScrStr *host_value = scr_str_new(hostbuf.data, hostbuf.len);
|
||||
free(hostbuf.data);
|
||||
scr_http_client_set_header(c, host_name, host_value);
|
||||
scr_str_release(host_name);
|
||||
scr_str_release(host_value);
|
||||
}
|
||||
if (cb) scr_net_ls_add(&c->resp_ls, cb, (void *)fn, true);
|
||||
|
||||
/* dial — or take the caller's pre-made socket (createConnection); dial
|
||||
|
||||
@@ -1525,6 +1525,7 @@ void scr_net_listen_opts_reuse_port(ScrNetServer *s, double port, ScrStr *host /
|
||||
}
|
||||
|
||||
double scr_net_server_port(ScrNetServer *s) { return (double)s->port; }
|
||||
bool scr_net_server_listening(ScrNetServer *s) { return s->listening; }
|
||||
|
||||
double scr_net_server_timeout_get(ScrNetServer *s, double field) {
|
||||
static const char *const names[] = {
|
||||
|
||||
@@ -5649,6 +5649,7 @@ void scr_net_listen_opts_reuse_port(ScrNetServer *s, double port, ScrStr *host /
|
||||
bool ipv6_only, bool reuse_port,
|
||||
ScrClosure *cb /*moves, nullable*/);
|
||||
double scr_net_server_port(ScrNetServer *s); /* address().port */
|
||||
bool scr_net_server_listening(ScrNetServer *s);
|
||||
/* Writable http.Server timeout property storage. `field` is the compiler
|
||||
* ABI selector: timeout, keepAliveTimeout, headersTimeout, requestTimeout,
|
||||
* keepAliveTimeoutBuffer. Typed reads validate that no dynamic write left
|
||||
@@ -6058,6 +6059,8 @@ bool scr_http_req_complete(ScrHttpReq *r);
|
||||
* SCR_DYNH_HTTP_RES) — emitted main() calls this exactly when the http
|
||||
* unit is linked (the scr_net_dyn_install story). */
|
||||
void scr_http_dyn_install(void);
|
||||
void scr_http_validate_header_name(ScrStr *name /*borrowed*/, ScrStr *label /*borrowed*/);
|
||||
void scr_http_validate_header_value(ScrStr *name /*borrowed*/, const ScrDyn *value /*borrowed*/);
|
||||
|
||||
ScrNetServer *scr_http_create_server(ScrClosure *handler /*moves, nullable*/, ScrHttpReqFn fn); /* +1 */
|
||||
/* The unguarded h2-only stream call: throws Node's exact catchable
|
||||
@@ -6092,6 +6095,9 @@ void scr_http_req_on_end(ScrHttpReq *r, ScrClosure *cb /*moves*/, bool once);
|
||||
void scr_http_res_set_header(ScrHttpRes *r, ScrStr *name /*borrowed*/, ScrStr *value /*borrowed*/);
|
||||
void scr_http_res_write_head(ScrHttpRes *r, double status);
|
||||
void scr_http_res_write_head_n(ScrHttpRes *r, double status, ScrArr *names /*borrowed*/, ScrArr *values /*borrowed*/);
|
||||
void scr_http_res_write_continue(ScrHttpRes *r);
|
||||
void scr_http_res_write_processing(ScrHttpRes *r);
|
||||
void scr_http_res_write_early_hints(ScrHttpRes *r, ScrArr *pairs /*borrowed: [name, value, ...] */);
|
||||
/* writeHead with a CHECKED-DYNAMIC headers object (borrowed): OBJ
|
||||
* entries setHeader in insertion order (string/number values), then the
|
||||
* head goes out; undefined/null = the plain head; may throw. */
|
||||
@@ -6109,6 +6115,7 @@ void scr_http_res_add_trailers(ScrHttpRes *r, ScrArr *pairs /*borrowed: [name, v
|
||||
void scr_http_res_write_dynv(ScrHttpRes *r, const ScrDyn *d /*borrowed*/);
|
||||
void scr_http_res_end_dynv(ScrHttpRes *r, const ScrDyn *d /*borrowed*/);
|
||||
bool scr_http_res_headers_sent(ScrHttpRes *r);
|
||||
bool scr_http_res_writable_ended(ScrHttpRes *r);
|
||||
/* The res member surface: statusCode (200 until assigned; inert once the
|
||||
* head went out), statusMessage (the reason phrase — assigned value, or
|
||||
* the code's default), the header CRUD trio, and end(cb)'s finish slot
|
||||
@@ -6118,6 +6125,9 @@ void scr_http_res_status_set(ScrHttpRes *r, double status);
|
||||
ScrStr *scr_http_res_status_msg_get(ScrHttpRes *r); /* +1 */
|
||||
void scr_http_res_status_msg_set(ScrHttpRes *r, ScrStr *msg /*borrowed*/);
|
||||
ScrStr *scr_http_res_get_header(ScrHttpRes *r, ScrStr *name /*borrowed*/); /* +1 or NULL */
|
||||
ScrArr *scr_http_res_get_header_names(ScrHttpRes *r); /* +1 */
|
||||
ScrArr *scr_http_res_get_raw_header_names(ScrHttpRes *r); /* +1 */
|
||||
ScrDyn *scr_http_res_get_headers(ScrHttpRes *r); /* +1 */
|
||||
bool scr_http_res_has_header_named(ScrHttpRes *r, ScrStr *name /*borrowed*/);
|
||||
void scr_http_res_remove_header(ScrHttpRes *r, ScrStr *name /*borrowed*/);
|
||||
void scr_http_res_on_finish(ScrHttpRes *r, ScrClosure *cb /*moves*/);
|
||||
@@ -6200,6 +6210,19 @@ void scr_http_client_end(ScrHttpClientReq *c);
|
||||
void scr_http_client_end_str(ScrHttpClientReq *c, ScrStr *data /*borrowed*/);
|
||||
void scr_http_client_end_bytes(ScrHttpClientReq *c, ScrBytes *data /*borrowed*/);
|
||||
void scr_http_client_flush_headers(ScrHttpClientReq *c);
|
||||
void scr_http_client_set_header(ScrHttpClientReq *c, ScrStr *name /*borrowed*/, ScrStr *value /*borrowed*/);
|
||||
ScrStr *scr_http_client_get_header(ScrHttpClientReq *c, ScrStr *name /*borrowed*/); /* +1 or NULL */
|
||||
bool scr_http_client_has_header_named(ScrHttpClientReq *c, ScrStr *name /*borrowed*/);
|
||||
void scr_http_client_remove_header(ScrHttpClientReq *c, ScrStr *name /*borrowed*/);
|
||||
ScrArr *scr_http_client_get_header_names(ScrHttpClientReq *c); /* +1 */
|
||||
ScrArr *scr_http_client_get_raw_header_names(ScrHttpClientReq *c); /* +1 */
|
||||
ScrDyn *scr_http_client_get_headers(ScrHttpClientReq *c); /* +1 */
|
||||
ScrStr *scr_http_client_method(ScrHttpClientReq *c); /* +1 */
|
||||
ScrStr *scr_http_client_path(ScrHttpClientReq *c); /* +1 */
|
||||
ScrStr *scr_http_client_host(ScrHttpClientReq *c); /* +1 */
|
||||
ScrStr *scr_http_client_protocol(ScrHttpClientReq *c); /* +1 */
|
||||
bool scr_http_client_headers_sent(ScrHttpClientReq *c);
|
||||
bool scr_http_client_writable_ended(ScrHttpClientReq *c);
|
||||
void scr_http_client_add_trailers(ScrHttpClientReq *c, ScrArr *pairs /*borrowed*/);
|
||||
void scr_http_client_cork(ScrHttpClientReq *c);
|
||||
void scr_http_client_uncork(ScrHttpClientReq *c);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import * as http from "node:http";
|
||||
import { validateHeaderName, validateHeaderValue } from "node:http";
|
||||
|
||||
function result(fn: () => void): string {
|
||||
try {
|
||||
fn();
|
||||
return "ok";
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return `${error.name}:${(error as NodeJS.ErrnoException).code}:${error.message}`;
|
||||
}
|
||||
return "unexpected error";
|
||||
}
|
||||
}
|
||||
|
||||
const dynamicName = "X-Valid_09";
|
||||
console.log("name", result(() => validateHeaderName(dynamicName)));
|
||||
console.log("token", result(() => http.validateHeaderName("!#$%&'*+-.^_`|~")));
|
||||
console.log("empty-name", result(() => validateHeaderName("")));
|
||||
console.log("space-name", result(() => http.validateHeaderName("Bad Name", "Field name")));
|
||||
console.log("colon-name", result(() => validateHeaderName("Bad:Name")));
|
||||
console.log("unicode-name", result(() => validateHeaderName("Café")));
|
||||
console.log("empty-label", result(() => validateHeaderName("bad name", "")));
|
||||
|
||||
console.log("empty-value", result(() => validateHeaderValue("x-test", "")));
|
||||
console.log("tab-value", result(() => http.validateHeaderValue("x-test", "a\tb")));
|
||||
console.log("latin1-value", result(() => validateHeaderValue("x-test", "café\u00ff")));
|
||||
console.log("number-value", result(() => validateHeaderValue("x-test", 42)));
|
||||
console.log("boolean-value", result(() => validateHeaderValue("x-test", true)));
|
||||
console.log("null-value", result(() => validateHeaderValue("x-test", null)));
|
||||
console.log("array-value", result(() => validateHeaderValue("x-test", [1, 2])));
|
||||
console.log("undefined-value", result(() => validateHeaderValue("x-test", undefined)));
|
||||
console.log("newline-value", result(() => validateHeaderValue("x-test", "a\nb")));
|
||||
console.log("del-value", result(() => validateHeaderValue("x-test", "a\u007fb")));
|
||||
console.log("wide-value", result(() => validateHeaderValue("x-test", "a\u0100b")));
|
||||
console.log("unchecked-name", result(() => validateHeaderValue("bad name", "valid")));
|
||||
@@ -0,0 +1,49 @@
|
||||
import { createServer, request } from "node:http";
|
||||
|
||||
function outcome(fn: () => void): string {
|
||||
try {
|
||||
fn();
|
||||
return "ok";
|
||||
} catch (error) {
|
||||
if (error instanceof Error) return `${error.name}:${(error as NodeJS.ErrnoException).code}:${error.message}`;
|
||||
return "unexpected error";
|
||||
}
|
||||
}
|
||||
|
||||
const server = createServer((incoming, outgoing) => {
|
||||
console.log("wire", incoming.headers["x-initial"], incoming.headers["x-new"], incoming.headers["x-drop"] === undefined);
|
||||
outgoing.setHeader("X-First", "one");
|
||||
const names = outgoing.getHeaderNames();
|
||||
const snapshot = outgoing.getHeaders();
|
||||
outgoing.setHeader("x-first", "two");
|
||||
outgoing.setHeader("X-Drop", "gone");
|
||||
outgoing.removeHeader("x-drop");
|
||||
console.log("server names", names.join(","), outgoing.getHeaderNames().join(","), outgoing.getRawHeaderNames().join(","));
|
||||
console.log("server headers", JSON.stringify(snapshot), JSON.stringify(outgoing.getHeaders()), outgoing.getHeader("X-FIRST"));
|
||||
outgoing.end("done");
|
||||
});
|
||||
|
||||
server.listen(0, () => {
|
||||
const port = server.address().port;
|
||||
const outbound = request({ hostname: "127.0.0.1", port, headers: { "X-Initial": "one" } }, (response) => {
|
||||
console.log("reply", response.statusCode, response.headers["x-first"]);
|
||||
response.on("data", () => {});
|
||||
response.on("end", () => server.close());
|
||||
});
|
||||
outbound.on("error", (error) => console.log("request error", error.message));
|
||||
const initial = outbound.getHeaders();
|
||||
const initialNames = outbound.getHeaderNames();
|
||||
console.log("client initial", initialNames.join(","), outbound.getRawHeaderNames().join(","), initial["x-initial"], outbound.getHeader("HOST") === `127.0.0.1:${port}`);
|
||||
outbound.setHeader("x-initial", "two");
|
||||
outbound.setHeader("X-Drop", "gone");
|
||||
outbound.setHeader("X-New", "yes");
|
||||
console.log("client changed", outbound.getHeader("X-INITIAL"), outbound.hasHeader("x-drop"), initial["x-initial"], outbound.getRawHeaderNames().join(","));
|
||||
outbound.removeHeader("x-drop");
|
||||
console.log("client removed", outbound.getHeader("X-Drop") === undefined, outbound.hasHeader("X-DROP"), JSON.stringify(outbound.getHeaderNames()));
|
||||
console.log("bad name", outcome(() => outbound.setHeader("bad name", "value")));
|
||||
console.log("bad value", outcome(() => outbound.setHeader("X-Bad", "line\nbreak")));
|
||||
outbound.flushHeaders();
|
||||
console.log("late set", outcome(() => outbound.setHeader("X-Late", "no")));
|
||||
console.log("late remove", outcome(() => outbound.removeHeader("X-New")));
|
||||
outbound.end();
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { createServer, request } from "node:http";
|
||||
|
||||
const server = createServer((incoming, outgoing) => {
|
||||
console.log("incoming", incoming.method, incoming.url, incoming.httpVersion);
|
||||
incoming.on("data", () => {});
|
||||
incoming.on("end", () => {
|
||||
console.log("incoming complete", incoming.complete);
|
||||
outgoing.end("done");
|
||||
console.log("response ended", outgoing.headersSent, outgoing.writableEnded);
|
||||
});
|
||||
console.log("response initial", outgoing.statusCode, outgoing.headersSent, outgoing.writableEnded);
|
||||
outgoing.setHeader("Content-Type", "text/plain");
|
||||
outgoing.flushHeaders();
|
||||
console.log("response flushed", outgoing.headersSent, outgoing.writableEnded);
|
||||
});
|
||||
|
||||
console.log("server initial", server.listening);
|
||||
server.listen(0, () => {
|
||||
console.log("server listening", server.listening);
|
||||
const port = server.address().port;
|
||||
const outbound = request({ hostname: "127.0.0.1", port, path: "/state?q=1", method: "POST" }, (incoming) => {
|
||||
console.log("reply", incoming.statusCode, incoming.httpVersion);
|
||||
incoming.on("data", () => {});
|
||||
incoming.on("end", () => {
|
||||
console.log("reply complete", incoming.complete);
|
||||
server.close(() => console.log("server closed", server.listening));
|
||||
console.log("server closing", server.listening);
|
||||
});
|
||||
});
|
||||
outbound.on("error", (error) => console.log("client error", error.message));
|
||||
console.log("client metadata", outbound.method, outbound.path, outbound.host, outbound.protocol);
|
||||
console.log("client initial", outbound.headersSent, outbound.writableEnded);
|
||||
outbound.flushHeaders();
|
||||
console.log("client flushed", outbound.headersSent, outbound.writableEnded);
|
||||
outbound.end("hi");
|
||||
console.log("client ended", outbound.headersSent, outbound.writableEnded);
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { createServer } from "node:http";
|
||||
import { connect } from "node:net";
|
||||
|
||||
const server = createServer((_request, response) => {
|
||||
response.writeEarlyHints({ "X-Ignored": "no link" });
|
||||
try {
|
||||
response.writeEarlyHints({ link: "" });
|
||||
} catch (error) {
|
||||
const caught = error as NodeJS.ErrnoException;
|
||||
console.log("empty link", caught.name, caught.code, caught.message);
|
||||
}
|
||||
response.writeContinue();
|
||||
response.writeProcessing();
|
||||
|
||||
try {
|
||||
response.writeEarlyHints({ link: "bad" });
|
||||
} catch (error) {
|
||||
const caught = error as NodeJS.ErrnoException;
|
||||
console.log("bad link", caught.name, caught.code, caught.message);
|
||||
}
|
||||
try {
|
||||
response.writeEarlyHints({ link: "</good.css>; rel=preload", "Bad Name": "value" });
|
||||
} catch (error) {
|
||||
const caught = error as NodeJS.ErrnoException;
|
||||
console.log("bad name", caught.name, caught.code, caught.message);
|
||||
}
|
||||
try {
|
||||
response.writeEarlyHints({ link: "</good.css>; rel=preload", "X-Bad": "bad\rvalue" });
|
||||
} catch (error) {
|
||||
const caught = error as NodeJS.ErrnoException;
|
||||
console.log("bad value", caught.name, caught.code, caught.message);
|
||||
}
|
||||
|
||||
const hints: Record<string, string> = {
|
||||
"X-Before": "first",
|
||||
link: "</main.css>; rel=preload; as=style",
|
||||
"X-After": "last",
|
||||
};
|
||||
response.writeEarlyHints(hints);
|
||||
console.log("final state", response.headersSent, response.writableEnded);
|
||||
response.end("ok");
|
||||
});
|
||||
|
||||
server.listen(0, () => {
|
||||
const socket = connect(server.address().port, "127.0.0.1");
|
||||
let wire = "";
|
||||
socket.on("connect", () => socket.write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"));
|
||||
socket.on("data", (chunk: Buffer) => { wire += chunk.toString("utf8"); });
|
||||
socket.on("end", () => {
|
||||
const parts = wire.split("\r\n\r\n");
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
console.log("head", parts[i]!.split("\r\n")[0]);
|
||||
}
|
||||
console.log("hints", parts[2]!.split("\r\n").slice(1).join("|"));
|
||||
console.log("body", parts[parts.length - 1]);
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user