koa-classic-server 3.0.0 → 3.1.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/README.md +44 -120
- package/index.cjs +236 -21
- package/package.json +10 -5
- package/.github/workflows/npm-publish.yml +0 -98
- package/CLAUDE.md +0 -101
- package/__tests__/benchmark-results-baseline-v1.2.0.txt +0 -354
- package/__tests__/benchmark-results-optimized-v2.0.0.txt +0 -354
- package/__tests__/benchmark-results-v3.0.0.txt +0 -372
- package/__tests__/benchmark.js +0 -239
- package/__tests__/caching-headers.test.js +0 -556
- package/__tests__/compression-fixtures/data.json +0 -1
- package/__tests__/compression-fixtures/large.txt +0 -1
- package/__tests__/compression-fixtures/small.txt +0 -1
- package/__tests__/compression.test.js +0 -284
- package/__tests__/customTest/README.md +0 -6
- package/__tests__/customTest/loadConfig.util.js +0 -41
- package/__tests__/customTest/serversToLoad.util.js +0 -93
- package/__tests__/demo-regex-index.js +0 -140
- package/__tests__/deprecation-warnings.test.js +0 -105
- package/__tests__/directory-sorting-links.test.js +0 -135
- package/__tests__/dt-unknown.test.js +0 -635
- package/__tests__/ejs.test.js +0 -299
- package/__tests__/hidden-fixtures/.dot-dir/inside.txt +0 -1
- package/__tests__/hidden-fixtures/.env +0 -2
- package/__tests__/hidden-fixtures/.well-known/acme-challenge.txt +0 -1
- package/__tests__/hidden-fixtures/config/secrets/password.txt +0 -1
- package/__tests__/hidden-fixtures/data.key +0 -1
- package/__tests__/hidden-fixtures/file.secret +0 -1
- package/__tests__/hidden-fixtures/index.html +0 -1
- package/__tests__/hidden-fixtures/normal.txt +0 -1
- package/__tests__/hidden-fixtures/subdir/.env +0 -1
- package/__tests__/hidden-fixtures/subdir/regular.txt +0 -1
- package/__tests__/hidden-option.test.js +0 -407
- package/__tests__/hideExtension.test.js +0 -607
- package/__tests__/index-option.test.js +0 -449
- package/__tests__/index.test.js +0 -345
- package/__tests__/listing.test.js +0 -437
- package/__tests__/logger.test.js +0 -232
- package/__tests__/performance.test.js +0 -370
- package/__tests__/publicWwwTest/cartella/sottocartella/ciao.html +0 -11
- package/__tests__/publicWwwTest/cartella/sottocartella/provaEjs/testEjs.ejs +0 -11
- package/__tests__/publicWwwTest/cartella vuota con spazi nel nome/file con spazio nel nome .txt +0 -1
- package/__tests__/publicWwwTest/cartella vuota con spazi nel nome /file con spazio nel nome .txt +0 -1
- package/__tests__/publicWwwTest/ejs-templates/complex.ejs +0 -56
- package/__tests__/publicWwwTest/ejs-templates/index.ejs +0 -30
- package/__tests__/publicWwwTest/ejs-templates/simple.ejs +0 -13
- package/__tests__/publicWwwTest/ejs-templates/with-conditional.ejs +0 -28
- package/__tests__/publicWwwTest/ejs-templates/with-escaping.ejs +0 -26
- package/__tests__/publicWwwTest/ejs-templates/with-loop.ejs +0 -16
- package/__tests__/publicWwwTest/hideext-test/about/index.html +0 -1
- package/__tests__/publicWwwTest/hideext-test/about.EJS +0 -1
- package/__tests__/publicWwwTest/hideext-test/about.ejs +0 -2
- package/__tests__/publicWwwTest/hideext-test/blog/articolo.ejs +0 -2
- package/__tests__/publicWwwTest/hideext-test/conflict-test/pagina +0 -1
- package/__tests__/publicWwwTest/hideext-test/conflict-test/pagina.ejs +0 -1
- package/__tests__/publicWwwTest/hideext-test/file.ejs.bak +0 -1
- package/__tests__/publicWwwTest/hideext-test/index.ejs +0 -2
- package/__tests__/publicWwwTest/hideext-test/photo.txt +0 -1
- package/__tests__/publicWwwTest/hideext-test/sezione/index.ejs +0 -1
- package/__tests__/publicWwwTest/hideext-test/style.css +0 -1
- package/__tests__/publicWwwTest/ile_vuoto.txt +0 -0
- package/__tests__/publicWwwTest/index.html +0 -11
- package/__tests__/publicWwwTest/prova file .txt +0 -2
- package/__tests__/publicWwwTest/semplicetxt.txt +0 -1
- package/__tests__/publicWwwTest/test-page.html +0 -1
- package/__tests__/publicWwwTest/test.txt +0 -1
- package/__tests__/range-fixtures/sample.txt +0 -1
- package/__tests__/range.test.js +0 -223
- package/__tests__/security-headers.test.js +0 -165
- package/__tests__/security.test.js +0 -322
- package/__tests__/server-cache-fixtures/large.txt +0 -1
- package/__tests__/server-cache-fixtures/small.txt +0 -1
- package/__tests__/server-cache.test.js +0 -594
- package/__tests__/setup-benchmark.js +0 -178
- package/__tests__/symlink.test.js +0 -441
- package/__tests__/template-timeout.test.js +0 -321
- package/__tests__/test-regex-quick.js +0 -158
- package/__tests__/useOriginalUrl.test.js +0 -213
- package/docs/ACTION_PLAN.md +0 -293
- package/docs/BENCHMARKS.md +0 -317
- package/docs/CHANGELOG.md +0 -880
- package/docs/CODE_REVIEW.md +0 -300
- package/docs/DEBUG_REPORT.md +0 -593
- package/docs/DOCUMENTATION.md +0 -1864
- package/docs/EXAMPLES_INDEX_OPTION.md +0 -395
- package/docs/FLOW_DIAGRAM.md +0 -954
- package/docs/INDEX_OPTION_PRIORITY.md +0 -527
- package/docs/OPTIMIZATION_HTTP_CACHING.md +0 -689
- package/docs/OPTIMIZATION_ROADMAP_for_V3.md +0 -864
- package/docs/PERFORMANCE_ANALYSIS.md +0 -839
- package/docs/PERFORMANCE_COMPARISON.md +0 -388
- package/docs/noteExports.md +0 -148
- package/docs/security_improvement_for_V3.md +0 -421
- package/docs/template-engine/TEMPLATE_ENGINE_GUIDE.md +0 -1734
- package/docs/template-engine/esempi-incrementali.js +0 -192
- package/docs/template-engine/examples/esempio1-nessun-dato.ejs +0 -12
- package/docs/template-engine/examples/esempio2-una-variabile.ejs +0 -11
- package/docs/template-engine/examples/esempio3-piu-variabili.ejs +0 -15
- package/docs/template-engine/examples/esempio4-condizionale.ejs +0 -18
- package/docs/template-engine/examples/esempio5-loop.ejs +0 -18
- package/docs/template-engine/examples/index-esempi.html +0 -181
- package/docs/template-engine/examples/index.html +0 -40
- package/docs/template-engine/examples/test.ejs +0 -64
- package/eslint.config.mjs +0 -17
- package/jest.config.js +0 -18
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Setup script for performance benchmarks
|
|
5
|
-
* Creates test files and directories for realistic performance testing
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
const BENCHMARK_DIR = path.join(__dirname, 'benchmark-data');
|
|
12
|
-
|
|
13
|
-
console.log('🔧 Setting up benchmark environment...\n');
|
|
14
|
-
|
|
15
|
-
// Clean up old benchmark data
|
|
16
|
-
if (fs.existsSync(BENCHMARK_DIR)) {
|
|
17
|
-
console.log('Cleaning old benchmark data...');
|
|
18
|
-
fs.rmSync(BENCHMARK_DIR, { recursive: true, force: true });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Create benchmark directory
|
|
22
|
-
fs.mkdirSync(BENCHMARK_DIR, { recursive: true });
|
|
23
|
-
|
|
24
|
-
// Create small files (1KB each)
|
|
25
|
-
console.log('Creating small files (1KB each)...');
|
|
26
|
-
const smallDir = path.join(BENCHMARK_DIR, 'small-files');
|
|
27
|
-
fs.mkdirSync(smallDir);
|
|
28
|
-
for (let i = 1; i <= 100; i++) {
|
|
29
|
-
const content = 'X'.repeat(1024); // 1KB
|
|
30
|
-
fs.writeFileSync(path.join(smallDir, `file-${i}.txt`), content);
|
|
31
|
-
}
|
|
32
|
-
console.log(`✓ Created 100 small files (1KB each) = 100KB total`);
|
|
33
|
-
|
|
34
|
-
// Create medium files (100KB each)
|
|
35
|
-
console.log('Creating medium files (100KB each)...');
|
|
36
|
-
const mediumDir = path.join(BENCHMARK_DIR, 'medium-files');
|
|
37
|
-
fs.mkdirSync(mediumDir);
|
|
38
|
-
for (let i = 1; i <= 50; i++) {
|
|
39
|
-
const content = 'X'.repeat(100 * 1024); // 100KB
|
|
40
|
-
fs.writeFileSync(path.join(mediumDir, `file-${i}.txt`), content);
|
|
41
|
-
}
|
|
42
|
-
console.log(`✓ Created 50 medium files (100KB each) = 5MB total`);
|
|
43
|
-
|
|
44
|
-
// Create large files (1MB each)
|
|
45
|
-
console.log('Creating large files (1MB each)...');
|
|
46
|
-
const largeDir = path.join(BENCHMARK_DIR, 'large-files');
|
|
47
|
-
fs.mkdirSync(largeDir);
|
|
48
|
-
for (let i = 1; i <= 10; i++) {
|
|
49
|
-
const content = 'X'.repeat(1024 * 1024); // 1MB
|
|
50
|
-
fs.writeFileSync(path.join(largeDir, `file-${i}.txt`), content);
|
|
51
|
-
}
|
|
52
|
-
console.log(`✓ Created 10 large files (1MB each) = 10MB total`);
|
|
53
|
-
|
|
54
|
-
// Create directory with many files for listing test
|
|
55
|
-
console.log('Creating large directory (1000 files)...');
|
|
56
|
-
const largeDirListing = path.join(BENCHMARK_DIR, 'large-directory');
|
|
57
|
-
fs.mkdirSync(largeDirListing);
|
|
58
|
-
for (let i = 1; i <= 1000; i++) {
|
|
59
|
-
const content = `File number ${i}\n`;
|
|
60
|
-
fs.writeFileSync(path.join(largeDirListing, `item-${String(i).padStart(4, '0')}.txt`), content);
|
|
61
|
-
}
|
|
62
|
-
console.log(`✓ Created directory with 1000 files`);
|
|
63
|
-
|
|
64
|
-
// Create directory with very many files (10,000) for stress test
|
|
65
|
-
console.log('Creating very large directory (10,000 files) - this may take a while...');
|
|
66
|
-
const veryLargeDirListing = path.join(BENCHMARK_DIR, 'very-large-directory');
|
|
67
|
-
fs.mkdirSync(veryLargeDirListing);
|
|
68
|
-
for (let i = 1; i <= 10000; i++) {
|
|
69
|
-
const content = `File number ${i}\n`;
|
|
70
|
-
fs.writeFileSync(path.join(veryLargeDirListing, `item-${String(i).padStart(5, '0')}.txt`), content);
|
|
71
|
-
if (i % 1000 === 0) {
|
|
72
|
-
process.stdout.write(` Progress: ${i}/10000 files created...\r`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
console.log(`✓ Created directory with 10,000 files `);
|
|
76
|
-
|
|
77
|
-
// Create HTML file for caching test
|
|
78
|
-
console.log('Creating HTML files for caching test...');
|
|
79
|
-
const htmlContent = `<!DOCTYPE html>
|
|
80
|
-
<html>
|
|
81
|
-
<head>
|
|
82
|
-
<meta charset="UTF-8">
|
|
83
|
-
<title>Benchmark Test Page</title>
|
|
84
|
-
<style>
|
|
85
|
-
body { font-family: Arial, sans-serif; margin: 40px; }
|
|
86
|
-
h1 { color: #333; }
|
|
87
|
-
p { line-height: 1.6; }
|
|
88
|
-
</style>
|
|
89
|
-
</head>
|
|
90
|
-
<body>
|
|
91
|
-
<h1>Benchmark Test Page</h1>
|
|
92
|
-
<p>${'Lorem ipsum dolor sit amet. '.repeat(100)}</p>
|
|
93
|
-
</body>
|
|
94
|
-
</html>`;
|
|
95
|
-
fs.writeFileSync(path.join(BENCHMARK_DIR, 'test.html'), htmlContent);
|
|
96
|
-
console.log(`✓ Created HTML file for caching test`);
|
|
97
|
-
|
|
98
|
-
// Create CSS file
|
|
99
|
-
const cssContent = `
|
|
100
|
-
body {
|
|
101
|
-
margin: 0;
|
|
102
|
-
padding: 20px;
|
|
103
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
104
|
-
}
|
|
105
|
-
${'h1 { color: #333; }\n'.repeat(50)}
|
|
106
|
-
`.trim();
|
|
107
|
-
fs.writeFileSync(path.join(BENCHMARK_DIR, 'style.css'), cssContent);
|
|
108
|
-
console.log(`✓ Created CSS file`);
|
|
109
|
-
|
|
110
|
-
// Create JS file
|
|
111
|
-
const jsContent = `
|
|
112
|
-
function benchmark() {
|
|
113
|
-
console.log('Benchmark test');
|
|
114
|
-
${'console.log("test");\n'.repeat(100)}
|
|
115
|
-
}
|
|
116
|
-
`.trim();
|
|
117
|
-
fs.writeFileSync(path.join(BENCHMARK_DIR, 'script.js'), jsContent);
|
|
118
|
-
console.log(`✓ Created JS file`);
|
|
119
|
-
|
|
120
|
-
// Create nested directory structure
|
|
121
|
-
console.log('Creating nested directory structure...');
|
|
122
|
-
const nestedBase = path.join(BENCHMARK_DIR, 'nested');
|
|
123
|
-
fs.mkdirSync(nestedBase);
|
|
124
|
-
for (let depth = 1; depth <= 5; depth++) {
|
|
125
|
-
const dirPath = path.join(nestedBase, ...Array(depth).fill('level'));
|
|
126
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
127
|
-
for (let i = 1; i <= 10; i++) {
|
|
128
|
-
fs.writeFileSync(
|
|
129
|
-
path.join(dirPath, `file-depth${depth}-${i}.txt`),
|
|
130
|
-
`Depth ${depth}, File ${i}\n`
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
console.log(`✓ Created nested directory structure (5 levels deep)`);
|
|
135
|
-
|
|
136
|
-
// Create .gitignore for benchmark-data
|
|
137
|
-
const gitignorePath = path.join(BENCHMARK_DIR, '.gitignore');
|
|
138
|
-
fs.writeFileSync(gitignorePath, '*\n');
|
|
139
|
-
console.log(`✓ Created .gitignore to exclude benchmark data from git`);
|
|
140
|
-
|
|
141
|
-
// Summary
|
|
142
|
-
console.log('\n✅ Benchmark environment setup complete!\n');
|
|
143
|
-
console.log('Directory structure:');
|
|
144
|
-
console.log(' benchmark-data/');
|
|
145
|
-
console.log(' ├── small-files/ (100 files × 1KB = 100KB)');
|
|
146
|
-
console.log(' ├── medium-files/ (50 files × 100KB = 5MB)');
|
|
147
|
-
console.log(' ├── large-files/ (10 files × 1MB = 10MB)');
|
|
148
|
-
console.log(' ├── large-directory/ (1,000 files for listing test)');
|
|
149
|
-
console.log(' ├── very-large-directory/ (10,000 files for stress test)');
|
|
150
|
-
console.log(' ├── nested/ (5 levels deep, 10 files each)');
|
|
151
|
-
console.log(' ├── test.html (HTML for caching test)');
|
|
152
|
-
console.log(' ├── style.css (CSS file)');
|
|
153
|
-
console.log(' └── script.js (JS file)');
|
|
154
|
-
|
|
155
|
-
const stats = getDirSize(BENCHMARK_DIR);
|
|
156
|
-
console.log(`\nTotal size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
|
|
157
|
-
console.log(`Total files: ${stats.files}`);
|
|
158
|
-
|
|
159
|
-
function getDirSize(dir) {
|
|
160
|
-
let totalSize = 0;
|
|
161
|
-
let totalFiles = 0;
|
|
162
|
-
|
|
163
|
-
function traverse(currentPath) {
|
|
164
|
-
const items = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
165
|
-
for (const item of items) {
|
|
166
|
-
const fullPath = path.join(currentPath, item.name);
|
|
167
|
-
if (item.isDirectory()) {
|
|
168
|
-
traverse(fullPath);
|
|
169
|
-
} else {
|
|
170
|
-
totalSize += fs.statSync(fullPath).size;
|
|
171
|
-
totalFiles++;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
traverse(dir);
|
|
177
|
-
return { size: totalSize, files: totalFiles };
|
|
178
|
-
}
|
|
@@ -1,441 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Symlink support tests for koa-classic-server
|
|
3
|
-
*
|
|
4
|
-
* Context: On NixOS with buildFHSEnv (chroot-like environment used for Playwright tests),
|
|
5
|
-
* files in the www/ directory appear as symlinks to the Nix store instead of regular files.
|
|
6
|
-
* This caused two failures:
|
|
7
|
-
* - GET / returned a directory listing ("Index of /") instead of rendering index.ejs
|
|
8
|
-
* - GET /index.ejs returned 404 instead of 200
|
|
9
|
-
*
|
|
10
|
-
* Root cause: fs.readdir({ withFileTypes: true }) classifies symlinks as
|
|
11
|
-
* isSymbolicLink()=true, isFile()=false. The findIndexFile() function filtered
|
|
12
|
-
* with dirent.isFile(), excluding all symlinks from index file discovery.
|
|
13
|
-
*
|
|
14
|
-
* The fix introduces isFileOrSymlinkToFile() / isDirOrSymlinkToDir() helpers
|
|
15
|
-
* that follow symlinks via fs.promises.stat() only when dirent.isSymbolicLink()
|
|
16
|
-
* is true, adding zero overhead for regular files.
|
|
17
|
-
*
|
|
18
|
-
* These tests also cover: Docker bind mounts, npm link, Capistrano-style deploys,
|
|
19
|
-
* and any other scenario where files are served through symbolic links.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
const fs = require('fs');
|
|
23
|
-
const os = require('os');
|
|
24
|
-
const path = require('path');
|
|
25
|
-
const Koa = require('koa');
|
|
26
|
-
const supertest = require('supertest');
|
|
27
|
-
const koaClassicServer = require('../index.cjs');
|
|
28
|
-
|
|
29
|
-
// Detect if the OS supports symlinks (Windows without dev mode may not)
|
|
30
|
-
let symlinkSupported = true;
|
|
31
|
-
try {
|
|
32
|
-
const testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'symlink-check-'));
|
|
33
|
-
const testFile = path.join(testDir, 'target');
|
|
34
|
-
const testLink = path.join(testDir, 'link');
|
|
35
|
-
fs.writeFileSync(testFile, 'test');
|
|
36
|
-
fs.symlinkSync(testFile, testLink);
|
|
37
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
38
|
-
} catch {
|
|
39
|
-
symlinkSupported = false;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const describeIfSymlinks = symlinkSupported ? describe : describe.skip;
|
|
43
|
-
|
|
44
|
-
describeIfSymlinks('koa-classic-server - symlink support', () => {
|
|
45
|
-
let tmpDir;
|
|
46
|
-
|
|
47
|
-
beforeAll(() => {
|
|
48
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kcs-symlink-test-'));
|
|
49
|
-
|
|
50
|
-
// --- Regular files ---
|
|
51
|
-
fs.writeFileSync(
|
|
52
|
-
path.join(tmpDir, 'index.html'),
|
|
53
|
-
'<html><head><title>Regular Index</title></head><body>Hello</body></html>'
|
|
54
|
-
);
|
|
55
|
-
fs.writeFileSync(
|
|
56
|
-
path.join(tmpDir, 'style.css'),
|
|
57
|
-
'body { color: red; }'
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
// --- Real file that will be the symlink target for index.ejs ---
|
|
61
|
-
fs.writeFileSync(
|
|
62
|
-
path.join(tmpDir, 'real-index.ejs'),
|
|
63
|
-
'<html><head><title>EJS via Symlink</title></head><body><h1>Works</h1></body></html>'
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
// --- Symlink to file (the core bug scenario) ---
|
|
67
|
-
fs.symlinkSync(
|
|
68
|
-
path.join(tmpDir, 'real-index.ejs'),
|
|
69
|
-
path.join(tmpDir, 'index.ejs')
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
// --- Symlink to regular file (non-index) ---
|
|
73
|
-
fs.symlinkSync(
|
|
74
|
-
path.join(tmpDir, 'style.css'),
|
|
75
|
-
path.join(tmpDir, 'linked-style.css')
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
// --- Real subdirectory with a file ---
|
|
79
|
-
const realSubdir = path.join(tmpDir, 'real-subdir');
|
|
80
|
-
fs.mkdirSync(realSubdir);
|
|
81
|
-
fs.writeFileSync(
|
|
82
|
-
path.join(realSubdir, 'file.txt'),
|
|
83
|
-
'content inside real-subdir'
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
// --- Symlink to directory ---
|
|
87
|
-
fs.symlinkSync(
|
|
88
|
-
realSubdir,
|
|
89
|
-
path.join(tmpDir, 'linked-subdir')
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
// --- Broken symlink (target does not exist) ---
|
|
93
|
-
fs.symlinkSync(
|
|
94
|
-
path.join(tmpDir, 'non-existent-file.html'),
|
|
95
|
-
path.join(tmpDir, 'broken-link.html')
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
// --- Circular symlinks ---
|
|
99
|
-
try {
|
|
100
|
-
fs.symlinkSync(
|
|
101
|
-
path.join(tmpDir, 'circular-b'),
|
|
102
|
-
path.join(tmpDir, 'circular-a')
|
|
103
|
-
);
|
|
104
|
-
fs.symlinkSync(
|
|
105
|
-
path.join(tmpDir, 'circular-a'),
|
|
106
|
-
path.join(tmpDir, 'circular-b')
|
|
107
|
-
);
|
|
108
|
-
} catch {
|
|
109
|
-
// Some systems may not support circular symlinks creation
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
afterAll(() => {
|
|
114
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// =========================================================================
|
|
118
|
-
// 1. REGRESSION - Regular file as index
|
|
119
|
-
// =========================================================================
|
|
120
|
-
describe('regular file as index (regression)', () => {
|
|
121
|
-
let server, request;
|
|
122
|
-
|
|
123
|
-
beforeAll(() => {
|
|
124
|
-
const app = new Koa();
|
|
125
|
-
app.use(koaClassicServer(tmpDir, {
|
|
126
|
-
index: ['index.html'],
|
|
127
|
-
dirListing: { enabled: true }
|
|
128
|
-
}));
|
|
129
|
-
server = app.listen();
|
|
130
|
-
request = supertest(server);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
afterAll(() => { server?.close(); });
|
|
134
|
-
|
|
135
|
-
test('GET / serves regular index.html, not directory listing', async () => {
|
|
136
|
-
const res = await request.get('/');
|
|
137
|
-
expect(res.status).toBe(200);
|
|
138
|
-
expect(res.text).toContain('Regular Index');
|
|
139
|
-
expect(res.text).not.toContain('Index of');
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// =========================================================================
|
|
144
|
-
// 2. BUG FIX - Symlink to file as index
|
|
145
|
-
// =========================================================================
|
|
146
|
-
describe('symlink to file as index (bug fix)', () => {
|
|
147
|
-
let server, request;
|
|
148
|
-
|
|
149
|
-
beforeAll(() => {
|
|
150
|
-
const app = new Koa();
|
|
151
|
-
app.use(koaClassicServer(tmpDir, {
|
|
152
|
-
index: ['index.ejs'],
|
|
153
|
-
dirListing: { enabled: true }
|
|
154
|
-
}));
|
|
155
|
-
server = app.listen();
|
|
156
|
-
request = supertest(server);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
afterAll(() => { server?.close(); });
|
|
160
|
-
|
|
161
|
-
test('GET / serves symlinked index.ejs, not directory listing', async () => {
|
|
162
|
-
const res = await request.get('/');
|
|
163
|
-
expect(res.status).toBe(200);
|
|
164
|
-
const body = res.text !== undefined ? res.text : res.body.toString('utf8');
|
|
165
|
-
expect(body).toContain('EJS via Symlink');
|
|
166
|
-
expect(body).not.toContain('Index of');
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// =========================================================================
|
|
171
|
-
// 3. BUG FIX - Direct GET to symlinked file returns 200
|
|
172
|
-
// =========================================================================
|
|
173
|
-
describe('direct GET to symlinked file (bug fix)', () => {
|
|
174
|
-
let server, request;
|
|
175
|
-
|
|
176
|
-
beforeAll(() => {
|
|
177
|
-
const app = new Koa();
|
|
178
|
-
app.use(koaClassicServer(tmpDir, {
|
|
179
|
-
index: [],
|
|
180
|
-
dirListing: { enabled: true }
|
|
181
|
-
}));
|
|
182
|
-
server = app.listen();
|
|
183
|
-
request = supertest(server);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
afterAll(() => { server?.close(); });
|
|
187
|
-
|
|
188
|
-
test('GET /index.ejs via symlink returns 200', async () => {
|
|
189
|
-
const res = await request.get('/index.ejs');
|
|
190
|
-
expect(res.status).toBe(200);
|
|
191
|
-
const body = res.text !== undefined ? res.text : res.body.toString('utf8');
|
|
192
|
-
expect(body).toContain('EJS via Symlink');
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
test('GET /linked-style.css via symlink returns 200 with correct mime', async () => {
|
|
196
|
-
const res = await request.get('/linked-style.css');
|
|
197
|
-
expect(res.status).toBe(200);
|
|
198
|
-
expect(res.headers['content-type']).toMatch(/css/);
|
|
199
|
-
expect(res.text).toContain('body { color: red; }');
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// =========================================================================
|
|
204
|
-
// 4. BUG FIX - Symlink to file with template engine
|
|
205
|
-
// =========================================================================
|
|
206
|
-
describe('EJS template via symlink (bug fix)', () => {
|
|
207
|
-
let server, request;
|
|
208
|
-
|
|
209
|
-
beforeAll(() => {
|
|
210
|
-
const app = new Koa();
|
|
211
|
-
app.use(koaClassicServer(tmpDir, {
|
|
212
|
-
index: ['index.ejs'],
|
|
213
|
-
dirListing: { enabled: true },
|
|
214
|
-
template: {
|
|
215
|
-
ext: ['ejs'],
|
|
216
|
-
render: async (ctx, next, filePath) => {
|
|
217
|
-
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
218
|
-
ctx.type = 'text/html';
|
|
219
|
-
ctx.body = content;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}));
|
|
223
|
-
server = app.listen();
|
|
224
|
-
request = supertest(server);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
afterAll(() => { server?.close(); });
|
|
228
|
-
|
|
229
|
-
test('GET / renders EJS template through symlink', async () => {
|
|
230
|
-
const res = await request.get('/');
|
|
231
|
-
expect(res.status).toBe(200);
|
|
232
|
-
expect(res.headers['content-type']).toMatch(/html/);
|
|
233
|
-
expect(res.text).toContain('EJS via Symlink');
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
// =========================================================================
|
|
238
|
-
// 5. Directory as symlink
|
|
239
|
-
// =========================================================================
|
|
240
|
-
describe('symlink to directory', () => {
|
|
241
|
-
let server, request;
|
|
242
|
-
|
|
243
|
-
beforeAll(() => {
|
|
244
|
-
const app = new Koa();
|
|
245
|
-
app.use(koaClassicServer(tmpDir, {
|
|
246
|
-
index: [],
|
|
247
|
-
dirListing: { enabled: true }
|
|
248
|
-
}));
|
|
249
|
-
server = app.listen();
|
|
250
|
-
request = supertest(server);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
afterAll(() => { server?.close(); });
|
|
254
|
-
|
|
255
|
-
test('GET /linked-subdir/ lists directory contents', async () => {
|
|
256
|
-
const res = await request.get('/linked-subdir/');
|
|
257
|
-
expect(res.status).toBe(200);
|
|
258
|
-
expect(res.text).toContain('file.txt');
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test('GET /linked-subdir/file.txt serves file inside symlinked dir', async () => {
|
|
262
|
-
const res = await request.get('/linked-subdir/file.txt');
|
|
263
|
-
expect(res.status).toBe(200);
|
|
264
|
-
expect(res.text).toContain('content inside real-subdir');
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// =========================================================================
|
|
269
|
-
// 6. Broken symlink
|
|
270
|
-
// =========================================================================
|
|
271
|
-
describe('broken symlink', () => {
|
|
272
|
-
let server, request;
|
|
273
|
-
|
|
274
|
-
beforeAll(() => {
|
|
275
|
-
const app = new Koa();
|
|
276
|
-
app.use(koaClassicServer(tmpDir, {
|
|
277
|
-
index: [],
|
|
278
|
-
dirListing: { enabled: true }
|
|
279
|
-
}));
|
|
280
|
-
server = app.listen();
|
|
281
|
-
request = supertest(server);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
afterAll(() => { server?.close(); });
|
|
285
|
-
|
|
286
|
-
test('GET /broken-link.html returns 404, not crash', async () => {
|
|
287
|
-
const res = await request.get('/broken-link.html');
|
|
288
|
-
expect(res.status).toBe(404);
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
// =========================================================================
|
|
293
|
-
// 7. Circular symlink
|
|
294
|
-
// =========================================================================
|
|
295
|
-
describe('circular symlink', () => {
|
|
296
|
-
let server, request;
|
|
297
|
-
let circularExists = false;
|
|
298
|
-
|
|
299
|
-
beforeAll(() => {
|
|
300
|
-
circularExists = fs.existsSync(path.join(tmpDir, 'circular-a'));
|
|
301
|
-
const app = new Koa();
|
|
302
|
-
app.use(koaClassicServer(tmpDir, {
|
|
303
|
-
index: [],
|
|
304
|
-
dirListing: { enabled: true }
|
|
305
|
-
}));
|
|
306
|
-
server = app.listen();
|
|
307
|
-
request = supertest(server);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
afterAll(() => { server?.close(); });
|
|
311
|
-
|
|
312
|
-
test('GET /circular-a does not cause infinite loop', async () => {
|
|
313
|
-
if (!circularExists) {
|
|
314
|
-
// Skip if circular symlinks could not be created on this OS
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
const res = await request.get('/circular-a');
|
|
318
|
-
// Should return an error status, not hang
|
|
319
|
-
expect([404, 500]).toContain(res.status);
|
|
320
|
-
});
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
// =========================================================================
|
|
324
|
-
// 8. REGRESSION - Regular non-index file unchanged
|
|
325
|
-
// =========================================================================
|
|
326
|
-
describe('regular non-index file (regression)', () => {
|
|
327
|
-
let server, request;
|
|
328
|
-
|
|
329
|
-
beforeAll(() => {
|
|
330
|
-
const app = new Koa();
|
|
331
|
-
app.use(koaClassicServer(tmpDir, {
|
|
332
|
-
index: ['index.html'],
|
|
333
|
-
dirListing: { enabled: true }
|
|
334
|
-
}));
|
|
335
|
-
server = app.listen();
|
|
336
|
-
request = supertest(server);
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
afterAll(() => { server?.close(); });
|
|
340
|
-
|
|
341
|
-
test('GET /style.css serves regular file correctly', async () => {
|
|
342
|
-
const res = await request.get('/style.css');
|
|
343
|
-
expect(res.status).toBe(200);
|
|
344
|
-
expect(res.headers['content-type']).toMatch(/css/);
|
|
345
|
-
expect(res.text).toContain('body { color: red; }');
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
// =========================================================================
|
|
350
|
-
// 9. Directory listing shows symlink indicators
|
|
351
|
-
// =========================================================================
|
|
352
|
-
describe('directory listing symlink indicators', () => {
|
|
353
|
-
let server, request;
|
|
354
|
-
|
|
355
|
-
beforeAll(() => {
|
|
356
|
-
const app = new Koa();
|
|
357
|
-
app.use(koaClassicServer(tmpDir, {
|
|
358
|
-
index: [],
|
|
359
|
-
dirListing: { enabled: true }
|
|
360
|
-
}));
|
|
361
|
-
server = app.listen();
|
|
362
|
-
request = supertest(server);
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
afterAll(() => { server?.close(); });
|
|
366
|
-
|
|
367
|
-
test('symlink to file shows ( Symlink ) indicator', async () => {
|
|
368
|
-
const res = await request.get('/');
|
|
369
|
-
expect(res.status).toBe(200);
|
|
370
|
-
// index.ejs is a symlink to a file
|
|
371
|
-
expect(res.text).toContain('index.ejs');
|
|
372
|
-
expect(res.text).toMatch(/index\.ejs<\/a>\s*\( Symlink \)/);
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
test('symlink to directory shows ( Symlink ) indicator', async () => {
|
|
376
|
-
const res = await request.get('/');
|
|
377
|
-
expect(res.status).toBe(200);
|
|
378
|
-
expect(res.text).toContain('linked-subdir');
|
|
379
|
-
expect(res.text).toMatch(/linked-subdir<\/a>\s*\( Symlink \)/);
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
test('broken symlink shows ( Broken Symlink ) indicator without link', async () => {
|
|
383
|
-
const res = await request.get('/');
|
|
384
|
-
expect(res.status).toBe(200);
|
|
385
|
-
expect(res.text).toContain('broken-link.html');
|
|
386
|
-
expect(res.text).toContain('( Broken Symlink )');
|
|
387
|
-
// Broken symlink name should NOT be wrapped in <a> tag
|
|
388
|
-
expect(res.text).not.toMatch(/<a[^>]*>broken-link\.html<\/a>/);
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
test('regular file does NOT show symlink indicator', async () => {
|
|
392
|
-
const res = await request.get('/');
|
|
393
|
-
expect(res.status).toBe(200);
|
|
394
|
-
expect(res.text).toContain('style.css');
|
|
395
|
-
// style.css (not linked-style.css) should not have any symlink indicator
|
|
396
|
-
expect(res.text).not.toMatch(/>style\.css<\/a>\s*\( Symlink \)/);
|
|
397
|
-
expect(res.text).not.toMatch(/>style\.css<\/a>\s*\( Broken Symlink \)/);
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
test('symlink to file shows target mime type, not "unknown"', async () => {
|
|
401
|
-
const res = await request.get('/');
|
|
402
|
-
expect(res.status).toBe(200);
|
|
403
|
-
// linked-style.css is a symlink to style.css - should show text/css mime
|
|
404
|
-
expect(res.text).toMatch(/linked-style\.css[\s\S]*?text\/css/);
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
test('symlink to directory shows DIR type', async () => {
|
|
408
|
-
const res = await request.get('/');
|
|
409
|
-
expect(res.status).toBe(200);
|
|
410
|
-
// linked-subdir is a symlink to a directory - should show DIR
|
|
411
|
-
expect(res.text).toMatch(/linked-subdir[\s\S]*?DIR/);
|
|
412
|
-
});
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
// =========================================================================
|
|
416
|
-
// 10. Symlink as index with RegExp pattern
|
|
417
|
-
// =========================================================================
|
|
418
|
-
describe('symlink as index with RegExp pattern', () => {
|
|
419
|
-
let server, request;
|
|
420
|
-
|
|
421
|
-
beforeAll(() => {
|
|
422
|
-
const app = new Koa();
|
|
423
|
-
app.use(koaClassicServer(tmpDir, {
|
|
424
|
-
index: [/index\.[eE][jJ][sS]/],
|
|
425
|
-
dirListing: { enabled: true }
|
|
426
|
-
}));
|
|
427
|
-
server = app.listen();
|
|
428
|
-
request = supertest(server);
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
afterAll(() => { server?.close(); });
|
|
432
|
-
|
|
433
|
-
test('GET / finds symlinked index.ejs via RegExp pattern', async () => {
|
|
434
|
-
const res = await request.get('/');
|
|
435
|
-
expect(res.status).toBe(200);
|
|
436
|
-
const body = res.text !== undefined ? res.text : res.body.toString('utf8');
|
|
437
|
-
expect(body).toContain('EJS via Symlink');
|
|
438
|
-
expect(body).not.toContain('Index of');
|
|
439
|
-
});
|
|
440
|
-
});
|
|
441
|
-
});
|