@wooksjs/http-static 0.4.10 → 0.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,134 +1198,118 @@ 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
+ 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
+ setHeader('etag', etag);
1238
+ setHeader('last-modified', lastModified.toUTCString());
1239
+ if (options.cacheControl !== undefined) {
1240
+ setCacheControl(options.cacheControl);
1241
+ }
1242
+ if (options.expires) {
1243
+ setExpires(options.expires);
1244
+ }
1245
+ if (options.pragmaNoCache) {
1246
+ setPragmaNoCache(options.pragmaNoCache);
1247
+ }
1248
+ if (fileStats.isDirectory()) {
1249
+ if (options.listDirectory) {
1250
+ restoreCtx();
1251
+ return listDirectory(normalizedPath);
1248
1252
  }
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
- }
1253
+ else if (options.index) {
1254
+ if (filePath[filePath.length - 1] !== '/' &&
1255
+ url &&
1256
+ url[url.length - 1] !== '/') {
1257
+ return new eventHttp.BaseHttpResponse()
1258
+ .setStatus(302)
1259
+ .setHeader('location', url + '/');
1256
1260
  }
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 '';
1261
+ restoreCtx();
1262
+ return serveFile(path.join(filePath, options.index), {
1263
+ ...options,
1264
+ index: '',
1265
+ });
1271
1266
  }
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);
1267
+ removeHeader('etag');
1268
+ removeHeader('last-modified');
1269
+ throw new eventHttp.HttpError(404);
1270
+ }
1271
+ let range = headers.range;
1272
+ let start, end;
1273
+ let size = fileStats.size;
1274
+ if (range) {
1275
+ const rangeParts = range
1276
+ .trim()
1277
+ .replace(/bytes=/, '')
1278
+ .split('-');
1279
+ const [s, e] = rangeParts;
1280
+ start = parseInt(s);
1281
+ end = e ? parseInt(e) : size - 1;
1282
+ end = Math.min(size - 1, end);
1283
+ if (start > end || isNaN(start) || isNaN(end)) {
1284
+ throw new eventHttp.HttpError(416);
1277
1285
  }
1278
- if (options.expires) {
1279
- setExpires(options.expires);
1286
+ size = end - start + 1;
1287
+ const ifRange = headers['if-range'] || '';
1288
+ const ifRangeTag = ifRange[0] === '"' ? ifRange : '';
1289
+ const ifRangeDate = ifRangeTag ? '' : ifRange;
1290
+ if (ifRange &&
1291
+ !isNotModified(etag, lastModified, ifRangeTag, ifRangeDate)) {
1292
+ status(200);
1293
+ size = fileStats.size;
1294
+ range = '';
1280
1295
  }
1281
- if (options.pragmaNoCache) {
1282
- setPragmaNoCache(options.pragmaNoCache);
1296
+ else {
1297
+ setHeader('content-range', `bytes ${start}-${end}/${fileStats.size}`);
1298
+ status(206);
1283
1299
  }
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
- }
1300
+ }
1301
+ setHeader('accept-ranges', 'bytes');
1302
+ setHeader('content-type', getMimeType(normalizedPath) || 'application/octet-stream');
1303
+ setHeader('content-length', size);
1304
+ if (options.headers) {
1305
+ for (const header of Object.keys(options.headers)) {
1306
+ setHeader(header, options.headers[header]);
1352
1307
  }
1353
- return method === 'HEAD'
1354
- ? ''
1355
- : fs.createReadStream(normalizedPath, !!range ? { start, end } : undefined);
1356
- });
1308
+ }
1309
+ return method === 'HEAD'
1310
+ ? ''
1311
+ : fs.createReadStream(normalizedPath, !!range ? { start, end } : undefined);
1357
1312
  }
1358
- // function toWeak(etag: string): string {
1359
- // return `W/${etag}`
1360
- // }
1361
1313
  function isNotModified(etag, lastModified, clientEtag, clientLM) {
1362
1314
  if (clientEtag) {
1363
1315
  const parts = clientEtag.split(',').map((v) => v.trim());
@@ -1366,71 +1318,60 @@ function isNotModified(etag, lastModified, clientEtag, clientLM) {
1366
1318
  return true;
1367
1319
  }
1368
1320
  }
1369
- // A recipient MUST ignore If-Modified-Since if the request contains an
1370
- // If-None-Match header field; the condition in If-None-Match is
1371
- // considered to be a more accurate replacement for the condition in
1372
- // If-Modified-Since, and the two are only combined for the sake of
1373
- // interoperating with older intermediaries that might not implement
1374
- // If-None-Match.
1375
1321
  return false;
1376
1322
  }
1377
1323
  const date = new Date(clientLM);
1378
- // A recipient MUST ignore the If-Modified-Since header field if the
1379
- // received field-value is not a valid HTTP-date, or if the request
1380
- // method is neither GET nor HEAD.
1381
1324
  if (date.toString() !== 'Invalid Date' &&
1382
1325
  date.getTime() > lastModified.getTime()) {
1383
1326
  return true;
1384
1327
  }
1385
1328
  return false;
1386
1329
  }
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
- });
1330
+ async function listDirectory(dirPath) {
1331
+ const { setContentType } = eventHttp.useSetHeaders();
1332
+ const { url } = eventHttp.useRequest();
1333
+ const list = await readdir(dirPath);
1334
+ const promises = [];
1335
+ let detailedList = [];
1336
+ for (const item of list) {
1337
+ promises.push({ name: item, promise: stat(path.join(dirPath, item)) });
1338
+ }
1339
+ for (const item of promises) {
1340
+ const data = await item.promise;
1341
+ detailedList.push({
1342
+ name: item.name,
1343
+ size: data.size,
1344
+ mtime: data.mtime,
1345
+ dir: data.isDirectory(),
1346
+ });
1347
+ }
1348
+ detailedList = detailedList.sort((a, b) => a.dir === b.dir ? (a.name > b.name ? 1 : -1) : a.dir > b.dir ? -1 : 1);
1349
+ detailedList.unshift({ name: '..', dir: true });
1350
+ setContentType('text/html');
1351
+ const styles = '<style type="text/css">\nhtml { font-family: monospace }\n' +
1352
+ 'span { padding: 0px 2px }\n' +
1353
+ '.text { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }\n' +
1354
+ '.icon { width: 20px; display: inline-block; text-align: center; }\n' +
1355
+ '.name { width: 250px; display: inline-block; }\n' +
1356
+ '.size { width: 80px; display: inline-block; color: grey; text-align: right; }\n' +
1357
+ '.date { width: 200px; display: inline-block; color: grey; text-align: right; }\n' +
1358
+ '\n</style>';
1359
+ return ('<html><head><title>Dir</title> ' +
1360
+ styles +
1361
+ ' </head><body><ul>' +
1362
+ detailedList
1363
+ .map((d) => `<li> <span class="icon">${d.dir ? '&#128193;' : '&#128462;'}</span>` +
1364
+ `<a href="${path.join(url || '', d.name)}"><span class="name text">${d.name}</span></a>` +
1365
+ `<span class="size text">${(d.size &&
1366
+ (d.size > 10000
1367
+ ? Math.round(d.size / 1024 / 1024).toString() +
1368
+ 'Mb'
1369
+ : Math.round(d.size / 1024).toString() +
1370
+ 'Kb')) ||
1371
+ ''}</span>` +
1372
+ `<span class="date text">${(d.mtime && d.mtime.toISOString()) || ''}</li>`)
1373
+ .join('\n') +
1374
+ '</ul></body></html>');
1434
1375
  }
1435
1376
 
1436
1377
  exports.serveFile = serveFile;
package/dist/index.d.ts CHANGED
@@ -1,20 +1,17 @@
1
- /// <reference types="node" />
2
-
3
- import { Readable } from 'stream';
4
- import { TCacheControl } from '@wooksjs/event-http';
5
-
6
- export declare function serveFile(filePath: string, options?: TServeFileOptions): Promise<Readable | string | string[] | unknown>;
7
-
8
- declare interface TServeFileOptions {
9
- headers?: Record<string, string>;
10
- cacheControl?: TCacheControl;
11
- expires?: Date | string | number;
12
- pragmaNoCache?: boolean;
13
- baseDir?: string;
14
- defaultExt?: string;
15
- listDirectory?: boolean;
16
- index?: string;
17
- allowDotDot?: boolean;
18
- }
19
-
20
- export { }
1
+ import { TCacheControl } from '@wooksjs/event-http';
2
+ import { Readable } from 'stream';
3
+
4
+ interface TServeFileOptions {
5
+ headers?: Record<string, string>;
6
+ cacheControl?: TCacheControl;
7
+ expires?: Date | string | number;
8
+ pragmaNoCache?: boolean;
9
+ baseDir?: string;
10
+ defaultExt?: string;
11
+ listDirectory?: boolean;
12
+ index?: string;
13
+ allowDotDot?: boolean;
14
+ }
15
+ declare function serveFile(filePath: string, options?: TServeFileOptions): Promise<Readable | string | string[] | unknown>;
16
+
17
+ export { 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,134 +1196,118 @@ 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
+ const etag = `"${[
1226
+ fileStats.ino,
1227
+ fileStats.size,
1228
+ fileStats.mtime.toISOString(),
1229
+ ].join('-')}"`;
1230
+ const lastModified = new Date(fileStats.mtime);
1231
+ if (isNotModified(etag, lastModified, headers['if-none-match'] || '', headers['if-modified-since'] || '')) {
1232
+ status(304);
1233
+ return '';
1234
+ }
1235
+ setHeader('etag', etag);
1236
+ setHeader('last-modified', lastModified.toUTCString());
1237
+ if (options.cacheControl !== undefined) {
1238
+ setCacheControl(options.cacheControl);
1239
+ }
1240
+ if (options.expires) {
1241
+ setExpires(options.expires);
1242
+ }
1243
+ if (options.pragmaNoCache) {
1244
+ setPragmaNoCache(options.pragmaNoCache);
1245
+ }
1246
+ if (fileStats.isDirectory()) {
1247
+ if (options.listDirectory) {
1248
+ restoreCtx();
1249
+ return listDirectory(normalizedPath);
1246
1250
  }
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
- }
1251
+ else if (options.index) {
1252
+ if (filePath[filePath.length - 1] !== '/' &&
1253
+ url &&
1254
+ url[url.length - 1] !== '/') {
1255
+ return new BaseHttpResponse()
1256
+ .setStatus(302)
1257
+ .setHeader('location', url + '/');
1254
1258
  }
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 '';
1259
+ restoreCtx();
1260
+ return serveFile(path.join(filePath, options.index), {
1261
+ ...options,
1262
+ index: '',
1263
+ });
1269
1264
  }
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);
1265
+ removeHeader('etag');
1266
+ removeHeader('last-modified');
1267
+ throw new HttpError(404);
1268
+ }
1269
+ let range = headers.range;
1270
+ let start, end;
1271
+ let size = fileStats.size;
1272
+ if (range) {
1273
+ const rangeParts = range
1274
+ .trim()
1275
+ .replace(/bytes=/, '')
1276
+ .split('-');
1277
+ const [s, e] = rangeParts;
1278
+ start = parseInt(s);
1279
+ end = e ? parseInt(e) : size - 1;
1280
+ end = Math.min(size - 1, end);
1281
+ if (start > end || isNaN(start) || isNaN(end)) {
1282
+ throw new HttpError(416);
1275
1283
  }
1276
- if (options.expires) {
1277
- setExpires(options.expires);
1284
+ size = end - start + 1;
1285
+ const ifRange = headers['if-range'] || '';
1286
+ const ifRangeTag = ifRange[0] === '"' ? ifRange : '';
1287
+ const ifRangeDate = ifRangeTag ? '' : ifRange;
1288
+ if (ifRange &&
1289
+ !isNotModified(etag, lastModified, ifRangeTag, ifRangeDate)) {
1290
+ status(200);
1291
+ size = fileStats.size;
1292
+ range = '';
1278
1293
  }
1279
- if (options.pragmaNoCache) {
1280
- setPragmaNoCache(options.pragmaNoCache);
1294
+ else {
1295
+ setHeader('content-range', `bytes ${start}-${end}/${fileStats.size}`);
1296
+ status(206);
1281
1297
  }
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
- }
1298
+ }
1299
+ setHeader('accept-ranges', 'bytes');
1300
+ setHeader('content-type', getMimeType(normalizedPath) || 'application/octet-stream');
1301
+ setHeader('content-length', size);
1302
+ if (options.headers) {
1303
+ for (const header of Object.keys(options.headers)) {
1304
+ setHeader(header, options.headers[header]);
1350
1305
  }
1351
- return method === 'HEAD'
1352
- ? ''
1353
- : createReadStream(normalizedPath, !!range ? { start, end } : undefined);
1354
- });
1306
+ }
1307
+ return method === 'HEAD'
1308
+ ? ''
1309
+ : createReadStream(normalizedPath, !!range ? { start, end } : undefined);
1355
1310
  }
1356
- // function toWeak(etag: string): string {
1357
- // return `W/${etag}`
1358
- // }
1359
1311
  function isNotModified(etag, lastModified, clientEtag, clientLM) {
1360
1312
  if (clientEtag) {
1361
1313
  const parts = clientEtag.split(',').map((v) => v.trim());
@@ -1364,71 +1316,60 @@ function isNotModified(etag, lastModified, clientEtag, clientLM) {
1364
1316
  return true;
1365
1317
  }
1366
1318
  }
1367
- // A recipient MUST ignore If-Modified-Since if the request contains an
1368
- // If-None-Match header field; the condition in If-None-Match is
1369
- // considered to be a more accurate replacement for the condition in
1370
- // If-Modified-Since, and the two are only combined for the sake of
1371
- // interoperating with older intermediaries that might not implement
1372
- // If-None-Match.
1373
1319
  return false;
1374
1320
  }
1375
1321
  const date = new Date(clientLM);
1376
- // A recipient MUST ignore the If-Modified-Since header field if the
1377
- // received field-value is not a valid HTTP-date, or if the request
1378
- // method is neither GET nor HEAD.
1379
1322
  if (date.toString() !== 'Invalid Date' &&
1380
1323
  date.getTime() > lastModified.getTime()) {
1381
1324
  return true;
1382
1325
  }
1383
1326
  return false;
1384
1327
  }
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
- });
1328
+ async function listDirectory(dirPath) {
1329
+ const { setContentType } = useSetHeaders();
1330
+ const { url } = useRequest();
1331
+ const list = await readdir(dirPath);
1332
+ const promises = [];
1333
+ let detailedList = [];
1334
+ for (const item of list) {
1335
+ promises.push({ name: item, promise: stat(path.join(dirPath, item)) });
1336
+ }
1337
+ for (const item of promises) {
1338
+ const data = await item.promise;
1339
+ detailedList.push({
1340
+ name: item.name,
1341
+ size: data.size,
1342
+ mtime: data.mtime,
1343
+ dir: data.isDirectory(),
1344
+ });
1345
+ }
1346
+ detailedList = detailedList.sort((a, b) => a.dir === b.dir ? (a.name > b.name ? 1 : -1) : a.dir > b.dir ? -1 : 1);
1347
+ detailedList.unshift({ name: '..', dir: true });
1348
+ setContentType('text/html');
1349
+ const styles = '<style type="text/css">\nhtml { font-family: monospace }\n' +
1350
+ 'span { padding: 0px 2px }\n' +
1351
+ '.text { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }\n' +
1352
+ '.icon { width: 20px; display: inline-block; text-align: center; }\n' +
1353
+ '.name { width: 250px; display: inline-block; }\n' +
1354
+ '.size { width: 80px; display: inline-block; color: grey; text-align: right; }\n' +
1355
+ '.date { width: 200px; display: inline-block; color: grey; text-align: right; }\n' +
1356
+ '\n</style>';
1357
+ return ('<html><head><title>Dir</title> ' +
1358
+ styles +
1359
+ ' </head><body><ul>' +
1360
+ detailedList
1361
+ .map((d) => `<li> <span class="icon">${d.dir ? '&#128193;' : '&#128462;'}</span>` +
1362
+ `<a href="${path.join(url || '', d.name)}"><span class="name text">${d.name}</span></a>` +
1363
+ `<span class="size text">${(d.size &&
1364
+ (d.size > 10000
1365
+ ? Math.round(d.size / 1024 / 1024).toString() +
1366
+ 'Mb'
1367
+ : Math.round(d.size / 1024).toString() +
1368
+ 'Kb')) ||
1369
+ ''}</span>` +
1370
+ `<span class="date text">${(d.mtime && d.mtime.toISOString()) || ''}</li>`)
1371
+ .join('\n') +
1372
+ '</ul></body></html>');
1432
1373
  }
1433
1374
 
1434
1375
  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.12",
4
4
  "description": "@wooksjs/http-static",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
@@ -8,6 +8,14 @@
8
8
  "files": [
9
9
  "dist"
10
10
  ],
11
+ "exports": {
12
+ "./package.json": "./package.json",
13
+ ".": {
14
+ "require": "./dist/index.cjs",
15
+ "import": "./dist/index.mjs",
16
+ "types": "./dist/index.d.ts"
17
+ }
18
+ },
11
19
  "repository": {
12
20
  "type": "git",
13
21
  "url": "git+https://github.com/wooksjs/wooksjs.git",
@@ -31,7 +39,7 @@
31
39
  "url": "https://github.com/wooksjs/wooksjs/issues"
32
40
  },
33
41
  "peerDependencies": {
34
- "@wooksjs/event-http": "0.4.10"
42
+ "@wooksjs/event-http": "0.4.12"
35
43
  },
36
44
  "homepage": "https://github.com/wooksjs/wooksjs/tree/main/packages/http-static#readme"
37
45
  }