@nodefony/http 10.0.0-beta.1 → 10.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +11 -5
- package/dist/nodefony/command/certificatesCommand.js +0 -1
- package/dist/nodefony/config/config.js +48 -7
- package/dist/nodefony/config/defineModuleConfig.js +1 -1
- package/dist/nodefony/interfaces/IReverseProxy.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +0 -1
- package/dist/nodefony/service/audit-logger.js +2 -3
- package/dist/nodefony/service/certificates.js +214 -158
- package/dist/nodefony/service/error-renderer.js +1 -1
- package/dist/nodefony/service/http-kernel.js +221 -47
- package/dist/nodefony/service/reverse-proxy.js +336 -0
- package/dist/nodefony/service/servers/server-http.js +3 -3
- package/dist/nodefony/service/servers/server-https.js +3 -3
- package/dist/nodefony/service/servers/server-static.js +4 -7
- package/dist/nodefony/service/servers/server-websocket-secure.js +12 -4
- package/dist/nodefony/service/servers/server-websocket.js +12 -4
- package/dist/nodefony/service/servers/wsHeartbeat.js +31 -15
- package/dist/nodefony/service/sessions/sessions-service.js +30 -9
- package/dist/nodefony/service/trace.js +2 -1
- package/dist/nodefony/service/upload/upload-service.js +3 -3
- package/dist/nodefony/service/x509.js +324 -0
- package/dist/nodefony/src/context/Context.js +28 -1
- package/dist/nodefony/src/context/domainMatcher.js +20 -2
- package/dist/nodefony/src/context/forwarded.js +2 -2
- package/dist/nodefony/src/context/http/HttpContext.js +91 -40
- package/dist/nodefony/src/context/http/Request.js +9 -4
- package/dist/nodefony/src/context/http/Response.js +46 -7
- package/dist/nodefony/src/context/http/SseStream.js +350 -0
- package/dist/nodefony/src/context/http/urlFastPath.js +35 -10
- package/dist/nodefony/src/context/responseEnded.js +27 -0
- package/dist/nodefony/src/context/websocket/Response.js +9 -0
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +2 -3
- package/dist/nodefony/src/context/websocket/websocketUrl.js +21 -0
- package/dist/nodefony/src/profiler/Profiler.js +6 -10
- package/dist/nodefony/src/proxy/forward.js +476 -0
- package/dist/nodefony/src/proxy/rules.js +182 -0
- package/dist/nodefony/src/servers/portBinder.js +57 -2
- package/dist/nodefony/src/servers/upgradeDispatch.js +49 -0
- package/dist/nodefony/src/session/session.js +28 -12
- package/dist/types/index.d.ts +5 -0
- package/dist/types/nodefony/config/config.d.ts +17 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +2 -0
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +1 -1
- package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +67 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +16 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +6 -0
- package/dist/types/nodefony/interfaces/index.d.ts +1 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +2 -1
- package/dist/types/nodefony/service/certificates.d.ts +126 -50
- package/dist/types/nodefony/service/error-renderer.d.ts +1 -1
- package/dist/types/nodefony/service/http-kernel.d.ts +111 -5
- package/dist/types/nodefony/service/reverse-proxy.d.ts +156 -0
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -0
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -0
- package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +26 -0
- package/dist/types/nodefony/service/sessions/sessions-service.d.ts +19 -1
- package/dist/types/nodefony/service/x509.d.ts +86 -0
- package/dist/types/nodefony/src/context/Context.d.ts +22 -2
- package/dist/types/nodefony/src/context/domainMatcher.d.ts +11 -1
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +47 -0
- package/dist/types/nodefony/src/context/http/Response.d.ts +3 -0
- package/dist/types/nodefony/src/context/http/SseStream.d.ts +155 -0
- package/dist/types/nodefony/src/context/responseEnded.d.ts +20 -0
- package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +16 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +1 -0
- package/dist/types/nodefony/src/proxy/forward.d.ts +156 -0
- package/dist/types/nodefony/src/proxy/rules.d.ts +85 -0
- package/dist/types/nodefony/src/servers/portBinder.d.ts +10 -46
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +39 -0
- package/dist/types/nodefony/src/session/session.d.ts +2 -0
- package/docs/cookies.md +13 -13
- package/docs/index.md +9 -3
- package/docs/observabilite.md +18 -18
- package/docs/rate-limit.md +30 -30
- package/docs/reverse-proxy.md +522 -0
- package/docs/servers.md +115 -78
- package/docs/session.md +61 -61
- package/docs/sse.md +283 -0
- package/docs/upload.md +37 -37
- package/package.json +10 -13
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { isIPv4, isIPv6 } from "node:net";
|
|
2
|
+
import { createHash, sign } from "node:crypto";
|
|
3
|
+
//#region nodefony/service/x509.ts
|
|
4
|
+
/**
|
|
5
|
+
* Fabrique et lecture de certificats X.509 par `node:crypto` seul — aucune
|
|
6
|
+
* dépendance tierce.
|
|
7
|
+
*
|
|
8
|
+
* `node:crypto` sait LIRE un certificat (`X509Certificate`) et SIGNER, pas
|
|
9
|
+
* en FABRIQUER un : ce module encode le `TBSCertificate` en DER (RFC 5280
|
|
10
|
+
* §4.1), le signe avec `crypto.sign`, puis l'assemble. Seul le sous-ensemble
|
|
11
|
+
* nécessaire à un certificat serveur auto-signé est couvert. Les fonctions
|
|
12
|
+
* sont PURES (aucun disque, aucun kernel) : elles s'éprouvent sans décor.
|
|
13
|
+
*
|
|
14
|
+
* Pourquoi pas node-forge : sa vérification de signature RSA PKCS#1 v1.5
|
|
15
|
+
* accepte des signatures forgées (GHSA-86w9-cpqp-85rv), sans aucune version
|
|
16
|
+
* corrigée. Nous ne vérifions aucune signature, mais `npm audit` signale le
|
|
17
|
+
* paquet pour tous ceux qui installent `@nodefony/http`.
|
|
18
|
+
*/
|
|
19
|
+
const SIGNATURE_OIDS = {
|
|
20
|
+
sha1: "1.2.840.113549.1.1.5",
|
|
21
|
+
sha256: "1.2.840.113549.1.1.11",
|
|
22
|
+
sha384: "1.2.840.113549.1.1.12",
|
|
23
|
+
sha512: "1.2.840.113549.1.1.13"
|
|
24
|
+
};
|
|
25
|
+
/** OID de la signature RSA-SHA1, que le service régénère. */
|
|
26
|
+
const SHA1_WITH_RSA_OID = SIGNATURE_OIDS.sha1;
|
|
27
|
+
/** Noms lisibles des algorithmes de signature courants (rendu de `describe`). */
|
|
28
|
+
const SIGNATURE_NAMES = Object.assign(Object.create(null), {
|
|
29
|
+
[SIGNATURE_OIDS.sha1]: "sha1WithRSAEncryption",
|
|
30
|
+
[SIGNATURE_OIDS.sha256]: "sha256WithRSAEncryption",
|
|
31
|
+
[SIGNATURE_OIDS.sha384]: "sha384WithRSAEncryption",
|
|
32
|
+
[SIGNATURE_OIDS.sha512]: "sha512WithRSAEncryption",
|
|
33
|
+
"1.2.840.113549.1.1.10": "RSASSA-PSS",
|
|
34
|
+
"1.2.840.10045.4.3.2": "ecdsa-with-SHA256",
|
|
35
|
+
"1.2.840.10045.4.3.3": "ecdsa-with-SHA384",
|
|
36
|
+
"1.2.840.10045.4.3.4": "ecdsa-with-SHA512",
|
|
37
|
+
"1.3.101.112": "Ed25519"
|
|
38
|
+
});
|
|
39
|
+
const ATTRIBUTE_OIDS = Object.assign(Object.create(null), {
|
|
40
|
+
commonName: "2.5.4.3",
|
|
41
|
+
CN: "2.5.4.3",
|
|
42
|
+
surname: "2.5.4.4",
|
|
43
|
+
SN: "2.5.4.4",
|
|
44
|
+
serialNumber: "2.5.4.5",
|
|
45
|
+
countryName: "2.5.4.6",
|
|
46
|
+
C: "2.5.4.6",
|
|
47
|
+
localityName: "2.5.4.7",
|
|
48
|
+
L: "2.5.4.7",
|
|
49
|
+
stateOrProvinceName: "2.5.4.8",
|
|
50
|
+
ST: "2.5.4.8",
|
|
51
|
+
streetAddress: "2.5.4.9",
|
|
52
|
+
street: "2.5.4.9",
|
|
53
|
+
organizationName: "2.5.4.10",
|
|
54
|
+
O: "2.5.4.10",
|
|
55
|
+
organizationalUnitName: "2.5.4.11",
|
|
56
|
+
OU: "2.5.4.11",
|
|
57
|
+
title: "2.5.4.12",
|
|
58
|
+
givenName: "2.5.4.42",
|
|
59
|
+
emailAddress: "1.2.840.113549.1.9.1",
|
|
60
|
+
E: "1.2.840.113549.1.9.1"
|
|
61
|
+
});
|
|
62
|
+
const OID_COUNTRY = "2.5.4.6";
|
|
63
|
+
const OID_EMAIL = "1.2.840.113549.1.9.1";
|
|
64
|
+
const OID_BASIC_CONSTRAINTS = "2.5.29.19";
|
|
65
|
+
const OID_KEY_USAGE = "2.5.29.15";
|
|
66
|
+
const OID_EXT_KEY_USAGE = "2.5.29.37";
|
|
67
|
+
const OID_SUBJECT_ALT_NAME = "2.5.29.17";
|
|
68
|
+
const OID_SUBJECT_KEY_ID = "2.5.29.14";
|
|
69
|
+
const OID_SERVER_AUTH = "1.3.6.1.5.5.7.3.1";
|
|
70
|
+
const OID_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2";
|
|
71
|
+
function derLength(length) {
|
|
72
|
+
if (length < 128) return Buffer.from([length]);
|
|
73
|
+
const bytes = [];
|
|
74
|
+
for (let rest = length; rest > 0; rest = Math.floor(rest / 256)) bytes.unshift(rest & 255);
|
|
75
|
+
return Buffer.from([128 | bytes.length, ...bytes]);
|
|
76
|
+
}
|
|
77
|
+
function tlv(tag, content) {
|
|
78
|
+
return Buffer.concat([
|
|
79
|
+
Buffer.from([tag]),
|
|
80
|
+
derLength(content.length),
|
|
81
|
+
content
|
|
82
|
+
]);
|
|
83
|
+
}
|
|
84
|
+
const sequence = (...items) => tlv(48, Buffer.concat(items));
|
|
85
|
+
const set = (...items) => tlv(49, Buffer.concat(items));
|
|
86
|
+
const explicit = (n, content) => tlv(160 | n, content);
|
|
87
|
+
const octetString = (content) => tlv(4, content);
|
|
88
|
+
const bitString = (content) => tlv(3, Buffer.concat([Buffer.from([0]), content]));
|
|
89
|
+
const DER_NULL = Buffer.from([5, 0]);
|
|
90
|
+
const DER_TRUE = tlv(1, Buffer.from([255]));
|
|
91
|
+
function objectIdentifier(dotted) {
|
|
92
|
+
const [first = 0, second = 0, ...rest] = dotted.split(".").map(Number);
|
|
93
|
+
const bytes = [first * 40 + second];
|
|
94
|
+
for (const arc of rest) {
|
|
95
|
+
const chunk = [arc & 127];
|
|
96
|
+
for (let value = Math.floor(arc / 128); value > 0; value = Math.floor(value / 128)) chunk.unshift(value & 127 | 128);
|
|
97
|
+
bytes.push(...chunk);
|
|
98
|
+
}
|
|
99
|
+
return tlv(6, Buffer.from(bytes));
|
|
100
|
+
}
|
|
101
|
+
/** Entier positif depuis sa forme hexadécimale (octet 0x00 ajouté si besoin). */
|
|
102
|
+
function positiveInteger(hex) {
|
|
103
|
+
let bytes = Buffer.from(hex.length % 2 === 0 ? hex : `0${hex}`, "hex");
|
|
104
|
+
let start = 0;
|
|
105
|
+
while (start < bytes.length - 1 && bytes[start] === 0) start++;
|
|
106
|
+
bytes = bytes.subarray(start);
|
|
107
|
+
if (bytes.length === 0) bytes = Buffer.from([0]);
|
|
108
|
+
if ((bytes[0] ?? 0) & 128) bytes = Buffer.concat([Buffer.from([0]), bytes]);
|
|
109
|
+
return tlv(2, bytes);
|
|
110
|
+
}
|
|
111
|
+
/** RFC 5280 §4.1.2.5 : UTCTime jusqu'en 2049, GeneralizedTime au-delà. */
|
|
112
|
+
function time(date) {
|
|
113
|
+
const iso = date.toISOString();
|
|
114
|
+
const digits = `${iso.slice(0, 4)}${iso.slice(5, 7)}${iso.slice(8, 10)}${iso.slice(11, 13)}${iso.slice(14, 16)}${iso.slice(17, 19)}Z`;
|
|
115
|
+
const year = date.getUTCFullYear();
|
|
116
|
+
if (year >= 1950 && year < 2050) return tlv(23, Buffer.from(digits.slice(2), "ascii"));
|
|
117
|
+
return tlv(24, Buffer.from(digits, "ascii"));
|
|
118
|
+
}
|
|
119
|
+
function attributeOid(attribute) {
|
|
120
|
+
if (attribute.type && /^\d+(\.\d+)+$/.test(attribute.type)) return attribute.type;
|
|
121
|
+
const key = attribute.name ?? attribute.shortName ?? attribute.type ?? "";
|
|
122
|
+
const oid = ATTRIBUTE_OIDS[key];
|
|
123
|
+
if (!oid) throw new Error(`Attribut de certificat inconnu : '${key}'. Noms reconnus : ${Object.keys(ATTRIBUTE_OIDS).join(", ")}.`);
|
|
124
|
+
return oid;
|
|
125
|
+
}
|
|
126
|
+
/** Nom distinctif : un RDN par attribut (RFC 5280 §4.1.2.4). */
|
|
127
|
+
function distinguishedName(attributes) {
|
|
128
|
+
const rdns = attributes.map((attribute) => {
|
|
129
|
+
const oid = attributeOid(attribute);
|
|
130
|
+
if (typeof attribute.value !== "string") throw new Error(`Attribut de certificat '${oid}' sans valeur.`);
|
|
131
|
+
const value = Buffer.from(attribute.value, "utf8");
|
|
132
|
+
const tag = oid === OID_COUNTRY ? 19 : oid === OID_EMAIL ? 22 : 12;
|
|
133
|
+
return set(sequence(objectIdentifier(oid), tlv(tag, value)));
|
|
134
|
+
});
|
|
135
|
+
return sequence(...rdns);
|
|
136
|
+
}
|
|
137
|
+
/** Octets d'une adresse IP pour un SAN `iPAddress` (4 ou 16 octets). */
|
|
138
|
+
function ipAddressBytes(ip) {
|
|
139
|
+
if (isIPv4(ip)) return Buffer.from(ip.split(".").map(Number));
|
|
140
|
+
if (!isIPv6(ip) || ip.includes("%")) throw new Error(`Adresse IP invalide pour un SAN : '${ip}'.`);
|
|
141
|
+
const toGroups = (part) => {
|
|
142
|
+
if (part === "") return [];
|
|
143
|
+
const groups = [];
|
|
144
|
+
for (const piece of part.split(":")) if (piece.includes(".")) {
|
|
145
|
+
const [a = 0, b = 0, c = 0, d = 0] = piece.split(".").map(Number);
|
|
146
|
+
groups.push(a << 8 | b, c << 8 | d);
|
|
147
|
+
} else groups.push(parseInt(piece, 16));
|
|
148
|
+
return groups;
|
|
149
|
+
};
|
|
150
|
+
const [head = "", tail] = ip.split("::");
|
|
151
|
+
const left = toGroups(head);
|
|
152
|
+
const right = tail === void 0 ? [] : toGroups(tail);
|
|
153
|
+
const zeros = new Array(8 - left.length - right.length).fill(0);
|
|
154
|
+
const out = Buffer.alloc(16);
|
|
155
|
+
[
|
|
156
|
+
...left,
|
|
157
|
+
...zeros,
|
|
158
|
+
...right
|
|
159
|
+
].forEach((group, i) => out.writeUInt16BE(group, i * 2));
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Octets d'un SAN `dNSName` — IA5String (RFC 5280 §4.2.1.6).
|
|
164
|
+
*
|
|
165
|
+
* 🔴 Jamais `Buffer.from(nom, "ascii")` : Node y garde l'octet BAS de chaque
|
|
166
|
+
* caractère, et `evil\u012Elocalhost` devient `evil.localhost` — un certificat
|
|
167
|
+
* qui couvre un nom que personne n'a configuré. Hors ASCII imprimable, le nom
|
|
168
|
+
* est REFUSÉ (un IDN s'écrit en punycode, `xn--…`).
|
|
169
|
+
*/
|
|
170
|
+
function dnsNameBytes(dns) {
|
|
171
|
+
if (!/^[\x20-\x7e]+$/u.test(dns)) throw new Error(`Nom DNS invalide pour un SAN : ${JSON.stringify(dns)} — ASCII imprimable seulement (un nom international s'écrit en punycode, xn--…).`);
|
|
172
|
+
return Buffer.from(dns, "ascii");
|
|
173
|
+
}
|
|
174
|
+
function extension(oid, critical, value) {
|
|
175
|
+
return critical ? sequence(objectIdentifier(oid), DER_TRUE, octetString(value)) : sequence(objectIdentifier(oid), octetString(value));
|
|
176
|
+
}
|
|
177
|
+
function readNode(der, offset) {
|
|
178
|
+
const tag = der[offset];
|
|
179
|
+
let length = der[offset + 1];
|
|
180
|
+
if (tag === void 0 || length === void 0) throw new Error("DER tronqué.");
|
|
181
|
+
let start = offset + 2;
|
|
182
|
+
if (length & 128) {
|
|
183
|
+
const count = length & 127;
|
|
184
|
+
length = 0;
|
|
185
|
+
for (let i = 0; i < count; i++) length = length * 256 + (der[start + i] ?? 0);
|
|
186
|
+
start += count;
|
|
187
|
+
}
|
|
188
|
+
const end = start + length;
|
|
189
|
+
if (end > der.length) throw new Error("DER tronqué.");
|
|
190
|
+
return {
|
|
191
|
+
tag,
|
|
192
|
+
start,
|
|
193
|
+
end
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function children(der, node) {
|
|
197
|
+
const out = [];
|
|
198
|
+
for (let offset = node.start; offset < node.end;) {
|
|
199
|
+
const child = readNode(der, offset);
|
|
200
|
+
out.push(child);
|
|
201
|
+
offset = child.end;
|
|
202
|
+
}
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
function decodeObjectIdentifier(bytes) {
|
|
206
|
+
const first = bytes[0] ?? 0;
|
|
207
|
+
const arcs = [Math.min(Math.floor(first / 40), 2), first - Math.min(Math.floor(first / 40), 2) * 40];
|
|
208
|
+
let value = 0;
|
|
209
|
+
for (let i = 1; i < bytes.length; i++) {
|
|
210
|
+
const byte = bytes[i] ?? 0;
|
|
211
|
+
value = value * 128 + (byte & 127);
|
|
212
|
+
if (!(byte & 128)) {
|
|
213
|
+
arcs.push(value);
|
|
214
|
+
value = 0;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return arcs.join(".");
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* OID de l'algorithme de signature d'un certificat DER (champ
|
|
221
|
+
* `signatureAlgorithm`, RFC 5280 §4.1.1.2). Lu dans le DER plutôt que par
|
|
222
|
+
* `X509Certificate.signatureAlgorithmOid`, absent des Node du plancher.
|
|
223
|
+
*
|
|
224
|
+
* @param der - certificat encodé DER (`X509Certificate.raw`)
|
|
225
|
+
* @throws Si le DER n'a pas la forme d'un certificat
|
|
226
|
+
*/
|
|
227
|
+
function signatureAlgorithmOid(der) {
|
|
228
|
+
const [, algorithm] = children(der, readNode(der, 0));
|
|
229
|
+
const oid = algorithm ? children(der, algorithm)[0] : void 0;
|
|
230
|
+
if (oid?.tag !== 6) throw new Error("Certificat DER sans algorithme de signature.");
|
|
231
|
+
return decodeObjectIdentifier(der.subarray(oid.start, oid.end));
|
|
232
|
+
}
|
|
233
|
+
/** Nom lisible d'un OID de signature (l'OID lui-même s'il est inconnu). */
|
|
234
|
+
function signatureAlgorithmName(oid) {
|
|
235
|
+
return SIGNATURE_NAMES[oid] ?? oid;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Entrées DNS et IP d'un `subjectAltName` tel que le rend
|
|
239
|
+
* `X509Certificate.subjectAltName` (`DNS:a, IP Address:1.2.3.4`).
|
|
240
|
+
*/
|
|
241
|
+
function parseSubjectAltName(value) {
|
|
242
|
+
const dns = [];
|
|
243
|
+
const ip = [];
|
|
244
|
+
if (!value) return {
|
|
245
|
+
dns,
|
|
246
|
+
ip
|
|
247
|
+
};
|
|
248
|
+
for (const raw of value.split(/,\s*/)) {
|
|
249
|
+
const colon = raw.indexOf(":");
|
|
250
|
+
if (colon < 0) continue;
|
|
251
|
+
const kind = raw.slice(0, colon);
|
|
252
|
+
let entry = raw.slice(colon + 1);
|
|
253
|
+
if (entry.startsWith("\"")) try {
|
|
254
|
+
entry = String(JSON.parse(entry));
|
|
255
|
+
} catch {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (kind === "DNS") dns.push(entry);
|
|
259
|
+
else if (kind === "IP Address") ip.push(entry);
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
dns,
|
|
263
|
+
ip
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Valeur d'un champ d'un nom distinctif tel que le rend
|
|
268
|
+
* `X509Certificate.subject` / `.issuer` (une ligne `CLÉ=valeur` par champ).
|
|
269
|
+
*/
|
|
270
|
+
function distinguishedNameField(dn, key) {
|
|
271
|
+
for (const line of dn.split("\n")) if (line.startsWith(`${key}=`)) return line.slice(key.length + 1);
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
/** Contenu de la BIT STRING `subjectPublicKey` d'une SPKI DER. */
|
|
275
|
+
function subjectPublicKeyBits(spki) {
|
|
276
|
+
const [, key] = children(spki, readNode(spki, 0));
|
|
277
|
+
if (key?.tag !== 3) throw new Error("Clé publique SPKI sans subjectPublicKey.");
|
|
278
|
+
return spki.subarray(key.start + 1, key.end);
|
|
279
|
+
}
|
|
280
|
+
function signAsync(hash, data, key) {
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
sign(hash, data, key, (error, signature) => {
|
|
283
|
+
if (error) reject(error);
|
|
284
|
+
else resolve(signature);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function toPem(der) {
|
|
289
|
+
return `-----BEGIN CERTIFICATE-----\n${der.toString("base64").replace(/(.{64})/g, "$1\n").replace(/\n$/, "")}\n-----END CERTIFICATE-----\n`;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Fabrique un certificat serveur auto-signé et le rend en PEM.
|
|
293
|
+
*
|
|
294
|
+
* Contenu : version 3, sujet = émetteur, `basicConstraints` cA=false,
|
|
295
|
+
* `keyUsage` (digitalSignature, keyEncipherment — critique),
|
|
296
|
+
* `extKeyUsage` (serverAuth, clientAuth), `subjectAltName` (critique si le
|
|
297
|
+
* sujet est vide, RFC 5280 §4.2.1.6) et `subjectKeyIdentifier` (SHA-1 de la
|
|
298
|
+
* clé publique, méthode 1 de RFC 5280 §4.2.1.2).
|
|
299
|
+
*
|
|
300
|
+
* @param spec - clés, série, validité, noms et hachage
|
|
301
|
+
* @returns le certificat encodé PEM
|
|
302
|
+
* @throws Si un attribut ou une adresse IP est invalide
|
|
303
|
+
*/
|
|
304
|
+
async function createSelfSignedCertificate(spec) {
|
|
305
|
+
const spki = spec.publicKey.export({
|
|
306
|
+
type: "spki",
|
|
307
|
+
format: "der"
|
|
308
|
+
});
|
|
309
|
+
const algorithm = sequence(objectIdentifier(SIGNATURE_OIDS[spec.hash]), DER_NULL);
|
|
310
|
+
const name = distinguishedName(spec.attributes);
|
|
311
|
+
const altNames = [...spec.dns.map((dns) => tlv(130, dnsNameBytes(dns))), ...spec.ip.map((ip) => tlv(135, ipAddressBytes(ip)))];
|
|
312
|
+
const extensions = [
|
|
313
|
+
extension(OID_BASIC_CONSTRAINTS, false, sequence()),
|
|
314
|
+
extension(OID_KEY_USAGE, true, tlv(3, Buffer.from([5, 160]))),
|
|
315
|
+
extension(OID_EXT_KEY_USAGE, false, sequence(objectIdentifier(OID_SERVER_AUTH), objectIdentifier(OID_CLIENT_AUTH)))
|
|
316
|
+
];
|
|
317
|
+
if (altNames.length > 0) extensions.push(extension(OID_SUBJECT_ALT_NAME, spec.attributes.length === 0, sequence(...altNames)));
|
|
318
|
+
extensions.push(extension(OID_SUBJECT_KEY_ID, false, octetString(createHash("sha1").update(subjectPublicKeyBits(spki)).digest())));
|
|
319
|
+
const tbs = sequence(explicit(0, tlv(2, Buffer.from([2]))), positiveInteger(spec.serialHex), algorithm, name, sequence(time(spec.notBefore), time(spec.notAfter)), name, spki, explicit(3, sequence(...extensions)));
|
|
320
|
+
const signature = await signAsync(spec.hash, tbs, spec.privateKey);
|
|
321
|
+
return toPem(sequence(tbs, algorithm, bitString(signature)));
|
|
322
|
+
}
|
|
323
|
+
//#endregion
|
|
324
|
+
export { SHA1_WITH_RSA_OID, createSelfSignedCertificate, distinguishedNameField, ipAddressBytes, parseSubjectAltName, signatureAlgorithmName, signatureAlgorithmOid };
|
|
@@ -47,7 +47,6 @@ var Context = class extends Service {
|
|
|
47
47
|
secure = false;
|
|
48
48
|
security = null;
|
|
49
49
|
cleaned = false;
|
|
50
|
-
isControlledAccess = false;
|
|
51
50
|
validDomain = false;
|
|
52
51
|
finished = false;
|
|
53
52
|
pushAllowed = false;
|
|
@@ -76,6 +75,14 @@ var Context = class extends Service {
|
|
|
76
75
|
session = null;
|
|
77
76
|
cookieSession = null;
|
|
78
77
|
user = null;
|
|
78
|
+
/**
|
|
79
|
+
* Identifiant de l'appelant AUTHENTIFIÉ, posé par le pare-feu ; `null` hors
|
|
80
|
+
* zone ou pour un anonyme. Lisible au démontage, hors de la bulle ALS : c'est
|
|
81
|
+
* ce que lisent le journal d'audit et le profileur. Distinct de `user`, qui
|
|
82
|
+
* lie la session à son principal de SESSION — y écrire l'identité d'un jeton
|
|
83
|
+
* rattacherait la session d'un autre compte.
|
|
84
|
+
*/
|
|
85
|
+
userIdentifier = null;
|
|
79
86
|
waitAsync = false;
|
|
80
87
|
isJson = false;
|
|
81
88
|
_isHtml = null;
|
|
@@ -133,6 +140,7 @@ var Context = class extends Service {
|
|
|
133
140
|
_phaseIndex = null;
|
|
134
141
|
_afterResponseFns = null;
|
|
135
142
|
_afterResponseFired = false;
|
|
143
|
+
requestStore = null;
|
|
136
144
|
_abortController = null;
|
|
137
145
|
metaData = {
|
|
138
146
|
nodefony: {},
|
|
@@ -300,9 +308,28 @@ var Context = class extends Service {
|
|
|
300
308
|
if (this.requestId && RequestContext.getRequestId() === void 0) return RequestContext.run({ requestId: this.requestId }, () => super.log(pci, severity, msgid, msg));
|
|
301
309
|
return super.log(pci, severity, msgid, msg);
|
|
302
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Confie au contexte le magasin `RequestContext` de son unité de travail
|
|
313
|
+
* (requête HTTP, connexion WebSocket), pour qu'il le VIDE à son nettoyage.
|
|
314
|
+
*
|
|
315
|
+
* Un minuteur ou une promesse armé pendant l'unité de travail capture ce
|
|
316
|
+
* magasin et le garde au-delà de la réponse — avec le contexte, l'identité
|
|
317
|
+
* et le jeton du pare-feu. Le vidage ({@link RequestContext.release}) n'y
|
|
318
|
+
* laisse que la corrélation et le scope refermé.
|
|
319
|
+
*
|
|
320
|
+
* @param store - le magasin passé à `RequestContext.run`
|
|
321
|
+
*/
|
|
322
|
+
holdRequestStore(store) {
|
|
323
|
+
this.requestStore = store;
|
|
324
|
+
}
|
|
303
325
|
clean() {
|
|
304
326
|
this.cleaned = true;
|
|
305
327
|
this.httpKernel = null;
|
|
328
|
+
const store = this.requestStore;
|
|
329
|
+
if (store) {
|
|
330
|
+
RequestContext.release(store);
|
|
331
|
+
this.requestStore = null;
|
|
332
|
+
}
|
|
306
333
|
return super.clean();
|
|
307
334
|
}
|
|
308
335
|
/**
|
|
@@ -15,12 +15,30 @@ import { escapeRegExp } from "nodefony";
|
|
|
15
15
|
* Politique de compilation (sûre, ancrée, ReDoS-free) :
|
|
16
16
|
* - string sans `*` → match EXACT ancré (`^...$`, le `.` est littéral).
|
|
17
17
|
* - string avec `*` → wildcard UN label (`*.example.com` → `^[^.]+\.example\.com$`,
|
|
18
|
-
* RFC
|
|
18
|
+
* RFC 9525 TLS-wildcard : matche `img.example.com`, pas `a.b.example.com` ni `example.com`).
|
|
19
19
|
* - `RegExp` → reprise telle quelle (l'auteur assume l'ancrage).
|
|
20
20
|
*
|
|
21
21
|
* Compilation faite UNE fois (boot / enregistrement de route) ; le test par
|
|
22
22
|
* requête est un simple `RegExp.test` sur une liste pré-compilée (zéro alloc hot-path).
|
|
23
23
|
*/
|
|
24
|
+
/**
|
|
25
|
+
* Nom d'hôte d'une autorité brute (`host[:port]`), sans port — la valeur que
|
|
26
|
+
* rendrait `URL.hostname` pour une autorité déjà canonique, sans en payer le
|
|
27
|
+
* parse (`indexOf` + `slice`, #508). Un littéral IPv6 garde ses crochets
|
|
28
|
+
* (`[::1]:5151` → `[::1]`) : couper au premier `:` le réduirait à `[`.
|
|
29
|
+
*
|
|
30
|
+
* @param host - autorité brute (`Host`), éventuellement absente
|
|
31
|
+
* @returns le nom d'hôte, `""` si l'autorité est absente
|
|
32
|
+
*/
|
|
33
|
+
function hostnameOf(host) {
|
|
34
|
+
if (!host) return "";
|
|
35
|
+
if (host.charCodeAt(0) === 91) {
|
|
36
|
+
const end = host.indexOf("]");
|
|
37
|
+
return end === -1 ? host : host.slice(0, end + 1);
|
|
38
|
+
}
|
|
39
|
+
const colon = host.indexOf(":");
|
|
40
|
+
return colon === -1 ? host : host.slice(0, colon);
|
|
41
|
+
}
|
|
24
42
|
const DEV_LOOPBACK = [
|
|
25
43
|
"localhost",
|
|
26
44
|
"127.0.0.1",
|
|
@@ -118,4 +136,4 @@ function isDomainAllowed(regAlias, domain) {
|
|
|
118
136
|
return false;
|
|
119
137
|
}
|
|
120
138
|
//#endregion
|
|
121
|
-
export { compileDomainPattern, compileDomainPatterns, compileTrustedHosts, isDomainAllowed, resolveTrustedHostNames };
|
|
139
|
+
export { compileDomainPattern, compileDomainPatterns, compileTrustedHosts, hostnameOf, isDomainAllowed, resolveTrustedHostNames };
|
|
@@ -3,7 +3,7 @@ import { isIPv4 } from "node:net";
|
|
|
3
3
|
//#region nodefony/src/context/forwarded.ts
|
|
4
4
|
/**
|
|
5
5
|
* Découpe une chaîne sur `sep` au **niveau supérieur uniquement**, en respectant
|
|
6
|
-
* les `quoted-string` RFC
|
|
6
|
+
* les `quoted-string` RFC 9110 §5.6.4 (`value = token / quoted-string`, RFC 7239 §4) : un
|
|
7
7
|
* séparateur à l'intérieur de guillemets (ex. un host quoté, un IPv6) n'est PAS
|
|
8
8
|
* un point de coupe. Les `\"` échappés à l'intérieur des guillemets sont préservés.
|
|
9
9
|
*/
|
|
@@ -40,7 +40,7 @@ function splitTopLevel(input, sep) {
|
|
|
40
40
|
out.push(buf);
|
|
41
41
|
return out;
|
|
42
42
|
}
|
|
43
|
-
/** Retire les guillemets d'une `quoted-string` et déséchappe `\x` (RFC
|
|
43
|
+
/** Retire les guillemets d'une `quoted-string` et déséchappe `\x` (RFC 9110 §5.6.4). */
|
|
44
44
|
function unquote(value) {
|
|
45
45
|
const v = value.trim();
|
|
46
46
|
if (v.length >= 2 && v.startsWith("\"") && v.endsWith("\"")) return v.slice(1, -1).replace(/\\(.)/gu, "$1");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import HttpError from "../../errors/httpError.js";
|
|
2
|
+
import { responseEnded } from "../responseEnded.js";
|
|
2
3
|
import Cookie from "../../cookies/cookie.js";
|
|
3
4
|
import Context from "../Context.js";
|
|
4
5
|
import { sanitizeRequestId } from "../requestId.js";
|
|
@@ -7,14 +8,51 @@ import HttpResponse from "./Response.js";
|
|
|
7
8
|
import Http2Request from "../http2/Request.js";
|
|
8
9
|
import Http2Response from "../http2/Response.js";
|
|
9
10
|
import { describeSessionStoreFailure } from "../../session/sessionStoreFailure.js";
|
|
10
|
-
import {
|
|
11
|
+
import { openSseStream } from "./SseStream.js";
|
|
12
|
+
import { finallyMaybe, isPromise, thenMaybe, typeOf } from "nodefony";
|
|
11
13
|
import url, { URL } from "node:url";
|
|
12
14
|
//#region nodefony/src/context/http/HttpContext.ts
|
|
15
|
+
/**
|
|
16
|
+
* Même URL (chemin, requête, hôte) sous un autre schéma et un autre port.
|
|
17
|
+
* `port` absent → port par défaut du schéma (omis de l'URL).
|
|
18
|
+
*
|
|
19
|
+
* @param from - URL de la requête.
|
|
20
|
+
* @param scheme - schéma cible.
|
|
21
|
+
* @param port - port du serveur cible, s'il est connu.
|
|
22
|
+
* @returns l'URL absolue de redirection.
|
|
23
|
+
*/
|
|
24
|
+
function switchUrlScheme(from, scheme, port) {
|
|
25
|
+
const target = new URL(from.href);
|
|
26
|
+
target.protocol = `${scheme}:`;
|
|
27
|
+
target.port = port === void 0 ? "" : String(port);
|
|
28
|
+
return target.href;
|
|
29
|
+
}
|
|
13
30
|
const socketActiveContext = /* @__PURE__ */ new WeakMap();
|
|
14
31
|
const socketTimeoutArmed = /* @__PURE__ */ new WeakSet();
|
|
32
|
+
/**
|
|
33
|
+
* Gestionnaire `timeout` PERMANENT d'une socket h1, fabriqué hors de toute
|
|
34
|
+
* méthode : une fermeture créée dans `HttpContext.setTimeout()` partageait
|
|
35
|
+
* l'environnement d'une autre qui lit `this`, et retenait ainsi le contexte de
|
|
36
|
+
* la PREMIÈRE requête de la connexion tant que celle-ci vivait (#561).
|
|
37
|
+
*
|
|
38
|
+
* @param socket - la socket h1 dont on route le délai d'inactivité
|
|
39
|
+
* @returns l'écouteur à attacher une fois par socket
|
|
40
|
+
*/
|
|
41
|
+
function idleSocketTimeoutHandler(socket) {
|
|
42
|
+
return () => {
|
|
43
|
+
const ctx = socketActiveContext.get(socket);
|
|
44
|
+
if (ctx !== void 0 && !ctx.cleaned && !ctx.response.response?.writableEnded) {
|
|
45
|
+
ctx._onTimeout();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
socket.destroy();
|
|
49
|
+
};
|
|
50
|
+
}
|
|
15
51
|
const PERF_PROBE_SUB = process.env.NF_PERF_PROBE === "1";
|
|
16
52
|
var HttpContext = class extends Context {
|
|
17
53
|
proxy = null;
|
|
54
|
+
/** Socket h1 inscrite dans `socketActiveContext` — retirée par `clean()`. */
|
|
55
|
+
activeSocket = null;
|
|
18
56
|
isRedirect = false;
|
|
19
57
|
sended = false;
|
|
20
58
|
request;
|
|
@@ -126,24 +164,39 @@ var HttpContext = class extends Context {
|
|
|
126
164
|
if (!res) return;
|
|
127
165
|
if (this.response.stream) {
|
|
128
166
|
res.setTimeout(this.response.timeout, () => {
|
|
129
|
-
if (!
|
|
167
|
+
if (!responseEnded(res)) this._onTimeout();
|
|
130
168
|
});
|
|
131
169
|
return;
|
|
132
170
|
}
|
|
133
171
|
const socket = res.socket;
|
|
134
172
|
if (!socket) return;
|
|
135
173
|
socketActiveContext.set(socket, this);
|
|
174
|
+
this.activeSocket = socket;
|
|
136
175
|
const ms = this.response.timeout;
|
|
137
176
|
if (socket.timeout !== ms) socket.setTimeout(ms);
|
|
138
177
|
if (!socketTimeoutArmed.has(socket)) {
|
|
139
178
|
socketTimeoutArmed.add(socket);
|
|
140
|
-
socket.on("timeout", ()
|
|
141
|
-
const ctx = socketActiveContext.get(socket);
|
|
142
|
-
if (ctx && !ctx.response.response?.writableEnded) ctx._onTimeout();
|
|
143
|
-
});
|
|
179
|
+
socket.on("timeout", idleSocketTimeoutHandler(socket));
|
|
144
180
|
}
|
|
145
181
|
}
|
|
146
182
|
/**
|
|
183
|
+
* Nettoie le contexte, et le DÉTACHE de sa socket h1 : la connexion
|
|
184
|
+
* persistante survit à la requête, et l'entrée de `socketActiveContext`
|
|
185
|
+
* retiendrait sinon tout le contexte jusqu'à sa fermeture. Seule l'entrée qui
|
|
186
|
+
* pointe encore sur CE contexte est retirée — une requête suivante sur la
|
|
187
|
+
* même socket a pu la réécrire. Le gestionnaire `timeout` de la socket ne
|
|
188
|
+
* trouve alors rien et prend le chemin « socket inactive → `destroy()` »,
|
|
189
|
+
* celui qu'il prenait déjà pour un contexte nettoyé.
|
|
190
|
+
*/
|
|
191
|
+
clean() {
|
|
192
|
+
const socket = this.activeSocket;
|
|
193
|
+
if (socket) {
|
|
194
|
+
if (socketActiveContext.get(socket) === this) socketActiveContext.delete(socket);
|
|
195
|
+
this.activeSocket = null;
|
|
196
|
+
}
|
|
197
|
+
super.clean();
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
147
200
|
* Sérialise la donnée selon son type (JSON, HTML, texte), pose statut et
|
|
148
201
|
* en-têtes, puis l'envoie ({@link send}). Synchrone quand l'envoi l'est (#505).
|
|
149
202
|
*
|
|
@@ -229,7 +282,7 @@ var HttpContext = class extends Context {
|
|
|
229
282
|
}
|
|
230
283
|
/** Hook `onSend` (attendu seulement s'il a un écouteur), puis en-têtes et corps. */
|
|
231
284
|
emitAndWrite(body, encoding) {
|
|
232
|
-
if (body) this.response.setBody(body);
|
|
285
|
+
if (body || body === "") this.response.setBody(body);
|
|
233
286
|
if (this.listenerCount("onSend") > 0) return this.fireAsync("onSend", this.response, this).then(() => this.writeHeadAndBody(body, encoding));
|
|
234
287
|
return this.writeHeadAndBody(body, encoding);
|
|
235
288
|
}
|
|
@@ -265,6 +318,18 @@ var HttpContext = class extends Context {
|
|
|
265
318
|
return this.close();
|
|
266
319
|
});
|
|
267
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* Répond par un flux d'événements serveur (`text/event-stream`).
|
|
323
|
+
*
|
|
324
|
+
* La requête ne se termine qu'à `close()` du flux, ou au départ du client.
|
|
325
|
+
*
|
|
326
|
+
* @param options - battement de cœur, délai `retry:` initial.
|
|
327
|
+
* @returns le flux, ou sa promesse quand une session est à sauver d'abord.
|
|
328
|
+
* @throws HttpError 500 quand la réponse est déjà partie.
|
|
329
|
+
*/
|
|
330
|
+
openSse(options) {
|
|
331
|
+
return openSseStream(this, options);
|
|
332
|
+
}
|
|
268
333
|
flush(chunk, encoding) {
|
|
269
334
|
return this.response.flush(chunk, encoding);
|
|
270
335
|
}
|
|
@@ -288,41 +353,27 @@ var HttpContext = class extends Context {
|
|
|
288
353
|
if (typeof Url === "object") return this.response.redirect(url.format(Url), status, headers);
|
|
289
354
|
return this.response.redirect(Url, status, headers);
|
|
290
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Redirige vers la même URL en HTTPS, sur le port du serveur HTTPS lié —
|
|
358
|
+
* ou le port par défaut derrière un proxy, dont le port public est inconnu.
|
|
359
|
+
*
|
|
360
|
+
* @param status - code de redirection (défaut de `redirect`).
|
|
361
|
+
* @param headers - en-têtes ajoutés à la réponse.
|
|
362
|
+
*/
|
|
291
363
|
redirectHttps(status, headers) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (this.proxy) urlExtend = {
|
|
295
|
-
protocol: "https",
|
|
296
|
-
href: "",
|
|
297
|
-
host: ""
|
|
298
|
-
};
|
|
299
|
-
else urlExtend = {
|
|
300
|
-
protocol: "https",
|
|
301
|
-
port: this.httpKernel?.httpsPort || 443,
|
|
302
|
-
href: "",
|
|
303
|
-
host: ""
|
|
304
|
-
};
|
|
305
|
-
const urlChange = extend({}, this.request.url, urlExtend);
|
|
306
|
-
const newUrl = url.format(urlChange);
|
|
307
|
-
return this.redirect(newUrl, status, headers);
|
|
364
|
+
const port = this.proxy ? void 0 : this.httpKernel?.httpsPort;
|
|
365
|
+
return this.redirect(switchUrlScheme(this.request.url, "https", port), status, headers);
|
|
308
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Redirige vers la même URL en HTTP, sur le port du serveur HTTP lié — ou
|
|
369
|
+
* le port par défaut derrière un proxy.
|
|
370
|
+
*
|
|
371
|
+
* @param status - code de redirection (défaut de `redirect`).
|
|
372
|
+
* @param headers - en-têtes ajoutés à la réponse.
|
|
373
|
+
*/
|
|
309
374
|
redirectHttp(status, headers) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (this.proxy) urlExtend = {
|
|
313
|
-
protocol: "http",
|
|
314
|
-
href: "",
|
|
315
|
-
host: ""
|
|
316
|
-
};
|
|
317
|
-
else urlExtend = {
|
|
318
|
-
protocol: "http",
|
|
319
|
-
port: this.httpKernel?.httpPort || 80,
|
|
320
|
-
href: "",
|
|
321
|
-
host: ""
|
|
322
|
-
};
|
|
323
|
-
const urlChange = extend({}, this.request.url, urlExtend);
|
|
324
|
-
const newUrl = url.format(urlChange);
|
|
325
|
-
return this.redirect(newUrl, status, headers);
|
|
375
|
+
const port = this.proxy ? void 0 : this.httpKernel?.httpPort;
|
|
376
|
+
return this.redirect(switchUrlScheme(this.request.url, "http", port), status, headers);
|
|
326
377
|
}
|
|
327
378
|
getHostName() {
|
|
328
379
|
return this.request.getHostName();
|
|
@@ -364,4 +415,4 @@ var HttpContext = class extends Context {
|
|
|
364
415
|
}
|
|
365
416
|
};
|
|
366
417
|
//#endregion
|
|
367
|
-
export { HttpContext as default };
|
|
418
|
+
export { HttpContext as default, switchUrlScheme };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import HttpError from "../../errors/httpError.js";
|
|
2
2
|
import { hasForwardingHeaders, resolveForwarded } from "../forwarded.js";
|
|
3
|
+
import { hostnameOf } from "../domainMatcher.js";
|
|
3
4
|
import { Parser, ParserJson, ParserQs, ParserXml, acceptParser } from "./parser.js";
|
|
4
5
|
import { isCanonicalAuthority, splitTarget } from "./urlFastPath.js";
|
|
5
6
|
import { RequestContext, extend, useConfig } from "nodefony";
|
|
@@ -56,6 +57,8 @@ var HttpRequest = class {
|
|
|
56
57
|
context;
|
|
57
58
|
request;
|
|
58
59
|
#url = null;
|
|
60
|
+
#rawHostname;
|
|
61
|
+
#rawHostSource;
|
|
59
62
|
headers = {};
|
|
60
63
|
host = "";
|
|
61
64
|
method;
|
|
@@ -162,7 +165,9 @@ var HttpRequest = class {
|
|
|
162
165
|
if (PERF_PROBE_SUB) perfMark("reqProxyNs");
|
|
163
166
|
this.method = this.getMethod();
|
|
164
167
|
this.host = this.getHost();
|
|
165
|
-
this
|
|
168
|
+
this.#rawHostSource = this.host;
|
|
169
|
+
this.#rawHostname = hostnameOf(this.host);
|
|
170
|
+
this.hostname = this.#rawHostname;
|
|
166
171
|
this.sUrl = this.getFullUrl(request);
|
|
167
172
|
const fast = (this.scheme === "http" || this.scheme === "https") && isCanonicalAuthority(this.host, this.scheme) ? splitTarget(this.getRawTarget()) : null;
|
|
168
173
|
if (fast !== null) {
|
|
@@ -534,9 +539,9 @@ var HttpRequest = class {
|
|
|
534
539
|
}
|
|
535
540
|
getHostName(host) {
|
|
536
541
|
if (this.#url?.hostname) return this.#url.hostname;
|
|
537
|
-
if (host) return host
|
|
538
|
-
|
|
539
|
-
return
|
|
542
|
+
if (host) return hostnameOf(host);
|
|
543
|
+
host = this.getHost();
|
|
544
|
+
return host === this.#rawHostSource ? this.#rawHostname : hostnameOf(host);
|
|
540
545
|
}
|
|
541
546
|
getHost() {
|
|
542
547
|
return this.request.headers.host;
|