@flancer32/teq-web 0.2.0 → 0.3.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/AGENTS.md +101 -0
- package/CHANGELOG.md +16 -3
- package/README.md +12 -6
- package/package.json +1 -1
- package/src/AGENTS.md +108 -0
- package/src/Back/Dto/Handler/Source.js +46 -0
- package/src/Back/Handler/Static/A/Config.js +58 -0
- package/src/Back/Handler/Static/A/Fallback.js +39 -0
- package/src/Back/Handler/Static/A/FileService.js +69 -0
- package/src/Back/Handler/Static/A/Registry.js +52 -0
- package/src/Back/Handler/Static/A/Resolver.js +83 -0
- package/src/Back/Handler/Static.js +25 -101
- package/src/Back/Helper/Cast.js +28 -0
- package/src/Back/Helper/Respond.js +77 -0
- package/test/accept/Server.test.mjs +58 -5
- package/test/dev/app/Plugin/Start.js +5 -7
- package/test/unit/AGENTS.md +106 -0
- package/test/unit/Back/Dispatcher.test.mjs +150 -0
- package/test/unit/Back/Dto/Handler/Source.test.mjs +40 -0
- package/test/unit/Back/Handler/Pre/Log.test.mjs +22 -0
- package/test/unit/Back/Handler/Static/A/Config.test.mjs +52 -0
- package/test/unit/Back/Handler/Static/A/Fallback.test.mjs +60 -0
- package/test/unit/Back/Handler/Static/A/FileService.test.mjs +225 -0
- package/test/unit/Back/Handler/Static/A/Registry.test.mjs +83 -0
- package/test/unit/Back/Handler/Static/A/Resolver.test.mjs +73 -0
- package/test/unit/Back/Handler/Static/Static.test.mjs +235 -0
- package/src/Back/Handler/Npm.js +0 -161
- package/test/unit/Back/Handler/Npm.test.mjs +0 -69
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {describe, it, beforeEach} from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {EventEmitter} from 'node:events';
|
|
4
|
+
import {buildTestContainer} from '../../../common.js';
|
|
5
|
+
|
|
6
|
+
/** Simple HTTP/2 constants mock */
|
|
7
|
+
const mockHttp2 = {
|
|
8
|
+
constants: {
|
|
9
|
+
HTTP2_HEADER_CONTENT_LENGTH: 'content-length',
|
|
10
|
+
HTTP2_HEADER_CONTENT_TYPE: 'content-type',
|
|
11
|
+
HTTP2_HEADER_LAST_MODIFIED: 'last-modified',
|
|
12
|
+
HTTP_STATUS_OK: 200
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Minimal path mock for FS key normalization */
|
|
17
|
+
const mockPath = {
|
|
18
|
+
resolve: (...parts) => parts.join('/').replace(/\/+/g, '/'),
|
|
19
|
+
join: (...parts) => parts.join('/').replace(/\/+/g, '/'),
|
|
20
|
+
isAbsolute: p => p.startsWith('/'),
|
|
21
|
+
extname: p => {
|
|
22
|
+
const m = p.match(/(\.[^./]+)$/);
|
|
23
|
+
return m ? m[1] : '';
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** In-memory FS storage */
|
|
28
|
+
let storage;
|
|
29
|
+
let mockFs;
|
|
30
|
+
|
|
31
|
+
function resetFs() {
|
|
32
|
+
storage = new Map();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function addFile(p, content) {
|
|
36
|
+
const key = mockPath.resolve(p);
|
|
37
|
+
storage.set(key, {
|
|
38
|
+
isFile: () => true,
|
|
39
|
+
isDirectory: () => false,
|
|
40
|
+
size: Buffer.byteLength(content),
|
|
41
|
+
mtime: new Date(),
|
|
42
|
+
content
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function addDir(p) {
|
|
47
|
+
const key = mockPath.resolve(p);
|
|
48
|
+
storage.set(key, {
|
|
49
|
+
isFile: () => false,
|
|
50
|
+
isDirectory: () => true,
|
|
51
|
+
size: 0,
|
|
52
|
+
mtime: new Date(),
|
|
53
|
+
content: null
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
resetFs();
|
|
59
|
+
mockFs = {
|
|
60
|
+
promises: {
|
|
61
|
+
stat: async p => {
|
|
62
|
+
const key = mockPath.resolve(p);
|
|
63
|
+
if (!storage.has(key)) throw new Error('ENOENT');
|
|
64
|
+
const entry = storage.get(key);
|
|
65
|
+
return {
|
|
66
|
+
isFile: entry.isFile,
|
|
67
|
+
isDirectory: entry.isDirectory,
|
|
68
|
+
size: entry.size,
|
|
69
|
+
mtime: entry.mtime
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
createReadStream: p => ({
|
|
74
|
+
pipe: res => {
|
|
75
|
+
setImmediate(() => {
|
|
76
|
+
const entry = storage.get(mockPath.resolve(p));
|
|
77
|
+
if (entry && entry.content != null) res.write(entry.content);
|
|
78
|
+
res.end();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
/** Mock response with writable stream semantics */
|
|
86
|
+
class MockRes extends EventEmitter {
|
|
87
|
+
constructor() {
|
|
88
|
+
super();
|
|
89
|
+
this.data = Buffer.alloc(0);
|
|
90
|
+
this.statusCode = undefined;
|
|
91
|
+
this.headers = undefined;
|
|
92
|
+
this._sent = false;
|
|
93
|
+
this._ended = false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get headersSent() { return this._sent; }
|
|
97
|
+
|
|
98
|
+
get writableEnded() { return this._ended; }
|
|
99
|
+
|
|
100
|
+
writeHead(status, headers) {
|
|
101
|
+
this.statusCode = status;
|
|
102
|
+
this.headers = headers;
|
|
103
|
+
this._sent = true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
write(chunk) {
|
|
107
|
+
this.data = Buffer.concat([this.data, Buffer.from(chunk)]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
end(chunk) {
|
|
111
|
+
if (chunk) this.write(chunk);
|
|
112
|
+
this._ended = true;
|
|
113
|
+
this.emit('finish');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
describe('Fl32_Web_Back_Handler_Static', () => {
|
|
118
|
+
let container;
|
|
119
|
+
|
|
120
|
+
beforeEach(() => {
|
|
121
|
+
container = buildTestContainer();
|
|
122
|
+
container.register('node:fs', mockFs);
|
|
123
|
+
container.register('node:http2', mockHttp2);
|
|
124
|
+
container.register('node:path', mockPath);
|
|
125
|
+
container.register('Fl32_Web_Back_Logger$', {
|
|
126
|
+
warn: () => {},
|
|
127
|
+
exception: () => {}
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('serves from the most specific source', async () => {
|
|
132
|
+
addDir('/a');
|
|
133
|
+
addFile('/a/test.txt', 'A');
|
|
134
|
+
addDir('/b');
|
|
135
|
+
addFile('/b/test.txt', 'B');
|
|
136
|
+
|
|
137
|
+
/** @type {Fl32_Web_Back_Dto_Handler_Source} */
|
|
138
|
+
const dtoSource = await container.get('Fl32_Web_Back_Dto_Handler_Source$');
|
|
139
|
+
/** @type {Fl32_Web_Back_Handler_Static} */
|
|
140
|
+
const handler = await container.get('Fl32_Web_Back_Handler_Static$');
|
|
141
|
+
|
|
142
|
+
await handler.init({
|
|
143
|
+
sources: [
|
|
144
|
+
dtoSource.create({prefix: '/files/', root: '/a', allow: {'.': ['.']}, defaults: []}),
|
|
145
|
+
dtoSource.create({prefix: '/files/special/', root: '/b', allow: {'.': ['.']}, defaults: []})
|
|
146
|
+
]
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const res = new MockRes();
|
|
150
|
+
const ok = await handler.handle({url: '/files/special/test.txt'}, res);
|
|
151
|
+
await new Promise(r => res.on('finish', r));
|
|
152
|
+
|
|
153
|
+
assert.strictEqual(ok, true);
|
|
154
|
+
assert.strictEqual(res.data.toString(), 'B');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('enforces allow-list rules', async () => {
|
|
158
|
+
addFile('src/Back/Server.js', 'class X {}');
|
|
159
|
+
addFile('src/Back/Handler/Static.js', 'ignore');
|
|
160
|
+
|
|
161
|
+
/** @type {Fl32_Web_Back_Dto_Handler_Source} */
|
|
162
|
+
const dtoSource = await container.get('Fl32_Web_Back_Dto_Handler_Source$');
|
|
163
|
+
/** @type {Fl32_Web_Back_Handler_Static} */
|
|
164
|
+
const handler = await container.get('Fl32_Web_Back_Handler_Static$');
|
|
165
|
+
|
|
166
|
+
await handler.init({
|
|
167
|
+
sources: [
|
|
168
|
+
dtoSource.create({prefix: '/s/', root: 'src', allow: {Back: ['Server.js']}, defaults: []})
|
|
169
|
+
]
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const okRes = new MockRes();
|
|
173
|
+
const ok = await handler.handle({url: '/s/Back/Server.js'}, okRes);
|
|
174
|
+
await new Promise(r => okRes.on('finish', r));
|
|
175
|
+
assert.strictEqual(ok, true);
|
|
176
|
+
|
|
177
|
+
const badRes = new MockRes();
|
|
178
|
+
const bad = await handler.handle({url: '/s/Back/Handler/Static.js'}, badRes);
|
|
179
|
+
assert.strictEqual(bad, false);
|
|
180
|
+
assert.strictEqual(badRes.headersSent, false);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('serves index files in directories', async () => {
|
|
184
|
+
addDir('/dir');
|
|
185
|
+
addDir('/dir/d');
|
|
186
|
+
addFile('/dir/d/index.txt', 'INDEX');
|
|
187
|
+
|
|
188
|
+
/** @type {Fl32_Web_Back_Dto_Handler_Source} */
|
|
189
|
+
const dtoSource = await container.get('Fl32_Web_Back_Dto_Handler_Source$');
|
|
190
|
+
/** @type {Fl32_Web_Back_Handler_Static} */
|
|
191
|
+
const handler = await container.get('Fl32_Web_Back_Handler_Static$');
|
|
192
|
+
|
|
193
|
+
await handler.init({
|
|
194
|
+
sources: [
|
|
195
|
+
dtoSource.create({
|
|
196
|
+
prefix: '/w/',
|
|
197
|
+
root: '/dir',
|
|
198
|
+
allow: {'.': ['.']},
|
|
199
|
+
defaults: ['index.txt']
|
|
200
|
+
})
|
|
201
|
+
]
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const res = new MockRes();
|
|
205
|
+
const ok = await handler.handle({url: '/w/d/'}, res);
|
|
206
|
+
await new Promise(r => res.on('finish', r));
|
|
207
|
+
|
|
208
|
+
assert.strictEqual(ok, true);
|
|
209
|
+
assert.strictEqual(res.data.toString(), 'INDEX');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('rejects traversal and unmatched prefixes', async () => {
|
|
213
|
+
addFile('/safe/file.txt', 'ok');
|
|
214
|
+
|
|
215
|
+
/** @type {Fl32_Web_Back_Dto_Handler_Source} */
|
|
216
|
+
const dtoSource = await container.get('Fl32_Web_Back_Dto_Handler_Source$');
|
|
217
|
+
/** @type {Fl32_Web_Back_Handler_Static} */
|
|
218
|
+
const handler = await container.get('Fl32_Web_Back_Handler_Static$');
|
|
219
|
+
|
|
220
|
+
await handler.init({
|
|
221
|
+
sources: [
|
|
222
|
+
dtoSource.create({prefix: '/p/', root: '/safe', allow: {'.': ['.']}, defaults: []})
|
|
223
|
+
]
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const res1 = new MockRes();
|
|
227
|
+
const bad1 = await handler.handle({url: '/p/../file.txt'}, res1);
|
|
228
|
+
assert.strictEqual(bad1, false);
|
|
229
|
+
|
|
230
|
+
const res2 = new MockRes();
|
|
231
|
+
const bad2 = await handler.handle({url: '/x/file.txt'}, res2);
|
|
232
|
+
assert.strictEqual(bad2, false);
|
|
233
|
+
assert.strictEqual(res2.headersSent, false);
|
|
234
|
+
});
|
|
235
|
+
});
|
package/src/Back/Handler/Npm.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Serves whitelisted files from ./node_modules directory.
|
|
3
|
-
* @implements Fl32_Web_Back_Api_Handler
|
|
4
|
-
*/
|
|
5
|
-
export default class Fl32_Web_Back_Handler_Npm {
|
|
6
|
-
/* eslint-disable jsdoc/require-param-description,jsdoc/check-param-names */
|
|
7
|
-
/**
|
|
8
|
-
* @param {typeof import('node:fs')} fs
|
|
9
|
-
* @param {typeof import('node:http2')} http2
|
|
10
|
-
* @param {typeof import('node:path')} path
|
|
11
|
-
* @param {Fl32_Web_Back_Logger} logger
|
|
12
|
-
* @param {Fl32_Web_Back_Helper_Mime} helpMime
|
|
13
|
-
* @param {Fl32_Web_Back_Helper_Respond} respond
|
|
14
|
-
* @param {Fl32_Web_Back_Dto_Handler_Info} dtoInfo
|
|
15
|
-
* @param {typeof Fl32_Web_Back_Enum_Stage} STAGE
|
|
16
|
-
*/
|
|
17
|
-
constructor(
|
|
18
|
-
{
|
|
19
|
-
'node:fs': fs,
|
|
20
|
-
'node:http2': http2,
|
|
21
|
-
'node:path': path,
|
|
22
|
-
Fl32_Web_Back_Logger$: logger,
|
|
23
|
-
Fl32_Web_Back_Helper_Mime$: helpMime,
|
|
24
|
-
Fl32_Web_Back_Helper_Respond$: respond,
|
|
25
|
-
Fl32_Web_Back_Dto_Handler_Info$: dtoInfo,
|
|
26
|
-
Fl32_Web_Back_Enum_Stage$: STAGE,
|
|
27
|
-
}
|
|
28
|
-
) {
|
|
29
|
-
/* eslint-enable jsdoc/check-param-names */
|
|
30
|
-
// VARS
|
|
31
|
-
const {promises: fsp} = fs;
|
|
32
|
-
const {constants: H2} = http2;
|
|
33
|
-
const {
|
|
34
|
-
HTTP2_HEADER_CONTENT_LENGTH,
|
|
35
|
-
HTTP2_HEADER_CONTENT_TYPE,
|
|
36
|
-
HTTP2_HEADER_LAST_MODIFIED,
|
|
37
|
-
HTTP_STATUS_OK,
|
|
38
|
-
} = H2;
|
|
39
|
-
const _root = path.resolve('node_modules');
|
|
40
|
-
|
|
41
|
-
/** @type {Fl32_Web_Back_Dto_Handler_Info.Dto} */
|
|
42
|
-
const _info = dtoInfo.create();
|
|
43
|
-
_info.name = this.constructor.name;
|
|
44
|
-
_info.stage = STAGE.PROCESS;
|
|
45
|
-
Object.freeze(_info);
|
|
46
|
-
|
|
47
|
-
/** @type {{[key: string]: string[]}} */
|
|
48
|
-
let _allow = {};
|
|
49
|
-
|
|
50
|
-
// MAIN
|
|
51
|
-
/**
|
|
52
|
-
* Handles request to serve allowed files from node_modules.
|
|
53
|
-
*
|
|
54
|
-
* @param {module:http.IncomingMessage|module:http2.Http2ServerRequest} req
|
|
55
|
-
* @param {module:http.ServerResponse|module:http2.Http2ServerResponse} res
|
|
56
|
-
* @returns {Promise<boolean>}
|
|
57
|
-
*/
|
|
58
|
-
this.handle = async function (req, res) {
|
|
59
|
-
if (!respond.isWritable(res)) return false;
|
|
60
|
-
|
|
61
|
-
const urlPath = decodeURIComponent(req.url.split('?')[0]);
|
|
62
|
-
const prefix = '/node_modules/';
|
|
63
|
-
if (!urlPath.startsWith(prefix)) return false;
|
|
64
|
-
|
|
65
|
-
const rel = urlPath.slice(prefix.length);
|
|
66
|
-
if (rel.includes('..') || path.isAbsolute(rel)) {
|
|
67
|
-
logger.warn(`NPM static access denied: ${rel}`);
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
let pkg; let subPath;
|
|
72
|
-
for (const key of Object.keys(_allow)) {
|
|
73
|
-
if (rel === key || rel.startsWith(`${key}/`)) {
|
|
74
|
-
pkg = key;
|
|
75
|
-
subPath = rel.slice(key.length);
|
|
76
|
-
if (subPath.startsWith('/')) subPath = subPath.slice(1);
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (!pkg) return false;
|
|
81
|
-
|
|
82
|
-
const rules = _allow[pkg] || [];
|
|
83
|
-
let allowed = false;
|
|
84
|
-
if (rules.includes('.')) {
|
|
85
|
-
allowed = true;
|
|
86
|
-
} else {
|
|
87
|
-
for (const p of rules) {
|
|
88
|
-
if (subPath === p || subPath.startsWith(`${p}/`)) {
|
|
89
|
-
allowed = true;
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (!allowed) {
|
|
96
|
-
logger.warn(`NPM static access denied: ${rel}`);
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const fsPath = path.resolve(_root, rel);
|
|
101
|
-
if (!fsPath.startsWith(_root)) {
|
|
102
|
-
logger.warn(`NPM static access denied: ${rel}`);
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
let stat;
|
|
107
|
-
try {
|
|
108
|
-
stat = await fsp.stat(fsPath);
|
|
109
|
-
} catch {
|
|
110
|
-
logger.warn(`NPM static file not found: ${rel}`);
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
if (!stat.isFile()) {
|
|
114
|
-
logger.warn(`NPM static file not found: ${rel}`);
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const stream = fs.createReadStream(fsPath);
|
|
119
|
-
const ext = path.extname(fsPath).toLowerCase();
|
|
120
|
-
const headers = {
|
|
121
|
-
[HTTP2_HEADER_CONTENT_LENGTH]: stat.size,
|
|
122
|
-
[HTTP2_HEADER_CONTENT_TYPE]: helpMime.getByExt(ext),
|
|
123
|
-
[HTTP2_HEADER_LAST_MODIFIED]: stat.mtime.toUTCString(),
|
|
124
|
-
};
|
|
125
|
-
res.writeHead(HTTP_STATUS_OK, headers);
|
|
126
|
-
stream.pipe(res);
|
|
127
|
-
return true;
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Initialize handler with allow list.
|
|
132
|
-
*
|
|
133
|
-
* @param {object} params
|
|
134
|
-
* @param {{[key: string]: string[]}} params.allow - Map of packages to paths
|
|
135
|
-
* that can be served from `node_modules`.
|
|
136
|
-
* @returns {Promise<void>}
|
|
137
|
-
*
|
|
138
|
-
* @example
|
|
139
|
-
* // Allow a single file from a package
|
|
140
|
-
* await npmHandler.init({
|
|
141
|
-
* allow: {
|
|
142
|
-
* vue: ['dist/vue.global.prod.js'],
|
|
143
|
-
* },
|
|
144
|
-
* });
|
|
145
|
-
*
|
|
146
|
-
* @example
|
|
147
|
-
* // Allow all files from a subfolder
|
|
148
|
-
* await npmHandler.init({
|
|
149
|
-
* allow: {
|
|
150
|
-
* '@teqfw/di/src': ['.'],
|
|
151
|
-
* },
|
|
152
|
-
* });
|
|
153
|
-
*/
|
|
154
|
-
this.init = async function ({allow}) {
|
|
155
|
-
_allow = allow || {};
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
/** @returns {Fl32_Web_Back_Dto_Handler_Info.Dto} */
|
|
159
|
-
this.getRegistrationInfo = () => _info;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import {describe, it, beforeEach} from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import {Writable} from 'node:stream';
|
|
4
|
-
import {buildTestContainer} from '../../common.js';
|
|
5
|
-
|
|
6
|
-
class MockRes extends Writable {
|
|
7
|
-
constructor() {
|
|
8
|
-
super();
|
|
9
|
-
this.data = Buffer.alloc(0);
|
|
10
|
-
this.statusCode = undefined;
|
|
11
|
-
this.headers = undefined;
|
|
12
|
-
this._headersSent = false;
|
|
13
|
-
this._ended = false;
|
|
14
|
-
}
|
|
15
|
-
get headersSent() { return this._headersSent; }
|
|
16
|
-
get writableEnded() { return this._ended; }
|
|
17
|
-
writeHead(status, headers) {
|
|
18
|
-
this.statusCode = status;
|
|
19
|
-
this.headers = headers;
|
|
20
|
-
this._headersSent = true;
|
|
21
|
-
}
|
|
22
|
-
_write(chunk, enc, cb) {
|
|
23
|
-
this.data = Buffer.concat([this.data, chunk]);
|
|
24
|
-
cb();
|
|
25
|
-
}
|
|
26
|
-
end(chunk) {
|
|
27
|
-
if (chunk) this.data = Buffer.concat([this.data, Buffer.from(chunk)]);
|
|
28
|
-
this._ended = true;
|
|
29
|
-
super.end();
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe('Fl32_Web_Back_Handler_Npm', () => {
|
|
34
|
-
let container;
|
|
35
|
-
const log = [];
|
|
36
|
-
|
|
37
|
-
beforeEach(() => {
|
|
38
|
-
container = buildTestContainer();
|
|
39
|
-
container.register('Fl32_Web_Back_Logger$', {
|
|
40
|
-
warn: (...args) => log.push(['warn', ...args]),
|
|
41
|
-
exception: (...args) => log.push(['exception', ...args]),
|
|
42
|
-
});
|
|
43
|
-
log.length = 0;
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should serve allowed file', async () => {
|
|
47
|
-
const handler = await container.get('Fl32_Web_Back_Handler_Npm$');
|
|
48
|
-
await handler.init({allow: {'@teqfw/di': ['package.json']}});
|
|
49
|
-
const req = {url: '/node_modules/@teqfw/di/package.json'};
|
|
50
|
-
const res = new MockRes();
|
|
51
|
-
const ok = await handler.handle(req, res);
|
|
52
|
-
await new Promise(resolve => res.on('finish', resolve));
|
|
53
|
-
assert.strictEqual(ok, true);
|
|
54
|
-
assert.strictEqual(res.statusCode, 200);
|
|
55
|
-
assert.match(res.data.toString(), /@teqfw\/di/);
|
|
56
|
-
assert.strictEqual(log.length, 0);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should deny disallowed path', async () => {
|
|
60
|
-
const handler = await container.get('Fl32_Web_Back_Handler_Npm$');
|
|
61
|
-
await handler.init({allow: {'@teqfw/di': ['package.json']}});
|
|
62
|
-
const req = {url: '/node_modules/@teqfw/di/secret.js'};
|
|
63
|
-
const res = new MockRes();
|
|
64
|
-
const ok = await handler.handle(req, res);
|
|
65
|
-
assert.strictEqual(ok, false);
|
|
66
|
-
assert.strictEqual(res.headersSent, false);
|
|
67
|
-
assert.ok(log[0][0] === 'warn');
|
|
68
|
-
});
|
|
69
|
-
});
|