@wooksjs/http-body 0.5.25 → 0.6.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/dist/index.cjs +52 -53
- package/dist/index.d.ts +1 -9
- package/dist/index.mjs +52 -51
- package/package.json +6 -5
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
//#region rolldown:runtime
|
|
3
2
|
var __create = Object.create;
|
|
4
3
|
var __defProp = Object.defineProperty;
|
|
@@ -24,19 +23,21 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
23
|
//#endregion
|
|
25
24
|
const __wooksjs_event_http = __toESM(require("@wooksjs/event-http"));
|
|
26
25
|
|
|
27
|
-
//#region packages/http-body/src/utils/
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
26
|
+
//#region packages/http-body/src/utils/safe-json.ts
|
|
27
|
+
const ILLEGAL_KEYS = [
|
|
28
|
+
"__proto__",
|
|
29
|
+
"constructor",
|
|
30
|
+
"prototype"
|
|
31
|
+
];
|
|
32
|
+
const illigalKeySet = new Set(ILLEGAL_KEYS);
|
|
33
|
+
function safeJsonParse(src) {
|
|
34
|
+
return JSON.parse(src, (key, value) => {
|
|
35
|
+
assertKey(key);
|
|
36
|
+
return value;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function assertKey(k) {
|
|
40
|
+
if (illigalKeySet.has(k)) throw new __wooksjs_event_http.HttpError(400, `Illegal key name "${k}"`);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
//#endregion
|
|
@@ -45,7 +46,7 @@ function useBody() {
|
|
|
45
46
|
const { store } = (0, __wooksjs_event_http.useHttpContext)();
|
|
46
47
|
const { init } = store("request");
|
|
47
48
|
const { rawBody } = (0, __wooksjs_event_http.useRequest)();
|
|
48
|
-
const { "content-type": contentType
|
|
49
|
+
const { "content-type": contentType } = (0, __wooksjs_event_http.useHeaders)();
|
|
49
50
|
function contentIs(type) {
|
|
50
51
|
return (contentType || "").includes(type);
|
|
51
52
|
}
|
|
@@ -56,27 +57,18 @@ function useBody() {
|
|
|
56
57
|
const isBinary = () => init("isBinary", () => contentIs("application/octet-stream"));
|
|
57
58
|
const isFormData = () => init("isFormData", () => contentIs("multipart/form-data"));
|
|
58
59
|
const isUrlencoded = () => init("isUrlencoded", () => contentIs("application/x-www-form-urlencoded"));
|
|
59
|
-
const isCompressed = () => init("isCompressed", () => {
|
|
60
|
-
const parts = contentEncodings();
|
|
61
|
-
for (const p of parts) if ([
|
|
62
|
-
"deflate",
|
|
63
|
-
"gzip",
|
|
64
|
-
"br"
|
|
65
|
-
].includes(p)) return true;
|
|
66
|
-
return false;
|
|
67
|
-
});
|
|
68
|
-
const contentEncodings = () => init("contentEncodings", () => (contentEncoding || "").split(",").map((p) => p.trim()).filter((p) => !!p));
|
|
69
60
|
const parseBody = () => init("parsed", async () => {
|
|
70
|
-
const body = await
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
else if (
|
|
74
|
-
else if (
|
|
75
|
-
else return textParser(
|
|
61
|
+
const body = await rawBody();
|
|
62
|
+
const sBody = body.toString();
|
|
63
|
+
if (isJson()) return jsonParser(sBody);
|
|
64
|
+
else if (isFormData()) return formDataParser(sBody);
|
|
65
|
+
else if (isUrlencoded()) return urlEncodedParser(sBody);
|
|
66
|
+
else if (isBinary()) return textParser(sBody);
|
|
67
|
+
else return textParser(sBody);
|
|
76
68
|
});
|
|
77
69
|
function jsonParser(v) {
|
|
78
70
|
try {
|
|
79
|
-
return
|
|
71
|
+
return safeJsonParse(v);
|
|
80
72
|
} catch (error) {
|
|
81
73
|
throw new __wooksjs_event_http.HttpError(400, error.message);
|
|
82
74
|
}
|
|
@@ -85,43 +77,57 @@ else return textParser(body);
|
|
|
85
77
|
return v;
|
|
86
78
|
}
|
|
87
79
|
function formDataParser(v) {
|
|
80
|
+
const MAX_PARTS = 255;
|
|
81
|
+
const MAX_KEY_LENGTH = 100;
|
|
82
|
+
const MAX_VALUE_LENGTH = 100 * 1024;
|
|
88
83
|
const boundary = `--${(/boundary=([^;]+)(?:;|$)/u.exec(contentType || "") || [, ""])[1]}`;
|
|
89
84
|
if (!boundary) throw new __wooksjs_event_http.HttpError(__wooksjs_event_http.EHttpStatusCode.BadRequest, "form-data boundary not recognized");
|
|
90
85
|
const parts = v.trim().split(boundary);
|
|
91
|
-
const result =
|
|
86
|
+
const result = Object.create(null);
|
|
92
87
|
let key = "";
|
|
93
88
|
let partContentType = "text/plain";
|
|
89
|
+
let partCount = 0;
|
|
94
90
|
for (const part of parts) {
|
|
95
91
|
parsePart();
|
|
96
92
|
key = "";
|
|
97
93
|
partContentType = "text/plain";
|
|
94
|
+
if (!part.trim() || part.trim() === "--") continue;
|
|
95
|
+
partCount++;
|
|
96
|
+
if (partCount > MAX_PARTS) throw new __wooksjs_event_http.HttpError(413, "Too many form fields");
|
|
98
97
|
let valueMode = false;
|
|
99
|
-
const lines = part.trim().split(/\n/u).map((
|
|
100
|
-
for (const line of lines)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
const lines = part.trim().split(/\n/u).map((l) => l.trim());
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
if (valueMode) {
|
|
101
|
+
if (line.length + String(result[key] ?? "").length > MAX_VALUE_LENGTH) throw new __wooksjs_event_http.HttpError(413, `Field "${key}" is too large`);
|
|
102
|
+
result[key] = (result[key] ? `${result[key]}\n` : "") + line;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (!line) {
|
|
104
106
|
valueMode = !!key;
|
|
105
|
-
if (valueMode) key = key.replace(/^["']/u, "").replace(/["']$/u, "");
|
|
106
107
|
continue;
|
|
107
108
|
}
|
|
108
109
|
if (line.toLowerCase().startsWith("content-disposition: form-data;")) {
|
|
109
|
-
key = (/name=([^;]+)/.exec(line) || [])[1];
|
|
110
|
-
if (!key) throw new __wooksjs_event_http.HttpError(
|
|
110
|
+
key = (/name=([^;]+)/.exec(line) || [])[1].replace(/^["']|["']$/g, "") ?? "";
|
|
111
|
+
if (!key) throw new __wooksjs_event_http.HttpError(400, `Could not read multipart name: ${line}`);
|
|
112
|
+
if (key.length > MAX_KEY_LENGTH) throw new __wooksjs_event_http.HttpError(413, "Field name too long");
|
|
113
|
+
if ([
|
|
114
|
+
"__proto__",
|
|
115
|
+
"constructor",
|
|
116
|
+
"prototype"
|
|
117
|
+
].includes(key)) throw new __wooksjs_event_http.HttpError(400, `Illegal key name "${key}"`);
|
|
111
118
|
continue;
|
|
112
119
|
}
|
|
113
120
|
if (line.toLowerCase().startsWith("content-type:")) {
|
|
114
|
-
partContentType = (/content-type:\s?([^;]+)/i.exec(line) || [])[1];
|
|
115
|
-
if (!partContentType) throw new __wooksjs_event_http.HttpError(__wooksjs_event_http.EHttpStatusCode.BadRequest, `Could not read content-type: ${line}`);
|
|
121
|
+
partContentType = (/content-type:\s?([^;]+)/i.exec(line) || [])[1] ?? "";
|
|
116
122
|
continue;
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
}
|
|
120
126
|
parsePart();
|
|
127
|
+
return result;
|
|
121
128
|
function parsePart() {
|
|
122
|
-
if (key && partContentType.includes("application/json")) result[key] =
|
|
129
|
+
if (key && partContentType.includes("application/json") && typeof result[key] === "string") result[key] = safeJsonParse(result[key]);
|
|
123
130
|
}
|
|
124
|
-
return result;
|
|
125
131
|
}
|
|
126
132
|
function urlEncodedParser(v) {
|
|
127
133
|
return new __wooksjs_event_http.WooksURLSearchParams(v.trim()).toJson();
|
|
@@ -134,17 +140,10 @@ else {
|
|
|
134
140
|
isBinary,
|
|
135
141
|
isFormData,
|
|
136
142
|
isUrlencoded,
|
|
137
|
-
isCompressed,
|
|
138
|
-
contentEncodings,
|
|
139
143
|
parseBody,
|
|
140
144
|
rawBody
|
|
141
145
|
};
|
|
142
146
|
}
|
|
143
|
-
function registerBodyCompressor(name, compressor) {
|
|
144
|
-
if (compressors[name]) throw new Error(`Body compressor "${name}" already registered.`);
|
|
145
|
-
compressors[name] = compressor;
|
|
146
|
-
}
|
|
147
147
|
|
|
148
148
|
//#endregion
|
|
149
|
-
exports.
|
|
150
|
-
exports.useBody = useBody
|
|
149
|
+
exports.useBody = useBody;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
interface TBodyCompressor {
|
|
2
|
-
compress: (data: string) => string | Promise<string>;
|
|
3
|
-
uncompress: (data: string) => string | Promise<string>;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
1
|
declare function useBody(): {
|
|
7
2
|
isJson: () => boolean;
|
|
8
3
|
isHtml: () => boolean;
|
|
@@ -11,11 +6,8 @@ declare function useBody(): {
|
|
|
11
6
|
isBinary: () => boolean;
|
|
12
7
|
isFormData: () => boolean;
|
|
13
8
|
isUrlencoded: () => boolean;
|
|
14
|
-
isCompressed: () => boolean;
|
|
15
|
-
contentEncodings: () => string[];
|
|
16
9
|
parseBody: <T>() => Promise<T>;
|
|
17
10
|
rawBody: () => Promise<Buffer<ArrayBufferLike>>;
|
|
18
11
|
};
|
|
19
|
-
declare function registerBodyCompressor(name: string, compressor: TBodyCompressor): void;
|
|
20
12
|
|
|
21
|
-
export {
|
|
13
|
+
export { useBody };
|
package/dist/index.mjs
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { EHttpStatusCode, HttpError, WooksURLSearchParams, useHeaders, useHttpContext, useRequest } from "@wooksjs/event-http";
|
|
2
2
|
|
|
3
|
-
//#region packages/http-body/src/utils/
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
3
|
+
//#region packages/http-body/src/utils/safe-json.ts
|
|
4
|
+
const ILLEGAL_KEYS = [
|
|
5
|
+
"__proto__",
|
|
6
|
+
"constructor",
|
|
7
|
+
"prototype"
|
|
8
|
+
];
|
|
9
|
+
const illigalKeySet = new Set(ILLEGAL_KEYS);
|
|
10
|
+
function safeJsonParse(src) {
|
|
11
|
+
return JSON.parse(src, (key, value) => {
|
|
12
|
+
assertKey(key);
|
|
13
|
+
return value;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function assertKey(k) {
|
|
17
|
+
if (illigalKeySet.has(k)) throw new HttpError(400, `Illegal key name "${k}"`);
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
//#endregion
|
|
@@ -21,7 +23,7 @@ function useBody() {
|
|
|
21
23
|
const { store } = useHttpContext();
|
|
22
24
|
const { init } = store("request");
|
|
23
25
|
const { rawBody } = useRequest();
|
|
24
|
-
const { "content-type": contentType
|
|
26
|
+
const { "content-type": contentType } = useHeaders();
|
|
25
27
|
function contentIs(type) {
|
|
26
28
|
return (contentType || "").includes(type);
|
|
27
29
|
}
|
|
@@ -32,27 +34,18 @@ function useBody() {
|
|
|
32
34
|
const isBinary = () => init("isBinary", () => contentIs("application/octet-stream"));
|
|
33
35
|
const isFormData = () => init("isFormData", () => contentIs("multipart/form-data"));
|
|
34
36
|
const isUrlencoded = () => init("isUrlencoded", () => contentIs("application/x-www-form-urlencoded"));
|
|
35
|
-
const isCompressed = () => init("isCompressed", () => {
|
|
36
|
-
const parts = contentEncodings();
|
|
37
|
-
for (const p of parts) if ([
|
|
38
|
-
"deflate",
|
|
39
|
-
"gzip",
|
|
40
|
-
"br"
|
|
41
|
-
].includes(p)) return true;
|
|
42
|
-
return false;
|
|
43
|
-
});
|
|
44
|
-
const contentEncodings = () => init("contentEncodings", () => (contentEncoding || "").split(",").map((p) => p.trim()).filter((p) => !!p));
|
|
45
37
|
const parseBody = () => init("parsed", async () => {
|
|
46
|
-
const body = await
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
else if (
|
|
50
|
-
else if (
|
|
51
|
-
else return textParser(
|
|
38
|
+
const body = await rawBody();
|
|
39
|
+
const sBody = body.toString();
|
|
40
|
+
if (isJson()) return jsonParser(sBody);
|
|
41
|
+
else if (isFormData()) return formDataParser(sBody);
|
|
42
|
+
else if (isUrlencoded()) return urlEncodedParser(sBody);
|
|
43
|
+
else if (isBinary()) return textParser(sBody);
|
|
44
|
+
else return textParser(sBody);
|
|
52
45
|
});
|
|
53
46
|
function jsonParser(v) {
|
|
54
47
|
try {
|
|
55
|
-
return
|
|
48
|
+
return safeJsonParse(v);
|
|
56
49
|
} catch (error) {
|
|
57
50
|
throw new HttpError(400, error.message);
|
|
58
51
|
}
|
|
@@ -61,43 +54,57 @@ else return textParser(body);
|
|
|
61
54
|
return v;
|
|
62
55
|
}
|
|
63
56
|
function formDataParser(v) {
|
|
57
|
+
const MAX_PARTS = 255;
|
|
58
|
+
const MAX_KEY_LENGTH = 100;
|
|
59
|
+
const MAX_VALUE_LENGTH = 100 * 1024;
|
|
64
60
|
const boundary = `--${(/boundary=([^;]+)(?:;|$)/u.exec(contentType || "") || [, ""])[1]}`;
|
|
65
61
|
if (!boundary) throw new HttpError(EHttpStatusCode.BadRequest, "form-data boundary not recognized");
|
|
66
62
|
const parts = v.trim().split(boundary);
|
|
67
|
-
const result =
|
|
63
|
+
const result = Object.create(null);
|
|
68
64
|
let key = "";
|
|
69
65
|
let partContentType = "text/plain";
|
|
66
|
+
let partCount = 0;
|
|
70
67
|
for (const part of parts) {
|
|
71
68
|
parsePart();
|
|
72
69
|
key = "";
|
|
73
70
|
partContentType = "text/plain";
|
|
71
|
+
if (!part.trim() || part.trim() === "--") continue;
|
|
72
|
+
partCount++;
|
|
73
|
+
if (partCount > MAX_PARTS) throw new HttpError(413, "Too many form fields");
|
|
74
74
|
let valueMode = false;
|
|
75
|
-
const lines = part.trim().split(/\n/u).map((
|
|
76
|
-
for (const line of lines)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
const lines = part.trim().split(/\n/u).map((l) => l.trim());
|
|
76
|
+
for (const line of lines) {
|
|
77
|
+
if (valueMode) {
|
|
78
|
+
if (line.length + String(result[key] ?? "").length > MAX_VALUE_LENGTH) throw new HttpError(413, `Field "${key}" is too large`);
|
|
79
|
+
result[key] = (result[key] ? `${result[key]}\n` : "") + line;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (!line) {
|
|
80
83
|
valueMode = !!key;
|
|
81
|
-
if (valueMode) key = key.replace(/^["']/u, "").replace(/["']$/u, "");
|
|
82
84
|
continue;
|
|
83
85
|
}
|
|
84
86
|
if (line.toLowerCase().startsWith("content-disposition: form-data;")) {
|
|
85
|
-
key = (/name=([^;]+)/.exec(line) || [])[1];
|
|
86
|
-
if (!key) throw new HttpError(
|
|
87
|
+
key = (/name=([^;]+)/.exec(line) || [])[1].replace(/^["']|["']$/g, "") ?? "";
|
|
88
|
+
if (!key) throw new HttpError(400, `Could not read multipart name: ${line}`);
|
|
89
|
+
if (key.length > MAX_KEY_LENGTH) throw new HttpError(413, "Field name too long");
|
|
90
|
+
if ([
|
|
91
|
+
"__proto__",
|
|
92
|
+
"constructor",
|
|
93
|
+
"prototype"
|
|
94
|
+
].includes(key)) throw new HttpError(400, `Illegal key name "${key}"`);
|
|
87
95
|
continue;
|
|
88
96
|
}
|
|
89
97
|
if (line.toLowerCase().startsWith("content-type:")) {
|
|
90
|
-
partContentType = (/content-type:\s?([^;]+)/i.exec(line) || [])[1];
|
|
91
|
-
if (!partContentType) throw new HttpError(EHttpStatusCode.BadRequest, `Could not read content-type: ${line}`);
|
|
98
|
+
partContentType = (/content-type:\s?([^;]+)/i.exec(line) || [])[1] ?? "";
|
|
92
99
|
continue;
|
|
93
100
|
}
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
103
|
parsePart();
|
|
104
|
+
return result;
|
|
97
105
|
function parsePart() {
|
|
98
|
-
if (key && partContentType.includes("application/json")) result[key] =
|
|
106
|
+
if (key && partContentType.includes("application/json") && typeof result[key] === "string") result[key] = safeJsonParse(result[key]);
|
|
99
107
|
}
|
|
100
|
-
return result;
|
|
101
108
|
}
|
|
102
109
|
function urlEncodedParser(v) {
|
|
103
110
|
return new WooksURLSearchParams(v.trim()).toJson();
|
|
@@ -110,16 +117,10 @@ else {
|
|
|
110
117
|
isBinary,
|
|
111
118
|
isFormData,
|
|
112
119
|
isUrlencoded,
|
|
113
|
-
isCompressed,
|
|
114
|
-
contentEncodings,
|
|
115
120
|
parseBody,
|
|
116
121
|
rawBody
|
|
117
122
|
};
|
|
118
123
|
}
|
|
119
|
-
function registerBodyCompressor(name, compressor) {
|
|
120
|
-
if (compressors[name]) throw new Error(`Body compressor "${name}" already registered.`);
|
|
121
|
-
compressors[name] = compressor;
|
|
122
|
-
}
|
|
123
124
|
|
|
124
125
|
//#endregion
|
|
125
|
-
export {
|
|
126
|
+
export { useBody };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wooksjs/http-body",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "@wooksjs/http-body",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -38,12 +38,13 @@
|
|
|
38
38
|
"bugs": {
|
|
39
39
|
"url": "https://github.com/wooksjs/wooksjs/issues"
|
|
40
40
|
},
|
|
41
|
-
"peerDependencies": {},
|
|
42
41
|
"homepage": "https://github.com/wooksjs/wooksjs/tree/main/packages/http-body#readme",
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@wooksjs/event-http": "^0.6.1"
|
|
44
|
+
},
|
|
43
45
|
"devDependencies": {
|
|
44
|
-
"typescript": "^5.
|
|
45
|
-
"vitest": "^2.
|
|
46
|
-
"@wooksjs/event-http": "^0.5.25"
|
|
46
|
+
"typescript": "^5.8.3",
|
|
47
|
+
"vitest": "^3.2.4"
|
|
47
48
|
},
|
|
48
49
|
"scripts": {
|
|
49
50
|
"build": "rolldown -c ../../rolldown.config.mjs"
|