Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

koa-simple-static

Package Overview
Dependencies
Maintainers
1
Versions
96
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

koa-simple-static - npm Package Compare versions

Comparing version 3.0.3 to 4.0.0

lib/index.d.ts

450

lib/index.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _crypto = require("crypto");
var _path = require("path");
var _fs = require("mz/fs");
var _zlib = require("mz/zlib");
var _mimeTypes = require("mime-types");
var _compressible = _interopRequireDefault(require("compressible"));
var _fsReaddirRecursive = _interopRequireDefault(require("fs-readdir-recursive"));
var _zeelib = require("zeelib");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const prefix = '/';
const loadFile = (name, dir, options, files) => {
const pathname = (0, _path.normalize)((0, _path.join)(prefix, name));
const obj = files[pathname] = files[pathname] ? files[pathname] : {};
const filename = obj.path = (0, _path.join)(dir, name);
const stats = (0, _fs.statSync)(filename);
let buffer = (0, _fs.readFileSync)(filename);
obj.cacheControl = options.cacheControl;
obj.maxAge = obj.maxAge ? obj.maxAge : options.maxAge || 0;
obj.type = obj.mime = (0, _mimeTypes.lookup)(pathname) || 'application/octet-stream';
obj.mtime = stats.mtime;
obj.length = stats.size; // $FlowFixMe see https://github.com/facebook/flow/blob/v0.65.0/lib/node.js#L359
obj.md5 = (0, _crypto.createHash)('md5').update(buffer).digest('base64');
obj.buffer = buffer;
buffer = null;
return obj;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
const simpleStatic = options => {
const dir = (0, _path.normalize)(options.dir);
const files = {};
(0, _fsReaddirRecursive.default)(dir).forEach(name => {
loadFile(name, dir, options, files);
});
return async (ctx, next) => {
// only accept HEAD and GET
if (ctx.method !== 'HEAD' && ctx.method !== 'GET') {
await next();
return;
} // check prefix first to avoid calculate
if (ctx.path.indexOf(prefix) !== 0) {
await next();
return;
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
if (options.extraHeaders && Array.isArray(options.extraHeaders) && options.extraHeaders.length) {
options.extraHeaders.forEach(header => {
for (const h in header) {
// eslint-disable-line guard-for-in
ctx.append(h, header[h]);
}
});
} // decode for `/%E4%B8%AD%E6%96%87`
// normalize for `//index`
let filename = (0, _zeelib.safeDecodeURIComponent)((0, _path.normalize)(ctx.path));
let file = files[filename]; // try to load file
if (!file) {
if ((0, _path.basename)(filename)[0] === '.') {
await next();
return;
} // handle index.html
let hasIndex = false;
try {
hasIndex = await (0, _fs.statSync)((0, _path.normalize)((0, _path.join)(dir, `${filename}/index.html`))).isFile();
} catch (_) {}
if (hasIndex) {
filename = `${filename}/index.html`;
}
if (filename.charAt(0) === _path.sep) {
filename = filename.slice(1);
} // disallow ../
const fullPath = (0, _path.join)(dir, filename);
if (fullPath.indexOf(dir) !== 0 && fullPath !== 'index.html') {
await next();
return;
}
let s;
try {
s = await (0, _fs.statSync)((0, _path.join)(dir, filename));
} catch (err) {
await next();
return;
}
if (!s.isFile()) {
await next();
return;
}
file = loadFile(filename, dir, options, files);
}
ctx.status = 200;
ctx.vary('Accept-Encoding');
if (!file.buffer) {
const stats = await (0, _fs.statSync)(file.path);
if (stats.mtime > file.mtime) {
file.mtime = stats.mtime;
file.md5 = null;
file.length = stats.size;
}
}
ctx.response.lastModified = file.mtime;
if (file.md5) {
ctx.response.etag = file.md5;
}
if (ctx.fresh) {
ctx.status = 304;
return;
}
ctx.type = file.type;
ctx.length = file.zipBuffer ? file.zipBuffer.length : file.length;
ctx.set('cache-control', `public, max-age=${file.maxAge}`);
if (file.md5) {
ctx.set('content-md5', file.md5);
}
if (ctx.method === 'HEAD') {
return;
}
const acceptGzip = ctx.acceptsEncodings('gzip') === 'gzip';
if (file.zipBuffer) {
if (acceptGzip) {
ctx.set('content-encoding', 'gzip');
ctx.body = file.zipBuffer;
} else {
ctx.body = file.buffer;
}
return;
}
const shouldGzip = file.length > 1024 && acceptGzip && (0, _compressible.default)(file.type);
if (file.buffer) {
if (shouldGzip) {
file.zipBuffer = await (0, _zlib.gzip)(file.buffer);
ctx.set('content-encoding', 'gzip');
ctx.body = file.zipBuffer;
} else {
ctx.body = file.buffer;
}
return;
}
const stream = await (0, _fs.createReadStream)(file.path); // update file hash
if (!file.md5) {
const hash = (0, _crypto.createHash)('md5');
stream.on('data', hash.update.bind(hash));
stream.on('end', () => {
file.md5 = hash.digest('base64');
});
}
ctx.body = stream; // enable gzip will remove content length
if (shouldGzip) {
ctx.remove('content-length');
ctx.set('content-encoding', 'gzip');
ctx.body = stream.pipe((0, _zlib.createGzip)());
}
};
};
var _default = simpleStatic;
exports.default = _default;
module.exports = exports.default;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var crypto_1 = require("crypto");
var path_1 = require("path");
var fs_1 = require("mz/fs");
var zlib_1 = require("mz/zlib");
var mime_types_1 = require("mime-types");
var compressible_1 = __importDefault(require("compressible"));
var fs_readdir_recursive_1 = __importDefault(require("fs-readdir-recursive"));
var zeelib_1 = require("zeelib");
var prefix = '/';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var loadFile = function (name, dir, options, files) {
var pathname = path_1.normalize(path_1.join(prefix, name));
var obj = (files[pathname] = files[pathname] ? files[pathname] : {});
var filename = (obj.path = path_1.join(dir, name));
var stats = fs_1.statSync(filename);
var buffer = fs_1.readFileSync(filename);
obj.cacheControl = options.cacheControl;
obj.maxAge = obj.maxAge ? obj.maxAge : options.maxAge || 0;
obj.type = obj.mime = mime_types_1.lookup(pathname) || 'application/octet-stream';
obj.mtime = stats.mtime;
obj.length = stats.size;
obj.md5 = crypto_1.createHash('md5')
.update(buffer)
.digest('base64');
obj.buffer = buffer;
buffer = null;
return obj;
};
var simpleStatic = function (options) {
var dir = path_1.normalize(options.dir);
var files = {};
fs_readdir_recursive_1.default(dir).forEach(function (name) {
loadFile(name, dir, options, files);
});
// eslint-disable-next-line max-statements
return function (ctx, next) { return __awaiter(void 0, void 0, void 0, function () {
var filename, file, hasIndex, _1, fullPath, s, err_1, stats, acceptGzip, shouldGzip, _a, stream, hash_1;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!!['HEAD', 'GET'].includes(ctx.method)) return [3 /*break*/, 2];
return [4 /*yield*/, next()];
case 1:
_b.sent();
return [2 /*return*/];
case 2:
if (!!ctx.path.startsWith(prefix)) return [3 /*break*/, 4];
return [4 /*yield*/, next()];
case 3:
_b.sent();
return [2 /*return*/];
case 4:
if (options.extraHeaders &&
Array.isArray(options.extraHeaders) &&
options.extraHeaders.length) {
options.extraHeaders.forEach(function (header) {
// eslint-disable-next-line fp/no-loops, guard-for-in
for (var h in header) {
// eslint-disable-line guard-for-in
ctx.append(h, header[h]);
}
});
}
filename = zeelib_1.safeDecodeURIComponent(path_1.normalize(ctx.path));
file = files[filename];
if (!!file) return [3 /*break*/, 20];
if (!path_1.basename(filename).startsWith('.')) return [3 /*break*/, 6];
return [4 /*yield*/, next()];
case 5:
_b.sent();
return [2 /*return*/];
case 6:
hasIndex = false;
_b.label = 7;
case 7:
_b.trys.push([7, 9, , 10]);
return [4 /*yield*/, fs_1.statSync(path_1.normalize(path_1.join(dir, filename + "/index.html"))).isFile()
/* eslint-enable @typescript-eslint/await-thenable */
];
case 8:
/* eslint-disable @typescript-eslint/await-thenable */
// @ts-ignore isFile is not in the types
hasIndex = _b.sent();
return [3 /*break*/, 10];
case 9:
_1 = _b.sent();
return [3 /*break*/, 10];
case 10:
if (hasIndex) {
filename = filename + "/index.html";
}
if (filename.startsWith(path_1.sep)) {
filename = filename.slice(1);
}
fullPath = path_1.join(dir, filename);
if (!(!fullPath.startsWith(dir) && fullPath !== 'index.html')) return [3 /*break*/, 12];
return [4 /*yield*/, next()];
case 11:
_b.sent();
return [2 /*return*/];
case 12:
s = void 0;
_b.label = 13;
case 13:
_b.trys.push([13, 15, , 17]);
return [4 /*yield*/, fs_1.statSync(path_1.join(dir, filename))];
case 14:
// eslint-disable-next-line @typescript-eslint/await-thenable
s = _b.sent();
return [3 /*break*/, 17];
case 15:
err_1 = _b.sent();
return [4 /*yield*/, next()];
case 16:
_b.sent();
return [2 /*return*/];
case 17:
if (!!s.isFile()) return [3 /*break*/, 19];
return [4 /*yield*/, next()];
case 18:
_b.sent();
return [2 /*return*/];
case 19:
file = loadFile(filename, dir, options, files);
_b.label = 20;
case 20:
ctx.status = 200;
ctx.vary('Accept-Encoding');
if (!!file.buffer) return [3 /*break*/, 22];
return [4 /*yield*/, fs_1.statSync(file.path)];
case 21:
stats = _b.sent();
if (stats.mtime > file.mtime) {
file.mtime = stats.mtime;
file.md5 = null;
file.length = stats.size;
}
_b.label = 22;
case 22:
ctx.response.lastModified = file.mtime;
if (file.md5) {
ctx.response.etag = file.md5;
}
if (ctx.fresh) {
ctx.status = 304;
return [2 /*return*/];
}
ctx.type = file.type;
ctx.length = file.zipBuffer ? file.zipBuffer.length : file.length;
ctx.set('cache-control', "public, max-age=" + file.maxAge);
if (file.md5) {
ctx.set('content-md5', file.md5);
}
if (ctx.method === 'HEAD') {
return [2 /*return*/];
}
acceptGzip = ctx.acceptsEncodings('gzip') === 'gzip';
if (file.zipBuffer) {
if (acceptGzip) {
ctx.set('content-encoding', 'gzip');
ctx.body = file.zipBuffer;
}
else {
ctx.body = file.buffer;
}
return [2 /*return*/];
}
shouldGzip = file.length > 1024 && acceptGzip && compressible_1.default(file.type);
if (!file.buffer) return [3 /*break*/, 26];
if (!shouldGzip) return [3 /*break*/, 24];
_a = file;
return [4 /*yield*/, zlib_1.gzip(file.buffer)];
case 23:
_a.zipBuffer = _b.sent();
ctx.set('content-encoding', 'gzip');
ctx.body = file.zipBuffer;
return [3 /*break*/, 25];
case 24:
ctx.body = file.buffer;
_b.label = 25;
case 25: return [2 /*return*/];
case 26: return [4 /*yield*/, fs_1.createReadStream(file.path)
// update file hash
];
case 27:
stream = _b.sent();
// update file hash
if (!file.md5) {
hash_1 = crypto_1.createHash('md5');
stream.on('data', hash_1.update.bind(hash_1));
stream.on('end', function () {
file.md5 = hash_1.digest('base64');
});
}
ctx.body = stream;
// enable gzip will remove content length
if (shouldGzip) {
ctx.remove('content-length');
ctx.set('content-encoding', 'gzip');
ctx.body = stream.pipe(zlib_1.createGzip());
}
return [2 /*return*/];
}
});
}); };
};
exports.default = simpleStatic;
{
"name": "koa-simple-static",
"description": "Simple caching static file server for Koa 2.",
"version": "3.0.3",
"version": "4.0.0",
"author": {

@@ -17,14 +17,13 @@ "email": "zac@zacanger.com",

"scripts": {
"build": "babel src/index.js --out-file lib/index.js",
"_test": "run-s -s test:lint test:ts cover",
"build": "tsc",
"clean": "rm -rf lib && mkdir lib",
"cover": "npm run cover:flow && npm run cover:tape",
"cover:flow": "flow-coverage-report --config .flow-coverage-report.json",
"cover:tape": "nyc npm run test:tape",
"postbuild": "flow-copy-source -i \"*.test.js\" src lib",
"prebuild": "npm run test:lint && npm run cover && npm run clean && sortpack",
"cover": "nyc npm run test:tape",
"prebuild": "run-s -s test clean",
"preversion": "npm run build",
"test": "npm run test:lint && npm run test:flow && npm run test:tape",
"test:flow": "flow check",
"test:lint": "eslint -c .eslintrc.json src",
"test:tape": "babel-tape-runner test.js | tap-spec"
"test": "run-s -s test:lint test:ts test:tape",
"test-server": "node test-server",
"test:lint": "eslint --ext .ts -c .eslintrc.json src",
"test:tape": "ts-node src/test.ts | tap-spec",
"test:ts": "tsc --noEmit"
},

@@ -51,38 +50,35 @@ "homepage": "https://github.com/zacanger/koa-simple-static#readme",

"dependencies": {
"compressible": "2.0.17",
"compressible": "2.0.18",
"fs-readdir-recursive": "1.1.0",
"mime-types": "2.1.24",
"mime-types": "2.1.26",
"mz": "2.7.0",
"zeelib": "8.6.0"
"zeelib": "11.0.9"
},
"devDependencies": {
"@babel/cli": "7.4.4",
"@babel/core": "7.4.4",
"@babel/preset-env": "7.4.4",
"@babel/preset-flow": "7.0.0",
"@babel/register": "7.4.4",
"babel-eslint": "10.0.1",
"babel-plugin-add-module-exports": "1.0.2",
"babel-tape-runner": "3.0.0",
"eslint": "5.16.0",
"eslint-config-zacanger": "3.4.2",
"eslint-plugin-babel": "5.3.0",
"eslint-plugin-flowtype": "3.9.0",
"eslint-plugin-import": "2.17.2",
"eslint-plugin-node": "9.0.1",
"eslint-plugin-promise": "4.1.1",
"eslint-plugin-unicorn": "8.0.2",
"flow-bin": "0.77.0",
"flow-copy-source": "2.0.6",
"flow-coverage-report": "0.6.1",
"@types/compressible": "2.0.0",
"@types/fs-readdir-recursive": "1.0.0",
"@types/koa": "2.11.0",
"@types/mime-types": "2.1.0",
"@types/mz": "2.7.0",
"@types/node": "13.1.8",
"@types/supertest": "2.0.8",
"@types/tape": "4.2.33",
"eslint": "6.8.0",
"eslint-plugin-zacanger": "1.0.0",
"husky": "4.0.10",
"istanbul": "0.4.5",
"koa": "2.7.0",
"nyc": "14.1.1",
"sortpack": "1.1.6",
"koa": "2.11.0",
"lint-staged": "10.0.1",
"npm-run-all": "4.1.5",
"nyc": "15.0.0",
"prettier": "1.19.1",
"sortpack": "2.0.4",
"supertest": "4.0.2",
"tap-spec": "5.0.0",
"tape": "4.10.1"
"tape": "4.13.0",
"ts-node": "8.6.2",
"typescript": "3.7.5"
},
"engines": {
"node": ">=10.0.0"
"node": ">=12.0.0"
},

@@ -100,3 +96,3 @@ "nyc": {

"require": [
"@babel/register"
"ts-node/register"
],

@@ -109,3 +105,16 @@ "sourceMap": true

"url": "https://ko-fi.com/zacanger"
},
"lint-staged": {
"*.ts": [
"prettier --write"
],
"package.json": [
"sortpack"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}

@@ -5,3 +5,3 @@ # Koa Simple Static

[![Known Vulnerabilities](https://snyk.io/test/github/zacanger/koa-simple-static/badge.svg)](https://snyk.io/test/github/zacanger/koa-simple-static) [![ko-fi](https://img.shields.io/badge/donate-KoFi-yellow.svg)](https://ko-fi.com/U7U2110VB) [![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://www.patreon.com/zacanger) [![Support with PayPal](https://img.shields.io/badge/paypal-donate-yellow.png)](https://paypal.me/zacanger)
[![CircleCI](https://circleci.com/gh/zacanger/koa-simple-static.svg?style=svg)](https://circleci.com/gh/zacanger/koa-simple-static) [![codecov](https://codecov.io/gh/zacanger/koa-simple-static/branch/master/graph/badge.svg)](https://codecov.io/gh/zacanger/koa-simple-static) [![Maintainability](https://api.codeclimate.com/v1/badges/56ce56310829afe0d717/maintainability)](https://codeclimate.com/github/zacanger/koa-simple-static/maintainability) [![Known Vulnerabilities](https://snyk.io/test/github/zacanger/koa-simple-static/badge.svg)](https://snyk.io/test/github/zacanger/koa-simple-static) [![ko-fi](https://img.shields.io/badge/donate-KoFi-yellow.svg)](https://ko-fi.com/U7U2110VB) [![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://www.patreon.com/zacanger) [![Support with PayPal](https://img.shields.io/badge/paypal-donate-yellow.png)](https://paypal.me/zacanger)

@@ -21,3 +21,3 @@ --------

```
npm i -S koa-simple-static
npm i koa-simple-static
```

@@ -35,5 +35,5 @@

* `dir: str` &mdash; directory you want to serve
* `maxAge: ?number = 0` &mdash; cache control max age (in seconds)
* `extraHeaders: ?Object[]` &mdash; any extra headers you wish to set for requests served by this module
* `dir: string` &mdash; directory you want to serve
* `maxAge?: number = 0` &mdash; cache control max age (in seconds)
* `extraHeaders?: Object[]` &mdash; any extra headers you wish to set for requests served by this module
* The format for this is `[ { 'Link': '</foo.js>; rel=preload; as=script' }, { 'Set-Cookie': 'foo=bar; path=/;' } ]`

@@ -56,6 +56,10 @@

app.listen(port)
console.log(`Serving on ${port}!`)
app.listen(port, () => {
console.log(`Serving on ${port}!`)
})
```
**Important** if you're using `require`, you'll need to
`require('koa-simple-static').default`.
## FAQ

@@ -68,15 +72,4 @@

* I'm getting errors but my code is fine?
* If you're on Node pre-8.0.0, you'll need to use Babel in front of your server.
Example:
```javascript
require('babel-register')({
babelrc: false,
presets: [ require('babel-preset-latest-minimal') ]
})
require('./server')
```
* I recommend using `babel-register` in development and compiling for
production.
* Is this production-ready?
* Yes.
* How old is your version of Node? You'll need to be on the versions supported
in the `engines` field, or else Babelify or otherwise compile your server.

@@ -86,4 +79,4 @@ ## Contributing

* Issues and PRs are welcome! Please keep in mind that this is feature complete.
* This project uses Flow. It will be easiest if your editor is configured to
work with `eslint` and `flow`.
* This project uses TypeScript. It will be easiest if your editor is configured to
work with `eslint` and `tsc`.
* Please run tests!

@@ -90,0 +83,0 @@ * If you're changing or adding functionality, please _add_ tests!

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc