godprotocol 2.3.30 → 2.3.31
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 +1 -1
- package/server/parse_multipart_data.js +113 -15
- package/server/serve_static_file.js +85 -17
- package/server/version_control.js +6 -4
package/package.json
CHANGED
|
@@ -1,36 +1,134 @@
|
|
|
1
1
|
import Busboy from "busboy";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
|
|
6
|
+
const allowedExtensions = new Set([
|
|
7
|
+
".jpg",
|
|
8
|
+
".jpeg",
|
|
9
|
+
".png",
|
|
10
|
+
".gif",
|
|
11
|
+
".webp",
|
|
12
|
+
".pdf",
|
|
13
|
+
".doc",
|
|
14
|
+
".docx",
|
|
15
|
+
".xls",
|
|
16
|
+
".xlsx",
|
|
17
|
+
".csv",
|
|
18
|
+
".txt",
|
|
19
|
+
".zip",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const allowedMimeTypes = new Set([
|
|
23
|
+
"image/jpeg",
|
|
24
|
+
"image/png",
|
|
25
|
+
"image/gif",
|
|
26
|
+
"image/webp",
|
|
27
|
+
"application/pdf",
|
|
28
|
+
"application/msword",
|
|
29
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
30
|
+
"application/vnd.ms-excel",
|
|
31
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
32
|
+
"text/plain",
|
|
33
|
+
"text/csv",
|
|
34
|
+
"application/zip",
|
|
35
|
+
"application/x-zip-compressed",
|
|
36
|
+
]);
|
|
4
37
|
|
|
5
38
|
const parseMultipart = (req, gp) => {
|
|
6
39
|
return new Promise((resolve, reject) => {
|
|
7
|
-
const busboy = Busboy({
|
|
40
|
+
const busboy = Busboy({
|
|
41
|
+
headers: req.headers,
|
|
42
|
+
});
|
|
8
43
|
|
|
9
44
|
const fields = {};
|
|
10
|
-
|
|
45
|
+
const files = {};
|
|
46
|
+
const writes = [];
|
|
47
|
+
|
|
48
|
+
const uploadDir = `.${gp.static_path}/uploads`;
|
|
11
49
|
|
|
12
|
-
const uploadDir = `./${gp.static_path.slice(1)}/uploads`;
|
|
13
50
|
if (!fs.existsSync(uploadDir)) {
|
|
14
51
|
fs.mkdirSync(uploadDir, { recursive: true });
|
|
15
52
|
}
|
|
16
53
|
|
|
17
|
-
busboy.on("file", (name, file, info) => {
|
|
18
|
-
const filename = `${Date.now()}-${info.filename}`;
|
|
19
|
-
filePath = path.join(uploadDir, filename);
|
|
20
|
-
|
|
21
|
-
const stream = fs.createWriteStream(filePath);
|
|
22
|
-
file.pipe(stream);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
54
|
busboy.on("field", (name, value) => {
|
|
26
55
|
fields[name] = value;
|
|
27
56
|
});
|
|
28
57
|
|
|
29
|
-
busboy.on("
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
58
|
+
busboy.on("file", (fieldName, file, info) => {
|
|
59
|
+
// -----------------------------
|
|
60
|
+
// Sanitise client filename
|
|
61
|
+
// -----------------------------
|
|
62
|
+
const originalName = path.basename(info.filename || "");
|
|
63
|
+
|
|
64
|
+
const extension = path.extname(originalName).toLowerCase();
|
|
65
|
+
|
|
66
|
+
if (
|
|
67
|
+
!allowedExtensions.has(extension) ||
|
|
68
|
+
!allowedMimeTypes.has(info.mimeType)
|
|
69
|
+
) {
|
|
70
|
+
file.resume();
|
|
71
|
+
|
|
72
|
+
return reject(
|
|
73
|
+
new Error(`Unsupported file type (${extension}, ${info.mimeType})`),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// -----------------------------
|
|
78
|
+
// Generate safe filename
|
|
79
|
+
// -----------------------------
|
|
80
|
+
const filename = `${Date.now()}-${crypto.randomUUID()}${extension}`;
|
|
81
|
+
|
|
82
|
+
const filepath = path.join(uploadDir, filename);
|
|
83
|
+
|
|
84
|
+
const stream = fs.createWriteStream(filepath);
|
|
85
|
+
|
|
86
|
+
let size = 0;
|
|
87
|
+
|
|
88
|
+
file.on("data", (chunk) => {
|
|
89
|
+
size += chunk.length;
|
|
33
90
|
});
|
|
91
|
+
|
|
92
|
+
const write = new Promise((resolveWrite, rejectWrite) => {
|
|
93
|
+
stream.on("finish", resolveWrite);
|
|
94
|
+
|
|
95
|
+
stream.on("error", rejectWrite);
|
|
96
|
+
|
|
97
|
+
file.on("error", rejectWrite);
|
|
98
|
+
|
|
99
|
+
file.pipe(stream);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
writes.push(write);
|
|
103
|
+
|
|
104
|
+
if (!files[fieldName]) {
|
|
105
|
+
files[fieldName] = [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
files[fieldName].push({
|
|
109
|
+
field: fieldName,
|
|
110
|
+
original_name: originalName,
|
|
111
|
+
filename,
|
|
112
|
+
mime_type: info.mimeType,
|
|
113
|
+
encoding: info.encoding,
|
|
114
|
+
extension,
|
|
115
|
+
size,
|
|
116
|
+
path: filepath,
|
|
117
|
+
url: `${gp.static_path}/uploads/${filename}`,
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
busboy.on("finish", async () => {
|
|
122
|
+
try {
|
|
123
|
+
await Promise.all(writes);
|
|
124
|
+
|
|
125
|
+
resolve({
|
|
126
|
+
...fields,
|
|
127
|
+
files,
|
|
128
|
+
});
|
|
129
|
+
} catch (err) {
|
|
130
|
+
reject(err);
|
|
131
|
+
}
|
|
34
132
|
});
|
|
35
133
|
|
|
36
134
|
busboy.on("error", reject);
|
|
@@ -1,20 +1,66 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
|
|
4
|
+
const MIME_TYPES = {
|
|
5
|
+
".pdf": "application/pdf",
|
|
6
|
+
".docx":
|
|
7
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
8
|
+
".json": "application/json",
|
|
9
|
+
".txt": "text/plain",
|
|
10
|
+
".png": "image/png",
|
|
11
|
+
".jpg": "image/jpeg",
|
|
12
|
+
".jpeg": "image/jpeg",
|
|
13
|
+
".gif": "image/gif",
|
|
14
|
+
".svg": "image/svg+xml",
|
|
15
|
+
".webp": "image/webp",
|
|
16
|
+
".css": "text/css",
|
|
17
|
+
".js": "application/javascript",
|
|
18
|
+
".html": "text/html",
|
|
19
|
+
};
|
|
20
|
+
|
|
4
21
|
const serveStaticFile = (req, res) => {
|
|
5
22
|
try {
|
|
6
23
|
const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
|
|
7
24
|
|
|
8
|
-
|
|
25
|
+
// Root static directory
|
|
26
|
+
const staticRoot = path.resolve(process.cwd(), `.${res.gp.static_path}`);
|
|
27
|
+
|
|
28
|
+
// Remove "/static" (or whatever your static path is)
|
|
29
|
+
let relativePath = parsedUrl.pathname.replace(res.gp.static_path, "");
|
|
30
|
+
|
|
31
|
+
// Decode URI
|
|
32
|
+
relativePath = decodeURIComponent(relativePath);
|
|
33
|
+
|
|
34
|
+
// Remove leading slashes
|
|
35
|
+
relativePath = relativePath.replace(/^[/\\]+/, "");
|
|
9
36
|
|
|
10
|
-
//
|
|
11
|
-
|
|
37
|
+
// Normalize path
|
|
38
|
+
relativePath = path.normalize(relativePath);
|
|
12
39
|
|
|
13
|
-
|
|
14
|
-
|
|
40
|
+
// Resolve final file
|
|
41
|
+
const fullPath = path.resolve(staticRoot, relativePath);
|
|
42
|
+
|
|
43
|
+
// Prevent path traversal
|
|
44
|
+
if (
|
|
45
|
+
!fullPath.startsWith(staticRoot + path.sep) &&
|
|
46
|
+
fullPath !== staticRoot
|
|
47
|
+
) {
|
|
48
|
+
res.statusCode = 403;
|
|
49
|
+
res.setHeader("Content-Type", "application/json");
|
|
50
|
+
|
|
51
|
+
return res.end(
|
|
52
|
+
JSON.stringify({
|
|
53
|
+
ok: false,
|
|
54
|
+
message: "Access denied",
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Must exist
|
|
15
60
|
if (!fs.existsSync(fullPath)) {
|
|
16
61
|
res.statusCode = 404;
|
|
17
62
|
res.setHeader("Content-Type", "application/json");
|
|
63
|
+
|
|
18
64
|
return res.end(
|
|
19
65
|
JSON.stringify({
|
|
20
66
|
ok: false,
|
|
@@ -23,30 +69,52 @@ const serveStaticFile = (req, res) => {
|
|
|
23
69
|
);
|
|
24
70
|
}
|
|
25
71
|
|
|
26
|
-
|
|
72
|
+
// Must be a file
|
|
73
|
+
const stat = fs.statSync(fullPath);
|
|
27
74
|
|
|
28
|
-
|
|
29
|
-
|
|
75
|
+
if (!stat.isFile()) {
|
|
76
|
+
res.statusCode = 404;
|
|
77
|
+
res.setHeader("Content-Type", "application/json");
|
|
78
|
+
|
|
79
|
+
return res.end(
|
|
80
|
+
JSON.stringify({
|
|
81
|
+
ok: false,
|
|
82
|
+
message: "File not found",
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
30
86
|
|
|
31
|
-
const
|
|
32
|
-
".pdf": "application/pdf",
|
|
33
|
-
".docx":
|
|
34
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
35
|
-
".json": "application/json",
|
|
36
|
-
".txt": "text/plain",
|
|
37
|
-
};
|
|
87
|
+
const ext = path.extname(fullPath).toLowerCase();
|
|
38
88
|
|
|
39
89
|
res.statusCode = 200;
|
|
40
|
-
res.setHeader(
|
|
90
|
+
res.setHeader(
|
|
91
|
+
"Content-Type",
|
|
92
|
+
MIME_TYPES[ext] || "application/octet-stream",
|
|
93
|
+
);
|
|
94
|
+
res.setHeader("Content-Length", stat.size);
|
|
95
|
+
|
|
96
|
+
const stream = fs.createReadStream(fullPath);
|
|
97
|
+
|
|
98
|
+
stream.on("error", () => {
|
|
99
|
+
if (!res.headersSent) {
|
|
100
|
+
res.statusCode = 500;
|
|
101
|
+
res.end();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
41
104
|
|
|
42
105
|
stream.pipe(res);
|
|
43
106
|
|
|
44
|
-
res.
|
|
107
|
+
res.on("finish", () => {
|
|
108
|
+
res.gp?.resource?.compute_response(res);
|
|
109
|
+
});
|
|
110
|
+
|
|
45
111
|
return true;
|
|
46
112
|
} catch (err) {
|
|
47
113
|
console.error("Static file error:", err);
|
|
48
114
|
|
|
49
115
|
res.statusCode = 500;
|
|
116
|
+
res.setHeader("Content-Type", "application/json");
|
|
117
|
+
|
|
50
118
|
res.end(
|
|
51
119
|
JSON.stringify({
|
|
52
120
|
ok: false,
|
|
@@ -117,14 +117,16 @@ const version_middleware = async (req, res, routers) => {
|
|
|
117
117
|
res.request_id = req.request_id;
|
|
118
118
|
|
|
119
119
|
// STATIC ROUTE DETECTION
|
|
120
|
-
if (routers.static_path && pathname.startsWith(routers.static_path)) {
|
|
121
|
-
return serveStaticFile(req, res);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
120
|
let version;
|
|
125
121
|
|
|
126
122
|
const versionMatch = pathname.match(/^\/api\/(v\d+)(\/|$)/i);
|
|
127
123
|
if (req.method === "GET") {
|
|
124
|
+
if (routers.static_path && pathname.startsWith(routers.static_path)) {
|
|
125
|
+
res.gp = routers.gp;
|
|
126
|
+
|
|
127
|
+
return serveStaticFile(req, res);
|
|
128
|
+
}
|
|
129
|
+
|
|
128
130
|
version = "__GET__";
|
|
129
131
|
} else if (versionMatch) {
|
|
130
132
|
version = versionMatch[1];
|