@wooksjs/http-static 0.4.10 → 0.4.11

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 (3) hide show
  1. package/dist/index.cjs +162 -195
  2. package/dist/index.mjs +162 -195
  3. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -4,38 +4,6 @@ var eventHttp = require('@wooksjs/event-http');
4
4
  var fs = require('fs');
5
5
  var path = require('path');
6
6
 
7
- /******************************************************************************
8
- Copyright (c) Microsoft Corporation.
9
-
10
- Permission to use, copy, modify, and/or distribute this software for any
11
- purpose with or without fee is hereby granted.
12
-
13
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
14
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
16
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
17
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
18
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
19
- PERFORMANCE OF THIS SOFTWARE.
20
- ***************************************************************************** */
21
- /* global Reflect, Promise, SuppressedError, Symbol */
22
-
23
-
24
- function __awaiter(thisArg, _arguments, P, generator) {
25
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
26
- return new (P || (P = Promise))(function (resolve, reject) {
27
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
28
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
29
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
30
- step((generator = generator.apply(thisArg, _arguments || [])).next());
31
- });
32
- }
33
-
34
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
35
- var e = new Error(message);
36
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
37
- };
38
-
39
7
  const extensions = {
40
8
  '123': 'application/vnd.lotus-1-2-3',
41
9
  ez: 'application/andrew-inset',
@@ -1230,130 +1198,131 @@ function normalizePath(filePath, baseDir) {
1230
1198
  }
1231
1199
 
1232
1200
  const { stat, readdir } = fs.promises;
1233
- function serveFile(filePath, options = {}) {
1234
- return __awaiter(this, void 0, void 0, function* () {
1235
- if (!options.allowDotDot && (/\/\.\.\//.test(filePath) || /^\.\.\//.test(filePath))) {
1236
- throw new Error('Parent Traversal ("/../") is not allowed.');
1201
+ async function serveFile(filePath, options = {}) {
1202
+ if (!options.allowDotDot && (/\/\.\.\//.test(filePath) || /^\.\.\//.test(filePath))) {
1203
+ throw new Error('Parent Traversal ("/../") is not allowed.');
1204
+ }
1205
+ const { restoreCtx } = eventHttp.useHttpContext();
1206
+ const { status } = eventHttp.useResponse();
1207
+ const { setHeader, removeHeader } = eventHttp.useSetHeaders();
1208
+ const headers = eventHttp.useHeaders();
1209
+ const { method, url } = eventHttp.useRequest();
1210
+ const { setCacheControl, setExpires, setPragmaNoCache } = eventHttp.useSetCacheControl();
1211
+ const normalizedPath = normalizePath(filePath, options.baseDir);
1212
+ let fileStats;
1213
+ try {
1214
+ fileStats = await stat(normalizedPath);
1215
+ }
1216
+ catch (e) {
1217
+ if (options.defaultExt) {
1218
+ const ext = path.extname(filePath);
1219
+ if (!ext) {
1220
+ restoreCtx();
1221
+ return serveFile(filePath + '.' + options.defaultExt);
1222
+ }
1237
1223
  }
1238
- const { restoreCtx } = eventHttp.useHttpContext();
1239
- const { status } = eventHttp.useResponse();
1240
- const { setHeader, removeHeader } = eventHttp.useSetHeaders();
1241
- const headers = eventHttp.useHeaders();
1242
- const { method, url } = eventHttp.useRequest();
1243
- const { setCacheControl, setExpires, setPragmaNoCache } = eventHttp.useSetCacheControl();
1244
- const normalizedPath = normalizePath(filePath, options.baseDir);
1245
- let fileStats;
1246
- try {
1247
- fileStats = yield stat(normalizedPath);
1224
+ throw new eventHttp.HttpError(404);
1225
+ }
1226
+ status(200);
1227
+ // if-none-match & if-modified-since processing start
1228
+ // rfc7232
1229
+ const etag = `"${[
1230
+ fileStats.ino,
1231
+ fileStats.size,
1232
+ fileStats.mtime.toISOString(),
1233
+ ].join('-')}"`;
1234
+ const lastModified = new Date(fileStats.mtime);
1235
+ if (isNotModified(etag, lastModified, headers['if-none-match'] || '', headers['if-modified-since'] || '')) {
1236
+ status(304);
1237
+ return '';
1238
+ }
1239
+ // if-none-match & if-modified-since processing end
1240
+ setHeader('etag', etag);
1241
+ setHeader('last-modified', lastModified.toUTCString());
1242
+ if (options.cacheControl !== undefined) {
1243
+ setCacheControl(options.cacheControl);
1244
+ }
1245
+ if (options.expires) {
1246
+ setExpires(options.expires);
1247
+ }
1248
+ if (options.pragmaNoCache) {
1249
+ setPragmaNoCache(options.pragmaNoCache);
1250
+ }
1251
+ if (fileStats.isDirectory()) {
1252
+ if (options.listDirectory) {
1253
+ restoreCtx();
1254
+ return listDirectory(normalizedPath);
1248
1255
  }
1249
- catch (e) {
1250
- if (options.defaultExt) {
1251
- const ext = path.extname(filePath);
1252
- if (!ext) {
1253
- restoreCtx();
1254
- return serveFile(filePath + '.' + options.defaultExt);
1255
- }
1256
+ else if (options.index) {
1257
+ if (filePath[filePath.length - 1] !== '/' &&
1258
+ url &&
1259
+ url[url.length - 1] !== '/') {
1260
+ return new eventHttp.BaseHttpResponse()
1261
+ .setStatus(302)
1262
+ .setHeader('location', url + '/');
1256
1263
  }
1257
- throw new eventHttp.HttpError(404);
1258
- }
1259
- status(200);
1260
- // if-none-match & if-modified-since processing start
1261
- // rfc7232
1262
- const etag = `"${[
1263
- fileStats.ino,
1264
- fileStats.size,
1265
- fileStats.mtime.toISOString(),
1266
- ].join('-')}"`;
1267
- const lastModified = new Date(fileStats.mtime);
1268
- if (isNotModified(etag, lastModified, headers['if-none-match'] || '', headers['if-modified-since'] || '')) {
1269
- status(304);
1270
- return '';
1264
+ restoreCtx();
1265
+ return serveFile(path.join(filePath, options.index), {
1266
+ ...options,
1267
+ index: '',
1268
+ });
1271
1269
  }
1272
- // if-none-match & if-modified-since processing end
1273
- setHeader('etag', etag);
1274
- setHeader('last-modified', lastModified.toUTCString());
1275
- if (options.cacheControl !== undefined) {
1276
- setCacheControl(options.cacheControl);
1270
+ removeHeader('etag');
1271
+ removeHeader('last-modified');
1272
+ throw new eventHttp.HttpError(404);
1273
+ }
1274
+ // range header processing start
1275
+ let range = headers.range;
1276
+ let start, end;
1277
+ let size = fileStats.size;
1278
+ if (range) {
1279
+ const rangeParts = range
1280
+ .trim()
1281
+ .replace(/bytes=/, '')
1282
+ .split('-');
1283
+ const [s, e] = rangeParts;
1284
+ start = parseInt(s);
1285
+ end = e ? parseInt(e) : size - 1;
1286
+ end = Math.min(size - 1, end);
1287
+ if (start > end || isNaN(start) || isNaN(end)) {
1288
+ throw new eventHttp.HttpError(416);
1277
1289
  }
1278
- if (options.expires) {
1279
- setExpires(options.expires);
1290
+ size = end - start + 1;
1291
+ // if-range processing start
1292
+ // rfc7233#section-3.2\
1293
+ const ifRange = headers['if-range'] || '';
1294
+ const ifRangeTag = ifRange[0] === '"' ? ifRange : '';
1295
+ const ifRangeDate = ifRangeTag ? '' : ifRange;
1296
+ if (ifRange &&
1297
+ !isNotModified(etag, lastModified, ifRangeTag, ifRangeDate)) {
1298
+ // If the validator does not match, the server MUST ignore
1299
+ // the Range header field.
1300
+ status(200);
1301
+ size = fileStats.size;
1302
+ range = '';
1280
1303
  }
1281
- if (options.pragmaNoCache) {
1282
- setPragmaNoCache(options.pragmaNoCache);
1304
+ else {
1305
+ // If the validator given in the If-Range header field matches the
1306
+ // current validator for the selected representation of the target
1307
+ // resource, then the server SHOULD process the Range header field as
1308
+ // requested.
1309
+ setHeader('content-range', `bytes ${start}-${end}/${fileStats.size}`);
1310
+ status(206);
1283
1311
  }
1284
- if (fileStats.isDirectory()) {
1285
- if (options.listDirectory) {
1286
- restoreCtx();
1287
- return listDirectory(normalizedPath);
1288
- }
1289
- else if (options.index) {
1290
- if (filePath[filePath.length - 1] !== '/' &&
1291
- url &&
1292
- url[url.length - 1] !== '/') {
1293
- return new eventHttp.BaseHttpResponse()
1294
- .setStatus(302)
1295
- .setHeader('location', url + '/');
1296
- }
1297
- restoreCtx();
1298
- return serveFile(path.join(filePath, options.index), Object.assign(Object.assign({}, options), { index: '' }));
1299
- }
1300
- removeHeader('etag');
1301
- removeHeader('last-modified');
1302
- throw new eventHttp.HttpError(404);
1303
- }
1304
- // range header processing start
1305
- let range = headers.range;
1306
- let start, end;
1307
- let size = fileStats.size;
1308
- if (range) {
1309
- const rangeParts = range
1310
- .trim()
1311
- .replace(/bytes=/, '')
1312
- .split('-');
1313
- const [s, e] = rangeParts;
1314
- start = parseInt(s);
1315
- end = e ? parseInt(e) : size - 1;
1316
- end = Math.min(size - 1, end);
1317
- if (start > end || isNaN(start) || isNaN(end)) {
1318
- throw new eventHttp.HttpError(416);
1319
- }
1320
- size = end - start + 1;
1321
- // if-range processing start
1322
- // rfc7233#section-3.2\
1323
- const ifRange = headers['if-range'] || '';
1324
- const ifRangeTag = ifRange[0] === '"' ? ifRange : '';
1325
- const ifRangeDate = ifRangeTag ? '' : ifRange;
1326
- if (ifRange &&
1327
- !isNotModified(etag, lastModified, ifRangeTag, ifRangeDate)) {
1328
- // If the validator does not match, the server MUST ignore
1329
- // the Range header field.
1330
- status(200);
1331
- size = fileStats.size;
1332
- range = '';
1333
- }
1334
- else {
1335
- // If the validator given in the If-Range header field matches the
1336
- // current validator for the selected representation of the target
1337
- // resource, then the server SHOULD process the Range header field as
1338
- // requested.
1339
- setHeader('content-range', `bytes ${start}-${end}/${fileStats.size}`);
1340
- status(206);
1341
- }
1342
- // if-range processing end
1343
- }
1344
- // range header processing end
1345
- setHeader('accept-ranges', 'bytes');
1346
- setHeader('content-type', getMimeType(normalizedPath) || 'application/octet-stream');
1347
- setHeader('content-length', size);
1348
- if (options.headers) {
1349
- for (const header of Object.keys(options.headers)) {
1350
- setHeader(header, options.headers[header]);
1351
- }
1312
+ // if-range processing end
1313
+ }
1314
+ // range header processing end
1315
+ setHeader('accept-ranges', 'bytes');
1316
+ setHeader('content-type', getMimeType(normalizedPath) || 'application/octet-stream');
1317
+ setHeader('content-length', size);
1318
+ if (options.headers) {
1319
+ for (const header of Object.keys(options.headers)) {
1320
+ setHeader(header, options.headers[header]);
1352
1321
  }
1353
- return method === 'HEAD'
1354
- ? ''
1355
- : fs.createReadStream(normalizedPath, !!range ? { start, end } : undefined);
1356
- });
1322
+ }
1323
+ return method === 'HEAD'
1324
+ ? ''
1325
+ : fs.createReadStream(normalizedPath, !!range ? { start, end } : undefined);
1357
1326
  }
1358
1327
  // function toWeak(etag: string): string {
1359
1328
  // return `W/${etag}`
@@ -1384,53 +1353,51 @@ function isNotModified(etag, lastModified, clientEtag, clientLM) {
1384
1353
  }
1385
1354
  return false;
1386
1355
  }
1387
- function listDirectory(dirPath) {
1388
- return __awaiter(this, void 0, void 0, function* () {
1389
- const { setContentType } = eventHttp.useSetHeaders();
1390
- const { url } = eventHttp.useRequest();
1391
- const list = yield readdir(dirPath);
1392
- const promises = [];
1393
- let detailedList = [];
1394
- for (const item of list) {
1395
- promises.push({ name: item, promise: stat(path.join(dirPath, item)) });
1396
- }
1397
- for (const item of promises) {
1398
- const data = yield item.promise;
1399
- detailedList.push({
1400
- name: item.name,
1401
- size: data.size,
1402
- mtime: data.mtime,
1403
- dir: data.isDirectory(),
1404
- });
1405
- }
1406
- detailedList = detailedList.sort((a, b) => a.dir === b.dir ? (a.name > b.name ? 1 : -1) : a.dir > b.dir ? -1 : 1);
1407
- detailedList.unshift({ name: '..', dir: true });
1408
- setContentType('text/html');
1409
- const styles = '<style type="text/css">\nhtml { font-family: monospace }\n' +
1410
- 'span { padding: 0px 2px }\n' +
1411
- '.text { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }\n' +
1412
- '.icon { width: 20px; display: inline-block; text-align: center; }\n' +
1413
- '.name { width: 250px; display: inline-block; }\n' +
1414
- '.size { width: 80px; display: inline-block; color: grey; text-align: right; }\n' +
1415
- '.date { width: 200px; display: inline-block; color: grey; text-align: right; }\n' +
1416
- '\n</style>';
1417
- return ('<html><head><title>Dir</title> ' +
1418
- styles +
1419
- ' </head><body><ul>' +
1420
- detailedList
1421
- .map((d) => `<li> <span class="icon">${d.dir ? '&#128193;' : '&#128462;'}</span>` +
1422
- `<a href="${path.join(url || '', d.name)}"><span class="name text">${d.name}</span></a>` +
1423
- `<span class="size text">${(d.size &&
1424
- (d.size > 10000
1425
- ? Math.round(d.size / 1024 / 1024).toString() +
1426
- 'Mb'
1427
- : Math.round(d.size / 1024).toString() +
1428
- 'Kb')) ||
1429
- ''}</span>` +
1430
- `<span class="date text">${(d.mtime && d.mtime.toISOString()) || ''}</li>`)
1431
- .join('\n') +
1432
- '</ul></body></html>');
1433
- });
1356
+ async function listDirectory(dirPath) {
1357
+ const { setContentType } = eventHttp.useSetHeaders();
1358
+ const { url } = eventHttp.useRequest();
1359
+ const list = await readdir(dirPath);
1360
+ const promises = [];
1361
+ let detailedList = [];
1362
+ for (const item of list) {
1363
+ promises.push({ name: item, promise: stat(path.join(dirPath, item)) });
1364
+ }
1365
+ for (const item of promises) {
1366
+ const data = await item.promise;
1367
+ detailedList.push({
1368
+ name: item.name,
1369
+ size: data.size,
1370
+ mtime: data.mtime,
1371
+ dir: data.isDirectory(),
1372
+ });
1373
+ }
1374
+ detailedList = detailedList.sort((a, b) => a.dir === b.dir ? (a.name > b.name ? 1 : -1) : a.dir > b.dir ? -1 : 1);
1375
+ detailedList.unshift({ name: '..', dir: true });
1376
+ setContentType('text/html');
1377
+ const styles = '<style type="text/css">\nhtml { font-family: monospace }\n' +
1378
+ 'span { padding: 0px 2px }\n' +
1379
+ '.text { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }\n' +
1380
+ '.icon { width: 20px; display: inline-block; text-align: center; }\n' +
1381
+ '.name { width: 250px; display: inline-block; }\n' +
1382
+ '.size { width: 80px; display: inline-block; color: grey; text-align: right; }\n' +
1383
+ '.date { width: 200px; display: inline-block; color: grey; text-align: right; }\n' +
1384
+ '\n</style>';
1385
+ return ('<html><head><title>Dir</title> ' +
1386
+ styles +
1387
+ ' </head><body><ul>' +
1388
+ detailedList
1389
+ .map((d) => `<li> <span class="icon">${d.dir ? '&#128193;' : '&#128462;'}</span>` +
1390
+ `<a href="${path.join(url || '', d.name)}"><span class="name text">${d.name}</span></a>` +
1391
+ `<span class="size text">${(d.size &&
1392
+ (d.size > 10000
1393
+ ? Math.round(d.size / 1024 / 1024).toString() +
1394
+ 'Mb'
1395
+ : Math.round(d.size / 1024).toString() +
1396
+ 'Kb')) ||
1397
+ ''}</span>` +
1398
+ `<span class="date text">${(d.mtime && d.mtime.toISOString()) || ''}</li>`)
1399
+ .join('\n') +
1400
+ '</ul></body></html>');
1434
1401
  }
1435
1402
 
1436
1403
  exports.serveFile = serveFile;
package/dist/index.mjs CHANGED
@@ -2,38 +2,6 @@ import { useHttpContext, useResponse, useSetHeaders, useHeaders, useRequest, use
2
2
  import { createReadStream, promises } from 'fs';
3
3
  import path from 'path';
4
4
 
5
- /******************************************************************************
6
- Copyright (c) Microsoft Corporation.
7
-
8
- Permission to use, copy, modify, and/or distribute this software for any
9
- purpose with or without fee is hereby granted.
10
-
11
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
- PERFORMANCE OF THIS SOFTWARE.
18
- ***************************************************************************** */
19
- /* global Reflect, Promise, SuppressedError, Symbol */
20
-
21
-
22
- function __awaiter(thisArg, _arguments, P, generator) {
23
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
24
- return new (P || (P = Promise))(function (resolve, reject) {
25
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
26
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
27
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
28
- step((generator = generator.apply(thisArg, _arguments || [])).next());
29
- });
30
- }
31
-
32
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
33
- var e = new Error(message);
34
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
35
- };
36
-
37
5
  const extensions = {
38
6
  '123': 'application/vnd.lotus-1-2-3',
39
7
  ez: 'application/andrew-inset',
@@ -1228,130 +1196,131 @@ function normalizePath(filePath, baseDir) {
1228
1196
  }
1229
1197
 
1230
1198
  const { stat, readdir } = promises;
1231
- function serveFile(filePath, options = {}) {
1232
- return __awaiter(this, void 0, void 0, function* () {
1233
- if (!options.allowDotDot && (/\/\.\.\//.test(filePath) || /^\.\.\//.test(filePath))) {
1234
- throw new Error('Parent Traversal ("/../") is not allowed.');
1199
+ async function serveFile(filePath, options = {}) {
1200
+ if (!options.allowDotDot && (/\/\.\.\//.test(filePath) || /^\.\.\//.test(filePath))) {
1201
+ throw new Error('Parent Traversal ("/../") is not allowed.');
1202
+ }
1203
+ const { restoreCtx } = useHttpContext();
1204
+ const { status } = useResponse();
1205
+ const { setHeader, removeHeader } = useSetHeaders();
1206
+ const headers = useHeaders();
1207
+ const { method, url } = useRequest();
1208
+ const { setCacheControl, setExpires, setPragmaNoCache } = useSetCacheControl();
1209
+ const normalizedPath = normalizePath(filePath, options.baseDir);
1210
+ let fileStats;
1211
+ try {
1212
+ fileStats = await stat(normalizedPath);
1213
+ }
1214
+ catch (e) {
1215
+ if (options.defaultExt) {
1216
+ const ext = path.extname(filePath);
1217
+ if (!ext) {
1218
+ restoreCtx();
1219
+ return serveFile(filePath + '.' + options.defaultExt);
1220
+ }
1235
1221
  }
1236
- const { restoreCtx } = useHttpContext();
1237
- const { status } = useResponse();
1238
- const { setHeader, removeHeader } = useSetHeaders();
1239
- const headers = useHeaders();
1240
- const { method, url } = useRequest();
1241
- const { setCacheControl, setExpires, setPragmaNoCache } = useSetCacheControl();
1242
- const normalizedPath = normalizePath(filePath, options.baseDir);
1243
- let fileStats;
1244
- try {
1245
- fileStats = yield stat(normalizedPath);
1222
+ throw new HttpError(404);
1223
+ }
1224
+ status(200);
1225
+ // if-none-match & if-modified-since processing start
1226
+ // rfc7232
1227
+ const etag = `"${[
1228
+ fileStats.ino,
1229
+ fileStats.size,
1230
+ fileStats.mtime.toISOString(),
1231
+ ].join('-')}"`;
1232
+ const lastModified = new Date(fileStats.mtime);
1233
+ if (isNotModified(etag, lastModified, headers['if-none-match'] || '', headers['if-modified-since'] || '')) {
1234
+ status(304);
1235
+ return '';
1236
+ }
1237
+ // if-none-match & if-modified-since processing end
1238
+ setHeader('etag', etag);
1239
+ setHeader('last-modified', lastModified.toUTCString());
1240
+ if (options.cacheControl !== undefined) {
1241
+ setCacheControl(options.cacheControl);
1242
+ }
1243
+ if (options.expires) {
1244
+ setExpires(options.expires);
1245
+ }
1246
+ if (options.pragmaNoCache) {
1247
+ setPragmaNoCache(options.pragmaNoCache);
1248
+ }
1249
+ if (fileStats.isDirectory()) {
1250
+ if (options.listDirectory) {
1251
+ restoreCtx();
1252
+ return listDirectory(normalizedPath);
1246
1253
  }
1247
- catch (e) {
1248
- if (options.defaultExt) {
1249
- const ext = path.extname(filePath);
1250
- if (!ext) {
1251
- restoreCtx();
1252
- return serveFile(filePath + '.' + options.defaultExt);
1253
- }
1254
+ else if (options.index) {
1255
+ if (filePath[filePath.length - 1] !== '/' &&
1256
+ url &&
1257
+ url[url.length - 1] !== '/') {
1258
+ return new BaseHttpResponse()
1259
+ .setStatus(302)
1260
+ .setHeader('location', url + '/');
1254
1261
  }
1255
- throw new HttpError(404);
1256
- }
1257
- status(200);
1258
- // if-none-match & if-modified-since processing start
1259
- // rfc7232
1260
- const etag = `"${[
1261
- fileStats.ino,
1262
- fileStats.size,
1263
- fileStats.mtime.toISOString(),
1264
- ].join('-')}"`;
1265
- const lastModified = new Date(fileStats.mtime);
1266
- if (isNotModified(etag, lastModified, headers['if-none-match'] || '', headers['if-modified-since'] || '')) {
1267
- status(304);
1268
- return '';
1262
+ restoreCtx();
1263
+ return serveFile(path.join(filePath, options.index), {
1264
+ ...options,
1265
+ index: '',
1266
+ });
1269
1267
  }
1270
- // if-none-match & if-modified-since processing end
1271
- setHeader('etag', etag);
1272
- setHeader('last-modified', lastModified.toUTCString());
1273
- if (options.cacheControl !== undefined) {
1274
- setCacheControl(options.cacheControl);
1268
+ removeHeader('etag');
1269
+ removeHeader('last-modified');
1270
+ throw new HttpError(404);
1271
+ }
1272
+ // range header processing start
1273
+ let range = headers.range;
1274
+ let start, end;
1275
+ let size = fileStats.size;
1276
+ if (range) {
1277
+ const rangeParts = range
1278
+ .trim()
1279
+ .replace(/bytes=/, '')
1280
+ .split('-');
1281
+ const [s, e] = rangeParts;
1282
+ start = parseInt(s);
1283
+ end = e ? parseInt(e) : size - 1;
1284
+ end = Math.min(size - 1, end);
1285
+ if (start > end || isNaN(start) || isNaN(end)) {
1286
+ throw new HttpError(416);
1275
1287
  }
1276
- if (options.expires) {
1277
- setExpires(options.expires);
1288
+ size = end - start + 1;
1289
+ // if-range processing start
1290
+ // rfc7233#section-3.2\
1291
+ const ifRange = headers['if-range'] || '';
1292
+ const ifRangeTag = ifRange[0] === '"' ? ifRange : '';
1293
+ const ifRangeDate = ifRangeTag ? '' : ifRange;
1294
+ if (ifRange &&
1295
+ !isNotModified(etag, lastModified, ifRangeTag, ifRangeDate)) {
1296
+ // If the validator does not match, the server MUST ignore
1297
+ // the Range header field.
1298
+ status(200);
1299
+ size = fileStats.size;
1300
+ range = '';
1278
1301
  }
1279
- if (options.pragmaNoCache) {
1280
- setPragmaNoCache(options.pragmaNoCache);
1302
+ else {
1303
+ // If the validator given in the If-Range header field matches the
1304
+ // current validator for the selected representation of the target
1305
+ // resource, then the server SHOULD process the Range header field as
1306
+ // requested.
1307
+ setHeader('content-range', `bytes ${start}-${end}/${fileStats.size}`);
1308
+ status(206);
1281
1309
  }
1282
- if (fileStats.isDirectory()) {
1283
- if (options.listDirectory) {
1284
- restoreCtx();
1285
- return listDirectory(normalizedPath);
1286
- }
1287
- else if (options.index) {
1288
- if (filePath[filePath.length - 1] !== '/' &&
1289
- url &&
1290
- url[url.length - 1] !== '/') {
1291
- return new BaseHttpResponse()
1292
- .setStatus(302)
1293
- .setHeader('location', url + '/');
1294
- }
1295
- restoreCtx();
1296
- return serveFile(path.join(filePath, options.index), Object.assign(Object.assign({}, options), { index: '' }));
1297
- }
1298
- removeHeader('etag');
1299
- removeHeader('last-modified');
1300
- throw new HttpError(404);
1301
- }
1302
- // range header processing start
1303
- let range = headers.range;
1304
- let start, end;
1305
- let size = fileStats.size;
1306
- if (range) {
1307
- const rangeParts = range
1308
- .trim()
1309
- .replace(/bytes=/, '')
1310
- .split('-');
1311
- const [s, e] = rangeParts;
1312
- start = parseInt(s);
1313
- end = e ? parseInt(e) : size - 1;
1314
- end = Math.min(size - 1, end);
1315
- if (start > end || isNaN(start) || isNaN(end)) {
1316
- throw new HttpError(416);
1317
- }
1318
- size = end - start + 1;
1319
- // if-range processing start
1320
- // rfc7233#section-3.2\
1321
- const ifRange = headers['if-range'] || '';
1322
- const ifRangeTag = ifRange[0] === '"' ? ifRange : '';
1323
- const ifRangeDate = ifRangeTag ? '' : ifRange;
1324
- if (ifRange &&
1325
- !isNotModified(etag, lastModified, ifRangeTag, ifRangeDate)) {
1326
- // If the validator does not match, the server MUST ignore
1327
- // the Range header field.
1328
- status(200);
1329
- size = fileStats.size;
1330
- range = '';
1331
- }
1332
- else {
1333
- // If the validator given in the If-Range header field matches the
1334
- // current validator for the selected representation of the target
1335
- // resource, then the server SHOULD process the Range header field as
1336
- // requested.
1337
- setHeader('content-range', `bytes ${start}-${end}/${fileStats.size}`);
1338
- status(206);
1339
- }
1340
- // if-range processing end
1341
- }
1342
- // range header processing end
1343
- setHeader('accept-ranges', 'bytes');
1344
- setHeader('content-type', getMimeType(normalizedPath) || 'application/octet-stream');
1345
- setHeader('content-length', size);
1346
- if (options.headers) {
1347
- for (const header of Object.keys(options.headers)) {
1348
- setHeader(header, options.headers[header]);
1349
- }
1310
+ // if-range processing end
1311
+ }
1312
+ // range header processing end
1313
+ setHeader('accept-ranges', 'bytes');
1314
+ setHeader('content-type', getMimeType(normalizedPath) || 'application/octet-stream');
1315
+ setHeader('content-length', size);
1316
+ if (options.headers) {
1317
+ for (const header of Object.keys(options.headers)) {
1318
+ setHeader(header, options.headers[header]);
1350
1319
  }
1351
- return method === 'HEAD'
1352
- ? ''
1353
- : createReadStream(normalizedPath, !!range ? { start, end } : undefined);
1354
- });
1320
+ }
1321
+ return method === 'HEAD'
1322
+ ? ''
1323
+ : createReadStream(normalizedPath, !!range ? { start, end } : undefined);
1355
1324
  }
1356
1325
  // function toWeak(etag: string): string {
1357
1326
  // return `W/${etag}`
@@ -1382,53 +1351,51 @@ function isNotModified(etag, lastModified, clientEtag, clientLM) {
1382
1351
  }
1383
1352
  return false;
1384
1353
  }
1385
- function listDirectory(dirPath) {
1386
- return __awaiter(this, void 0, void 0, function* () {
1387
- const { setContentType } = useSetHeaders();
1388
- const { url } = useRequest();
1389
- const list = yield readdir(dirPath);
1390
- const promises = [];
1391
- let detailedList = [];
1392
- for (const item of list) {
1393
- promises.push({ name: item, promise: stat(path.join(dirPath, item)) });
1394
- }
1395
- for (const item of promises) {
1396
- const data = yield item.promise;
1397
- detailedList.push({
1398
- name: item.name,
1399
- size: data.size,
1400
- mtime: data.mtime,
1401
- dir: data.isDirectory(),
1402
- });
1403
- }
1404
- detailedList = detailedList.sort((a, b) => a.dir === b.dir ? (a.name > b.name ? 1 : -1) : a.dir > b.dir ? -1 : 1);
1405
- detailedList.unshift({ name: '..', dir: true });
1406
- setContentType('text/html');
1407
- const styles = '<style type="text/css">\nhtml { font-family: monospace }\n' +
1408
- 'span { padding: 0px 2px }\n' +
1409
- '.text { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }\n' +
1410
- '.icon { width: 20px; display: inline-block; text-align: center; }\n' +
1411
- '.name { width: 250px; display: inline-block; }\n' +
1412
- '.size { width: 80px; display: inline-block; color: grey; text-align: right; }\n' +
1413
- '.date { width: 200px; display: inline-block; color: grey; text-align: right; }\n' +
1414
- '\n</style>';
1415
- return ('<html><head><title>Dir</title> ' +
1416
- styles +
1417
- ' </head><body><ul>' +
1418
- detailedList
1419
- .map((d) => `<li> <span class="icon">${d.dir ? '&#128193;' : '&#128462;'}</span>` +
1420
- `<a href="${path.join(url || '', d.name)}"><span class="name text">${d.name}</span></a>` +
1421
- `<span class="size text">${(d.size &&
1422
- (d.size > 10000
1423
- ? Math.round(d.size / 1024 / 1024).toString() +
1424
- 'Mb'
1425
- : Math.round(d.size / 1024).toString() +
1426
- 'Kb')) ||
1427
- ''}</span>` +
1428
- `<span class="date text">${(d.mtime && d.mtime.toISOString()) || ''}</li>`)
1429
- .join('\n') +
1430
- '</ul></body></html>');
1431
- });
1354
+ async function listDirectory(dirPath) {
1355
+ const { setContentType } = useSetHeaders();
1356
+ const { url } = useRequest();
1357
+ const list = await readdir(dirPath);
1358
+ const promises = [];
1359
+ let detailedList = [];
1360
+ for (const item of list) {
1361
+ promises.push({ name: item, promise: stat(path.join(dirPath, item)) });
1362
+ }
1363
+ for (const item of promises) {
1364
+ const data = await item.promise;
1365
+ detailedList.push({
1366
+ name: item.name,
1367
+ size: data.size,
1368
+ mtime: data.mtime,
1369
+ dir: data.isDirectory(),
1370
+ });
1371
+ }
1372
+ detailedList = detailedList.sort((a, b) => a.dir === b.dir ? (a.name > b.name ? 1 : -1) : a.dir > b.dir ? -1 : 1);
1373
+ detailedList.unshift({ name: '..', dir: true });
1374
+ setContentType('text/html');
1375
+ const styles = '<style type="text/css">\nhtml { font-family: monospace }\n' +
1376
+ 'span { padding: 0px 2px }\n' +
1377
+ '.text { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }\n' +
1378
+ '.icon { width: 20px; display: inline-block; text-align: center; }\n' +
1379
+ '.name { width: 250px; display: inline-block; }\n' +
1380
+ '.size { width: 80px; display: inline-block; color: grey; text-align: right; }\n' +
1381
+ '.date { width: 200px; display: inline-block; color: grey; text-align: right; }\n' +
1382
+ '\n</style>';
1383
+ return ('<html><head><title>Dir</title> ' +
1384
+ styles +
1385
+ ' </head><body><ul>' +
1386
+ detailedList
1387
+ .map((d) => `<li> <span class="icon">${d.dir ? '&#128193;' : '&#128462;'}</span>` +
1388
+ `<a href="${path.join(url || '', d.name)}"><span class="name text">${d.name}</span></a>` +
1389
+ `<span class="size text">${(d.size &&
1390
+ (d.size > 10000
1391
+ ? Math.round(d.size / 1024 / 1024).toString() +
1392
+ 'Mb'
1393
+ : Math.round(d.size / 1024).toString() +
1394
+ 'Kb')) ||
1395
+ ''}</span>` +
1396
+ `<span class="date text">${(d.mtime && d.mtime.toISOString()) || ''}</li>`)
1397
+ .join('\n') +
1398
+ '</ul></body></html>');
1432
1399
  }
1433
1400
 
1434
1401
  export { serveFile };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wooksjs/http-static",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
4
4
  "description": "@wooksjs/http-static",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
@@ -31,7 +31,7 @@
31
31
  "url": "https://github.com/wooksjs/wooksjs/issues"
32
32
  },
33
33
  "peerDependencies": {
34
- "@wooksjs/event-http": "0.4.10"
34
+ "@wooksjs/event-http": "0.4.11"
35
35
  },
36
36
  "homepage": "https://github.com/wooksjs/wooksjs/tree/main/packages/http-static#readme"
37
37
  }