@nodefony/http 10.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +544 -0
- package/README.md +77 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js +8 -0
- package/dist/index.js +108 -0
- package/dist/nodefony/command/assetsPublishCommand.js +102 -0
- package/dist/nodefony/command/certificatesCommand.js +47 -0
- package/dist/nodefony/command/networkCommand.js +27 -0
- package/dist/nodefony/command/proxyGenerateCommand.js +66 -0
- package/dist/nodefony/config/config.js +335 -0
- package/dist/nodefony/config/defineModuleConfig.js +93 -0
- package/dist/nodefony/interfaces/IContext.js +1 -0
- package/dist/nodefony/interfaces/ICookie.js +1 -0
- package/dist/nodefony/interfaces/IErrorRenderer.js +1 -0
- package/dist/nodefony/interfaces/IHttpConfig.js +1 -0
- package/dist/nodefony/interfaces/IHttpKernel.js +1 -0
- package/dist/nodefony/interfaces/IRequest.js +1 -0
- package/dist/nodefony/interfaces/IRequestLogger.js +1 -0
- package/dist/nodefony/interfaces/IResponse.js +1 -0
- package/dist/nodefony/interfaces/ISession.js +1 -0
- package/dist/nodefony/interfaces/IUpload.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +376 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +73 -0
- package/dist/nodefony/service/audit-logger.js +159 -0
- package/dist/nodefony/service/certificates.js +545 -0
- package/dist/nodefony/service/error-renderer.js +320 -0
- package/dist/nodefony/service/http-kernel.js +948 -0
- package/dist/nodefony/service/pretty-request-logger.js +72 -0
- package/dist/nodefony/service/request-logger.js +54 -0
- package/dist/nodefony/service/servers/clientError.js +20 -0
- package/dist/nodefony/service/servers/server-http.js +135 -0
- package/dist/nodefony/service/servers/server-https.js +204 -0
- package/dist/nodefony/service/servers/server-static.js +192 -0
- package/dist/nodefony/service/servers/server-websocket-secure.js +104 -0
- package/dist/nodefony/service/servers/server-websocket.js +104 -0
- package/dist/nodefony/service/servers/serverShutdown.js +31 -0
- package/dist/nodefony/service/servers/wsHeartbeat.js +64 -0
- package/dist/nodefony/service/sessions/sessions-service.js +580 -0
- package/dist/nodefony/service/trace.js +72 -0
- package/dist/nodefony/service/upload/upload-service.js +171 -0
- package/dist/nodefony/src/assets/collectAssets.js +34 -0
- package/dist/nodefony/src/assets/prebuiltUi.js +125 -0
- package/dist/nodefony/src/context/Context.js +415 -0
- package/dist/nodefony/src/context/domainMatcher.js +88 -0
- package/dist/nodefony/src/context/forwarded.js +185 -0
- package/dist/nodefony/src/context/http/HttpContext.js +309 -0
- package/dist/nodefony/src/context/http/Request.js +543 -0
- package/dist/nodefony/src/context/http/Response.js +368 -0
- package/dist/nodefony/src/context/http/parser.js +188 -0
- package/dist/nodefony/src/context/http/urlFastPath.js +103 -0
- package/dist/nodefony/src/context/http2/Request.js +29 -0
- package/dist/nodefony/src/context/http2/Response.js +97 -0
- package/dist/nodefony/src/context/metaData.js +47 -0
- package/dist/nodefony/src/context/requestId.js +41 -0
- package/dist/nodefony/src/context/trustProxy.js +167 -0
- package/dist/nodefony/src/context/websocket/Response.js +181 -0
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +389 -0
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +56 -0
- package/dist/nodefony/src/context/websocket/wsLogContent.js +68 -0
- package/dist/nodefony/src/cookies/cookie.js +258 -0
- package/dist/nodefony/src/errors/httpError.js +69 -0
- package/dist/nodefony/src/profiler/FrameProfile.js +95 -0
- package/dist/nodefony/src/profiler/Profiler.js +139 -0
- package/dist/nodefony/src/proxy/generateProxyConfig.js +157 -0
- package/dist/nodefony/src/rateLimit/IRateLimitStore.js +1 -0
- package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +146 -0
- package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +64 -0
- package/dist/nodefony/src/rateLimit/rateLimitFilters.js +20 -0
- package/dist/nodefony/src/servers/portBinder.js +114 -0
- package/dist/nodefony/src/session/session.js +390 -0
- package/dist/nodefony/src/session/storage/MemorySessionStorage.js +185 -0
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +137 -0
- package/dist/nodefony/src/session/storage/sessionFilters.js +83 -0
- package/dist/nodefony/src/session/storage/sessionSort.js +53 -0
- package/dist/types/index.d.ts +83 -0
- package/dist/types/nodefony/command/assetsPublishCommand.d.ts +23 -0
- package/dist/types/nodefony/command/certificatesCommand.d.ts +17 -0
- package/dist/types/nodefony/command/networkCommand.d.ts +8 -0
- package/dist/types/nodefony/command/proxyGenerateCommand.d.ts +19 -0
- package/dist/types/nodefony/config/config.d.ts +197 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +39 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +138 -0
- package/dist/types/nodefony/interfaces/ICookie.d.ts +47 -0
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +55 -0
- package/dist/types/nodefony/interfaces/IHttpConfig.d.ts +12 -0
- package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -0
- package/dist/types/nodefony/interfaces/IRequest.d.ts +35 -0
- package/dist/types/nodefony/interfaces/IRequestLogger.d.ts +31 -0
- package/dist/types/nodefony/interfaces/IResponse.d.ts +39 -0
- package/dist/types/nodefony/interfaces/ISession.d.ts +283 -0
- package/dist/types/nodefony/interfaces/IUpload.d.ts +66 -0
- package/dist/types/nodefony/interfaces/index.d.ts +7 -0
- package/dist/types/nodefony/service/HttpAdminApi.d.ts +18 -0
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +23 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +143 -0
- package/dist/types/nodefony/service/certificates.d.ts +246 -0
- package/dist/types/nodefony/service/error-renderer.d.ts +74 -0
- package/dist/types/nodefony/service/http-kernel.d.ts +377 -0
- package/dist/types/nodefony/service/pretty-request-logger.d.ts +25 -0
- package/dist/types/nodefony/service/request-logger.d.ts +18 -0
- package/dist/types/nodefony/service/servers/clientError.d.ts +14 -0
- package/dist/types/nodefony/service/servers/server-http.d.ts +42 -0
- package/dist/types/nodefony/service/servers/server-https.d.ts +41 -0
- package/dist/types/nodefony/service/servers/server-static.d.ts +62 -0
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +29 -0
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +29 -0
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +27 -0
- package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +46 -0
- package/dist/types/nodefony/service/sessions/sessions-service.d.ts +218 -0
- package/dist/types/nodefony/service/trace.d.ts +39 -0
- package/dist/types/nodefony/service/upload/upload-service.d.ts +61 -0
- package/dist/types/nodefony/src/assets/collectAssets.d.ts +35 -0
- package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +99 -0
- package/dist/types/nodefony/src/context/Context.d.ts +195 -0
- package/dist/types/nodefony/src/context/domainMatcher.d.ts +67 -0
- package/dist/types/nodefony/src/context/forwarded.d.ts +95 -0
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +85 -0
- package/dist/types/nodefony/src/context/http/Request.d.ts +203 -0
- package/dist/types/nodefony/src/context/http/Response.d.ts +68 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +65 -0
- package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +52 -0
- package/dist/types/nodefony/src/context/http2/Request.d.ts +14 -0
- package/dist/types/nodefony/src/context/http2/Response.d.ts +20 -0
- package/dist/types/nodefony/src/context/metaData.d.ts +58 -0
- package/dist/types/nodefony/src/context/requestId.d.ts +28 -0
- package/dist/types/nodefony/src/context/trustProxy.d.ts +77 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +53 -0
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +125 -0
- package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +73 -0
- package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +37 -0
- package/dist/types/nodefony/src/cookies/cookie.d.ts +88 -0
- package/dist/types/nodefony/src/errors/httpError.d.ts +15 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +110 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +192 -0
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +76 -0
- package/dist/types/nodefony/src/rateLimit/IRateLimitStore.d.ts +98 -0
- package/dist/types/nodefony/src/rateLimit/MemoryRateLimitStore.d.ts +40 -0
- package/dist/types/nodefony/src/rateLimit/WsConnectionCounter.d.ts +37 -0
- package/dist/types/nodefony/src/rateLimit/rateLimitFilters.d.ts +18 -0
- package/dist/types/nodefony/src/servers/portBinder.d.ts +102 -0
- package/dist/types/nodefony/src/session/session.d.ts +171 -0
- package/dist/types/nodefony/src/session/storage/MemorySessionStorage.d.ts +77 -0
- package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +81 -0
- package/dist/types/nodefony/src/session/storage/sessionFilters.d.ts +102 -0
- package/dist/types/nodefony/src/session/storage/sessionSort.d.ts +45 -0
- package/docs/cookies.md +365 -0
- package/docs/index.md +163 -0
- package/docs/observabilite.md +460 -0
- package/docs/rate-limit.md +372 -0
- package/docs/servers.md +935 -0
- package/docs/session.md +768 -0
- package/docs/upload.md +460 -0
- package/package.json +101 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import Cookie from "../../cookies/cookie.js";
|
|
2
|
+
import { typeOf } from "nodefony";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import mime from "mime-types";
|
|
5
|
+
//#region nodefony/src/context/http/Response.ts
|
|
6
|
+
const ANSI_REGEX = new RegExp(["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"].join("|"), "g");
|
|
7
|
+
const stripAnsi = function(val) {
|
|
8
|
+
return typeof val === "string" ? val.replace(ANSI_REGEX, "") : val;
|
|
9
|
+
};
|
|
10
|
+
const REDIRECT_STATUS_CODES = /* @__PURE__ */ new Set([
|
|
11
|
+
301,
|
|
12
|
+
302,
|
|
13
|
+
303,
|
|
14
|
+
307,
|
|
15
|
+
308
|
|
16
|
+
]);
|
|
17
|
+
const NO_CONTENT_LENGTH_METHODS = /* @__PURE__ */ new Set([
|
|
18
|
+
"HEAD",
|
|
19
|
+
"OPTIONS",
|
|
20
|
+
"TRACE"
|
|
21
|
+
]);
|
|
22
|
+
const NO_CONTENT_LENGTH_STATUS = /* @__PURE__ */ new Set([204, 304]);
|
|
23
|
+
var HttpResponse = class {
|
|
24
|
+
context;
|
|
25
|
+
response;
|
|
26
|
+
statusCode = 200;
|
|
27
|
+
statusMessage = "";
|
|
28
|
+
flushing = false;
|
|
29
|
+
encoding = "utf-8";
|
|
30
|
+
body = null;
|
|
31
|
+
contentType = "application/octet-stream";
|
|
32
|
+
headers = {};
|
|
33
|
+
timeout;
|
|
34
|
+
cookies = {};
|
|
35
|
+
constructor(response, context) {
|
|
36
|
+
this.context = context;
|
|
37
|
+
this.response = response;
|
|
38
|
+
this.timeout = this.context?.httpKernel?.responseTimeout[this.context.type];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Filet posé juste avant l'émission des en-têtes : si aucun `Content-Type`
|
|
42
|
+
* n'a été choisi (ni négociation, ni `render`, ni controller), émet le défaut
|
|
43
|
+
* `this.contentType` (application/octet-stream) — comportement identique à
|
|
44
|
+
* l'ancienne pose au constructeur, sans le ping-pong set/remove/re-set.
|
|
45
|
+
*/
|
|
46
|
+
ensureContentTypeHeader() {
|
|
47
|
+
if (this.response && !this.response.hasHeader("content-type")) this.response.setHeader("content-type", this.contentType);
|
|
48
|
+
}
|
|
49
|
+
clean() {
|
|
50
|
+
this.response = null;
|
|
51
|
+
this.body = null;
|
|
52
|
+
this.cookies = {};
|
|
53
|
+
}
|
|
54
|
+
isHeaderSent() {
|
|
55
|
+
if (this.response) return this.response.headersSent;
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
isHtml() {
|
|
59
|
+
let ct = this.getHeader("Content-Type");
|
|
60
|
+
return mime.extension(ct) === "html";
|
|
61
|
+
}
|
|
62
|
+
setTimeout(ms) {
|
|
63
|
+
this.timeout = ms;
|
|
64
|
+
}
|
|
65
|
+
addCookie(cookie) {
|
|
66
|
+
if (cookie instanceof Cookie) return this.cookies[cookie.name] = cookie;
|
|
67
|
+
throw new Error("Response addCookies not valid cookies");
|
|
68
|
+
}
|
|
69
|
+
deleteCookie(cookie) {
|
|
70
|
+
if (cookie instanceof Cookie) {
|
|
71
|
+
if (this.cookies[cookie.name]) {
|
|
72
|
+
delete this.cookies[cookie.name];
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
throw new Error("Response delCookie not valid cookies");
|
|
78
|
+
}
|
|
79
|
+
deleteCookieByName(name) {
|
|
80
|
+
if (this.cookies[name]) {
|
|
81
|
+
delete this.cookies[name];
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
setCookies() {
|
|
87
|
+
const names = Object.keys(this.cookies);
|
|
88
|
+
if (names.length === 0) return;
|
|
89
|
+
if (names.length === 1) return this.setCookie(this.cookies[names[0]]);
|
|
90
|
+
const serialized = [];
|
|
91
|
+
for (const name of names) {
|
|
92
|
+
const s = this.cookies[name].serialize();
|
|
93
|
+
this.log(`ADD COOKIE ==> ${s}`, "DEBUG");
|
|
94
|
+
serialized.push(s);
|
|
95
|
+
}
|
|
96
|
+
return this.setHeader("Set-Cookie", serialized);
|
|
97
|
+
}
|
|
98
|
+
setCookie(cookie) {
|
|
99
|
+
const serialize = cookie.serialize();
|
|
100
|
+
this.log(`ADD COOKIE ==> ${serialize}`, "DEBUG");
|
|
101
|
+
return this.setHeader("Set-Cookie", serialize);
|
|
102
|
+
}
|
|
103
|
+
setHeader(name, value) {
|
|
104
|
+
if (this.response) {
|
|
105
|
+
if (this.flushing) {
|
|
106
|
+
const obj = {};
|
|
107
|
+
obj[name] = value;
|
|
108
|
+
return this.addTrailers(obj);
|
|
109
|
+
}
|
|
110
|
+
if (!this.response.headersSent) {
|
|
111
|
+
const lower = name.toLowerCase();
|
|
112
|
+
if (lower === "vary") return this.response.setHeader(lower, this.#mergeVary(value));
|
|
113
|
+
return this.response.setHeader(lower, value);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Union des tokens `Vary` déjà posés et de ceux qu'on ajoute, sans doublon.
|
|
119
|
+
*
|
|
120
|
+
* `*` absorbe tout (RFC 9110 : « la réponse varie sur des paramètres non
|
|
121
|
+
* exprimables ») — le conserver seul évite une liste qui contredirait ce total.
|
|
122
|
+
* Comparaison insensible à la casse : les noms d'en-têtes le sont.
|
|
123
|
+
*
|
|
124
|
+
* Coût : ne s'exécute QUE sur `Vary`, jamais sur les autres en-têtes.
|
|
125
|
+
*/
|
|
126
|
+
#mergeVary(value) {
|
|
127
|
+
const incoming = (Array.isArray(value) ? value.join(",") : String(value)).split(",").map((t) => t.trim()).filter(Boolean);
|
|
128
|
+
const current = this.response?.getHeader?.("vary");
|
|
129
|
+
const all = [...(Array.isArray(current) ? current.join(",") : String(current ?? "")).split(",").map((t) => t.trim()).filter(Boolean), ...incoming];
|
|
130
|
+
if (all.some((t) => t === "*")) return "*";
|
|
131
|
+
const seen = /* @__PURE__ */ new Set();
|
|
132
|
+
const merged = [];
|
|
133
|
+
for (const token of all) {
|
|
134
|
+
const key = token.toLowerCase();
|
|
135
|
+
if (seen.has(key)) continue;
|
|
136
|
+
seen.add(key);
|
|
137
|
+
merged.push(token);
|
|
138
|
+
}
|
|
139
|
+
return merged.join(", ");
|
|
140
|
+
}
|
|
141
|
+
setHeaders(obj) {
|
|
142
|
+
if (!this.response?.headersSent) {
|
|
143
|
+
if (obj instanceof Object) for (const head in obj) {
|
|
144
|
+
const value = obj[head];
|
|
145
|
+
if (value !== void 0) this.setHeader(head, value);
|
|
146
|
+
}
|
|
147
|
+
return this.headers = this.response?.getHeaders();
|
|
148
|
+
}
|
|
149
|
+
this.log("headers already sended ", "WARNING");
|
|
150
|
+
return this.headers = this.response.getHeaders();
|
|
151
|
+
}
|
|
152
|
+
setContentType(type, encoding) {
|
|
153
|
+
if (type && encoding) {
|
|
154
|
+
let mytype = mime.contentType(type);
|
|
155
|
+
if (mytype) {
|
|
156
|
+
mytype = mytype.split(";")[0];
|
|
157
|
+
this.contentType = mytype;
|
|
158
|
+
this.encoding = encoding;
|
|
159
|
+
if (mytype === "application/json" || mytype.endsWith("+json")) return this.setHeader("Content-Type", mytype);
|
|
160
|
+
return this.setHeader("Content-Type", `${mytype}; charset=${encoding}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (type && !encoding) {
|
|
164
|
+
const mytype = mime.contentType(type);
|
|
165
|
+
if (mytype) {
|
|
166
|
+
this.contentType = mytype;
|
|
167
|
+
let charset = mime.charset(this.contentType);
|
|
168
|
+
if (charset) this.encoding = charset;
|
|
169
|
+
return this.setHeader("Content-Type", mytype);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return this.setHeader("Content-Type", `${this.contentType}; charset=${this.encoding}`);
|
|
173
|
+
}
|
|
174
|
+
setFileMimeType(type, encoding) {
|
|
175
|
+
let myType = this.getMimeType(type);
|
|
176
|
+
if (!myType) {
|
|
177
|
+
this.log(`Content-Type not valid !!! : ${type}`, "WARNING");
|
|
178
|
+
myType = "application/octet-stream";
|
|
179
|
+
}
|
|
180
|
+
this.contentType = myType;
|
|
181
|
+
return this.setContentType(myType, encoding || this.encoding);
|
|
182
|
+
}
|
|
183
|
+
setContentTypeByExtension(extention) {
|
|
184
|
+
const ismime = mime.contentType(extention);
|
|
185
|
+
if (ismime) {
|
|
186
|
+
this.contentType = ismime;
|
|
187
|
+
let charset = mime.charset(this.contentType);
|
|
188
|
+
if (charset) this.encoding = charset;
|
|
189
|
+
return this.setHeader("Content-Type", ismime);
|
|
190
|
+
}
|
|
191
|
+
this.log(`setContentTypeByExtension: ${extention} not found`, "WARNING");
|
|
192
|
+
}
|
|
193
|
+
getMimeType(filenameOrExt) {
|
|
194
|
+
return mime.lookup(filenameOrExt);
|
|
195
|
+
}
|
|
196
|
+
setEncoding(encoding) {
|
|
197
|
+
return this.encoding = encoding;
|
|
198
|
+
}
|
|
199
|
+
setStatusCode(status, message) {
|
|
200
|
+
if (status && typeof status !== "number") {
|
|
201
|
+
status = parseInt(status, 10);
|
|
202
|
+
if (isNaN(status)) status = 500;
|
|
203
|
+
}
|
|
204
|
+
this.statusCode = status || this.statusCode;
|
|
205
|
+
if (message) {
|
|
206
|
+
const ascii = stripAnsi(message).replace(/[^\x20-\x7E]/g, "").trim();
|
|
207
|
+
this.statusMessage = ascii || (http.STATUS_CODES[this.statusCode] ?? "Unknown Error");
|
|
208
|
+
} else if (!this.statusMessage) {
|
|
209
|
+
if (http.STATUS_CODES[this.statusCode]) this.statusMessage = http.STATUS_CODES[this.statusCode];
|
|
210
|
+
else this.statusMessage = http.STATUS_CODES[500];
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
code: this.statusCode,
|
|
214
|
+
message: this.statusMessage
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
getStatus() {
|
|
218
|
+
return {
|
|
219
|
+
code: this.getStatusCode(),
|
|
220
|
+
message: this.getStatusMessage()
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
getStatusCode() {
|
|
224
|
+
return this.statusCode;
|
|
225
|
+
}
|
|
226
|
+
getStatusMessage(code) {
|
|
227
|
+
if (code) {
|
|
228
|
+
if (this.response) return http.STATUS_CODES[code] || this.statusMessage || this.response.statusMessage;
|
|
229
|
+
}
|
|
230
|
+
if (this.response) return this.statusMessage || this.response.statusMessage || http.STATUS_CODES[this.statusCode];
|
|
231
|
+
return this.statusMessage || http.STATUS_CODES[this.statusCode];
|
|
232
|
+
}
|
|
233
|
+
setBody(ele, encoding) {
|
|
234
|
+
if (typeof ele === "string") this.body = Buffer.from(ele, encoding || this.encoding);
|
|
235
|
+
else if (ele instanceof ArrayBuffer || ele instanceof SharedArrayBuffer) this.body = Buffer.from(ele);
|
|
236
|
+
else if (ArrayBuffer.isView(ele) && ele.buffer instanceof ArrayBuffer) this.body = Buffer.from(ele.buffer, ele.byteOffset, ele.byteLength);
|
|
237
|
+
else try {
|
|
238
|
+
this.body = Buffer.from(JSON.stringify(ele));
|
|
239
|
+
} catch (e) {
|
|
240
|
+
this.body = Buffer.from(String(ele));
|
|
241
|
+
}
|
|
242
|
+
return this.body;
|
|
243
|
+
}
|
|
244
|
+
setLength(body) {
|
|
245
|
+
if (this.response?.headersSent) throw new Error("Headers already sended");
|
|
246
|
+
const actualBody = body || this.body;
|
|
247
|
+
const isChunked = this.getHeader("Transfer-Encoding") === "chunked";
|
|
248
|
+
if (!NO_CONTENT_LENGTH_METHODS.has(this.context?.method) && !NO_CONTENT_LENGTH_STATUS.has(this.statusCode) && !isChunked) {
|
|
249
|
+
if (actualBody) {
|
|
250
|
+
const length = Buffer.byteLength(actualBody);
|
|
251
|
+
this.setHeader("Content-Length", String(length));
|
|
252
|
+
return length;
|
|
253
|
+
}
|
|
254
|
+
return 0;
|
|
255
|
+
}
|
|
256
|
+
if (!NO_CONTENT_LENGTH_STATUS.has(this.statusCode)) this.setHeader("Content-Length", "0");
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
writeHead(statusCode, headers) {
|
|
260
|
+
if (statusCode) this.setStatusCode(statusCode);
|
|
261
|
+
if (this.response && !this.response.headersSent) {
|
|
262
|
+
if (this.statusCode) {
|
|
263
|
+
if (typeof this.statusCode === "string") this.statusCode = parseInt(this.statusCode, 10);
|
|
264
|
+
if (this.statusCode > 599) this.statusCode = 500;
|
|
265
|
+
}
|
|
266
|
+
this.statusMessage = this.getStatusMessage();
|
|
267
|
+
if (this.context.requestId && !this.response.headersSent) this.response.setHeader("x-request-id", this.context.requestId);
|
|
268
|
+
if (this.context.traceparent && !this.response.headersSent) this.response.setHeader("traceparent", this.context.traceparent);
|
|
269
|
+
this.setLength();
|
|
270
|
+
this.ensureContentTypeHeader();
|
|
271
|
+
const std = http.STATUS_CODES[this.statusCode];
|
|
272
|
+
if (this.statusMessage === std) this.response.writeHead(this.statusCode, headers);
|
|
273
|
+
else {
|
|
274
|
+
const safeMsg = this.statusMessage.replace(/[^\x20-\x7E]/g, "").trim() || (std ?? "Unknown Error");
|
|
275
|
+
this.response.writeHead(this.statusCode, safeMsg, headers);
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
this.log("Headers already sent !!", "WARNING");
|
|
279
|
+
throw new Error(`Headers already sent !!`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
addTrailers(headers) {
|
|
283
|
+
return this.response?.addTrailers(headers);
|
|
284
|
+
}
|
|
285
|
+
flush(chunk, encoding) {
|
|
286
|
+
this.flushing = true;
|
|
287
|
+
this.setHeader("Transfer-Encoding", "chunked");
|
|
288
|
+
return this.send(chunk, encoding, true);
|
|
289
|
+
}
|
|
290
|
+
async send(chunk, encoding, _flush = false) {
|
|
291
|
+
if (this.context.isRedirect) {
|
|
292
|
+
if (!this.response?.headersSent) this.writeHead();
|
|
293
|
+
await this.end();
|
|
294
|
+
return this;
|
|
295
|
+
}
|
|
296
|
+
if (chunk) this.setBody(chunk);
|
|
297
|
+
if (!this.response) throw new Error(`Http Response not found`);
|
|
298
|
+
if (this.body === null) this.body = Buffer.alloc(0);
|
|
299
|
+
const res = this.response;
|
|
300
|
+
return new Promise((resolve) => {
|
|
301
|
+
let settled = false;
|
|
302
|
+
const done = () => {
|
|
303
|
+
if (!settled) {
|
|
304
|
+
settled = true;
|
|
305
|
+
resolve(this);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
const onDrain = () => done();
|
|
309
|
+
if (res.write(this.body, encoding || this.encoding, (error) => {
|
|
310
|
+
if (error) {
|
|
311
|
+
this.log(error, "ERROR");
|
|
312
|
+
res.removeListener("drain", onDrain);
|
|
313
|
+
done();
|
|
314
|
+
}
|
|
315
|
+
})) done();
|
|
316
|
+
else res.once("drain", onDrain);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
async write(chunk, encoding) {
|
|
320
|
+
return await this.send(chunk, encoding || this.encoding);
|
|
321
|
+
}
|
|
322
|
+
writeContinue() {
|
|
323
|
+
return this.response?.writeContinue();
|
|
324
|
+
}
|
|
325
|
+
async end(chunk, encoding) {
|
|
326
|
+
return new Promise((resolve, reject) => {
|
|
327
|
+
if (this.response) return resolve(this.response.end(chunk, encoding || this.encoding));
|
|
328
|
+
return reject(/* @__PURE__ */ new Error(`response not found`));
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
getHeader(name) {
|
|
332
|
+
return this.response?.getHeader(name);
|
|
333
|
+
}
|
|
334
|
+
hasHeader(name) {
|
|
335
|
+
if (this.response) return this.response.hasHeader(name);
|
|
336
|
+
throw new Error(`Respose not foud`);
|
|
337
|
+
}
|
|
338
|
+
getHeaders() {
|
|
339
|
+
return this.response?.getHeaders();
|
|
340
|
+
}
|
|
341
|
+
redirect(url, status, headers) {
|
|
342
|
+
this.context.isRedirect = true;
|
|
343
|
+
if (typeof status === "string") status = parseInt(status, 10);
|
|
344
|
+
if (typeof status !== "number" || !REDIRECT_STATUS_CODES.has(status)) {
|
|
345
|
+
if (status !== void 0 && !Number.isNaN(status)) this.log(`Invalid redirect status ${status} → fallback 302 (RFC 9110 §15.4)`, "WARNING");
|
|
346
|
+
status = 302;
|
|
347
|
+
}
|
|
348
|
+
this.setStatusCode(status);
|
|
349
|
+
if (headers) switch (typeOf(headers)) {
|
|
350
|
+
case "object":
|
|
351
|
+
this.setHeaders(headers);
|
|
352
|
+
break;
|
|
353
|
+
case "boolean": this.setHeaders({
|
|
354
|
+
"Cache-Control": "no-store, no-cache, must-revalidate",
|
|
355
|
+
Expires: "Thu, 01 Jan 1970 00:00:00 GMT"
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
this.setHeader("Location", url);
|
|
359
|
+
this.log(`REDIRECT ${status} : ${url} `, "DEBUG");
|
|
360
|
+
return this;
|
|
361
|
+
}
|
|
362
|
+
log(pci, severity, msgid, msg) {
|
|
363
|
+
if (!msgid) msgid = `${this.context.type} RESPONSE `;
|
|
364
|
+
return this.context.log(pci, severity, msgid, msg);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
//#endregion
|
|
368
|
+
export { HttpResponse as default };
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import HttpError from "../../errors/httpError.js";
|
|
2
|
+
import { escapeRegExp, extend } from "nodefony";
|
|
3
|
+
import QS from "qs";
|
|
4
|
+
import xml2js from "xml2js";
|
|
5
|
+
//#region nodefony/src/context/http/parser.ts
|
|
6
|
+
var Parser = class {
|
|
7
|
+
request;
|
|
8
|
+
chunks;
|
|
9
|
+
received = 0;
|
|
10
|
+
aborted = false;
|
|
11
|
+
overflow = null;
|
|
12
|
+
onOverflow = null;
|
|
13
|
+
constructor(request) {
|
|
14
|
+
this.request = request;
|
|
15
|
+
this.chunks = [];
|
|
16
|
+
this.request.request.on("data", (data) => {
|
|
17
|
+
this.write(data);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
write(buffer) {
|
|
21
|
+
if (this.aborted) return buffer;
|
|
22
|
+
const max = this.request.maxBodySize ?? 0;
|
|
23
|
+
this.received += buffer.length;
|
|
24
|
+
if (max > 0 && this.received > max) {
|
|
25
|
+
this.aborted = true;
|
|
26
|
+
this.chunks.length = 0;
|
|
27
|
+
this.overflow = new HttpError(`Request body exceeds maxBodySize (${max} bytes)`, 413, this.request.context);
|
|
28
|
+
this.request.request.pause?.();
|
|
29
|
+
if (this.onOverflow) this.onOverflow(this.overflow);
|
|
30
|
+
return buffer;
|
|
31
|
+
}
|
|
32
|
+
this.chunks.push(buffer);
|
|
33
|
+
return buffer;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Résout quand le flux requête est **entièrement reçu** (`end`). No-op si déjà
|
|
37
|
+
* terminé (corps vide / déjà drainé) → pas de hang d'un `once("end")` tardif.
|
|
38
|
+
* Indispensable avant de concaténer les chunks pour un corps à décoder
|
|
39
|
+
* (JSON…) : sinon on lit un buffer encore incomplet. (formidable drainait via
|
|
40
|
+
* son `await form.parse()` ; ses remplaçants doivent drainer explicitement.)
|
|
41
|
+
* Rejette en 413 si le corps dépasse `maxBodySize` (avant ou pendant l'attente).
|
|
42
|
+
*/
|
|
43
|
+
ended() {
|
|
44
|
+
if (this.overflow) return Promise.reject(this.overflow);
|
|
45
|
+
const req = this.request.request;
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
if (req.readableEnded || req.complete) return resolve();
|
|
48
|
+
const cleanup = () => {
|
|
49
|
+
req.removeListener("end", onEnd);
|
|
50
|
+
req.removeListener("error", onError);
|
|
51
|
+
this.onOverflow = null;
|
|
52
|
+
};
|
|
53
|
+
const onEnd = () => {
|
|
54
|
+
cleanup();
|
|
55
|
+
resolve();
|
|
56
|
+
};
|
|
57
|
+
const onError = (e) => {
|
|
58
|
+
cleanup();
|
|
59
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
60
|
+
};
|
|
61
|
+
this.onOverflow = (err) => {
|
|
62
|
+
cleanup();
|
|
63
|
+
reject(err);
|
|
64
|
+
};
|
|
65
|
+
req.once("end", onEnd);
|
|
66
|
+
req.once("error", onError);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async parse() {
|
|
70
|
+
await this.ended();
|
|
71
|
+
this.request.data = Buffer.concat(this.chunks);
|
|
72
|
+
this.request.context.requestEnded = true;
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var ParserQs = class extends Parser {
|
|
77
|
+
parserOptions;
|
|
78
|
+
charset = "utf8";
|
|
79
|
+
constructor(request) {
|
|
80
|
+
super(request);
|
|
81
|
+
this.parserOptions = this.request.queryStringOptions || {};
|
|
82
|
+
this.charset = this.request.charset;
|
|
83
|
+
}
|
|
84
|
+
async parse() {
|
|
85
|
+
await super.parse();
|
|
86
|
+
this.request.queryPost = QS.parse(this.request.data.toString(this.charset), this.parserOptions);
|
|
87
|
+
this.request.query = extend({}, this.request.query, this.request.queryPost);
|
|
88
|
+
this.request.context.requestEnded = true;
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var ParserXml = class extends Parser {
|
|
93
|
+
xmlParser;
|
|
94
|
+
charset = "utf8";
|
|
95
|
+
constructor(request, settingsXml) {
|
|
96
|
+
super(request);
|
|
97
|
+
this.xmlParser = new xml2js.Parser(settingsXml);
|
|
98
|
+
this.charset = this.request.charset;
|
|
99
|
+
}
|
|
100
|
+
async parse() {
|
|
101
|
+
await super.parse();
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
this.xmlParser.parseString(this.request.data.toString(this.charset), (err, result) => {
|
|
104
|
+
if (err) return reject(err);
|
|
105
|
+
this.request.queryPost = result;
|
|
106
|
+
this.request.context.requestEnded = true;
|
|
107
|
+
return resolve(this);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Parse un corps `application/json` (ou `*+json`) en objet → `queryPost`
|
|
114
|
+
* (source lue par le décorateur `@Body`). Reprend le rôle de l'ancien plugin
|
|
115
|
+
* `json` de formidable, supprimé avec le passage à busboy (multipart seul).
|
|
116
|
+
* Lenient : un corps JSON malformé est ignoré (pas de throw — `parse()` est
|
|
117
|
+
* invoqué non-awaité par `initialize`, un rejet serait non géré) ; le brut
|
|
118
|
+
* reste disponible dans `request.data`.
|
|
119
|
+
*/
|
|
120
|
+
var ParserJson = class extends Parser {
|
|
121
|
+
charset = "utf8";
|
|
122
|
+
constructor(request) {
|
|
123
|
+
super(request);
|
|
124
|
+
this.charset = this.request.charset;
|
|
125
|
+
}
|
|
126
|
+
async parse() {
|
|
127
|
+
await super.parse();
|
|
128
|
+
const text = this.request.data.toString(this.charset).trim();
|
|
129
|
+
if (text) try {
|
|
130
|
+
this.request.queryPost = JSON.parse(text);
|
|
131
|
+
this.request.query = extend({}, this.request.query, this.request.queryPost);
|
|
132
|
+
} catch {}
|
|
133
|
+
this.request.context.requestEnded = true;
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const ACCEPT_ANY = [{
|
|
138
|
+
type: /.*/,
|
|
139
|
+
subtype: /.*/
|
|
140
|
+
}];
|
|
141
|
+
/**
|
|
142
|
+
* Construit le matcher d'un composant de media-range `Accept` (`type` ou `subtype`).
|
|
143
|
+
* Le wildcard `*` devient `.*` ; **tout autre token est ÉCHAPPÉ puis ancré** avant
|
|
144
|
+
* `new RegExp` — jamais passé brut.
|
|
145
|
+
*
|
|
146
|
+
* ⚠️ Sécurité : le `Accept` est un en-tête CLIENT arbitraire. Le passer brut à
|
|
147
|
+
* `new RegExp` (ancien code) faisait **throw** sur un métacaractère non balancé —
|
|
148
|
+
* ex. un token à `*` en tête (`*x`) → `SyntaxError: Nothing to repeat` — throw NON
|
|
149
|
+
* rattrapé remontant dans le constructeur de la requête = **crash / DoS trivial via
|
|
150
|
+
* un simple en-tête**. L'échappement rend `new RegExp` infaillible ; l'ancrage
|
|
151
|
+
* corrige aussi la négociation (match du token ENTIER, pas d'une sous-chaîne).
|
|
152
|
+
*/
|
|
153
|
+
const acceptMatcher = (token) => !token || token === "*" ? /.*/ : new RegExp(`^${escapeRegExp(token)}$`);
|
|
154
|
+
const acceptParser = function(acc) {
|
|
155
|
+
if (!acc) return ACCEPT_ANY;
|
|
156
|
+
const arr = [];
|
|
157
|
+
try {
|
|
158
|
+
const types = acc.split(",");
|
|
159
|
+
for (let i = 0; i < types.length; i++) {
|
|
160
|
+
const type = types[i].split(";");
|
|
161
|
+
const mine = type.shift();
|
|
162
|
+
if (!mine) continue;
|
|
163
|
+
const dec = mine.trim().split("/");
|
|
164
|
+
const ele1 = dec.shift();
|
|
165
|
+
const ele2 = dec.shift();
|
|
166
|
+
const obj = {
|
|
167
|
+
type: acceptMatcher(ele1),
|
|
168
|
+
subtype: acceptMatcher(ele2)
|
|
169
|
+
};
|
|
170
|
+
for (let j = 0; j < type.length; j++) {
|
|
171
|
+
const params = type[j].split("=");
|
|
172
|
+
const name = params.shift();
|
|
173
|
+
const value = params.shift();
|
|
174
|
+
obj[name] = name === "q" ? parseFloat(value) : value;
|
|
175
|
+
}
|
|
176
|
+
arr.push(obj);
|
|
177
|
+
}
|
|
178
|
+
if (arr.length === 0) return ACCEPT_ANY;
|
|
179
|
+
return arr.sort((a, b) => {
|
|
180
|
+
const qA = a.q || 1;
|
|
181
|
+
return (b.q || 1) - qA;
|
|
182
|
+
});
|
|
183
|
+
} catch {
|
|
184
|
+
return ACCEPT_ANY;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
//#endregion
|
|
188
|
+
export { Parser, ParserJson, ParserQs, ParserXml, acceptParser };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//#region nodefony/src/context/http/urlFastPath.ts
|
|
2
|
+
const PATH_SAFE = /* @__PURE__ */ new Uint8Array(128);
|
|
3
|
+
const SEARCH_SAFE = /* @__PURE__ */ new Uint8Array(128);
|
|
4
|
+
const HOST_SAFE = /* @__PURE__ */ new Uint8Array(128);
|
|
5
|
+
for (let c = 0; c < 128; c++) {
|
|
6
|
+
const ch = String.fromCharCode(c);
|
|
7
|
+
if (/[A-Za-z0-9\-._~!$&'()*+,;=:@/]/.test(ch)) PATH_SAFE[c] = 1;
|
|
8
|
+
if (c > 32 && c < 127 && !"\"#<>'".includes(ch)) SEARCH_SAFE[c] = 1;
|
|
9
|
+
if (/[a-z0-9\-._]/.test(ch)) HOST_SAFE[c] = 1;
|
|
10
|
+
}
|
|
11
|
+
const SLASH = 47;
|
|
12
|
+
const DOT = 46;
|
|
13
|
+
const QMARK = 63;
|
|
14
|
+
const COLON = 58;
|
|
15
|
+
/**
|
|
16
|
+
* Découpe un request-target origin-form (`/path[?query]`) en
|
|
17
|
+
* pathname/search SI — et seulement si — le parse WHATWG serait l'identité.
|
|
18
|
+
*
|
|
19
|
+
* @param target - le request-target brut (`IncomingMessage.url` en HTTP/1,
|
|
20
|
+
* pseudo-header `:path` en HTTP/2).
|
|
21
|
+
* @returns les composants découpés, ou `null` (bail-out → vrai `new URL`).
|
|
22
|
+
*/
|
|
23
|
+
function splitTarget(target) {
|
|
24
|
+
if (typeof target !== "string" || target.length === 0) return null;
|
|
25
|
+
if (target.charCodeAt(0) !== SLASH) return null;
|
|
26
|
+
let prev = 0;
|
|
27
|
+
for (let i = 0; i < target.length; i++) {
|
|
28
|
+
const c = target.charCodeAt(i);
|
|
29
|
+
if (c >= 128) return null;
|
|
30
|
+
if (c === QMARK) {
|
|
31
|
+
for (let j = i + 1; j < target.length; j++) {
|
|
32
|
+
const s = target.charCodeAt(j);
|
|
33
|
+
if (s >= 128 || SEARCH_SAFE[s] === 0) return null;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
pathname: target.slice(0, i),
|
|
37
|
+
search: i === target.length - 1 ? "" : target.slice(i)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (PATH_SAFE[c] === 0) return null;
|
|
41
|
+
if (c === SLASH && prev === SLASH) return null;
|
|
42
|
+
if (c === DOT && prev === SLASH) return null;
|
|
43
|
+
prev = c;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
pathname: target,
|
|
47
|
+
search: ""
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* L'autorité (`host[:port]` brut de l'en-tête `Host` / `:authority`) est-elle
|
|
52
|
+
* déjà sous la forme exacte que le parseur WHATWG rendrait ?
|
|
53
|
+
*
|
|
54
|
+
* Refusé (→ bail-out) : majuscules (lowercase WHATWG), IPv6 (`[`), toute
|
|
55
|
+
* forme « IPv4-like » ambiguë (`127.1`, `0x7f.1`, `2130706433` — WHATWG les
|
|
56
|
+
* NORMALISE en dotted-quad, un matcher les lisant brut serait contournable),
|
|
57
|
+
* label vide (`a..b`, `.a`, `a.`), port par défaut du scheme (élidé par
|
|
58
|
+
* WHATWG) ou avec zéro de tête, percent-encoding, non-ASCII (punycode).
|
|
59
|
+
*
|
|
60
|
+
* @param host - autorité brute (peut contenir `:port`).
|
|
61
|
+
* @param scheme - scheme effectif (`http` | `https`) — décide du port par défaut.
|
|
62
|
+
*/
|
|
63
|
+
function isCanonicalAuthority(host, scheme) {
|
|
64
|
+
if (typeof host !== "string" || host.length === 0) return false;
|
|
65
|
+
let colon = -1;
|
|
66
|
+
for (let i = 0; i < host.length; i++) {
|
|
67
|
+
const c = host.charCodeAt(i);
|
|
68
|
+
if (c === COLON) {
|
|
69
|
+
if (colon !== -1) return false;
|
|
70
|
+
colon = i;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (c >= 128 || (colon === -1 ? HOST_SAFE[c] === 0 : c < 48 || c > 57)) return false;
|
|
74
|
+
}
|
|
75
|
+
const name = colon === -1 ? host : host.slice(0, colon);
|
|
76
|
+
if (name.length === 0) return false;
|
|
77
|
+
if (colon !== -1) {
|
|
78
|
+
const port = host.slice(colon + 1);
|
|
79
|
+
if (port.length === 0 || port.charCodeAt(0) === 48) return false;
|
|
80
|
+
if (scheme === "https" && port === "443" || scheme === "http" && port === "80") return false;
|
|
81
|
+
}
|
|
82
|
+
const labels = name.split(".");
|
|
83
|
+
for (const label of labels) if (label.length === 0) return false;
|
|
84
|
+
const last = labels[labels.length - 1];
|
|
85
|
+
if (isDigits(last)) {
|
|
86
|
+
if (labels.length !== 4) return false;
|
|
87
|
+
for (const label of labels) {
|
|
88
|
+
if (!isDigits(label) || label.length > 3) return false;
|
|
89
|
+
if (label.length > 1 && label.charCodeAt(0) === 48) return false;
|
|
90
|
+
if (Number(label) > 255) return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
function isDigits(s) {
|
|
96
|
+
for (let i = 0; i < s.length; i++) {
|
|
97
|
+
const c = s.charCodeAt(i);
|
|
98
|
+
if (c < 48 || c > 57) return false;
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
export { isCanonicalAuthority, splitTarget };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import HttpRequest from "../http/Request.js";
|
|
2
|
+
//#region nodefony/src/context/http2/Request.ts
|
|
3
|
+
var Http2Request = class extends HttpRequest {
|
|
4
|
+
request;
|
|
5
|
+
constructor(request, context) {
|
|
6
|
+
super(request, context);
|
|
7
|
+
this.context = context;
|
|
8
|
+
this.request = request;
|
|
9
|
+
}
|
|
10
|
+
getHost() {
|
|
11
|
+
return this.headers[":authority"];
|
|
12
|
+
}
|
|
13
|
+
getUserAgent() {
|
|
14
|
+
return this.headers["user-agent"];
|
|
15
|
+
}
|
|
16
|
+
getMethod() {
|
|
17
|
+
return this.headers[":method"];
|
|
18
|
+
}
|
|
19
|
+
getRawTarget() {
|
|
20
|
+
return this.headers[":path"];
|
|
21
|
+
}
|
|
22
|
+
resolveScheme() {
|
|
23
|
+
if (this.forwarded?.proto) return this.forwarded.proto;
|
|
24
|
+
if (this.headers[":scheme"] === "https") return "https";
|
|
25
|
+
return "http";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { Http2Request as default };
|