koa-classic-server 2.6.1 → 3.0.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 (57) hide show
  1. package/CLAUDE.md +101 -0
  2. package/README.md +564 -591
  3. package/__tests__/benchmark-results-v3.0.0.txt +372 -0
  4. package/__tests__/benchmark.js +1 -1
  5. package/__tests__/caching-headers.test.js +30 -30
  6. package/__tests__/compression-fixtures/data.json +1 -0
  7. package/__tests__/compression-fixtures/large.txt +1 -0
  8. package/__tests__/compression-fixtures/small.txt +1 -0
  9. package/__tests__/compression.test.js +284 -0
  10. package/__tests__/customTest/serversToLoad.util.js +5 -5
  11. package/__tests__/demo-regex-index.js +4 -4
  12. package/__tests__/deprecation-warnings.test.js +71 -183
  13. package/__tests__/directory-sorting-links.test.js +1 -1
  14. package/__tests__/dt-unknown.test.js +39 -28
  15. package/__tests__/ejs.test.js +1 -1
  16. package/__tests__/hidden-fixtures/.dot-dir/inside.txt +1 -0
  17. package/__tests__/hidden-fixtures/.env +2 -0
  18. package/__tests__/hidden-fixtures/.well-known/acme-challenge.txt +1 -0
  19. package/__tests__/hidden-fixtures/config/secrets/password.txt +1 -0
  20. package/__tests__/hidden-fixtures/data.key +1 -0
  21. package/__tests__/hidden-fixtures/file.secret +1 -0
  22. package/__tests__/hidden-fixtures/index.html +1 -0
  23. package/__tests__/hidden-fixtures/normal.txt +1 -0
  24. package/__tests__/hidden-fixtures/subdir/.env +1 -0
  25. package/__tests__/hidden-fixtures/subdir/regular.txt +1 -0
  26. package/__tests__/hidden-option.test.js +407 -0
  27. package/__tests__/hideExtension.test.js +70 -13
  28. package/__tests__/index-option.test.js +18 -16
  29. package/__tests__/index.test.js +14 -10
  30. package/__tests__/listing.test.js +437 -0
  31. package/__tests__/logger.test.js +232 -0
  32. package/__tests__/range-fixtures/sample.txt +1 -0
  33. package/__tests__/range.test.js +223 -0
  34. package/__tests__/security-headers.test.js +165 -0
  35. package/__tests__/security.test.js +148 -162
  36. package/__tests__/server-cache-fixtures/large.txt +1 -0
  37. package/__tests__/server-cache-fixtures/small.txt +1 -0
  38. package/__tests__/server-cache.test.js +594 -0
  39. package/__tests__/symlink.test.js +18 -15
  40. package/__tests__/template-timeout.test.js +321 -0
  41. package/docs/ACTION_PLAN.md +293 -0
  42. package/docs/CHANGELOG.md +289 -0
  43. package/docs/CODE_REVIEW.md +2 -0
  44. package/docs/DOCUMENTATION.md +259 -32
  45. package/docs/EXAMPLES_INDEX_OPTION.md +3 -3
  46. package/docs/FLOW_DIAGRAM.md +15 -13
  47. package/docs/INDEX_OPTION_PRIORITY.md +2 -2
  48. package/docs/OPTIMIZATION_HTTP_CACHING.md +2 -0
  49. package/docs/OPTIMIZATION_ROADMAP_for_V3.md +864 -0
  50. package/docs/PERFORMANCE_COMPARISON.md +7 -7
  51. package/docs/security_improvement_for_V3.md +421 -0
  52. package/docs/template-engine/TEMPLATE_ENGINE_GUIDE.md +5 -5
  53. package/docs/template-engine/esempi-incrementali.js +1 -1
  54. package/eslint.config.mjs +17 -0
  55. package/index.cjs +1507 -429
  56. package/index.mjs +1 -5
  57. package/package.json +9 -1
@@ -157,7 +157,7 @@ const koaClassicServer = require('koa-classic-server');
157
157
  const app = new Koa();
158
158
 
159
159
  app.use(koaClassicServer(__dirname + '/public', {
160
- showDirContents: true,
160
+ dirListing: { enabled: true },
161
161
  index: 'index.html',
162
162
  urlPrefix: '/static'
163
163
  }));
@@ -193,7 +193,7 @@ const options = {
193
193
 
194
194
  // Mostra il contenuto delle directory
195
195
  // Default: true
196
- showDirContents: true,
196
+ dirListing: { enabled: true },
197
197
 
198
198
  // Nome del file index da caricare automaticamente nelle directory
199
199
  // Se presente, viene caricato invece del directory listing
@@ -245,16 +245,16 @@ method: ['GET', 'HEAD']
245
245
  method: ['GET', 'HEAD', 'POST']
246
246
  ```
247
247
 
248
- #### `showDirContents` (Boolean)
248
+ #### `dirListing.enabled` (Boolean)
249
249
 
250
250
  Controlla se mostrare il contenuto delle directory.
251
251
 
252
252
  ```javascript
253
253
  // Mostra directory listing
254
- showDirContents: true
254
+ dirListing: { enabled: true }
255
255
 
256
256
  // Non mostra directory (restituisce "Not Found")
257
- showDirContents: false
257
+ dirListing: { enabled: false }
258
258
  ```
259
259
 
260
260
  #### `index` (String)
@@ -275,7 +275,7 @@ index: ''
275
275
  **Comportamento:**
276
276
  1. Utente accede a `/cartella/`
277
277
  2. Se esiste `/cartella/index.html` → viene servito
278
- 3. Altrimenti → mostra directory listing (se `showDirContents: true`)
278
+ 3. Altrimenti → mostra directory listing (se `dirListing: { enabled: true }`)
279
279
 
280
280
  #### `urlPrefix` (String)
281
281
 
@@ -373,7 +373,7 @@ const koaClassicServer = require('koa-classic-server');
373
373
  const app = new Koa();
374
374
 
375
375
  app.use(koaClassicServer(__dirname + '/public', {
376
- showDirContents: true,
376
+ dirListing: { enabled: true },
377
377
  index: 'index.html'
378
378
  }));
379
379
 
@@ -400,7 +400,7 @@ const app = new Koa();
400
400
  app.use(koaClassicServer(__dirname + '/public', {
401
401
  urlPrefix: '/static',
402
402
  method: ['GET', 'HEAD'],
403
- showDirContents: true
403
+ dirListing: { enabled: true }
404
404
  }));
405
405
 
406
406
  // Altri middleware per route diverse
@@ -427,7 +427,7 @@ const koaClassicServer = require('koa-classic-server');
427
427
  const app = new Koa();
428
428
 
429
429
  app.use(koaClassicServer(__dirname + '/www', {
430
- showDirContents: true,
430
+ dirListing: { enabled: true },
431
431
  // Protegge directory sensibili
432
432
  urlsReserved: ['/config', '/private', '/.git', '/node_modules']
433
433
  }));
@@ -452,7 +452,7 @@ const ejs = require('ejs');
452
452
  const app = new Koa();
453
453
 
454
454
  app.use(koaClassicServer(__dirname + '/views', {
455
- showDirContents: false,
455
+ dirListing: { enabled: false },
456
456
  template: {
457
457
  render: async (ctx, next, filePath) => {
458
458
  // Rendering EJS con dati
@@ -485,19 +485,19 @@ const app = new Koa();
485
485
  // File statici pubblici
486
486
  app.use(koaClassicServer(__dirname + '/public', {
487
487
  urlPrefix: '/public',
488
- showDirContents: true
488
+ dirListing: { enabled: true }
489
489
  }));
490
490
 
491
491
  // Asset (CSS, JS, images)
492
492
  app.use(koaClassicServer(__dirname + '/assets', {
493
493
  urlPrefix: '/assets',
494
- showDirContents: false
494
+ dirListing: { enabled: false }
495
495
  }));
496
496
 
497
497
  // Download area
498
498
  app.use(koaClassicServer(__dirname + '/downloads', {
499
499
  urlPrefix: '/downloads',
500
- showDirContents: true,
500
+ dirListing: { enabled: true },
501
501
  index: 'README.txt'
502
502
  }));
503
503
 
@@ -549,7 +549,7 @@ const templateRender = async (ctx, next, filePath) => {
549
549
  // File server
550
550
  app.use(koaClassicServer(path.join(__dirname, 'public'), {
551
551
  method: ['GET', 'HEAD'],
552
- showDirContents: process.env.NODE_ENV !== 'production',
552
+ dirListing: { enabled: process.env.NODE_ENV !== 'production' },
553
553
  index: 'index.html',
554
554
  urlPrefix: '/files',
555
555
  urlsReserved: ['/admin', '/private', '/config', '/.env'],
@@ -599,7 +599,7 @@ app.use(router.allowedMethods());
599
599
 
600
600
  // File statici (dopo le route API)
601
601
  app.use(koaClassicServer(__dirname + '/public', {
602
- showDirContents: true
602
+ dirListing: { enabled: true }
603
603
  }));
604
604
 
605
605
  app.listen(3000);
@@ -631,7 +631,7 @@ Crea e restituisce un middleware Koa per servire file statici.
631
631
 
632
632
  ```javascript
633
633
  const middleware = koaClassicServer('/path/to/files', {
634
- showDirContents: true
634
+ dirListing: { enabled: true }
635
635
  });
636
636
 
637
637
  app.use(middleware);
@@ -682,7 +682,7 @@ template: {
682
682
 
683
683
  ### Gestione delle Directory
684
684
 
685
- #### Caso 1: showDirContents = true, index presente
685
+ #### Caso 1: dirListing.enabled = true, index presente
686
686
 
687
687
  ```
688
688
  Richiesta: GET /cartella/
@@ -693,7 +693,7 @@ Filesystem:
693
693
  Risultato: Serve /cartella/index.html
694
694
  ```
695
695
 
696
- #### Caso 2: showDirContents = true, index assente
696
+ #### Caso 2: dirListing.enabled = true, index assente
697
697
 
698
698
  ```
699
699
  Richiesta: GET /cartella/
@@ -704,7 +704,7 @@ Filesystem:
704
704
  Risultato: Mostra directory listing HTML
705
705
  ```
706
706
 
707
- #### Caso 3: showDirContents = false
707
+ #### Caso 3: dirListing.enabled = false
708
708
 
709
709
  ```
710
710
  Richiesta: GET /cartella/
@@ -925,7 +925,7 @@ describe('My custom test', () => {
925
925
  beforeAll(() => {
926
926
  app = new Koa();
927
927
  app.use(koaClassicServer(__dirname + '/test-files', {
928
- showDirContents: true
928
+ dirListing: { enabled: true }
929
929
  }));
930
930
  server = app.listen();
931
931
  });
@@ -1042,11 +1042,11 @@ npm install ejs
1042
1042
  **Soluzione:**
1043
1043
 
1044
1044
  ```javascript
1045
- // ❌ showDirContents disabilitato
1046
- showDirContents: false
1045
+ // ❌ dirListing.enabled disabilitato
1046
+ dirListing: { enabled: false }
1047
1047
 
1048
- // ✅ showDirContents abilitato
1049
- showDirContents: true
1048
+ // ✅ dirListing.enabled abilitato
1049
+ dirListing: { enabled: true }
1050
1050
  ```
1051
1051
 
1052
1052
  ---
@@ -1128,12 +1128,12 @@ const url = '/' + encodeURIComponent(filename);
1128
1128
 
1129
1129
  1. Disabilita directory listing:
1130
1130
  ```javascript
1131
- showDirContents: false
1131
+ dirListing: { enabled: false }
1132
1132
  ```
1133
1133
 
1134
1134
  2. Usa file index:
1135
1135
  ```javascript
1136
- showDirContents: true,
1136
+ dirListing: { enabled: true },
1137
1137
  index: 'index.html' // Carica index invece di listing
1138
1138
  ```
1139
1139
 
@@ -1278,11 +1278,238 @@ method: ['GET', 'HEAD']
1278
1278
  #### Disabilita Directory Listing in Produzione
1279
1279
  ```javascript
1280
1280
  app.use(koaClassicServer(rootDir, {
1281
- showDirContents: process.env.NODE_ENV !== 'production',
1281
+ dirListing: { enabled: process.env.NODE_ENV !== 'production' },
1282
1282
  index: 'index.html'
1283
1283
  }));
1284
1284
  ```
1285
1285
 
1286
+ #### DNS Rebinding — valida l'header `Host` a monte
1287
+
1288
+ `koa-classic-server` **non valida l'header `Host`** delle richieste in entrata. Questo è intenzionale: la validazione del Virtual Host appartiene allo strato di rete (reverse proxy o middleware Koa applicativo), non a un file server.
1289
+
1290
+ In assenza di tale validazione, un host LAN/loopback (es. `127.0.0.1`, `192.168.x.x`) è vulnerabile a **DNS rebinding**: un attaccante remoto può far risolvere il proprio dominio all'IP della vittima ed eseguire richieste cross-origin verso il server locale come se provenissero da un'origine legittima del browser.
1291
+
1292
+ **Quando è un problema concreto**
1293
+ - Server raggiunto da `localhost` / IP privati senza reverse proxy davanti.
1294
+ - Browser dell'utente che visita pagine non fidate mentre il server è attivo.
1295
+
1296
+ **Quando NON è un problema**
1297
+ - Deploy dietro reverse proxy (nginx, Caddy, Apache, Traefik) configurato con allowlist di `server_name` / hostname.
1298
+ - Server raggiungibile solo da IP pubblico dietro CDN/WAF.
1299
+ - Binding esplicito a interfaccia non instradabile (`app.listen(port, '127.0.0.1')`) e nessun browser locale espone l'utente a pagine ostili.
1300
+
1301
+ **Mitigazione 1 — reverse proxy (raccomandato in produzione)**
1302
+
1303
+ Esempio nginx:
1304
+ ```nginx
1305
+ server {
1306
+ listen 80;
1307
+ server_name app.example.com; # ← allowlist
1308
+ location / { proxy_pass http://127.0.0.1:3000; }
1309
+ }
1310
+ # Tutte le richieste con Host diverso da app.example.com vengono rifiutate qui.
1311
+ ```
1312
+
1313
+ **Mitigazione 2 — middleware Koa applicativo (utile in dev/LAN)**
1314
+
1315
+ Antepone una guardia su `ctx.host` PRIMA di `koa-classic-server`:
1316
+
1317
+ ```javascript
1318
+ const ALLOWED_HOSTS = new Set([
1319
+ 'app.example.com',
1320
+ 'localhost:3000',
1321
+ '127.0.0.1:3000',
1322
+ ]);
1323
+
1324
+ app.use(async (ctx, next) => {
1325
+ // ctx.host include la porta (es. "localhost:3000")
1326
+ if (!ALLOWED_HOSTS.has(ctx.host)) {
1327
+ ctx.status = 421; // Misdirected Request
1328
+ ctx.body = 'Host not allowed';
1329
+ return;
1330
+ }
1331
+ await next();
1332
+ });
1333
+
1334
+ app.use(koaClassicServer(rootDir));
1335
+ ```
1336
+
1337
+ > ⚠️ Se il proxy a monte termina TLS e inoltra all'app, configura `app.proxy = true` e usa `ctx.hostname` con `X-Forwarded-Host` solo se il proxy è fidato.
1338
+
1339
+ #### Limiti dei Security Headers sui file statici
1340
+
1341
+ `koa-classic-server` imposta automaticamente i seguenti header SOLO sulle **risposte generate** dal middleware (directory listing HTML, pagine di errore 400/403/404/405/500):
1342
+
1343
+ | Header | Valore |
1344
+ |---|---|
1345
+ | `Content-Security-Policy` | `default-src 'none'; ...` (hash-based per il listing, fully restrictive per gli errori) |
1346
+ | `X-Content-Type-Options` | `nosniff` |
1347
+ | `X-Frame-Options` | `DENY` |
1348
+ | `Referrer-Policy` | `no-referrer` |
1349
+ | `Permissions-Policy` | `camera=(), microphone=(), geolocation=(), payment=()` |
1350
+
1351
+ **Cosa NON riceve questi header**
1352
+
1353
+ I **file statici** serviti dal disco (HTML, JS, CSS, immagini, font, video, qualsiasi altra estensione) sono restituiti **senza** alcun header di sicurezza aggiunto. Questo è **by-design**:
1354
+
1355
+ - Le policy di sicurezza appropriate sono diverse caso per caso (CSP per app SPA, sandbox per documenti, COEP/COOP per pagine con SharedArrayBuffer, ecc.).
1356
+ - Imporre una CSP rigida di default romperebbe la maggior parte dei siti reali (script inline, CDN, Google Fonts, ecc.).
1357
+ - L'utente possiede la propria policy e deve dichiararla esplicitamente.
1358
+
1359
+ > ⚠️ Non assumere che il middleware "metta in sicurezza" i tuoi file: gli header sopra elencati vengono inviati **solo** quando la risposta è generata dal middleware stesso.
1360
+
1361
+ **Come aggiungere security headers ai file statici**
1362
+
1363
+ Inserisci un middleware Koa **prima** di `koa-classic-server`. Il middleware si applica a ogni risposta uscente, statica o generata che sia, e i `ctx.set()` rimangono attivi anche dopo che il file server scrive il body:
1364
+
1365
+ ```javascript
1366
+ const Koa = require('koa');
1367
+ const koaClassicServer = require('koa-classic-server');
1368
+
1369
+ const app = new Koa();
1370
+
1371
+ // 1) Header globali su TUTTE le risposte (statiche + generate).
1372
+ app.use(async (ctx, next) => {
1373
+ // Header sempre validi per un file server pubblico:
1374
+ ctx.set('X-Content-Type-Options', 'nosniff');
1375
+ ctx.set('Referrer-Policy', 'strict-origin-when-cross-origin');
1376
+ ctx.set('Strict-Transport-Security', 'max-age=63072000; includeSubDomains');
1377
+
1378
+ // CSP per file HTML serviti dall'utente (mantieni il file server per gli altri MIME).
1379
+ // Esempio strict-by-default per un sito statico moderno:
1380
+ if (ctx.path.endsWith('.html') || ctx.path === '/' || ctx.path.endsWith('/')) {
1381
+ ctx.set('Content-Security-Policy',
1382
+ "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; " +
1383
+ "script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
1384
+ );
1385
+ }
1386
+
1387
+ await next();
1388
+ });
1389
+
1390
+ // 2) Quindi il file server.
1391
+ app.use(koaClassicServer(__dirname + '/public'));
1392
+
1393
+ app.listen(3000);
1394
+ ```
1395
+
1396
+ **Note operative**
1397
+
1398
+ - Il middleware sopra **non sovrascrive** gli header che `koa-classic-server` imposta sulle proprie pagine (listing/errori): Koa preserva il primo `set()`, ma se vuoi essere esplicito puoi applicare le tue policy solo a `ctx.status < 400` e a `Content-Type` HTML.
1399
+ - Una CSP rigida con `default-src 'self'` rompe pagine che usano script inline o CDN: parti **report-only** (`Content-Security-Policy-Report-Only`) per rilevare le violazioni prima di enforce-arla.
1400
+ - Per progetti SPA/PWA che richiedono `SharedArrayBuffer`, considera anche `Cross-Origin-Opener-Policy: same-origin` e `Cross-Origin-Embedder-Policy: require-corp`.
1401
+
1402
+ #### Security Checklist & Suggested Configuration
1403
+
1404
+ `koa-classic-server` parte dal principio **"file server first"** (vedi [`CLAUDE.md`](../CLAUDE.md)): i default servono i file senza restrizioni di policy nascoste. L'operatore irrobustisce il deploy tramite **configurazione esplicita**.
1405
+
1406
+ ##### Checklist per categoria
1407
+
1408
+ **Static site / public asset serving**
1409
+
1410
+ | ✓ | Cosa | Snippet |
1411
+ |---|---|---|
1412
+ | ☐ | Nascondi dot-files con potenziali segreti | `hidden: { dotFiles: { default: 'hidden', whitelist: ['.well-known'] } }` |
1413
+ | ☐ | Blocca dot-directories tipo `.git` | `hidden: { dotDirs: { default: 'hidden', whitelist: ['.well-known'] } }` |
1414
+ | ☐ | Disabilita listing in produzione | `dirListing: { enabled: false }` + `index` |
1415
+ | ☐ | Abilita HTTP caching | `browserCacheEnabled: true, browserCacheMaxAge: 86400` |
1416
+ | ☐ | Restringi i metodi | `method: ['GET', 'HEAD']` |
1417
+ | ☐ | Riserva path applicativi | `urlsReserved: ['/api', '/admin']` |
1418
+ | ☐ | Aggiungi security headers upstream sui file utente | middleware Koa upstream (vedi *Limiti dei Security Headers* sopra) |
1419
+
1420
+ **User uploads / multi-tenant / directory scrivibili da terzi**
1421
+
1422
+ | ✓ | Cosa | Snippet |
1423
+ |---|---|---|
1424
+ | ☐ | Cap entries più stretto contro dir gonfiate | `dirListing: { maxEntries: 1000 }` |
1425
+ | ☐ | Hide dot-files a ogni profondità | `hidden: { dotFiles: { default: 'hidden' }, dotDirs: { default: 'hidden' } }` |
1426
+ | ☐ | Blocklist path-aware per pattern noti | `hidden: { alwaysHide: ['*.key', '*.pem', /\.secret$/, 'config/secrets/**'] }` |
1427
+ | ☐ | Monitora la crescita dir esternamente (cron + alert) | — |
1428
+
1429
+ > **Caveat v3.0:** `dirListing.maxEntries` bounda rendering/CPU ma NON la lettura iniziale `readdir()`. Per workload adversarial (utenti non fidati che possono creare milioni di file) la protezione RAM completa arriverà con il `readMode: 'bounded'` di v3.1 (vedi `[F-1]` in `docs/security_improvement_for_V3.md`).
1430
+
1431
+ **Production hygiene (qualsiasi deploy)**
1432
+
1433
+ | ✓ | Cosa | Snippet |
1434
+ |---|---|---|
1435
+ | ☐ | Allowlist Host upstream | nginx `server_name` o middleware Koa allowlist su `ctx.host` |
1436
+ | ☐ | Disabilita template engine se non SSR | ometti `template` |
1437
+ | ☐ | Tune `template.renderTimeout` | abbassa da 30 s per SLA stretti |
1438
+ | ☐ | Logger strutturato (Pino/Winston) | `logger: pino()` |
1439
+ | ☐ | Pin patch version + `npm audit` in CI | `package.json` + workflow CI |
1440
+
1441
+ ##### Suggested production security configuration
1442
+
1443
+ Una sola configurazione di partenza che copre l'80% dei deploy. Tweak per workload specifici.
1444
+
1445
+ ```javascript
1446
+ const Koa = require('koa');
1447
+ const pino = require('pino')({ level: 'info' });
1448
+ const path = require('path');
1449
+ const koaClassicServer = require('koa-classic-server');
1450
+
1451
+ const app = new Koa();
1452
+
1453
+ // 1) Host allowlist — mitiga DNS rebinding su esposizione LAN/loopback.
1454
+ const ALLOWED_HOSTS = new Set([
1455
+ 'app.example.com',
1456
+ 'localhost:3000',
1457
+ ]);
1458
+ app.use(async (ctx, next) => {
1459
+ if (!ALLOWED_HOSTS.has(ctx.host)) {
1460
+ ctx.status = 421;
1461
+ ctx.body = 'Misdirected Request';
1462
+ return;
1463
+ }
1464
+ await next();
1465
+ });
1466
+
1467
+ // 2) Security headers sui file utente (il middleware li imposta solo su
1468
+ // listing/errori, NON sui file statici — by design).
1469
+ app.use(async (ctx, next) => {
1470
+ ctx.set('X-Content-Type-Options', 'nosniff');
1471
+ ctx.set('Referrer-Policy', 'strict-origin-when-cross-origin');
1472
+ ctx.set('Strict-Transport-Security', 'max-age=63072000; includeSubDomains');
1473
+ await next();
1474
+ });
1475
+
1476
+ // 3) Il file server con default irrobustiti per produzione.
1477
+ app.use(koaClassicServer(path.join(__dirname, 'public'), {
1478
+ method: ['GET', 'HEAD'],
1479
+
1480
+ index: ['index.html'],
1481
+
1482
+ dirListing: {
1483
+ enabled: process.env.NODE_ENV !== 'production',
1484
+ maxEntries: 10000, // più stretto del default 100000 anti-OOM
1485
+ entriesPerPage: 100,
1486
+ },
1487
+
1488
+ hidden: {
1489
+ dotFiles: {
1490
+ default: 'hidden', // hardening esplicito vs default 'visible' filosofico
1491
+ whitelist: ['.well-known'], // ACME / Let's Encrypt
1492
+ },
1493
+ dotDirs: {
1494
+ default: 'hidden',
1495
+ whitelist: ['.well-known'],
1496
+ },
1497
+ alwaysHide: ['*.key', '*.pem', /^backup-/, /\.secret$/],
1498
+ },
1499
+
1500
+ browserCacheEnabled: true,
1501
+ browserCacheMaxAge: 86400, // 24 h cache
1502
+
1503
+ logger: pino, // structured logging
1504
+
1505
+ urlsReserved: ['/api', '/admin'], // route applicative gestite altrove
1506
+ }));
1507
+
1508
+ app.listen(3000);
1509
+ ```
1510
+
1511
+ Per **user-uploads / multi-tenant**: oltre al blocco sopra, riduci `dirListing.maxEntries` a `1000` e monitora la dimensione della directory dall'esterno fino a quando v3.1 non aggiunge il `readMode: 'bounded'`.
1512
+
1286
1513
  ---
1287
1514
 
1288
1515
  ### 2. Performance
@@ -1292,7 +1519,7 @@ app.use(koaClassicServer(rootDir, {
1292
1519
  const isProduction = process.env.NODE_ENV === 'production';
1293
1520
 
1294
1521
  app.use(koaClassicServer(rootDir, {
1295
- showDirContents: !isProduction,
1522
+ dirListing: { enabled: !isProduction },
1296
1523
  method: ['GET', 'HEAD']
1297
1524
  }));
1298
1525
  ```
@@ -1334,7 +1561,7 @@ module.exports = {
1334
1561
  rootDir: path.join(__dirname, '../public'),
1335
1562
  options: {
1336
1563
  method: ['GET', 'HEAD'],
1337
- showDirContents: true,
1564
+ dirListing: { enabled: true },
1338
1565
  index: 'index.html',
1339
1566
  urlPrefix: '/static',
1340
1567
  urlsReserved: ['/admin', '/config']
@@ -1459,7 +1686,7 @@ if (isDev) {
1459
1686
 
1460
1687
  // File server con configurazione ambiente-specifica
1461
1688
  app.use(koaClassicServer(path.join(__dirname, 'public'), {
1462
- showDirContents: isDev, // Solo in dev
1689
+ dirListing: { enabled: isDev }, // Solo in dev
1463
1690
  index: 'index.html',
1464
1691
  urlPrefix: isDev ? '' : '/static', // Prefix solo in prod
1465
1692
  urlsReserved: ['/admin', '/config', '/.env'],
@@ -1491,7 +1718,7 @@ app.listen(process.env.PORT || 3000);
1491
1718
  const configs = [
1492
1719
  { name: 'base', options: {} },
1493
1720
  { name: 'with-prefix', options: { urlPrefix: '/public' } },
1494
- { name: 'no-listing', options: { showDirContents: false } }
1721
+ { name: 'no-listing', options: { dirListing: { enabled: false } } }
1495
1722
  ];
1496
1723
 
1497
1724
  configs.forEach(({ name, options }) => {
@@ -1521,7 +1748,7 @@ configs.forEach(({ name, options }) => {
1521
1748
  ```javascript
1522
1749
  app.use(koaClassicServer(__dirname + '/public', {
1523
1750
  // Mostra directory solo in development per sicurezza
1524
- showDirContents: process.env.NODE_ENV !== 'production',
1751
+ dirListing: { enabled: process.env.NODE_ENV !== 'production' },
1525
1752
 
1526
1753
  // Protegge configurazioni sensibili
1527
1754
  // Nota: funziona solo per directory di primo livello
@@ -339,7 +339,7 @@ const app = new Koa();
339
339
  // Configurazione production-ready
340
340
  app.use(koaClassicServer('./public', {
341
341
  method: ['GET', 'HEAD'],
342
- showDirContents: true,
342
+ dirListing: { enabled: true },
343
343
 
344
344
  // Index configuration con fallback multipli
345
345
  index: [
@@ -359,8 +359,8 @@ app.use(koaClassicServer('./public', {
359
359
  ],
360
360
 
361
361
  // HTTP Caching
362
- enableCaching: true,
363
- cacheMaxAge: 3600,
362
+ browserCacheEnabled: true,
363
+ browserCacheMaxAge: 3600,
364
364
 
365
365
  // URL configuration
366
366
  urlPrefix: '/static',
@@ -1,5 +1,7 @@
1
1
  # Flow Diagram - koa-classic-server
2
2
 
3
+ > **Nota storica:** diagrammi e snippet di questo documento sono pre-V3. Riferimenti a `options.showDirContents` corrispondono a `options.dirListing.enabled` nell'API V3 corrente. Vedi [README.md → Migration Guide](../README.md#from-v2x-to-v3x).
4
+
3
5
  ## Table of Contents
4
6
  - [Overview](#overview)
5
7
  - [Main Flow Diagram](#main-flow-diagram)
@@ -141,13 +143,13 @@ module.exports = function koaClassicServer(rootDir, opts = {}) {
141
143
  : [];
142
144
 
143
145
  // 7. Configure HTTP caching
144
- options.cacheMaxAge = typeof options.cacheMaxAge === 'number' &&
145
- options.cacheMaxAge >= 0
146
- ? options.cacheMaxAge
146
+ options.browserCacheMaxAge = typeof options.browserCacheMaxAge === 'number' &&
147
+ options.browserCacheMaxAge >= 0
148
+ ? options.browserCacheMaxAge
147
149
  : 3600;
148
- options.enableCaching = typeof options.enableCaching === 'boolean'
149
- ? options.enableCaching
150
- : true;
150
+ options.browserCacheEnabled = typeof options.browserCacheEnabled === 'boolean'
151
+ ? options.browserCacheEnabled
152
+ : false;
151
153
 
152
154
  // 8. Return Koa middleware function
153
155
  return async (ctx, next) => {
@@ -172,8 +174,8 @@ START
172
174
  │ ├─ urlsReserved: []
173
175
  │ ├─ template.render: undefined
174
176
  │ ├─ template.ext: []
175
- │ ├─ cacheMaxAge: 3600 (1 hour)
176
- │ └─ enableCaching: true
177
+ │ ├─ browserCacheMaxAge: 3600 (1 hour)
178
+ │ └─ browserCacheEnabled: false
177
179
 
178
180
  └─> Return async middleware function
179
181
  ```
@@ -415,7 +417,7 @@ Handles serving individual files with caching, template rendering, and streaming
415
417
 
416
418
 
417
419
  ┌─────────────────────────────────────────────────────────────────┐
418
- │ 3. HTTP Caching (if enableCaching = true)
420
+ │ 3. HTTP Caching (if browserCacheEnabled = true)
419
421
  │ Generate ETag: "mtime-size" │
420
422
  │ Set Last-Modified: mtime.toUTCString() │
421
423
  │ Set Cache-Control: public, max-age=3600 │
@@ -492,7 +494,7 @@ fileExt in template.ext? → YES
492
494
  ### Code Example: HTTP Caching
493
495
  ```javascript
494
496
  // index.cjs:313-350
495
- if (options.enableCaching) {
497
+ if (options.browserCacheEnabled) {
496
498
  // Generate ETag
497
499
  const etag = `"${fileStat.mtime.getTime()}-${fileStat.size}"`;
498
500
 
@@ -502,7 +504,7 @@ if (options.enableCaching) {
502
504
  // Set headers
503
505
  ctx.set('ETag', etag);
504
506
  ctx.set('Last-Modified', lastModified);
505
- ctx.set('Cache-Control', `public, max-age=${options.cacheMaxAge}, must-revalidate`);
507
+ ctx.set('Cache-Control', `public, max-age=${options.browserCacheMaxAge}, must-revalidate`);
506
508
 
507
509
  // Check If-None-Match (ETag validation)
508
510
  const clientEtag = ctx.get('If-None-Match');
@@ -782,8 +784,8 @@ app.use(koaClassicServer('/var/www/public', {
782
784
  },
783
785
 
784
786
  // HTTP caching (1 hour)
785
- cacheMaxAge: 3600,
786
- enableCaching: true
787
+ browserCacheMaxAge: 3600,
788
+ browserCacheEnabled: true
787
789
  }));
788
790
 
789
791
  app.listen(3000);
@@ -61,7 +61,7 @@ index: ['index1.html', 'index2.html', 'index3.html']
61
61
  - ✅ Se trovato → **SERVE index3.html** (STOP)
62
62
  - ❌ Se non trovato → Passa al passo 4
63
63
 
64
- 4. Nessun file trovato → **Mostra directory listing** (se `showDirContents: true`)
64
+ 4. Nessun file trovato → **Mostra directory listing** (se `dirListing: { enabled: true }`)
65
65
 
66
66
  ---
67
67
 
@@ -502,7 +502,7 @@ app.use(koaClassicServer('./public', {
502
502
  **A:** Le stringhe sono **molto più veloci** (O(1) vs O(n)). Metti sempre le stringhe prima delle RegExp.
503
503
 
504
504
  ### Q: Cosa succede se nessun pattern matcha?
505
- **A:** Se `showDirContents: true`, mostra directory listing. Altrimenti restituisce 404.
505
+ **A:** Se `dirListing: { enabled: true }`, mostra directory listing. Altrimenti restituisce 404.
506
506
 
507
507
  ---
508
508
 
@@ -1,5 +1,7 @@
1
1
  # Ottimizzazione #3: HTTP Caching Headers
2
2
 
3
+ > **Nota storica:** documento di analisi pre-V3. Riferimenti a `showDirContents` corrispondono a `dirListing.enabled` nell'API V3 corrente. Vedi [README.md → Migration Guide](../README.md#from-v2x-to-v3x).
4
+
3
5
  ## Panoramica
4
6
 
5
7
  **Problema:** I file statici vengono riscaricati dal browser ad ogni richiesta, anche se non sono cambiati.