kempo-server 1.7.0 → 1.7.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/package.json +1 -1
- package/src/router.js +48 -32
- package/tests/customRoute-outside-root.node-test.js +81 -0
package/package.json
CHANGED
package/src/router.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
|
|
18
18
|
export default async (flags, log) => {
|
|
19
19
|
log('Initializing router', 2);
|
|
20
|
-
const rootPath = path.join(process.cwd(), flags.root);
|
|
20
|
+
const rootPath = path.isAbsolute(flags.root) ? flags.root : path.join(process.cwd(), flags.root);
|
|
21
21
|
log(`Root path: ${rootPath}`, 2);
|
|
22
22
|
|
|
23
23
|
let config = defaultConfig;
|
|
@@ -136,27 +136,19 @@ export default async (flags, log) => {
|
|
|
136
136
|
|
|
137
137
|
if (config.customRoutes && Object.keys(config.customRoutes).length > 0) {
|
|
138
138
|
log(`Processing ${Object.keys(config.customRoutes).length} custom routes`, 2);
|
|
139
|
-
|
|
140
139
|
for (const [urlPath, filePath] of Object.entries(config.customRoutes)) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
await stat(resolvedPath);
|
|
154
|
-
|
|
155
|
-
customRoutes.set(urlPath, resolvedPath);
|
|
156
|
-
log(`Custom route mapped: ${urlPath} -> ${resolvedPath}`, 2);
|
|
157
|
-
}
|
|
158
|
-
} catch (error) {
|
|
159
|
-
log(`Custom route error for ${urlPath} -> ${filePath}: ${error.message}`, 1);
|
|
140
|
+
// Check if this is a wildcard route
|
|
141
|
+
if (urlPath.includes('*')) {
|
|
142
|
+
// Resolve the file path relative to the current working directory
|
|
143
|
+
const resolvedPath = path.resolve(filePath);
|
|
144
|
+
// Store wildcard routes separately for pattern matching
|
|
145
|
+
wildcardRoutes.set(urlPath, resolvedPath);
|
|
146
|
+
log(`Wildcard route mapped: ${urlPath} -> ${resolvedPath}`, 2);
|
|
147
|
+
} else {
|
|
148
|
+
// Resolve the file path relative to the current working directory
|
|
149
|
+
const resolvedPath = path.resolve(filePath);
|
|
150
|
+
customRoutes.set(urlPath, resolvedPath);
|
|
151
|
+
log(`Custom route mapped: ${urlPath} -> ${resolvedPath}`, 2);
|
|
160
152
|
}
|
|
161
153
|
}
|
|
162
154
|
}
|
|
@@ -247,39 +239,63 @@ export default async (flags, log) => {
|
|
|
247
239
|
const requestPath = req.url.split('?')[0];
|
|
248
240
|
log(`${req.method} ${requestPath}`, 0);
|
|
249
241
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
242
|
+
|
|
243
|
+
// Check custom routes first (allow outside rootPath)
|
|
244
|
+
log(`customRoutes keys: ${Array.from(customRoutes.keys()).join(', ')}`, 1);
|
|
245
|
+
// Normalize requestPath and keys for matching
|
|
246
|
+
const normalizePath = p => {
|
|
247
|
+
let np = decodeURIComponent(p);
|
|
248
|
+
if (!np.startsWith('/')) np = '/' + np;
|
|
249
|
+
if (np.length > 1 && np.endsWith('/')) np = np.slice(0, -1);
|
|
250
|
+
return np;
|
|
251
|
+
};
|
|
252
|
+
const normalizedRequestPath = normalizePath(requestPath);
|
|
253
|
+
log(`Normalized requestPath: ${normalizedRequestPath}`, 1);
|
|
254
|
+
let matchedKey = null;
|
|
255
|
+
for (const key of customRoutes.keys()) {
|
|
256
|
+
if (normalizePath(key) === normalizedRequestPath) {
|
|
257
|
+
matchedKey = key;
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (matchedKey) {
|
|
262
|
+
const customFilePath = customRoutes.get(matchedKey);
|
|
263
|
+
log(`Serving custom route: ${normalizedRequestPath} -> ${customFilePath}`, 2);
|
|
255
264
|
try {
|
|
265
|
+
const { stat } = await import('fs/promises');
|
|
266
|
+
try {
|
|
267
|
+
await stat(customFilePath);
|
|
268
|
+
log(`Custom route file exists: ${customFilePath}`, 2);
|
|
269
|
+
} catch (e) {
|
|
270
|
+
log(`Custom route file does NOT exist: ${customFilePath}`, 0);
|
|
271
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
272
|
+
res.end('Custom route file not found');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
256
275
|
const fileContent = await readFile(customFilePath);
|
|
257
276
|
const fileExtension = path.extname(customFilePath).toLowerCase().slice(1);
|
|
258
277
|
const mimeType = config.allowedMimes[fileExtension] || 'application/octet-stream';
|
|
259
|
-
|
|
260
278
|
log(`Serving custom file as ${mimeType} (${fileContent.length} bytes)`, 2);
|
|
261
279
|
res.writeHead(200, { 'Content-Type': mimeType });
|
|
262
280
|
res.end(fileContent);
|
|
263
281
|
return; // Successfully served custom route
|
|
264
282
|
} catch (error) {
|
|
265
|
-
log(`Error serving custom route ${
|
|
283
|
+
log(`Error serving custom route ${normalizedRequestPath}: ${error.message}`, 0);
|
|
266
284
|
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
267
285
|
res.end('Internal Server Error');
|
|
268
286
|
return;
|
|
269
287
|
}
|
|
270
288
|
}
|
|
271
|
-
|
|
272
|
-
// Check wildcard routes
|
|
289
|
+
|
|
290
|
+
// Check wildcard routes (allow outside rootPath)
|
|
273
291
|
const wildcardMatch = findWildcardRoute(requestPath);
|
|
274
292
|
if (wildcardMatch) {
|
|
275
293
|
const resolvedFilePath = resolveWildcardPath(wildcardMatch.filePath, wildcardMatch.matches);
|
|
276
294
|
log(`Serving wildcard route: ${requestPath} -> ${resolvedFilePath}`, 2);
|
|
277
|
-
|
|
278
295
|
try {
|
|
279
296
|
const fileContent = await readFile(resolvedFilePath);
|
|
280
297
|
const fileExtension = path.extname(resolvedFilePath).toLowerCase().slice(1);
|
|
281
298
|
const mimeType = config.allowedMimes[fileExtension] || 'application/octet-stream';
|
|
282
|
-
|
|
283
299
|
log(`Serving wildcard file as ${mimeType} (${fileContent.length} bytes)`, 2);
|
|
284
300
|
res.writeHead(200, { 'Content-Type': mimeType });
|
|
285
301
|
res.end(fileContent);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import router from '../src/router.js';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
import { writeFile, mkdir } from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { randomPort, httpGet, withTempDir, write } from './test-utils.js';
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
This test verifies that a customRoute pointing outside the rootPath is served
|
|
9
|
+
instead of a static file inside the rootPath, if both exist.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
'customRoute outside rootPath takes precedence over static file': async ({pass, fail, log}) => {
|
|
14
|
+
try {
|
|
15
|
+
await withTempDir(async (dir) => {
|
|
16
|
+
// Print initial working directory
|
|
17
|
+
log('Initial process.cwd(): ' + process.cwd());
|
|
18
|
+
|
|
19
|
+
// Setup: create static file in rootPath
|
|
20
|
+
const rootDir = path.join(dir, 'public');
|
|
21
|
+
await mkdir(path.join(rootDir, 'src'), { recursive: true });
|
|
22
|
+
await writeFile(path.join(rootDir, 'src', 'file.txt'), 'static');
|
|
23
|
+
// Create custom file outside rootPath, matching resolved customRoute
|
|
24
|
+
const customFilePath = path.resolve(dir, '..', 'src', 'file.txt');
|
|
25
|
+
await mkdir(path.dirname(customFilePath), { recursive: true });
|
|
26
|
+
await writeFile(customFilePath, 'custom');
|
|
27
|
+
log('Custom file path: ' + customFilePath);
|
|
28
|
+
|
|
29
|
+
// Check file existence
|
|
30
|
+
try {
|
|
31
|
+
const { stat } = await import('fs/promises');
|
|
32
|
+
await stat(customFilePath);
|
|
33
|
+
log('Custom file exists at setup');
|
|
34
|
+
} catch (e) {
|
|
35
|
+
log('Custom file does NOT exist at setup');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Write config with customRoute pointing outside rootPath
|
|
39
|
+
const config = {
|
|
40
|
+
customRoutes: {
|
|
41
|
+
'/src/file.txt': '../src/file.txt'
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
await writeFile(path.join(rootDir, '.config.json'), JSON.stringify(config));
|
|
45
|
+
log('Config written: ' + JSON.stringify(config));
|
|
46
|
+
|
|
47
|
+
// Set working directory to temp dir so relative paths resolve correctly
|
|
48
|
+
const prevCwd = process.cwd();
|
|
49
|
+
process.chdir(dir);
|
|
50
|
+
const flags = { root: rootDir, logging: 4 };
|
|
51
|
+
const logFn = (...args) => log(args.map(String).join(' '));
|
|
52
|
+
log('Starting server with flags: ' + JSON.stringify(flags));
|
|
53
|
+
log('process.cwd() before router: ' + process.cwd());
|
|
54
|
+
const handler = await router(flags, logFn);
|
|
55
|
+
const server = http.createServer(handler);
|
|
56
|
+
const port = randomPort();
|
|
57
|
+
await new Promise(r => server.listen(port, r));
|
|
58
|
+
await new Promise(r => setTimeout(r, 50));
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Print process.cwd() before request
|
|
62
|
+
log('process.cwd() before request: ' + process.cwd());
|
|
63
|
+
// Request the file
|
|
64
|
+
const requestUrl = `http://localhost:${port}/src/file.txt`;
|
|
65
|
+
log('Requesting URL: ' + requestUrl);
|
|
66
|
+
const response = await httpGet(requestUrl);
|
|
67
|
+
log('status: ' + response.res.statusCode);
|
|
68
|
+
log('response body: ' + response.body.toString());
|
|
69
|
+
if(response.res.statusCode !== 200) throw new Error('status not 200');
|
|
70
|
+
if(response.body.toString() !== 'custom') throw new Error('did not serve customRoute file');
|
|
71
|
+
} finally {
|
|
72
|
+
server.close();
|
|
73
|
+
process.chdir(prevCwd);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
pass('customRoute outside rootPath is served');
|
|
77
|
+
} catch(e){
|
|
78
|
+
fail(e.message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|