@server/next 0.14.0 → 0.15.0
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/package.json +21 -28
- package/readme.md +66 -67
- package/src/RequestLogger.js +77 -0
- package/src/ServerUrl.js +32 -0
- package/src/ServerUrl.test.js +45 -0
- package/src/color.js +26 -0
- package/src/index.js +280 -0
- package/src/parseBody.js +96 -0
- package/src/parseBody.test.js +69 -0
- package/src/pathPattern.js +24 -0
- package/src/pathPattern.test.js +72 -0
- package/index.js +0 -799
package/src/parseBody.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
function getBoundary(header) {
|
|
2
|
+
if (!header) return null;
|
|
3
|
+
var items = header.split(";");
|
|
4
|
+
if (items)
|
|
5
|
+
for (var j = 0; j < items.length; j++) {
|
|
6
|
+
var item = new String(items[j]).trim();
|
|
7
|
+
if (item.indexOf("boundary") >= 0) {
|
|
8
|
+
var k = item.split("=");
|
|
9
|
+
return new String(k[1]).trim();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getMatching(string, regex) {
|
|
16
|
+
// Helper function when using non-matching groups
|
|
17
|
+
const matches = string.match(regex);
|
|
18
|
+
if (!matches || matches.length < 2) {
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
return matches[1];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const getBody = async (req) => {
|
|
25
|
+
return await new Promise((done) => {
|
|
26
|
+
let data = "";
|
|
27
|
+
req.on("data", (chunk) => {
|
|
28
|
+
data += chunk;
|
|
29
|
+
});
|
|
30
|
+
req.on("end", () => {
|
|
31
|
+
done(data);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default async function Parse(req, contentType) {
|
|
37
|
+
const rawData = await (typeof req === "string" ? req : getBody(req));
|
|
38
|
+
if (!rawData) return null;
|
|
39
|
+
|
|
40
|
+
if (/application\/json/.test(contentType)) {
|
|
41
|
+
return { body: JSON.parse(rawData), files: {} };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const boundary = getBoundary(contentType);
|
|
45
|
+
if (!boundary) return null;
|
|
46
|
+
|
|
47
|
+
let result = {};
|
|
48
|
+
|
|
49
|
+
const body = {};
|
|
50
|
+
const files = {};
|
|
51
|
+
|
|
52
|
+
const rawDataArray = rawData.split(boundary);
|
|
53
|
+
for (let item of rawDataArray) {
|
|
54
|
+
// Use non-matching groups to exclude part of the result
|
|
55
|
+
const name = getMatching(item, /(?:name=")(.+?)(?:")/)
|
|
56
|
+
.trim()
|
|
57
|
+
.replace(/\[\]$/, "");
|
|
58
|
+
if (!name) continue;
|
|
59
|
+
const value = getMatching(item, /(?:\r\n\r\n)([\S\s]*)(?:\r\n--$)/);
|
|
60
|
+
if (!value) continue;
|
|
61
|
+
|
|
62
|
+
const filename = getMatching(item, /(?:filename=")(.*?)(?:")/).trim();
|
|
63
|
+
// It's a file!
|
|
64
|
+
if (filename) {
|
|
65
|
+
const file = { name: filename };
|
|
66
|
+
const type = getMatching(item, /(?:Content-Type:)(.*?)(?:\r\n)/).trim();
|
|
67
|
+
if (type) {
|
|
68
|
+
file.type = type;
|
|
69
|
+
}
|
|
70
|
+
file.value = value;
|
|
71
|
+
|
|
72
|
+
// Already exists, so (maybe convert to an array) and push the item in it
|
|
73
|
+
if (files[name]) {
|
|
74
|
+
if (!Array.isArray(files.name)) {
|
|
75
|
+
files[name] = [files[name]];
|
|
76
|
+
}
|
|
77
|
+
files[name].push(file);
|
|
78
|
+
} else {
|
|
79
|
+
files[name] = file;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// It's a body
|
|
83
|
+
} else {
|
|
84
|
+
if (body[name]) {
|
|
85
|
+
if (!Array.isArray(body[name])) {
|
|
86
|
+
body[name] = [body[name]];
|
|
87
|
+
}
|
|
88
|
+
body[name].push(value);
|
|
89
|
+
} else {
|
|
90
|
+
body[name] = value;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { body, files };
|
|
96
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import parseBody from "./parseBody.js";
|
|
2
|
+
|
|
3
|
+
const getBody = () => {
|
|
4
|
+
let body = "trash1\r\n";
|
|
5
|
+
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp\r\n";
|
|
6
|
+
body += 'Content-Disposition: form-data; name="hello";\r\n\r\n';
|
|
7
|
+
body += "world\r\n";
|
|
8
|
+
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp\r\n";
|
|
9
|
+
body +=
|
|
10
|
+
'Content-Disposition: form-data; name="profile"; filename="profile.md"\r\n';
|
|
11
|
+
body += "Content-Type: text/plain\r\n\r\n";
|
|
12
|
+
body += "@11X";
|
|
13
|
+
body += "111Y\r\n";
|
|
14
|
+
body += "111Z\rCCCC\nCCCC\r\nCCCCC@\r\n\r\n";
|
|
15
|
+
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp\r\n";
|
|
16
|
+
body +=
|
|
17
|
+
'Content-Disposition: form-data; name="gallery[]"; filename="A.txt"\r\n';
|
|
18
|
+
body += "Content-Type: text/plain\r\n\r\n";
|
|
19
|
+
body += "@11X";
|
|
20
|
+
body += "111Y\r\n";
|
|
21
|
+
body += "111Z\rCCCC\nCCCC\r\nCCCCC@\r\n\r\n";
|
|
22
|
+
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp\r\n";
|
|
23
|
+
body += 'Content-Disposition: form-data; name="test";\r\n\r\n';
|
|
24
|
+
body += "test message 123456\r\n";
|
|
25
|
+
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp\r\n";
|
|
26
|
+
body += 'Content-Disposition: form-data; name="test";\r\n\r\n';
|
|
27
|
+
body += "test message number two\r\n";
|
|
28
|
+
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp\r\n";
|
|
29
|
+
body +=
|
|
30
|
+
'Content-Disposition: form-data; name="gallery[]"; filename="C.txt"\r\n';
|
|
31
|
+
body += "Content-Type: text/plain\r\n\r\n";
|
|
32
|
+
body += "@CCC";
|
|
33
|
+
body += "CCCY\r\n";
|
|
34
|
+
body += "CCCZ\rCCCW\nCCC0\r\n666@\r\n";
|
|
35
|
+
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp--\r\n";
|
|
36
|
+
return body;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe("parseBody", () => {
|
|
40
|
+
it("can parse the example body", async () => {
|
|
41
|
+
const parsed = await parseBody(
|
|
42
|
+
getBody(),
|
|
43
|
+
"multipart/form-data; boundary=----WebKitFormBoundaryvef1fLxmoUdYZWXp"
|
|
44
|
+
);
|
|
45
|
+
expect(parsed.body).toEqual({
|
|
46
|
+
hello: "world",
|
|
47
|
+
test: ["test message 123456", "test message number two"],
|
|
48
|
+
});
|
|
49
|
+
expect(parsed.files).toEqual({
|
|
50
|
+
profile: {
|
|
51
|
+
name: "profile.md",
|
|
52
|
+
type: "text/plain",
|
|
53
|
+
value: "@11X111Y\r\n111Z\rCCCC\nCCCC\r\nCCCCC@\r\n",
|
|
54
|
+
},
|
|
55
|
+
gallery: [
|
|
56
|
+
{
|
|
57
|
+
name: "A.txt",
|
|
58
|
+
type: "text/plain",
|
|
59
|
+
value: "@11X111Y\r\n111Z\rCCCC\nCCCC\r\nCCCCC@\r\n",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "C.txt",
|
|
63
|
+
type: "text/plain",
|
|
64
|
+
value: "@CCCCCCY\r\nCCCZ\rCCCW\nCCC0\r\n666@",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { URLPattern } from "urlpattern-polyfill";
|
|
2
|
+
|
|
3
|
+
export default function pathPattern(pattern, path) {
|
|
4
|
+
pattern = pattern.replace(/\/$/, "") || "/";
|
|
5
|
+
path = path.replace(/\/$/, "") || "/";
|
|
6
|
+
const origin =
|
|
7
|
+
typeof location === "object" ? location.origin : "https://example.com/";
|
|
8
|
+
const patt = new URLPattern(pattern, origin);
|
|
9
|
+
const match = patt.exec(path, origin);
|
|
10
|
+
// console.log(match, path, pattern);
|
|
11
|
+
if (!match) return false;
|
|
12
|
+
const groups = match.pathname.groups;
|
|
13
|
+
const rest = Object.keys(groups)
|
|
14
|
+
.filter((k) => /^\d+$/.test(k))
|
|
15
|
+
.reduce((all, key) => {
|
|
16
|
+
const value = groups[key];
|
|
17
|
+
delete groups[key];
|
|
18
|
+
if (!value) return all;
|
|
19
|
+
all = all.concat(...value.split("/"));
|
|
20
|
+
return all;
|
|
21
|
+
}, []);
|
|
22
|
+
if (rest.length) groups["*"] = rest;
|
|
23
|
+
return groups;
|
|
24
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import pathPattern from "./pathPattern.js";
|
|
2
|
+
|
|
3
|
+
describe("pathPattern.js", () => {
|
|
4
|
+
it("matches the same string", () => {
|
|
5
|
+
expect(pathPattern("/hello", "/hello")).toEqual({});
|
|
6
|
+
expect(pathPattern("/hello/world", "/hello/world")).toEqual({});
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("is trailing slash insensitive both ways", () => {
|
|
10
|
+
expect(pathPattern("/hello", "/hello")).toEqual({});
|
|
11
|
+
expect(pathPattern("/hello", "/hello/")).toEqual({});
|
|
12
|
+
expect(pathPattern("/hello/", "/hello")).toEqual({});
|
|
13
|
+
expect(pathPattern("/hello/", "/hello/")).toEqual({});
|
|
14
|
+
|
|
15
|
+
expect(pathPattern("/hello/world", "/hello/world")).toEqual({});
|
|
16
|
+
expect(pathPattern("/hello/world", "/hello/world/")).toEqual({});
|
|
17
|
+
expect(pathPattern("/hello/world/", "/hello/world")).toEqual({});
|
|
18
|
+
expect(pathPattern("/hello/world/", "/hello/world/")).toEqual({});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("doesn't do partial matches", () => {
|
|
22
|
+
expect(pathPattern("/hello", "/hello/John")).toEqual(false);
|
|
23
|
+
expect(pathPattern("/hello/", "/hello/John")).toEqual(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("can capture simple groups", () => {
|
|
27
|
+
expect(pathPattern("/:hello", "/john")).toEqual({ hello: "john" });
|
|
28
|
+
expect(pathPattern("/hello/:there", "/hello/john")).toEqual({
|
|
29
|
+
there: "john",
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("requires a part for the asterisk", () => {
|
|
34
|
+
expect(pathPattern("/hello/:there/*", "/hello/John")).toEqual(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("can make a part optional", () => {
|
|
38
|
+
// expect(pathPattern("/:name?", "/")).toEqual({});
|
|
39
|
+
expect(pathPattern("/hello/:name?", "/hello/")).toEqual({});
|
|
40
|
+
expect(pathPattern("/:name?", "/john")).toEqual({ name: "john" });
|
|
41
|
+
expect(pathPattern("/:name/*?", "/john")).toEqual({ name: "john" });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("correctly matches the asterisk as an array of parts", () => {
|
|
45
|
+
expect(pathPattern("/*", "/john")).toEqual({ "*": ["john"] });
|
|
46
|
+
expect(pathPattern("/*", "/john/doe")).toEqual({ "*": ["john", "doe"] });
|
|
47
|
+
expect(pathPattern("/*/*", "/john/doe")).toEqual({ "*": ["john", "doe"] });
|
|
48
|
+
|
|
49
|
+
expect(pathPattern("/hello/*", "/hello/john")).toEqual({ "*": ["john"] });
|
|
50
|
+
expect(pathPattern("/hello/*", "/hello/john/doe")).toEqual({
|
|
51
|
+
"*": ["john", "doe"],
|
|
52
|
+
});
|
|
53
|
+
expect(pathPattern("/hello/*/*", "/hello/john/doe")).toEqual({
|
|
54
|
+
"*": ["john", "doe"],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(pathPattern("/:name/*", "/john/doe")).toEqual({
|
|
58
|
+
name: "john",
|
|
59
|
+
"*": ["doe"],
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(pathPattern("/:name/*", "/john/doe/derek")).toEqual({
|
|
63
|
+
name: "john",
|
|
64
|
+
"*": ["doe", "derek"],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(pathPattern("/:name/*/*", "/john/doe/derek")).toEqual({
|
|
68
|
+
name: "john",
|
|
69
|
+
"*": ["doe", "derek"],
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|