appstage 0.2.16 → 0.2.18
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 +27 -17
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +36 -26
- package/package.json +1 -1
- package/src/controllers/files.ts +40 -29
package/dist/index.cjs
CHANGED
|
@@ -48,7 +48,7 @@ const maxLanguages = 3;
|
|
|
48
48
|
async function resolve(...parts) {
|
|
49
49
|
let fullPath = (0, node_path.join)(...parts);
|
|
50
50
|
try {
|
|
51
|
-
if ((await (0, node_fs_promises.lstat)(fullPath)).isFile()) return fullPath;
|
|
51
|
+
if ((await (0, node_fs_promises.lstat)(fullPath)).isFile()) return (0, node_path.resolve)(fullPath);
|
|
52
52
|
} catch {}
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
@@ -88,23 +88,30 @@ const files = (params) => {
|
|
|
88
88
|
let p = typeof params === "string" ? { base: params } : params;
|
|
89
89
|
let bases = Array.isArray(p.base) ? p.base : [p.base];
|
|
90
90
|
let exts = p.extensions ?? defaultExtensions;
|
|
91
|
-
|
|
91
|
+
let fallthrough = p.fallthrough ?? true;
|
|
92
|
+
return async (req, res, next) => {
|
|
92
93
|
let langs = (p.languages ?? defaultLanguages)(req);
|
|
93
94
|
let path = typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
|
|
94
95
|
if (!matches(path, p.matches)) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
if (fallthrough) next();
|
|
97
|
+
else {
|
|
98
|
+
emitLog(req.app, "Unmatched path", { data: { path } });
|
|
99
|
+
res.status(404).send(await req.app.renderStatus?.(req, res, {
|
|
100
|
+
code: "unmatched_path",
|
|
101
|
+
path
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
100
104
|
return;
|
|
101
105
|
}
|
|
102
106
|
if (path.includes("../")) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
if (fallthrough) next();
|
|
108
|
+
else {
|
|
109
|
+
emitLog(req.app, "Invalid path (potential traversal attempt)", { data: { path } });
|
|
110
|
+
res.status(400).send(await req.app.renderStatus?.(req, res, {
|
|
111
|
+
code: "invalid_path",
|
|
112
|
+
path
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
108
115
|
return;
|
|
109
116
|
}
|
|
110
117
|
let filePath = null;
|
|
@@ -121,11 +128,14 @@ const files = (params) => {
|
|
|
121
128
|
for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, path, `index.${exts[i]}`);
|
|
122
129
|
}
|
|
123
130
|
if (filePath === null) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
if (fallthrough) next();
|
|
132
|
+
else {
|
|
133
|
+
emitLog(req.app, "Unknown path", { data: { path } });
|
|
134
|
+
res.status(404).send(await req.app.renderStatus?.(req, res, {
|
|
135
|
+
code: "unknown_path",
|
|
136
|
+
path
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
129
139
|
return;
|
|
130
140
|
}
|
|
131
141
|
if (!p.transform?.length) {
|
package/dist/index.d.ts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { access, lstat, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
import { basename, extname, join, posix, relative, sep } from "node:path";
|
|
2
|
+
import { basename, extname, join, posix, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { formatDate, formatDuration } from "dateshape";
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
5
|
import { STATUS_CODES } from "node:http";
|
|
@@ -19,10 +19,10 @@ function emitLog(app, message, payload) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const maxLanguages = 3;
|
|
22
|
-
async function resolve(...parts) {
|
|
22
|
+
async function resolve$1(...parts) {
|
|
23
23
|
let fullPath = join(...parts);
|
|
24
24
|
try {
|
|
25
|
-
if ((await lstat(fullPath)).isFile()) return fullPath;
|
|
25
|
+
if ((await lstat(fullPath)).isFile()) return resolve(fullPath);
|
|
26
26
|
} catch {}
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
@@ -62,44 +62,54 @@ const files = (params) => {
|
|
|
62
62
|
let p = typeof params === "string" ? { base: params } : params;
|
|
63
63
|
let bases = Array.isArray(p.base) ? p.base : [p.base];
|
|
64
64
|
let exts = p.extensions ?? defaultExtensions;
|
|
65
|
-
|
|
65
|
+
let fallthrough = p.fallthrough ?? true;
|
|
66
|
+
return async (req, res, next) => {
|
|
66
67
|
let langs = (p.languages ?? defaultLanguages)(req);
|
|
67
68
|
let path = typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
|
|
68
69
|
if (!matches(path, p.matches)) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
if (fallthrough) next();
|
|
71
|
+
else {
|
|
72
|
+
emitLog(req.app, "Unmatched path", { data: { path } });
|
|
73
|
+
res.status(404).send(await req.app.renderStatus?.(req, res, {
|
|
74
|
+
code: "unmatched_path",
|
|
75
|
+
path
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
74
78
|
return;
|
|
75
79
|
}
|
|
76
80
|
if (path.includes("../")) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
if (fallthrough) next();
|
|
82
|
+
else {
|
|
83
|
+
emitLog(req.app, "Invalid path (potential traversal attempt)", { data: { path } });
|
|
84
|
+
res.status(400).send(await req.app.renderStatus?.(req, res, {
|
|
85
|
+
code: "invalid_path",
|
|
86
|
+
path
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
82
89
|
return;
|
|
83
90
|
}
|
|
84
91
|
let filePath = null;
|
|
85
92
|
for (let k = 0; k < bases.length && filePath === null; k++) {
|
|
86
93
|
let base = bases[k];
|
|
87
94
|
if (!path.endsWith("/")) {
|
|
88
|
-
for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve(base, `${path}.${langs[i]}`);
|
|
89
|
-
if (filePath === null) filePath = await resolve(base, path);
|
|
90
|
-
for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, `${path}.${langs[i]}.${exts[j]}`);
|
|
91
|
-
for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, `${path}.${exts[i]}`);
|
|
95
|
+
for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve$1(base, `${path}.${langs[i]}`);
|
|
96
|
+
if (filePath === null) filePath = await resolve$1(base, path);
|
|
97
|
+
for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve$1(base, `${path}.${langs[i]}.${exts[j]}`);
|
|
98
|
+
for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve$1(base, `${path}.${exts[i]}`);
|
|
92
99
|
}
|
|
93
|
-
for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, `${path}.${langs[i]}`, `index.${exts[j]}`);
|
|
94
|
-
for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, path, `index.${langs[i]}.${exts[j]}`);
|
|
95
|
-
for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, path, `index.${exts[i]}`);
|
|
100
|
+
for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve$1(base, `${path}.${langs[i]}`, `index.${exts[j]}`);
|
|
101
|
+
for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve$1(base, path, `index.${langs[i]}.${exts[j]}`);
|
|
102
|
+
for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve$1(base, path, `index.${exts[i]}`);
|
|
96
103
|
}
|
|
97
104
|
if (filePath === null) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
if (fallthrough) next();
|
|
106
|
+
else {
|
|
107
|
+
emitLog(req.app, "Unknown path", { data: { path } });
|
|
108
|
+
res.status(404).send(await req.app.renderStatus?.(req, res, {
|
|
109
|
+
code: "unknown_path",
|
|
110
|
+
path
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
103
113
|
return;
|
|
104
114
|
}
|
|
105
115
|
if (!p.transform?.length) {
|
package/package.json
CHANGED
package/src/controllers/files.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { lstat, readFile } from "node:fs/promises";
|
|
2
|
-
import { basename, extname, join } from "node:path";
|
|
2
|
+
import { basename, extname, join, resolve as resolveAbsPath } from "node:path";
|
|
3
3
|
import type { Request } from "express";
|
|
4
4
|
import type { Controller } from "../types/Controller.ts";
|
|
5
5
|
import type { TransformContent } from "../types/TransformContent.ts";
|
|
@@ -17,7 +17,7 @@ const maxLanguages = 3;
|
|
|
17
17
|
async function resolve(...parts: string[]) {
|
|
18
18
|
let fullPath = join(...parts);
|
|
19
19
|
try {
|
|
20
|
-
if ((await lstat(fullPath)).isFile()) return fullPath;
|
|
20
|
+
if ((await lstat(fullPath)).isFile()) return resolveAbsPath(fullPath);
|
|
21
21
|
} catch {}
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
@@ -69,6 +69,7 @@ export type FilesParams = {
|
|
|
69
69
|
extensions?: string[];
|
|
70
70
|
languages?: (req: Request) => string[];
|
|
71
71
|
transform?: TransformContent[];
|
|
72
|
+
fallthrough?: boolean;
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
const defaultExtensions = ["html", "htm"];
|
|
@@ -84,37 +85,44 @@ export const files: Controller<string | FilesParams> = (params) => {
|
|
|
84
85
|
|
|
85
86
|
let bases = Array.isArray(p.base) ? p.base : [p.base];
|
|
86
87
|
let exts = p.extensions ?? defaultExtensions;
|
|
88
|
+
let fallthrough = p.fallthrough ?? true;
|
|
87
89
|
|
|
88
|
-
return async (req, res) => {
|
|
90
|
+
return async (req, res, next) => {
|
|
89
91
|
let langs = (p.languages ?? defaultLanguages)(req);
|
|
90
92
|
|
|
91
93
|
let path =
|
|
92
94
|
typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
|
|
93
95
|
|
|
94
96
|
if (!matches(path, p.matches)) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
if (fallthrough) next();
|
|
98
|
+
else {
|
|
99
|
+
emitLog(req.app, "Unmatched path", { data: { path } });
|
|
100
|
+
|
|
101
|
+
res.status(404).send(
|
|
102
|
+
await req.app.renderStatus?.(req, res, {
|
|
103
|
+
code: "unmatched_path",
|
|
104
|
+
path,
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
103
108
|
|
|
104
109
|
return;
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
if (path.includes("../")) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
if (fallthrough) next();
|
|
114
|
+
else {
|
|
115
|
+
emitLog(req.app, "Invalid path (potential traversal attempt)", {
|
|
116
|
+
data: { path },
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
res.status(400).send(
|
|
120
|
+
await req.app.renderStatus?.(req, res, {
|
|
121
|
+
code: "invalid_path",
|
|
122
|
+
path,
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
118
126
|
|
|
119
127
|
return;
|
|
120
128
|
}
|
|
@@ -167,14 +175,17 @@ export const files: Controller<string | FilesParams> = (params) => {
|
|
|
167
175
|
}
|
|
168
176
|
|
|
169
177
|
if (filePath === null) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
+
if (fallthrough) next();
|
|
179
|
+
else {
|
|
180
|
+
emitLog(req.app, "Unknown path", { data: { path } });
|
|
181
|
+
|
|
182
|
+
res.status(404).send(
|
|
183
|
+
await req.app.renderStatus?.(req, res, {
|
|
184
|
+
code: "unknown_path",
|
|
185
|
+
path,
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
178
189
|
|
|
179
190
|
return;
|
|
180
191
|
}
|