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.
Files changed (105) hide show
  1. package/README.md +44 -120
  2. package/index.cjs +236 -21
  3. package/package.json +10 -5
  4. package/.github/workflows/npm-publish.yml +0 -98
  5. package/CLAUDE.md +0 -101
  6. package/__tests__/benchmark-results-baseline-v1.2.0.txt +0 -354
  7. package/__tests__/benchmark-results-optimized-v2.0.0.txt +0 -354
  8. package/__tests__/benchmark-results-v3.0.0.txt +0 -372
  9. package/__tests__/benchmark.js +0 -239
  10. package/__tests__/caching-headers.test.js +0 -556
  11. package/__tests__/compression-fixtures/data.json +0 -1
  12. package/__tests__/compression-fixtures/large.txt +0 -1
  13. package/__tests__/compression-fixtures/small.txt +0 -1
  14. package/__tests__/compression.test.js +0 -284
  15. package/__tests__/customTest/README.md +0 -6
  16. package/__tests__/customTest/loadConfig.util.js +0 -41
  17. package/__tests__/customTest/serversToLoad.util.js +0 -93
  18. package/__tests__/demo-regex-index.js +0 -140
  19. package/__tests__/deprecation-warnings.test.js +0 -105
  20. package/__tests__/directory-sorting-links.test.js +0 -135
  21. package/__tests__/dt-unknown.test.js +0 -635
  22. package/__tests__/ejs.test.js +0 -299
  23. package/__tests__/hidden-fixtures/.dot-dir/inside.txt +0 -1
  24. package/__tests__/hidden-fixtures/.env +0 -2
  25. package/__tests__/hidden-fixtures/.well-known/acme-challenge.txt +0 -1
  26. package/__tests__/hidden-fixtures/config/secrets/password.txt +0 -1
  27. package/__tests__/hidden-fixtures/data.key +0 -1
  28. package/__tests__/hidden-fixtures/file.secret +0 -1
  29. package/__tests__/hidden-fixtures/index.html +0 -1
  30. package/__tests__/hidden-fixtures/normal.txt +0 -1
  31. package/__tests__/hidden-fixtures/subdir/.env +0 -1
  32. package/__tests__/hidden-fixtures/subdir/regular.txt +0 -1
  33. package/__tests__/hidden-option.test.js +0 -407
  34. package/__tests__/hideExtension.test.js +0 -607
  35. package/__tests__/index-option.test.js +0 -449
  36. package/__tests__/index.test.js +0 -345
  37. package/__tests__/listing.test.js +0 -437
  38. package/__tests__/logger.test.js +0 -232
  39. package/__tests__/performance.test.js +0 -370
  40. package/__tests__/publicWwwTest/cartella/sottocartella/ciao.html +0 -11
  41. package/__tests__/publicWwwTest/cartella/sottocartella/provaEjs/testEjs.ejs +0 -11
  42. package/__tests__/publicWwwTest/cartella vuota con spazi nel nome/file con spazio nel nome .txt +0 -1
  43. package/__tests__/publicWwwTest/cartella vuota con spazi nel nome /file con spazio nel nome .txt +0 -1
  44. package/__tests__/publicWwwTest/ejs-templates/complex.ejs +0 -56
  45. package/__tests__/publicWwwTest/ejs-templates/index.ejs +0 -30
  46. package/__tests__/publicWwwTest/ejs-templates/simple.ejs +0 -13
  47. package/__tests__/publicWwwTest/ejs-templates/with-conditional.ejs +0 -28
  48. package/__tests__/publicWwwTest/ejs-templates/with-escaping.ejs +0 -26
  49. package/__tests__/publicWwwTest/ejs-templates/with-loop.ejs +0 -16
  50. package/__tests__/publicWwwTest/hideext-test/about/index.html +0 -1
  51. package/__tests__/publicWwwTest/hideext-test/about.EJS +0 -1
  52. package/__tests__/publicWwwTest/hideext-test/about.ejs +0 -2
  53. package/__tests__/publicWwwTest/hideext-test/blog/articolo.ejs +0 -2
  54. package/__tests__/publicWwwTest/hideext-test/conflict-test/pagina +0 -1
  55. package/__tests__/publicWwwTest/hideext-test/conflict-test/pagina.ejs +0 -1
  56. package/__tests__/publicWwwTest/hideext-test/file.ejs.bak +0 -1
  57. package/__tests__/publicWwwTest/hideext-test/index.ejs +0 -2
  58. package/__tests__/publicWwwTest/hideext-test/photo.txt +0 -1
  59. package/__tests__/publicWwwTest/hideext-test/sezione/index.ejs +0 -1
  60. package/__tests__/publicWwwTest/hideext-test/style.css +0 -1
  61. package/__tests__/publicWwwTest/ile_vuoto.txt +0 -0
  62. package/__tests__/publicWwwTest/index.html +0 -11
  63. package/__tests__/publicWwwTest/prova file .txt +0 -2
  64. package/__tests__/publicWwwTest/semplicetxt.txt +0 -1
  65. package/__tests__/publicWwwTest/test-page.html +0 -1
  66. package/__tests__/publicWwwTest/test.txt +0 -1
  67. package/__tests__/range-fixtures/sample.txt +0 -1
  68. package/__tests__/range.test.js +0 -223
  69. package/__tests__/security-headers.test.js +0 -165
  70. package/__tests__/security.test.js +0 -322
  71. package/__tests__/server-cache-fixtures/large.txt +0 -1
  72. package/__tests__/server-cache-fixtures/small.txt +0 -1
  73. package/__tests__/server-cache.test.js +0 -594
  74. package/__tests__/setup-benchmark.js +0 -178
  75. package/__tests__/symlink.test.js +0 -441
  76. package/__tests__/template-timeout.test.js +0 -321
  77. package/__tests__/test-regex-quick.js +0 -158
  78. package/__tests__/useOriginalUrl.test.js +0 -213
  79. package/docs/ACTION_PLAN.md +0 -293
  80. package/docs/BENCHMARKS.md +0 -317
  81. package/docs/CHANGELOG.md +0 -880
  82. package/docs/CODE_REVIEW.md +0 -300
  83. package/docs/DEBUG_REPORT.md +0 -593
  84. package/docs/DOCUMENTATION.md +0 -1864
  85. package/docs/EXAMPLES_INDEX_OPTION.md +0 -395
  86. package/docs/FLOW_DIAGRAM.md +0 -954
  87. package/docs/INDEX_OPTION_PRIORITY.md +0 -527
  88. package/docs/OPTIMIZATION_HTTP_CACHING.md +0 -689
  89. package/docs/OPTIMIZATION_ROADMAP_for_V3.md +0 -864
  90. package/docs/PERFORMANCE_ANALYSIS.md +0 -839
  91. package/docs/PERFORMANCE_COMPARISON.md +0 -388
  92. package/docs/noteExports.md +0 -148
  93. package/docs/security_improvement_for_V3.md +0 -421
  94. package/docs/template-engine/TEMPLATE_ENGINE_GUIDE.md +0 -1734
  95. package/docs/template-engine/esempi-incrementali.js +0 -192
  96. package/docs/template-engine/examples/esempio1-nessun-dato.ejs +0 -12
  97. package/docs/template-engine/examples/esempio2-una-variabile.ejs +0 -11
  98. package/docs/template-engine/examples/esempio3-piu-variabili.ejs +0 -15
  99. package/docs/template-engine/examples/esempio4-condizionale.ejs +0 -18
  100. package/docs/template-engine/examples/esempio5-loop.ejs +0 -18
  101. package/docs/template-engine/examples/index-esempi.html +0 -181
  102. package/docs/template-engine/examples/index.html +0 -40
  103. package/docs/template-engine/examples/test.ejs +0 -64
  104. package/eslint.config.mjs +0 -17
  105. package/jest.config.js +0 -18
package/docs/CHANGELOG.md DELETED
@@ -1,880 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to koa-classic-server will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [3.0.0] - 2026-05-13
9
-
10
- ### ๐Ÿ†• New Features
11
-
12
- #### `hidden` option โ€” protect dot-files, dot-dirs and custom patterns (Fase 1 + Fase 2)
13
-
14
- A new `hidden` option controls which files and directories are blocked from both directory listing and direct HTTP access (returning **404**). Applies recursively to the entire directory tree.
15
-
16
- **Default behavior (secure out of the box):**
17
- - Dot-files (names starting with `.`) โ†’ **hidden by default** (`dotFiles.default: 'hidden'`)
18
- - Dot-directories โ†’ **visible by default** (`dotDirs.default: 'visible'`)
19
-
20
- ```js
21
- app.use(koaClassicServer('/public', {
22
- hidden: {
23
- dotFiles: {
24
- default: 'hidden', // 'hidden' | 'visible'
25
- whitelist: ['.well-known'], // Always visible (string exact/glob or RegExp)
26
- blacklist: [], // Always hidden โ€” overrides whitelist
27
- },
28
- dotDirs: {
29
- default: 'visible',
30
- whitelist: [],
31
- blacklist: ['.git', /^\.svn/],
32
- },
33
- alwaysHide: ['*.secret', 'config/secrets/**', /\.key$/], // Path-aware patterns
34
- }
35
- }));
36
- ```
37
-
38
- **Priority (highest to lowest):**
39
- 1. `blacklist` โ€” always hidden, beats everything
40
- 2. `whitelist` โ€” always visible, overrides `alwaysHide` and `default`
41
- 3. `alwaysHide` โ€” path-aware patterns, beats `default`
42
- 4. `default` โ€” fallback behavior for unmatched dot-entries
43
-
44
- **`alwaysHide` pattern rules:**
45
- - String without `/`: matches basename at any depth (e.g. `*.secret` hides `a/b/file.secret`)
46
- - String with `/`: path-anchored from root (e.g. `config/secrets/**`)
47
- - RegExp: tested against the full relative path
48
-
49
- **Blocked dot-dirs block sub-paths too:**
50
- `GET /.git/config` returns 404 if `.git` is in `dotDirs.blacklist`.
51
-
52
- #### `template.renderTimeout` โ€” bounded template execution (Security M-1)
53
-
54
- The template `render` callback now runs under a configurable timeout (default **30 000 ms**, `0` = disabled). When a render exceeds the timeout the middleware responds **`504 Gateway Timeout`** with the usual security headers, instead of leaving the client connection blocked on a slow/hung render. Protects against DoS via connection exhaustion when a render performs unbounded I/O (DB queries, remote fetches, etc.).
55
-
56
- The `render` function now receives an **`AbortSignal` as 5th argument**. The signal aborts on timeout *and* when the client disconnects (even when `renderTimeout: 0`). Cooperative renders that propagate the signal to `fetch` / DB clients / `fs.promises.*` also free backend resources on timeout.
57
-
58
- ```js
59
- app.use(koaClassicServer('/public', {
60
- template: {
61
- ext: ['ejs'],
62
- renderTimeout: 5000, // ms; 0 disables
63
- render: async (ctx, next, filePath, rawBuffer, signal) => {
64
- const data = await db.query('SELECT ...', { signal }); // honour signal
65
- const ext = await fetch('https://api/...', { signal });
66
- signal.throwIfAborted();
67
- ctx.type = 'text/html';
68
- ctx.body = ejs.render(rawBuffer.toString(), { data, ext });
69
- }
70
- }
71
- }));
72
- ```
73
-
74
- **Backward compatible:** existing 4-argument render functions keep working โ€” the 5th argument is simply ignored.
75
-
76
- #### `serverCache.*.maxAge` โ€” time-based cache invalidation (Security M-2)
77
-
78
- Both server-side caches (`serverCache.rawFile` and `serverCache.compressedFile`) accept a new `maxAge` option (ms, default `0` = disabled). When `> 0`, an entry is considered stale after `maxAge` ms regardless of `mtime + size`, forcing a fresh disk read on the next request.
79
-
80
- Designed for **NFS / SMB / Docker bind mounts** where the OS attribute cache can keep `stat()` returning a stale `mtime` for several seconds after a remote modification โ€” making the mtime+size invariant insufficient to detect changes. `maxAge` bounds the worst-case staleness window to a known value.
81
-
82
- ```js
83
- app.use(koaClassicServer('/public', {
84
- serverCache: {
85
- rawFile: { enabled: true, maxAge: 30000 }, // refresh every 30 s
86
- compressedFile: { enabled: true, maxAge: 30000 }
87
- }
88
- }));
89
- ```
90
-
91
- > **Limitation:** `maxAge` limits but does not eliminate NFS staleness. For strict freshness combine with a low `actimeo=` on the mount.
92
-
93
- Internally a new `LFUCache.refresh(key, fields)` method updates the entry in place while preserving its LFU frequency, so popular files refreshed by `maxAge` don't fall to the bottom of the eviction bucket.
94
-
95
- #### `logger` option โ€” pluggable structured logging (Security N-1)
96
-
97
- All internal `console.error` / `console.warn` calls now route through an injectable logger. Pass any object that exposes `error(...)` and `warn(...)` methods โ€” `console` (default), `pino`, `winston`, `bunyan`, or a custom adapter โ€” to integrate with aggregated logging pipelines in production.
98
-
99
- ```js
100
- const pino = require('pino')();
101
-
102
- app.use(koaClassicServer('/public', {
103
- logger: pino
104
- }));
105
- ```
106
-
107
- **Contract:**
108
- - Must be an object with `typeof logger.error === 'function'` and `typeof logger.warn === 'function'`
109
- - Invalid loggers (missing methods, non-objects, `null`, `false`, arrays) throw at factory time
110
- - Extra methods (`info`, `debug`, `fatal`, ...) are ignored โ€” pass any superset freely
111
-
112
- **ANSI escape codes** in warning messages are only emitted when the logger is the global `console` (detected by reference). Structured loggers receive the plain text, keeping log aggregators clean.
113
-
114
- **Backward compatible:** when `logger` is omitted, the default is `console` โ€” existing code and tests that spy on `console.error` / `console.warn` continue to work unchanged.
115
-
116
- #### `dirListing` namespace โ€” bounded and paginated directory listings (Security N-2)
117
-
118
- A new namespaced option groups all directory-listing config together, replacing the v2-era flat `showDirContents` knob with a structured object. Hardens the listing against indirect DoS via very large directories and improves usability on big folders.
119
-
120
- ```js
121
- app.use(koaClassicServer('/public', {
122
- dirListing: {
123
- enabled: true, // render listing HTML when no index file matches (default: true)
124
- maxEntries: 10000, // hard cap on visible / sorted / stat'd entries (default; 0 = disabled)
125
- entriesPerPage: 100, // entries per page in the listing UI (default; 0 = disabled)
126
- }
127
- }));
128
- ```
129
-
130
- **dirListing.enabled**
131
- - Replaces the v2 top-level `showDirContents`. Accepts `true` / `false`. When `false`, requests for a directory without a matching index file return 404 instead of an HTML listing.
132
-
133
- **dirListing.maxEntries**
134
- - Caps how many entries are sorted, stat'd, and rendered per directory listing. Excess entries trigger a yellow banner at the top of the page and an `X-Dir-Truncated: <N>` response header so monitoring can distinguish capped listings.
135
- - Implementation: the middleware calls `fs.promises.readdir()` once and then slices the result. This bounds rendering and CPU cost but **not** the size of the initial `readdir()` allocation. For typical static-file servers (where the directory contents are controlled by the operator) this is the right trade-off โ€” it recovers v2-class listing performance.
136
- - Default `10000` is permissive enough for normal use while bounding rendering cost on accidentally-large folders.
137
- - **Caveat for adversarial workloads:** if you serve a directory writable by untrusted parties, an attacker creating millions of files could still force a large `readdir()` allocation. Tracked for v3.1 as opt-in streaming reads โ€” see `docs/security_improvement_for_V3.md` โ†’ *Future Work* โ†’ *[F-1]*.
138
-
139
- **dirListing.entriesPerPage**
140
- - Pagination kicks in only when the visible entries exceed `entriesPerPage`; small directories render in a single page exactly like before.
141
- - The current page is selected by `?page=N` (0-based). Invalid or out-of-range values clamp silently to the nearest valid page.
142
- - A numbered paginator (`ยซ First | โ€น Prev | 0 1 โ€ฆ N-1 | Next โ€บ | Last ยป`) is rendered below the table, preserving any active `sort`/`order`. An `X-Dir-Pagination: <current>/<last>` header is also emitted.
143
-
144
- **Migration from v2**
145
-
146
- `showDirContents` (a v2-stable option) keeps working as a **backward-compatibility alias** for `dirListing.enabled`. v2 code that passes it continues to function unchanged. A one-time deprecation warning is emitted via the configured `logger.warn(...)` to encourage migration:
147
-
148
- ```
149
- [koa-classic-server] DEPRECATION: options.showDirContents was renamed to dirListing.enabled in v3.0.0.
150
- The old name is currently accepted as an alias and may be removed in a future major version.
151
- Replace with: dirListing: { enabled: true }
152
- ```
153
-
154
- Passing both `showDirContents` and `dirListing.enabled` at the same time throws โ€” pick one.
155
-
156
- **Migration from v3.0.0-alpha.0**
157
-
158
- The two V3-alpha-only legacy names throw helpful errors at startup (no v2 user can have these in production):
159
-
160
- ```
161
- options.maxDirEntries was relocated in v3.0.0.
162
- Replace with: dirListing: { maxEntries: 10000 }
163
-
164
- options.pageSize was relocated and renamed in v3.0.0.
165
- Replace with: dirListing: { entriesPerPage: 100 }
166
- ```
167
-
168
- **CSP impact:** the listing CSS now includes rules for `.kcs-banner` and `.kcs-pagination`. The page's CSP hash is auto-recomputed at module load (no manual config change needed).
169
-
170
- ### ๐Ÿ“ Documentation
171
-
172
- #### DNS Rebinding deployment guidance (Security M-3)
173
-
174
- The `Host` header is intentionally not validated by the middleware โ€” host validation belongs to the reverse proxy or to a dedicated application-level guard. The new *Best Practices โ†’ Sicurezza โ†’ DNS Rebinding* section in `docs/DOCUMENTATION.md` explains:
175
-
176
- - When the risk applies (LAN/loopback exposure without a fronting proxy).
177
- - When it doesn't (reverse proxy with `server_name` allowlist, public IP behind CDN/WAF).
178
- - A drop-in nginx allowlist snippet.
179
- - A Koa middleware that checks `ctx.host` against an allowlist and returns `421 Misdirected Request`, plus a note on `app.proxy = true` + `X-Forwarded-Host` when terminating TLS upstream.
180
-
181
- No code change in `index.cjs` โ€” documentation only.
182
-
183
- #### Security headers scope and limits (Security M-4)
184
-
185
- Clarify that the security headers emitted by the middleware (`Content-Security-Policy`, `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Permissions-Policy`) are applied **only** to middleware-generated responses (directory listing + error pages). User-served static files are returned without these headers โ€” by design, because the right policy is application-specific.
186
-
187
- The new *Best Practices โ†’ Sicurezza โ†’ Limiti dei Security Headers sui file statici* section in `docs/DOCUMENTATION.md` covers:
188
-
189
- - A table listing which headers are emitted automatically and on which responses.
190
- - An upstream Koa middleware example that adds `X-Content-Type-Options`, `Referrer-Policy`, `Strict-Transport-Security` to every response and a strict CSP only to HTML responses.
191
- - Notes on rolling out CSP via `Content-Security-Policy-Report-Only`, and on `COOP`/`COEP` for projects using `SharedArrayBuffer`.
192
-
193
- No code change in `index.cjs` โ€” documentation only.
194
-
195
- ### ๐ŸŽฏ Design Philosophy
196
-
197
- v3.0.0 codifies the project's design intent in a new top-level `CLAUDE.md`: **koa-classic-server is an HTTP file server first**. The contract with the operator is: *"if a file is in `rootDir`, `GET` on its path returns it"*. Defaults serve files without applying surprise restrictions โ€” the operator's directory is the source of truth.
198
-
199
- This drove a revision of two v3-alpha defaults late in the cycle (see *Breaking Changes* below) and shapes how new features will be designed going forward. Operators harden via explicit configuration; the README and `docs/DOCUMENTATION.md` now ship a **Security Checklist** and a **Suggested Production Security Configuration** to help with that.
200
-
201
- ### โš ๏ธ Breaking Changes
202
-
203
- #### Dot-files visible by default (philosophy alignment)
204
-
205
- Earlier in the v3.0.0 alpha cycle, `hidden.dotFiles.default` was flipped to `'hidden'` as a security-by-default choice. This created surprise behavior โ€” `GET /.env` returning 404 even when the file exists โ€” which violates the "file server first" design philosophy.
206
-
207
- **Final v3.0.0 behavior:** `hidden.dotFiles.default` is `'visible'`, restoring v2 behavior. The implicit-default warning that fired in alpha when the option was omitted is also removed.
208
-
209
- | Default | v2 | v3.0.0-alpha early | **v3.0.0 final** |
210
- |---|---|---|---|
211
- | `hidden.dotFiles.default` | `'visible'` | `'hidden'` | **`'visible'`** |
212
- | Implicit-default runtime warning | โ€” | emitted | **removed** |
213
-
214
- **Operators upgrading from v2:** no change in behavior โ€” your existing dot-files keep being served. **Migration to harden** (recommended for production): set `hidden.dotFiles.default: 'hidden'` explicitly and whitelist `.well-known` for ACME. See the *Security Checklist* in `README.md`.
215
-
216
- #### `dirListing.maxEntries` default raised from 10,000 โ†’ 100,000
217
-
218
- The earlier v3-alpha default of `10,000` was tight enough that operators with normal-sized media catalogs, releases archives, or asset directories would hit truncation silently (the listing banner would appear). This violated the "no surprise restrictions" rule โ€” the cap was acting as a policy restriction rather than a safety net.
219
-
220
- **Final v3.0.0 behavior:** `dirListing.maxEntries` defaults to `100,000` โ€” high enough that 99% of legitimate deployments never hit it, low enough to bound rendering cost on accidentally-huge directories (log rotation broken, mistakenly mounted FS).
221
-
222
- | Default | v3.0.0-alpha early | **v3.0.0 final** |
223
- |---|---|---|
224
- | `dirListing.maxEntries` | `10000` | **`100000`** |
225
- | `dirListing.entriesPerPage` | `100` | `100` (unchanged) |
226
-
227
- **Caveat:** even with `maxEntries: 100000`, the initial `fs.promises.readdir()` allocation is not bounded. For adversarial-directory workloads (multi-tenant uploads, untrusted writes), this gap will be closed by the v3.1 `dirListing.readMode: 'bounded'` option โ€” tracked under `[F-1]` in `docs/security_improvement_for_V3.md`.
228
-
229
- #### Dot-files hardening is now opt-in (was implicit "secure by default")
230
-
231
- Operators who *want* the v3-alpha behavior (dot-files hidden by default, including `.env`, `.git/config`, etc.) must now opt in explicitly:
232
-
233
- ```javascript
234
- app.use(koaClassicServer('/public', {
235
- hidden: {
236
- dotFiles: { default: 'hidden', whitelist: ['.well-known'] },
237
- dotDirs: { default: 'hidden', whitelist: ['.well-known'] },
238
- },
239
- }));
240
- ```
241
-
242
- This snippet now appears in the *Suggested Production Security Configuration* in both `README.md` and `docs/DOCUMENTATION.md`.
243
-
244
- #### Removed string format for `index` option
245
- - **Removed**: `index: 'index.html'` โ€” passing a non-empty string now throws an `Error` at startup
246
- - **Empty string** `index: ''` is still silently treated as `[]` (no index file, show directory listing)
247
- - **Migration**:
248
- ```js
249
- // Before (v2.x โ€” now throws)
250
- app.use(koaClassicServer('/public', { index: 'index.html' }));
251
-
252
- // After (v3.0.0)
253
- app.use(koaClassicServer('/public', { index: ['index.html'] }));
254
- ```
255
-
256
- #### Removed deprecated option names `cacheMaxAge` and `enableCaching`
257
- - **Removed**: `cacheMaxAge` โ€” use `browserCacheMaxAge` instead
258
- - **Removed**: `enableCaching` โ€” use `browserCacheEnabled` instead
259
- - **Behaviour**: Passing either removed option now throws an `Error` at startup with a clear migration message pointing to the new name and the current value.
260
- - **Migration**:
261
- ```js
262
- // Before (v2.x โ€” now throws)
263
- app.use(koaClassicServer('/public', {
264
- enableCaching: true,
265
- cacheMaxAge: 3600
266
- }));
267
-
268
- // After (v3.0.0)
269
- app.use(koaClassicServer('/public', {
270
- browserCacheEnabled: true,
271
- browserCacheMaxAge: 3600
272
- }));
273
- ```
274
-
275
- #### Renamed `compression.minSize` โ†’ `compression.minFileSize`
276
-
277
- The threshold below which files are served uncompressed has a clearer name. Brings naming into line with `serverCache.rawFile.maxFileSize`, where "file size" is the explicit unit. Affects only alpha-tester code (the `compression` namespace was introduced in v3 and is not present in v2).
278
-
279
- - **Removed**: `compression.minSize` โ€” passing it now throws an `Error` at startup
280
- - **Migration**:
281
- ```js
282
- // Before (v3.0.0-alpha.0 โ€” now throws)
283
- app.use(koaClassicServer('/public', {
284
- compression: { minSize: 2048 }
285
- }));
286
-
287
- // After (v3.0.0)
288
- app.use(koaClassicServer('/public', {
289
- compression: { minFileSize: 2048 }
290
- }));
291
- ```
292
-
293
- The `false` shorthand (disable the threshold entirely) is preserved on the new name: `compression: { minFileSize: false }`.
294
-
295
- ---
296
-
297
- ## [2.6.1] - 2026-03-04
298
-
299
- ### ๐Ÿ› Bug Fix
300
-
301
- #### Fixed DT_UNKNOWN Handling (type 0) on overlayfs, NFS, FUSE, NixOS buildFHSEnv, ecryptfs
302
- - **Issue**: On filesystems where `readdir({ withFileTypes: true })` returns dirents with `DT_UNKNOWN` (type 0), all `dirent.is*()` methods return `false`. This caused three failures:
303
- 1. `isFileOrSymlinkToFile()` missed valid files โ€” `findIndexFile()` returned empty results, so `GET /` showed a directory listing instead of rendering the index file
304
- 2. `isDirOrSymlinkToDir()` missed valid directories โ€” directory type resolution failed
305
- 3. `show_dir()` skipped entries with type 0, logging `"Unknown file type: 0"` โ€” directory listings appeared empty or partial
306
- - **Affected environments**: overlayfs (Docker image layers), NFS (some implementations), FUSE filesystems (sshfs, s3fs, rclone mount), NixOS with buildFHSEnv, ecryptfs (encrypted home directories), and any filesystem that doesn't fill `d_type` in the kernel's `getdents64` syscall
307
- - **Impact**: HIGH โ€” Server unusable on affected filesystems (index file not served, directory listing empty)
308
- - **Fix**: Added `fs.promises.stat()` fallback in all three locations when none of the `dirent.is*()` type methods return `true` (i.e., type is genuinely unknown). On standard filesystems (ext4, btrfs, xfs, APFS, NTFS), `d_type` is always filled correctly, so the `stat()` fallback is never reached โ€” **zero performance overhead** on the fast path.
309
- - **Code**:
310
- - `isFileOrSymlinkToFile()` โ€” DT_UNKNOWN fallback via `stat().isFile()`
311
- - `isDirOrSymlinkToDir()` โ€” DT_UNKNOWN fallback via `stat().isDirectory()`
312
- - `show_dir()` โ€” Accept type 0 entries and resolve via `stat()` instead of skipping them
313
- - **Reference**: Linux `man 2 getdents` โ€” *"Currently, only some filesystems have full support for returning the file type in d_type. All applications must properly handle a return of DT_UNKNOWN."*
314
-
315
- ### ๐Ÿงช Testing
316
- - Added `__tests__/dt-unknown.test.js` with 20 tests covering:
317
- - `isFileOrSymlinkToFile` / `isDirOrSymlinkToDir` with DT_UNKNOWN dirents
318
- - `findIndexFile` with all-unknown-type entries (string and RegExp patterns)
319
- - `show_dir` rendering (resolved types, no skipped entries, correct MIME types and sizes)
320
- - Full integration tests (index file serving, direct file access, complete directory listing)
321
- - Edge cases (mixed regular + DT_UNKNOWN dirents, index priority, Dirent type 0 verification)
322
- - Tests use `jest.spyOn(fs.promises, 'readdir')` to mock DT_UNKNOWN dirents via `new fs.Dirent(name, 0)` while keeping `fs.promises.stat()` working normally
323
- - All 329 tests pass across 12 test suites (zero regressions)
324
-
325
- ### ๐Ÿ“ฆ Package Changes
326
- - **Version**: `2.6.0` โ†’ `2.6.1`
327
- - **Semver**: Patch version bump (bug fix only, no API changes)
328
-
329
- ---
330
-
331
- ## [2.6.0] - 2026-03-01
332
-
333
- ### ๐Ÿ“ฆ Dependency Upgrades
334
-
335
- #### mime-types: ^2.1.35 โ†’ ^3.0.2 (Major)
336
- - **Breaking change upstream**: New `mimeScore` algorithm for extension conflict resolution
337
- - **Impact on this project**: Minimal โ€” the 11 changed MIME mappings affect only uncommon extensions
338
- - **Notable mapping changes**:
339
- - `.wav`: `audio/wave` โ†’ `audio/wav` (equivalent, all browsers accept both)
340
- - `.js`: `application/javascript` โ†’ `text/javascript` (correct per RFC 9239)
341
- - `.rtf`: `text/rtf` โ†’ `application/rtf` (marginal, rare usage)
342
- - `.mp4`: Unchanged in v3.0.2 โ€” still resolves to `video/mp4`
343
- - **Node.js requirement**: mime-types 3 requires Node.js >= 18
344
-
345
- #### ejs: ^3.1.10 โ†’ ^4.0.0 (Major)
346
- - **Breaking changes upstream**: None affecting this project
347
- - EJS 4 removed deprecated `with()` statement support (this project never used it)
348
- - EJS 4 added stricter `exports` map in package.json
349
- - **API fully compatible**: `ejs.render()` and `ejs.renderFile()` work identically
350
- - **Security**: EJS 3.x is EOL โ€” v4 resolves known CVEs in the 3.x line
351
-
352
- ### ๐Ÿ”ง Configuration Changes
353
-
354
- #### Added `engines` field
355
- - Added `"engines": { "node": ">=18" }` to package.json
356
- - Formalizes the Node.js minimum version requirement imposed by mime-types 3
357
-
358
- #### Tightened Koa peerDependency for 2.x
359
- - **koa**: `"^2.0.0 || >=3.1.2"` โ†’ `"^2.16.4 || >=3.1.2"`
360
- - Excludes Koa 2.0.0โ€“2.16.3 which are affected by 4 known CVEs:
361
- - CVE-2025-25200: ReDoS via `X-Forwarded-Proto`/`X-Forwarded-Host` (CVSS 9.2, fixed in 2.15.4)
362
- - CVE-2025-32379: XSS via `ctx.redirect()` (fixed in 2.16.1)
363
- - CVE-2025-62595: Open Redirect via trailing `//` (fixed in 2.16.3)
364
- - CVE-2026-27959: Host Header Injection via `ctx.hostname` (CVSS 7.5, fixed in 2.16.4)
365
-
366
- ### ๐Ÿงช Testing
367
- - All 309 tests pass across 11 test suites (zero regressions)
368
- - No code changes required โ€” both library upgrades are API-compatible
369
-
370
- ### ๐Ÿ“ฆ Package Changes
371
- - **Version**: `2.5.2` โ†’ `2.6.0`
372
- - **Semver**: Minor version bump (dependency upgrades, no API changes)
373
-
374
- ---
375
-
376
- ## [2.5.2] - 2026-03-01
377
-
378
- ### ๐Ÿ”’ Security Fix
379
-
380
- #### Resolved all 11 npm audit vulnerabilities
381
- - **jest**: `^29.7.0` โ†’ `^30.2.0` (major โ€” fixes minimatch ReDoS, brace-expansion ReDoS, @babel/helpers inefficient RegExp)
382
- - **supertest**: `^7.0.0` โ†’ `^7.2.2` (fixes critical form-data unsafe random boundary)
383
- - **inquirer**: `^12.4.1` โ†’ `^13.3.0` (fixes tmp arbitrary file write via symlink, external-editor chain)
384
- - **autocannon**: `^7.15.0` โ†’ `^8.0.0` (major)
385
-
386
- #### Updated peerDependency
387
- - **koa**: `"^2.0.0 || ^3.0.0"` โ†’ `"^2.0.0 || >=3.1.2"`
388
- - Excludes Koa 3.0.0โ€“3.1.1 which had Host Header Injection via `ctx.hostname`
389
-
390
- ### ๐Ÿงช Testing
391
- - All 309 tests pass across 11 test suites (zero regressions)
392
- - `npm audit` reports 0 vulnerabilities
393
-
394
- ### ๐Ÿ“ฆ Package Changes
395
- - **Version**: `2.5.1` โ†’ `2.5.2`
396
- - **Semver**: Patch version bump (security fixes only, no API changes)
397
-
398
- ---
399
-
400
- ## [2.5.1] - 2026-03-01
401
-
402
- ### ๐Ÿ“ Documentation
403
-
404
- - Added dedicated usage example for `useOriginalUrl` (Section 7) with realistic i18n middleware scenario (/it/, /en/, /fr/)
405
- - Added "Advanced hideExtension Scenarios" section (Section 8):
406
- - Recommended file/directory structure (ASCII tree)
407
- - Combined `hideExtension` + i18n middleware example with `useOriginalUrl: false`
408
- - Temporary redirect (302) variant with guidance on 301 vs 302 usage
409
- - Added `hideExtension` and `useOriginalUrl` to the Complete Production Example (Section 11)
410
-
411
- ### ๐Ÿ“ฆ Package Changes
412
- - **Version**: `2.5.0` โ†’ `2.5.1`
413
- - **Semver**: Patch version bump (documentation only, no code changes)
414
-
415
- ---
416
-
417
- ## [2.5.0] - 2026-02-28
418
-
419
- ### โœจ New Feature
420
-
421
- #### hideExtension - Clean URLs (mod_rewrite-like)
422
- - **New Option**: `hideExtension: { ext: '.ejs', redirect: 301 }`
423
- - **Purpose**: Hide file extensions from URLs for SEO-friendly clean URLs
424
- - **Clean URL Resolution**: `/about` โ†’ serves `about.ejs` (when file exists)
425
- - **Extension Redirect**: `/about.ejs` โ†’ 301 redirect to `/about` (preserves query string)
426
- - **Index File Redirect**: `/index.ejs` โ†’ redirect to `/`, `/section/index.ejs` โ†’ redirect to `/section/`
427
- - **Conflict Resolution**: `.ejs` file wins over both directories and extensionless files with same base name
428
- - **Case-Sensitive**: Extension matching is case-sensitive (`.ejs` โ‰  `.EJS`)
429
- - **No Interference**: URLs with other extensions (`.css`, `.png`, etc.) pass through normally
430
- - **Trailing Slash**: `/about/` always means directory, never resolves to file
431
- - **Redirect uses `ctx.originalUrl`**: Preserves URL prefixes from upstream middleware (i18n, routing)
432
-
433
- #### Input Validation
434
- - `hideExtension: true` โ†’ throws Error (must be an object)
435
- - `hideExtension: {}` โ†’ throws Error (missing `ext`)
436
- - `hideExtension: { ext: '' }` โ†’ throws Error (empty ext)
437
- - `hideExtension: { ext: 'ejs' }` โ†’ warning + auto-normalizes to `.ejs`
438
- - `hideExtension: { ext: '.ejs', redirect: 'abc' }` โ†’ throws Error (redirect must be number)
439
-
440
- #### Integration with Existing Options
441
- - **urlsReserved**: Checked before `hideExtension`, no interference
442
- - **urlPrefix**: `hideExtension` works on path after prefix removal
443
- - **useOriginalUrl**: Resolution follows setting; redirect always uses `ctx.originalUrl`
444
- - **template**: Resolved files pass through template engine normally
445
- - **method**: `hideExtension` only applies to allowed HTTP methods
446
-
447
- ### ๐Ÿงช Testing
448
- - Added `__tests__/hideExtension.test.js` with 31 tests covering:
449
- - Clean URL resolution (single and multi-level paths)
450
- - Extension redirect (301/302, query string preservation)
451
- - Directory/file conflict resolution
452
- - Trailing slash behavior
453
- - Extensionless file conflict
454
- - Index file redirect (`/index.ejs` โ†’ `/`)
455
- - `urlsReserved` interaction
456
- - `useOriginalUrl` interaction (redirect preserves prefix)
457
- - Case-sensitive matching
458
- - No interference with other extensions
459
- - Template engine integration
460
- - Input validation (7 validation tests)
461
- - All 278 existing tests still pass (zero regressions)
462
-
463
- ### ๐Ÿ“ฆ Package Changes
464
- - **Version**: `2.4.0` โ†’ `2.5.0`
465
- - **Semver**: Minor version bump (new feature, backward compatible)
466
-
467
- ---
468
-
469
- ## [2.4.0] - 2026-02-28
470
-
471
- ### ๐Ÿ› Bug Fix
472
-
473
- #### Fixed Symlink Support in Index File Discovery and Directory Listing
474
- - **Issue**: On systems where served files are symbolic links (NixOS buildFHSEnv, Docker bind mounts, `npm link`, Capistrano-style deploys), `findIndexFile()` failed because `dirent.isFile()` returns `false` for symlinks. This caused `GET /` to show directory listing instead of rendering the index file, and `GET /index.ejs` to return 404.
475
- - **Impact**: HIGH - Server unusable on NixOS/buildFHSEnv and similar environments
476
- - **Fix**: Added `isFileOrSymlinkToFile()` / `isDirOrSymlinkToDir()` helpers that follow symlinks via `fs.promises.stat()` only when `dirent.isSymbolicLink()` is true, adding zero overhead for regular files.
477
- - **Code**: `index.cjs` - new helpers + `findIndexFile()` + `show_dir()`
478
-
479
- ### โœจ Improvements
480
-
481
- #### Directory Listing Symlink Indicators
482
- - Symlinks to files/directories show `( Symlink )` label next to the name
483
- - Broken/circular symlinks show `( Broken Symlink )` label (name visible but not clickable)
484
- - Symlinks resolved to effective type for MIME and size display (e.g. symlink to dir shows `DIR`)
485
- - Sorting uses effective type (symlink-to-dir sorts with directories)
486
-
487
- ### ๐Ÿงช Testing
488
- - Added `__tests__/symlink.test.js` with 17 tests covering:
489
- - Regular file as index (regression)
490
- - Symlink to file as index (string and RegExp patterns)
491
- - Direct GET to symlinked file
492
- - EJS template via symlink
493
- - Symlink to directory (listing and file access)
494
- - Broken and circular symlinks
495
- - Directory listing indicators (`( Symlink )`, `( Broken Symlink )`)
496
- - Regular file regression (no false symlink indicator)
497
- - All 187 existing tests still pass (zero regressions)
498
-
499
- ### ๐Ÿ“ฆ Package Changes
500
- - **Semver**: Minor version bump (new feature, backward compatible)
501
-
502
- ---
503
-
504
- ## [2.3.0] - 2026-01-03
505
-
506
- ### ๐Ÿ”„ Renamed Options (with Backward Compatibility)
507
-
508
- #### Renamed Caching Options for Clarity
509
- - **Old Names** (DEPRECATED): `enableCaching`, `cacheMaxAge`
510
- - **New Names**: `browserCacheEnabled`, `browserCacheMaxAge`
511
- - **Reason**: Improved clarity - these options specifically control browser-side HTTP caching
512
- - **Backward Compatible**: Old names still work but display deprecation warnings
513
-
514
- #### Deprecation Warnings
515
- When using deprecated option names, a warning is displayed on the terminal:
516
- ```
517
- [koa-classic-server] DEPRECATION WARNING: The "enableCaching" option is deprecated and will be removed in future versions.
518
- Current usage: enableCaching: true
519
- Recommended: browserCacheEnabled: true
520
- Please update your configuration to use the new option name.
521
- ```
522
-
523
- ### ๐Ÿ“ Documentation Updates
524
-
525
- - Updated README.md with new option names
526
- - Updated JSDoc comments in index.cjs
527
- - Added deprecation notes in Options table
528
- - All examples updated to use new names
529
-
530
- ### ๐Ÿ”ง Changes
531
-
532
- - **index.cjs**: Lines 109-135 - Added backward compatibility logic with deprecation warnings
533
- - **index.cjs**: Lines 47-58 - Updated JSDoc comments
534
- - **index.cjs**: Lines 350, 361 - Updated code to use new option names
535
- - **README.md**: Updated all references to use new names, added deprecation notes
536
- - **package.json**: Version bumped from `2.2.0` to `2.3.0`
537
-
538
- ### โš ๏ธ Migration Guide
539
-
540
- **No immediate changes required** - old option names still work.
541
-
542
- **Recommended migration:**
543
-
544
- ```javascript
545
- // Old (still works, but deprecated)
546
- app.use(koaClassicServer('/public', {
547
- enableCaching: true,
548
- cacheMaxAge: 3600
549
- }));
550
-
551
- // New (recommended)
552
- app.use(koaClassicServer('/public', {
553
- browserCacheEnabled: true,
554
- browserCacheMaxAge: 3600
555
- }));
556
- ```
557
-
558
- **Timeline:**
559
- - **v2.3.0**: Old names work with deprecation warnings
560
- - **Future versions**: Old names may be removed (will be announced in advance)
561
-
562
- ### ๐Ÿ“ฆ Package Changes
563
-
564
- - **Version**: `2.2.0` โ†’ `2.3.0`
565
- - **Semver**: Minor version bump (new feature names, backward compatible)
566
-
567
- ---
568
-
569
- ## [2.2.0] - 2026-01-03
570
-
571
- ### โœจ Features
572
-
573
- #### Added useOriginalUrl Option
574
- - **New Option**: `useOriginalUrl` (Boolean, default: `true`)
575
- - **Purpose**: Controls URL resolution for file serving - use `ctx.originalUrl` (immutable) or `ctx.url` (mutable)
576
- - **Use Case**: Compatibility with URL rewriting middleware (i18n, routing)
577
- - **Backward Compatible**: Default value `true` maintains existing behavior
578
-
579
- #### URL Rewriting Middleware Support
580
- - **Problem Solved**: koa-classic-server previously used `ctx.href` (based on `ctx.originalUrl`), which caused 404 errors when middleware rewrites URLs by modifying `ctx.url`
581
- - **Solution**: Set `useOriginalUrl: false` to use the rewritten URL from `ctx.url` instead
582
- - **Example**: i18n middleware that strips language prefixes (`/it/page.html` โ†’ `/page.html`)
583
-
584
- ### ๐Ÿ“ Documentation
585
-
586
- - Added comprehensive `useOriginalUrl` documentation in README.md
587
- - Added JSDoc comments in index.cjs
588
- - Included practical i18n middleware example
589
- - Added option to API reference table
590
-
591
- ### ๐Ÿ”ง Changes
592
-
593
- - **index.cjs**: Line 108 - Added `useOriginalUrl` option initialization
594
- - **index.cjs**: Lines 117-125 - Modified URL construction logic to support both `ctx.originalUrl` and `ctx.url`
595
- - **README.md**: Added detailed section explaining `useOriginalUrl` with examples
596
- - **package.json**: Version bumped from `2.1.4` to `2.2.0`
597
-
598
- ### ๐Ÿ’ก Usage Example
599
-
600
- ```javascript
601
- // i18n middleware example
602
- app.use(async (ctx, next) => {
603
- if (ctx.path.match(/^\/it\//)) {
604
- ctx.url = ctx.path.replace(/^\/it/, ''); // /it/page.html โ†’ /page.html
605
- }
606
- await next();
607
- });
608
-
609
- app.use(koaClassicServer('/www', {
610
- useOriginalUrl: false // Use rewritten URL
611
- }));
612
- ```
613
-
614
- ### โš ๏ธ Migration Notes
615
-
616
- **No breaking changes** - this is a backward-compatible release.
617
-
618
- - **Default behavior unchanged**: `useOriginalUrl` defaults to `true`
619
- - **No code changes required** for existing implementations
620
- - **New feature**: Set `useOriginalUrl: false` if you use URL rewriting middleware
621
-
622
- ### ๐Ÿ“ฆ Package Changes
623
-
624
- - **Version**: `2.1.4` โ†’ `2.2.0`
625
- - **Semver**: Minor version bump (new feature, backward compatible)
626
-
627
- ---
628
-
629
- ## [2.1.3] - 2025-11-25
630
-
631
- ### ๐Ÿ”ง Configuration Changes
632
-
633
- #### Changed Default Caching Behavior
634
- - **Change**: `enableCaching` default value changed from `true` to `false`
635
- - **Rationale**: Better development experience - changes are immediately visible without cache invalidation
636
- - **Production Impact**: **Users should explicitly set `enableCaching: true` in production environments**
637
- - **Benefits in Production**:
638
- - 80-95% bandwidth reduction
639
- - Faster page loads with 304 Not Modified responses
640
- - Reduced server load
641
- - **Code**: `index.cjs:107`
642
-
643
- ### ๐Ÿ“ Documentation Improvements
644
-
645
- #### Enhanced Caching Documentation
646
- - Added comprehensive production recommendations in README.md
647
- - Added inline code comments explaining the default behavior
648
- - Clear guidance on when to enable caching (development vs production)
649
- - **Files**: `README.md`, `index.cjs`
650
-
651
- ### โš ๏ธ Migration Notice
652
-
653
- **IMPORTANT**: If you are upgrading from 2.1.2 or earlier and rely on HTTP caching:
654
-
655
- ```javascript
656
- // You must now explicitly enable caching in production
657
- app.use(koaClassicServer(__dirname + '/public', {
658
- enableCaching: true // โ† Add this for production environments
659
- }));
660
- ```
661
-
662
- **Development**: No changes needed - the new default (`false`) is better for development.
663
-
664
- **Production**: Explicitly set `enableCaching: true` to maintain previous behavior and performance benefits.
665
-
666
- ### ๐Ÿ“ฆ Package Changes
667
-
668
- - **Version**: `2.1.2` โ†’ `2.1.3`
669
-
670
- ---
671
-
672
- ## [2.1.2] - 2025-11-24
673
-
674
- ### ๐ŸŽจ Features
675
-
676
- #### Sortable Directory Columns
677
- - Apache2-like directory listing with clickable column headers
678
- - Sort by Name, Type, or Size (ascending/descending)
679
- - Fixed navigation bug after sorting
680
-
681
- #### File Size Display
682
- - Human-readable file sizes (B, KB, MB, GB, TB)
683
- - Proper formatting and precision
684
-
685
- #### HTTP Caching
686
- - ETag and Last-Modified headers
687
- - 304 Not Modified responses
688
- - 80-95% bandwidth reduction
689
-
690
- ### ๐Ÿงช Testing
691
- - 153 tests passing
692
- - Comprehensive test coverage
693
-
694
- ---
695
-
696
- ## [2.1.1] - 2025-11-23
697
-
698
- ### ๐Ÿš€ Production Release
699
-
700
- - Async/await implementation
701
- - Non-blocking I/O
702
- - Performance optimizations
703
- - Flow documentation
704
-
705
- ---
706
-
707
- ## [1.2.0] - 2025-11-17
708
-
709
- ### ๐ŸŽ‰ SECURITY & BUG FIX RELEASE
710
-
711
- This release contains **critical security fixes** and important bug fixes. All users should upgrade immediately.
712
-
713
- ### ๐Ÿ”’ Security Fixes (CRITICAL)
714
-
715
- #### Fixed Path Traversal Vulnerability
716
- - **Issue**: Attackers could access files outside the served directory using `../` sequences
717
- - **Impact**: CRITICAL - Unauthorized file access
718
- - **Fix**: Added path normalization and validation to ensure all file access stays within `rootDir`
719
- - **Code**: `index.cjs:106-124`
720
-
721
- #### Fixed Template Rendering Crash
722
- - **Issue**: Unhandled errors in template rendering could crash the entire server
723
- - **Impact**: CRITICAL - Denial of Service
724
- - **Fix**: Added try-catch around template render calls with proper error handling
725
- - **Code**: `index.cjs:195-205`
726
-
727
- ### โœ… Bug Fixes
728
-
729
- #### Fixed HTTP Status Code 404
730
- - **Issue**: Missing files returned HTML "Not Found" with HTTP 200 status instead of 404
731
- - **Impact**: HIGH - Violates HTTP standards, affects SEO, breaks caching
732
- - **Fix**: Properly set `ctx.status = 404` when resources are not found
733
- - **Locations**:
734
- - `index.cjs:130` - File/directory not found
735
- - `index.cjs:158` - Directory listing disabled
736
-
737
- #### Fixed Race Condition in File Access
738
- - **Issue**: Files could be deleted between existence check and reading, causing uncaught errors
739
- - **Impact**: HIGH - Server crashes on file access errors
740
- - **Fix**: Added `fs.promises.access()` check before streaming files with error handling
741
- - **Code**: `index.cjs:208-216`
742
-
743
- #### Fixed File Extension Extraction
744
- - **Issue**: Using `split(".")` failed for:
745
- - Files without extension (`README`)
746
- - Hidden files (`.gitignore`)
747
- - Paths with dots (`/folder.backup/file`)
748
- - **Impact**: HIGH - Template rendering activated incorrectly
749
- - **Fix**: Use `path.extname()` for robust extension extraction
750
- - **Code**: `index.cjs:192`
751
-
752
- #### Fixed Directory Read Errors
753
- - **Issue**: `fs.readdirSync()` could throw unhandled errors (permissions, deleted directories)
754
- - **Impact**: MEDIUM - Server crashes on directory access errors
755
- - **Fix**: Added try-catch with user-friendly error message
756
- - **Code**: `index.cjs:245-264`
757
-
758
- #### Fixed Content-Disposition Header
759
- - **Issue**: Filename in Content-Disposition header was not quoted and included full path
760
- - **Impact**: MEDIUM - Download issues with special characters in filenames
761
- - **Fix**:
762
- - Use only basename (not full path)
763
- - Quote filename and escape quotes
764
- - **Code**: `index.cjs:234-239`
765
-
766
- ### ๐ŸŽจ Improvements
767
-
768
- #### Added Input Validation
769
- - Validate `rootDir` is a non-empty string
770
- - Validate `rootDir` is an absolute path
771
- - Throw meaningful errors for invalid input
772
-
773
- #### Added XSS Protection
774
- - HTML-escape all user-controlled content in directory listings
775
- - Escapes filenames, paths, and MIME types
776
- - Prevents XSS attacks through malicious filenames
777
-
778
- #### Improved Error Messages
779
- - More descriptive error messages
780
- - Console logging for debugging
781
- - Stream error handling
782
-
783
- #### Code Quality
784
- - Fixed usage of `Array()` constructor to literal syntax `[]`
785
- - Better code organization and comments
786
- - Improved HTML output formatting
787
-
788
- ### ๐Ÿ“ Added Files
789
-
790
- - **`__tests__/security.test.js`**: Comprehensive security and bug tests
791
- - **`DEBUG_REPORT.md`**: Detailed analysis of all bugs and fixes
792
- - **`DOCUMENTATION.md`**: Complete documentation (1500+ lines)
793
- - **`CHANGELOG.md`**: This file
794
-
795
- ### ๐Ÿงช Testing
796
-
797
- - All 71 tests passing
798
- - Added security test suite
799
- - Path traversal tests
800
- - Template error handling tests
801
- - Status code validation tests
802
- - Race condition tests
803
- - Content-Disposition tests
804
-
805
- ### ๐Ÿ“ฆ Package Changes
806
-
807
- - **Version**: `1.1.0` โ†’ `1.2.0`
808
- - **Description**: Enhanced with security fixes
809
- - **Keywords**: Added `secure`, `middleware`, `file-server`, `directory-listing`
810
- - **Scripts**: Added `test:security` command
811
-
812
- ### โš ๏ธ Breaking Changes
813
-
814
- **None** - This is a backwards-compatible release. However, behavior changes for security:
815
-
816
- 1. **404 Status Codes**: Now properly returns 404 instead of 200 for missing resources
817
- 2. **Path Traversal**: Requests with `../` now return 403 Forbidden instead of allowing access
818
- 3. **Error Handling**: Template errors return 500 instead of crashing the server
819
-
820
- These changes fix bugs and security issues. The new behavior is correct and standards-compliant.
821
-
822
- ### ๐Ÿ”„ Migration Guide
823
-
824
- No code changes required! Simply update:
825
-
826
- ```bash
827
- npm update koa-classic-server
828
- ```
829
-
830
- **Recommended**: Verify that:
831
- 1. `rootDir` is an absolute path (e.g., `__dirname + '/public'`)
832
- 2. Your error handling expects proper 404/403/500 status codes
833
- 3. Your tests pass with the new behavior
834
-
835
- ### ๐Ÿ“Š Statistics
836
-
837
- - **Lines of code fixed**: ~200
838
- - **Security vulnerabilities fixed**: 2 critical
839
- - **Bugs fixed**: 6
840
- - **Tests added**: 12 security tests
841
- - **Documentation added**: 2000+ lines
842
- - **Test coverage**: 71 tests passing
843
-
844
- ### ๐Ÿ™ Credits
845
-
846
- - **Author**: Italo Paesano
847
- - **Security Audit**: Comprehensive code analysis
848
- - **Testing**: Jest & Supertest
849
-
850
- ---
851
-
852
- ## [1.1.0] - Previous Release
853
-
854
- ### Features
855
- - Basic static file serving
856
- - Directory listing
857
- - Template engine support
858
- - URL prefixes
859
- - Reserved URLs
860
-
861
- ### Known Issues (Fixed in 1.2.0)
862
- - Path traversal vulnerability โš ๏ธ CRITICAL
863
- - Missing 404 status codes
864
- - Unhandled template errors โš ๏ธ CRITICAL
865
- - Race condition in file access
866
- - Fragile file extension extraction
867
- - Missing error handling
868
-
869
- ---
870
-
871
- ## Links
872
-
873
- - [Full Documentation](./DOCUMENTATION.md)
874
- - [Debug Report](./DEBUG_REPORT.md)
875
- - [Repository](https://github.com/italopaesano/koa-classic-server)
876
- - [npm Package](https://www.npmjs.com/package/koa-classic-server)
877
-
878
- ---
879
-
880
- **โš ๏ธ Security Notice**: Version 1.2.0 fixes critical vulnerabilities. Update immediately if using 1.1.0 or earlier.