@j3r3mcdev/oast-server 1.1.6 → 1.1.8
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/.github/workflows/ci.yml +29 -29
- package/.github/workflows/publish.yml +31 -31
- package/README.md +192 -192
- package/dist/core/router.d.ts +1 -0
- package/dist/core/router.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16 -1
- package/jest.config.js +14 -14
- package/package.json +45 -45
- package/sadmin list shadows +9 -9
- package/src/api/controllers/__tests__/tasks.controller.test.ts +74 -74
- package/src/api/controllers/events.controller.ts +10 -10
- package/src/api/controllers/health.controller.ts +7 -7
- package/src/api/controllers/tasks.controller.ts +41 -41
- package/src/api/dto/__tests__/create-task.dto.test.ts +41 -41
- package/src/api/dto/__tests__/filter-tasks.dto.test.ts +35 -35
- package/src/api/dto/create-task.dto.ts +33 -33
- package/src/api/dto/filter-tasks.dto.ts +33 -33
- package/src/api/services/__tests__/events.service.test.ts +41 -41
- package/src/api/services/__tests__/tasks.service.test.ts +41 -41
- package/src/api/services/events.service.ts +17 -17
- package/src/api/services/tasks.service.ts +79 -79
- package/src/api/sse/events.stream.ts +90 -90
- package/src/bootstrap.ts +89 -89
- package/src/core/__tests__/core-router.test.ts +30 -30
- package/src/core/__tests__/core-server.test.ts +44 -44
- package/src/core/__tests__/event.normalizer.test.ts +56 -56
- package/src/core/__tests__/event.router.test.ts +89 -89
- package/src/core/__tests__/logger.test.ts +32 -32
- package/src/core/__tests__/storage-manager.test.ts +74 -74
- package/src/core/event.normalizer.ts +147 -147
- package/src/core/event.router.ts +13 -13
- package/src/core/http/__tests__/adapter-node.test.ts +52 -52
- package/src/core/http/__tests__/body-parser-multipart.test.ts +41 -41
- package/src/core/http/__tests__/body-parser-raw.test.ts +28 -28
- package/src/core/http/__tests__/body-parser-text.test.ts +28 -28
- package/src/core/http/__tests__/compile-path.test.ts +39 -39
- package/src/core/http/__tests__/middleware-pipeline.test.ts +51 -51
- package/src/core/http/__tests__/request.test.ts +34 -34
- package/src/core/http/__tests__/response.test.ts +35 -35
- package/src/core/http/__tests__/router-match.test.ts +171 -171
- package/src/core/http/adapter-node.ts +51 -51
- package/src/core/http/buildRequest.ts +18 -18
- package/src/core/http/compile-path.ts +32 -32
- package/src/core/http/errors.ts +37 -37
- package/src/core/http/http-server.ts +52 -52
- package/src/core/http/middleware.ts +160 -160
- package/src/core/http/request.ts +55 -55
- package/src/core/http/response.ts +93 -93
- package/src/core/http/router.ts +138 -138
- package/src/core/id-generator.ts +8 -8
- package/src/core/logger.ts +113 -113
- package/src/core/router.ts +44 -44
- package/src/core/server.ts +85 -85
- package/src/core/storage.ts +64 -64
- package/src/index.ts +14 -14
- package/src/listeners/api/__tests__/api.controller.test.ts +116 -116
- package/src/listeners/api/__tests__/api.extractor.test.ts +46 -46
- package/src/listeners/api/__tests__/api.listener.test.ts +82 -82
- package/src/listeners/api/__tests__/api.routes.test.ts +155 -155
- package/src/listeners/api/__tests__/api.sse.test.ts +105 -105
- package/src/listeners/api/api.controllers.ts +67 -67
- package/src/listeners/api/api.extractor.ts +43 -43
- package/src/listeners/api/api.listener.ts +50 -50
- package/src/listeners/api/api.routes.ts +76 -76
- package/src/listeners/api/api.sse.ts +38 -38
- package/src/listeners/dns/__tests__/dns.test.ts +118 -118
- package/src/listeners/dns/dns.extractor.ts +14 -14
- package/src/listeners/dns/dns.listener.ts +61 -61
- package/src/listeners/http/__tests__/http.extractor.test.ts +59 -59
- package/src/listeners/http/__tests__/http.listener.test.ts +133 -133
- package/src/listeners/http/http.extractor.ts +15 -15
- package/src/listeners/http/http.listener.ts +110 -110
- package/src/listeners/listener.interface.ts +4 -4
- package/src/listeners/smtp/__tests__/smtp.extractor.test.ts +69 -69
- package/src/listeners/smtp/__tests__/smtp.listener.test.ts +150 -150
- package/src/listeners/smtp/smtp.extractor.ts +18 -18
- package/src/listeners/smtp/smtp.listener.ts +60 -60
- package/src/listeners/ssrf/__tests__/ssrf.extractor.test.ts +41 -41
- package/src/listeners/ssrf/__tests__/ssrf.listener.test.ts +87 -87
- package/src/listeners/ssrf/ssrf.extractor.ts +14 -14
- package/src/listeners/ssrf/ssrf.listener.ts +37 -37
- package/src/listeners/tcp/tcp.extractor.ts +16 -16
- package/src/listeners/tcp/tcp.listener.ts +61 -61
- package/src/listeners/webhook/__tests__/webhook.extractor.test.ts +35 -35
- package/src/listeners/webhook/__tests__/webhook.listener.test.ts +122 -122
- package/src/listeners/webhook/webhook.extractor.ts +12 -12
- package/src/listeners/webhook/webhook.listener.ts +58 -58
- package/src/listeners/websocket/__tests__/websocket.extractor.test.ts +33 -33
- package/src/listeners/websocket/__tests__/websocket.listener.test.ts +90 -90
- package/src/listeners/websocket/websocket.extractor.ts +11 -11
- package/src/listeners/websocket/websocket.listener.ts +40 -40
- package/src/storage-adapters/adapters/__tests__/memory.storage.test.ts +75 -75
- package/src/storage-adapters/adapters/memory.storage.ts +64 -64
- package/src/storage-adapters/storage.interface.ts +26 -26
- package/src/types/event.types.ts +147 -147
- package/tsconfig.json +20 -21
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
// src/api/http/http-server.ts
|
|
2
|
-
|
|
3
|
-
import http from "node:http";
|
|
4
|
-
import { Router } from "./router";
|
|
5
|
-
|
|
6
|
-
export class HttpServer {
|
|
7
|
-
private server: http.Server | null = null;
|
|
8
|
-
|
|
9
|
-
constructor(private router: Router) {}
|
|
10
|
-
|
|
11
|
-
start(port: number) {
|
|
12
|
-
this.server = http.createServer((req, res) => {
|
|
13
|
-
const method = req.method || "GET";
|
|
14
|
-
const url = req.url || "/";
|
|
15
|
-
|
|
16
|
-
const match = this.router.match(method, url);
|
|
17
|
-
|
|
18
|
-
if (!match) {
|
|
19
|
-
res.statusCode = 404;
|
|
20
|
-
res.end("Not found");
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const result = match.handler({
|
|
25
|
-
params: match.params,
|
|
26
|
-
req,
|
|
27
|
-
res,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
if (!res.writableEnded) {
|
|
31
|
-
res.end(result ?? "");
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
this.server.listen(port, () => {
|
|
36
|
-
console.log(`HTTP server running on port ${port}`);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// 🔥 Shutdown propre
|
|
40
|
-
const shutdown = () => {
|
|
41
|
-
console.log("Shutting down HTTP server gracefully...");
|
|
42
|
-
|
|
43
|
-
this.server?.close(() => {
|
|
44
|
-
console.log("HTTP server closed.");
|
|
45
|
-
process.exit(0);
|
|
46
|
-
});
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
process.on("SIGINT", shutdown);
|
|
50
|
-
process.on("SIGTERM", shutdown);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
1
|
+
// src/api/http/http-server.ts
|
|
2
|
+
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import { Router } from "./router";
|
|
5
|
+
|
|
6
|
+
export class HttpServer {
|
|
7
|
+
private server: http.Server | null = null;
|
|
8
|
+
|
|
9
|
+
constructor(private router: Router) {}
|
|
10
|
+
|
|
11
|
+
start(port: number) {
|
|
12
|
+
this.server = http.createServer((req, res) => {
|
|
13
|
+
const method = req.method || "GET";
|
|
14
|
+
const url = req.url || "/";
|
|
15
|
+
|
|
16
|
+
const match = this.router.match(method, url);
|
|
17
|
+
|
|
18
|
+
if (!match) {
|
|
19
|
+
res.statusCode = 404;
|
|
20
|
+
res.end("Not found");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const result = match.handler({
|
|
25
|
+
params: match.params,
|
|
26
|
+
req,
|
|
27
|
+
res,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!res.writableEnded) {
|
|
31
|
+
res.end(result ?? "");
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
this.server.listen(port, () => {
|
|
36
|
+
console.log(`HTTP server running on port ${port}`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// 🔥 Shutdown propre
|
|
40
|
+
const shutdown = () => {
|
|
41
|
+
console.log("Shutting down HTTP server gracefully...");
|
|
42
|
+
|
|
43
|
+
this.server?.close(() => {
|
|
44
|
+
console.log("HTTP server closed.");
|
|
45
|
+
process.exit(0);
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
process.on("SIGINT", shutdown);
|
|
50
|
+
process.on("SIGTERM", shutdown);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,160 +1,160 @@
|
|
|
1
|
-
// src/http/middleware.ts
|
|
2
|
-
|
|
3
|
-
import { Request } from "./request";
|
|
4
|
-
import { Response } from "./response";
|
|
5
|
-
|
|
6
|
-
export type Middleware = (
|
|
7
|
-
req: Request,
|
|
8
|
-
res: Response,
|
|
9
|
-
next: () => Promise<void>,
|
|
10
|
-
) => Promise<void> | void;
|
|
11
|
-
|
|
12
|
-
export class MiddlewarePipeline {
|
|
13
|
-
private middlewares: Middleware[] = [];
|
|
14
|
-
|
|
15
|
-
use(mw: Middleware): this {
|
|
16
|
-
this.middlewares.push(mw);
|
|
17
|
-
return this;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async run(req: Request, res: Response): Promise<void> {
|
|
21
|
-
let index = -1;
|
|
22
|
-
|
|
23
|
-
const next = async (): Promise<void> => {
|
|
24
|
-
index++;
|
|
25
|
-
|
|
26
|
-
if (index >= this.middlewares.length) return;
|
|
27
|
-
|
|
28
|
-
const mw = this.middlewares[index];
|
|
29
|
-
|
|
30
|
-
await Promise.resolve(mw(req, res, next));
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
await next();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/* ---------------------------------------------------------
|
|
38
|
-
JSON PARSER
|
|
39
|
-
--------------------------------------------------------- */
|
|
40
|
-
export async function bodyParserJson(req: Request, res: Response) {
|
|
41
|
-
const raw = req.raw;
|
|
42
|
-
const contentType = raw.headers["content-type"] ?? "";
|
|
43
|
-
|
|
44
|
-
if (!contentType.includes("application/json")) {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const chunks: Buffer[] = [];
|
|
49
|
-
for await (const chunk of raw) chunks.push(chunk);
|
|
50
|
-
|
|
51
|
-
const text = Buffer.concat(chunks).toString("utf8");
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
req.body = text.length > 0 ? JSON.parse(text) : {};
|
|
55
|
-
} catch {
|
|
56
|
-
res.status(400).json({ error: "Invalid JSON" });
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/* ---------------------------------------------------------
|
|
61
|
-
RAW PARSER
|
|
62
|
-
--------------------------------------------------------- */
|
|
63
|
-
export async function bodyParserRaw(req: Request, res: Response) {
|
|
64
|
-
const raw = req.raw;
|
|
65
|
-
const contentType = raw.headers["content-type"] ?? "";
|
|
66
|
-
|
|
67
|
-
if (contentType.includes("application/json")) return;
|
|
68
|
-
|
|
69
|
-
const chunks: Buffer[] = [];
|
|
70
|
-
for await (const chunk of raw) chunks.push(chunk);
|
|
71
|
-
|
|
72
|
-
req.bodyRaw = Buffer.concat(chunks);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/* ---------------------------------------------------------
|
|
76
|
-
TEXT PARSER
|
|
77
|
-
--------------------------------------------------------- */
|
|
78
|
-
export async function bodyParserText(req: Request, res: Response) {
|
|
79
|
-
const raw = req.raw;
|
|
80
|
-
const contentType = raw.headers["content-type"] ?? "";
|
|
81
|
-
|
|
82
|
-
const isText =
|
|
83
|
-
contentType.startsWith("text/") ||
|
|
84
|
-
contentType.includes("application/xml") ||
|
|
85
|
-
contentType.includes("application/xhtml+xml");
|
|
86
|
-
|
|
87
|
-
if (!isText) return;
|
|
88
|
-
|
|
89
|
-
const chunks: Buffer[] = [];
|
|
90
|
-
for await (const chunk of raw) chunks.push(chunk);
|
|
91
|
-
|
|
92
|
-
req.bodyText = Buffer.concat(chunks).toString("utf8");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/* ---------------------------------------------------------
|
|
96
|
-
MULTIPART PARSER
|
|
97
|
-
--------------------------------------------------------- */
|
|
98
|
-
export async function bodyParserMultipart(req: Request, res: Response) {
|
|
99
|
-
const raw = req.raw;
|
|
100
|
-
const contentType = raw.headers["content-type"] ?? "";
|
|
101
|
-
|
|
102
|
-
if (!contentType.startsWith("multipart/form-data")) return;
|
|
103
|
-
|
|
104
|
-
const boundaryMatch = contentType.match(/boundary=(.+)$/);
|
|
105
|
-
if (!boundaryMatch) {
|
|
106
|
-
res.status(400).json({ error: "Missing multipart boundary" });
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const boundary = "--" + boundaryMatch[1];
|
|
111
|
-
|
|
112
|
-
const chunks: Buffer[] = [];
|
|
113
|
-
for await (const chunk of raw) chunks.push(chunk);
|
|
114
|
-
|
|
115
|
-
const buffer = Buffer.concat(chunks);
|
|
116
|
-
const parts = buffer.toString("binary").split(boundary);
|
|
117
|
-
|
|
118
|
-
req.files = [];
|
|
119
|
-
req.form = {};
|
|
120
|
-
|
|
121
|
-
for (const part of parts) {
|
|
122
|
-
if (part === "--\r\n" || part === "" || part === "\r\n") continue;
|
|
123
|
-
|
|
124
|
-
const [rawHeaders, rawBody] = part.split("\r\n\r\n");
|
|
125
|
-
if (!rawBody) continue;
|
|
126
|
-
|
|
127
|
-
// Nettoyage du body brut
|
|
128
|
-
const cleaned = rawBody.replace(/\r\n--$/, "");
|
|
129
|
-
const body = Buffer.from(cleaned, "binary");
|
|
130
|
-
|
|
131
|
-
const disposition = rawHeaders.match(/name="([^"]+)"/);
|
|
132
|
-
if (!disposition) continue;
|
|
133
|
-
|
|
134
|
-
const fieldname = disposition[1];
|
|
135
|
-
const filenameMatch = rawHeaders.match(/filename="([^"]+)"/);
|
|
136
|
-
|
|
137
|
-
if (filenameMatch) {
|
|
138
|
-
// ----------- FICHIER -----------
|
|
139
|
-
const filename = filenameMatch[1];
|
|
140
|
-
const contentTypeMatch = rawHeaders.match(/Content-Type: (.+)/);
|
|
141
|
-
|
|
142
|
-
req.files.push({
|
|
143
|
-
fieldname,
|
|
144
|
-
filename,
|
|
145
|
-
contentType: contentTypeMatch
|
|
146
|
-
? contentTypeMatch[1]
|
|
147
|
-
: "application/octet-stream",
|
|
148
|
-
|
|
149
|
-
// 🔥 Correction : retirer le \r\n final
|
|
150
|
-
data: Buffer.from(
|
|
151
|
-
body.toString("binary").replace(/\r\n$/, ""),
|
|
152
|
-
"binary",
|
|
153
|
-
),
|
|
154
|
-
});
|
|
155
|
-
} else {
|
|
156
|
-
// ----------- CHAMP TEXTE -----------
|
|
157
|
-
req.form[fieldname] = body.toString("utf8").replace(/\r\n$/, ""); // 🔥 Correction champ texte
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
1
|
+
// src/http/middleware.ts
|
|
2
|
+
|
|
3
|
+
import { Request } from "./request";
|
|
4
|
+
import { Response } from "./response";
|
|
5
|
+
|
|
6
|
+
export type Middleware = (
|
|
7
|
+
req: Request,
|
|
8
|
+
res: Response,
|
|
9
|
+
next: () => Promise<void>,
|
|
10
|
+
) => Promise<void> | void;
|
|
11
|
+
|
|
12
|
+
export class MiddlewarePipeline {
|
|
13
|
+
private middlewares: Middleware[] = [];
|
|
14
|
+
|
|
15
|
+
use(mw: Middleware): this {
|
|
16
|
+
this.middlewares.push(mw);
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async run(req: Request, res: Response): Promise<void> {
|
|
21
|
+
let index = -1;
|
|
22
|
+
|
|
23
|
+
const next = async (): Promise<void> => {
|
|
24
|
+
index++;
|
|
25
|
+
|
|
26
|
+
if (index >= this.middlewares.length) return;
|
|
27
|
+
|
|
28
|
+
const mw = this.middlewares[index];
|
|
29
|
+
|
|
30
|
+
await Promise.resolve(mw(req, res, next));
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
await next();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* ---------------------------------------------------------
|
|
38
|
+
JSON PARSER
|
|
39
|
+
--------------------------------------------------------- */
|
|
40
|
+
export async function bodyParserJson(req: Request, res: Response) {
|
|
41
|
+
const raw = req.raw;
|
|
42
|
+
const contentType = raw.headers["content-type"] ?? "";
|
|
43
|
+
|
|
44
|
+
if (!contentType.includes("application/json")) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const chunks: Buffer[] = [];
|
|
49
|
+
for await (const chunk of raw) chunks.push(chunk);
|
|
50
|
+
|
|
51
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
req.body = text.length > 0 ? JSON.parse(text) : {};
|
|
55
|
+
} catch {
|
|
56
|
+
res.status(400).json({ error: "Invalid JSON" });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* ---------------------------------------------------------
|
|
61
|
+
RAW PARSER
|
|
62
|
+
--------------------------------------------------------- */
|
|
63
|
+
export async function bodyParserRaw(req: Request, res: Response) {
|
|
64
|
+
const raw = req.raw;
|
|
65
|
+
const contentType = raw.headers["content-type"] ?? "";
|
|
66
|
+
|
|
67
|
+
if (contentType.includes("application/json")) return;
|
|
68
|
+
|
|
69
|
+
const chunks: Buffer[] = [];
|
|
70
|
+
for await (const chunk of raw) chunks.push(chunk);
|
|
71
|
+
|
|
72
|
+
req.bodyRaw = Buffer.concat(chunks);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* ---------------------------------------------------------
|
|
76
|
+
TEXT PARSER
|
|
77
|
+
--------------------------------------------------------- */
|
|
78
|
+
export async function bodyParserText(req: Request, res: Response) {
|
|
79
|
+
const raw = req.raw;
|
|
80
|
+
const contentType = raw.headers["content-type"] ?? "";
|
|
81
|
+
|
|
82
|
+
const isText =
|
|
83
|
+
contentType.startsWith("text/") ||
|
|
84
|
+
contentType.includes("application/xml") ||
|
|
85
|
+
contentType.includes("application/xhtml+xml");
|
|
86
|
+
|
|
87
|
+
if (!isText) return;
|
|
88
|
+
|
|
89
|
+
const chunks: Buffer[] = [];
|
|
90
|
+
for await (const chunk of raw) chunks.push(chunk);
|
|
91
|
+
|
|
92
|
+
req.bodyText = Buffer.concat(chunks).toString("utf8");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ---------------------------------------------------------
|
|
96
|
+
MULTIPART PARSER
|
|
97
|
+
--------------------------------------------------------- */
|
|
98
|
+
export async function bodyParserMultipart(req: Request, res: Response) {
|
|
99
|
+
const raw = req.raw;
|
|
100
|
+
const contentType = raw.headers["content-type"] ?? "";
|
|
101
|
+
|
|
102
|
+
if (!contentType.startsWith("multipart/form-data")) return;
|
|
103
|
+
|
|
104
|
+
const boundaryMatch = contentType.match(/boundary=(.+)$/);
|
|
105
|
+
if (!boundaryMatch) {
|
|
106
|
+
res.status(400).json({ error: "Missing multipart boundary" });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const boundary = "--" + boundaryMatch[1];
|
|
111
|
+
|
|
112
|
+
const chunks: Buffer[] = [];
|
|
113
|
+
for await (const chunk of raw) chunks.push(chunk);
|
|
114
|
+
|
|
115
|
+
const buffer = Buffer.concat(chunks);
|
|
116
|
+
const parts = buffer.toString("binary").split(boundary);
|
|
117
|
+
|
|
118
|
+
req.files = [];
|
|
119
|
+
req.form = {};
|
|
120
|
+
|
|
121
|
+
for (const part of parts) {
|
|
122
|
+
if (part === "--\r\n" || part === "" || part === "\r\n") continue;
|
|
123
|
+
|
|
124
|
+
const [rawHeaders, rawBody] = part.split("\r\n\r\n");
|
|
125
|
+
if (!rawBody) continue;
|
|
126
|
+
|
|
127
|
+
// Nettoyage du body brut
|
|
128
|
+
const cleaned = rawBody.replace(/\r\n--$/, "");
|
|
129
|
+
const body = Buffer.from(cleaned, "binary");
|
|
130
|
+
|
|
131
|
+
const disposition = rawHeaders.match(/name="([^"]+)"/);
|
|
132
|
+
if (!disposition) continue;
|
|
133
|
+
|
|
134
|
+
const fieldname = disposition[1];
|
|
135
|
+
const filenameMatch = rawHeaders.match(/filename="([^"]+)"/);
|
|
136
|
+
|
|
137
|
+
if (filenameMatch) {
|
|
138
|
+
// ----------- FICHIER -----------
|
|
139
|
+
const filename = filenameMatch[1];
|
|
140
|
+
const contentTypeMatch = rawHeaders.match(/Content-Type: (.+)/);
|
|
141
|
+
|
|
142
|
+
req.files.push({
|
|
143
|
+
fieldname,
|
|
144
|
+
filename,
|
|
145
|
+
contentType: contentTypeMatch
|
|
146
|
+
? contentTypeMatch[1]
|
|
147
|
+
: "application/octet-stream",
|
|
148
|
+
|
|
149
|
+
// 🔥 Correction : retirer le \r\n final
|
|
150
|
+
data: Buffer.from(
|
|
151
|
+
body.toString("binary").replace(/\r\n$/, ""),
|
|
152
|
+
"binary",
|
|
153
|
+
),
|
|
154
|
+
});
|
|
155
|
+
} else {
|
|
156
|
+
// ----------- CHAMP TEXTE -----------
|
|
157
|
+
req.form[fieldname] = body.toString("utf8").replace(/\r\n$/, ""); // 🔥 Correction champ texte
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
package/src/core/http/request.ts
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
// src/http/request.ts
|
|
2
|
-
|
|
3
|
-
export interface RequestContext {
|
|
4
|
-
// espace réservé pour les middlewares
|
|
5
|
-
[key: string]: any;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class Request {
|
|
9
|
-
readonly method: string;
|
|
10
|
-
readonly path: string;
|
|
11
|
-
readonly headers: Record<string, string>;
|
|
12
|
-
readonly query: Record<string, string | string[]>;
|
|
13
|
-
readonly params: Record<string, string>;
|
|
14
|
-
body: any;
|
|
15
|
-
bodyRaw?: Buffer;
|
|
16
|
-
bodyText?: string;
|
|
17
|
-
files?: Array<{
|
|
18
|
-
fieldname: string;
|
|
19
|
-
filename: string;
|
|
20
|
-
contentType: string;
|
|
21
|
-
data: Buffer;
|
|
22
|
-
}>;
|
|
23
|
-
form?: Record<string, string>;
|
|
24
|
-
|
|
25
|
-
readonly ip: string;
|
|
26
|
-
readonly raw: any; // Node IncomingMessage ou autre selon l'adaptateur
|
|
27
|
-
readonly context: RequestContext;
|
|
28
|
-
|
|
29
|
-
constructor(options: {
|
|
30
|
-
method: string;
|
|
31
|
-
path: string;
|
|
32
|
-
headers: Record<string, string>;
|
|
33
|
-
query?: Record<string, string | string[]>;
|
|
34
|
-
params?: Record<string, string>;
|
|
35
|
-
body?: any;
|
|
36
|
-
ip?: string;
|
|
37
|
-
raw?: any;
|
|
38
|
-
context?: RequestContext;
|
|
39
|
-
}) {
|
|
40
|
-
this.method = options.method;
|
|
41
|
-
this.path = options.path;
|
|
42
|
-
this.headers = options.headers;
|
|
43
|
-
this.query = options.query ?? {};
|
|
44
|
-
this.params = options.params ?? {};
|
|
45
|
-
this.body = options.body ?? null;
|
|
46
|
-
this.ip = options.ip ?? "";
|
|
47
|
-
this.raw = options.raw ?? null;
|
|
48
|
-
this.context = options.context ?? {};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Exemple d'utilitaire PRO
|
|
52
|
-
header(name: string): string | undefined {
|
|
53
|
-
return this.headers[name.toLowerCase()];
|
|
54
|
-
}
|
|
55
|
-
}
|
|
1
|
+
// src/http/request.ts
|
|
2
|
+
|
|
3
|
+
export interface RequestContext {
|
|
4
|
+
// espace réservé pour les middlewares
|
|
5
|
+
[key: string]: any;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class Request {
|
|
9
|
+
readonly method: string;
|
|
10
|
+
readonly path: string;
|
|
11
|
+
readonly headers: Record<string, string>;
|
|
12
|
+
readonly query: Record<string, string | string[]>;
|
|
13
|
+
readonly params: Record<string, string>;
|
|
14
|
+
body: any;
|
|
15
|
+
bodyRaw?: Buffer;
|
|
16
|
+
bodyText?: string;
|
|
17
|
+
files?: Array<{
|
|
18
|
+
fieldname: string;
|
|
19
|
+
filename: string;
|
|
20
|
+
contentType: string;
|
|
21
|
+
data: Buffer;
|
|
22
|
+
}>;
|
|
23
|
+
form?: Record<string, string>;
|
|
24
|
+
|
|
25
|
+
readonly ip: string;
|
|
26
|
+
readonly raw: any; // Node IncomingMessage ou autre selon l'adaptateur
|
|
27
|
+
readonly context: RequestContext;
|
|
28
|
+
|
|
29
|
+
constructor(options: {
|
|
30
|
+
method: string;
|
|
31
|
+
path: string;
|
|
32
|
+
headers: Record<string, string>;
|
|
33
|
+
query?: Record<string, string | string[]>;
|
|
34
|
+
params?: Record<string, string>;
|
|
35
|
+
body?: any;
|
|
36
|
+
ip?: string;
|
|
37
|
+
raw?: any;
|
|
38
|
+
context?: RequestContext;
|
|
39
|
+
}) {
|
|
40
|
+
this.method = options.method;
|
|
41
|
+
this.path = options.path;
|
|
42
|
+
this.headers = options.headers;
|
|
43
|
+
this.query = options.query ?? {};
|
|
44
|
+
this.params = options.params ?? {};
|
|
45
|
+
this.body = options.body ?? null;
|
|
46
|
+
this.ip = options.ip ?? "";
|
|
47
|
+
this.raw = options.raw ?? null;
|
|
48
|
+
this.context = options.context ?? {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Exemple d'utilitaire PRO
|
|
52
|
+
header(name: string): string | undefined {
|
|
53
|
+
return this.headers[name.toLowerCase()];
|
|
54
|
+
}
|
|
55
|
+
}
|