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

koa-send

Package Overview
Dependencies
Maintainers
9
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

koa-send - npm Package Compare versions

Comparing version 4.0.0 to 4.1.0

6

History.md
4.1.0 / 2017-04-26
==================
* Add support for Cache-Control: immutable with a new "immutable" option
* Added serving of brotli versions of files
4.0.0 / 2017-04-09

@@ -3,0 +9,0 @@ ==================

138

index.js

@@ -5,7 +5,7 @@ /**

const debug = require('debug')('koa-send');
const resolvePath = require('resolve-path');
const createError = require('http-errors');
const assert = require('assert');
const fs = require('mz/fs');
const debug = require('debug')('koa-send')
const resolvePath = require('resolve-path')
const createError = require('http-errors')
const assert = require('assert')
const fs = require('mz/fs')

@@ -19,3 +19,3 @@ const {

sep
} = require('path');
} = require('path')

@@ -26,3 +26,3 @@ /**

module.exports = send;
module.exports = send

@@ -40,18 +40,20 @@ /**

async function send(ctx, path, opts = {}) {
assert(ctx, 'koa context required');
assert(path, 'pathname required');
async function send (ctx, path, opts = {}) {
assert(ctx, 'koa context required')
assert(path, 'pathname required')
// options
debug('send "%s" %j', path, opts);
const root = opts.root ? normalize(resolve(opts.root)) : '';
const trailingSlash = '/' == path[path.length - 1];
path = path.substr(parse(path).root.length);
const index = opts.index;
const maxage = opts.maxage || opts.maxAge || 0;
const hidden = opts.hidden || false;
const format = opts.format === false ? false : true;
const extensions = Array.isArray(opts.extensions) ? opts.extensions : false;
const gzip = opts.gzip === false ? false : true;
const setHeaders = opts.setHeaders;
debug('send "%s" %j', path, opts)
const root = opts.root ? normalize(resolve(opts.root)) : ''
const trailingSlash = path[path.length - 1] === '/'
path = path.substr(parse(path).root.length)
const index = opts.index
const maxage = opts.maxage || opts.maxAge || 0
const immutable = opts.immutable || false
const hidden = opts.hidden || false
const format = opts.format !== false
const extensions = Array.isArray(opts.extensions) ? opts.extensions : false
const brotli = opts.brotli !== false
const gzip = opts.gzip !== false
const setHeaders = opts.setHeaders

@@ -62,35 +64,37 @@ if (setHeaders && typeof setHeaders !== 'function') {

const encoding = ctx.acceptsEncodings('gzip', 'deflate', 'identity');
// normalize path
path = decode(path);
path = decode(path)
if (-1 == path) return ctx.throw(400, 'failed to decode');
if (path === -1) return ctx.throw(400, 'failed to decode')
// index file support
if (index && trailingSlash) path += index;
if (index && trailingSlash) path += index
path = resolvePath(root, path);
path = resolvePath(root, path)
// hidden file support, ignore
if (!hidden && isHidden(root, path)) return;
if (!hidden && isHidden(root, path)) return
// serve gzipped file when possible
if (encoding === 'gzip' && gzip && (await fs.exists(path + '.gz'))) {
path = path + '.gz';
ctx.set('Content-Encoding', 'gzip');
ctx.res.removeHeader('Content-Length');
// serve brotli file when possible otherwise gzipped file when possible
if (ctx.acceptsEncodings('br', 'deflate', 'identity') === 'br' && brotli && (await fs.exists(path + '.br'))) {
path = path + '.br'
ctx.set('Content-Encoding', 'br')
ctx.res.removeHeader('Content-Length')
} else if (ctx.acceptsEncodings('gzip', 'deflate', 'identity') === 'gzip' && gzip && (await fs.exists(path + '.gz'))) {
path = path + '.gz'
ctx.set('Content-Encoding', 'gzip')
ctx.res.removeHeader('Content-Length')
}
if (extensions && !/\..*$/.exec(path)) {
const list = [].concat(extensions);
const list = [].concat(extensions)
for (let i = 0; i < list.length; i++) {
let ext = list[i];
let ext = list[i]
if (typeof ext !== 'string') {
throw new TypeError('option extensions must be array of strings or false');
throw new TypeError('option extensions must be array of strings or false')
}
if (!/^\./.exec(ext)) ext = '.' + ext;
if (!/^\./.exec(ext)) ext = '.' + ext
if (await fs.exists(path + ext)) {
path = path + ext;
break;
path = path + ext
break
}

@@ -103,3 +107,3 @@ }

try {
stats = await fs.stat(path);
stats = await fs.stat(path)

@@ -111,27 +115,33 @@ // Format the path to serve static file servers

if (format && index) {
path += '/' + index;
stats = await fs.stat(path);
path += '/' + index
stats = await fs.stat(path)
} else {
return;
return
}
}
} catch (err) {
const notfound = ['ENOENT', 'ENAMETOOLONG', 'ENOTDIR'];
const notfound = ['ENOENT', 'ENAMETOOLONG', 'ENOTDIR']
if (notfound.includes(err.code)) {
throw createError(404, err);
throw createError(404, err)
}
err.status = 500;
throw err;
err.status = 500
throw err
}
if (setHeaders) setHeaders(ctx.res, path, stats);
if (setHeaders) setHeaders(ctx.res, path, stats)
// stream
ctx.set('Content-Length', stats.size);
if (!ctx.response.get('Last-Modified')) ctx.set('Last-Modified', stats.mtime.toUTCString());
if (!ctx.response.get('Cache-Control')) ctx.set('Cache-Control', 'max-age=' + (maxage / 1000 | 0));
ctx.type = type(path);
ctx.body = fs.createReadStream(path);
ctx.set('Content-Length', stats.size)
if (!ctx.response.get('Last-Modified')) ctx.set('Last-Modified', stats.mtime.toUTCString())
if (!ctx.response.get('Cache-Control')) {
const directives = ['max-age=' + (maxage / 1000 | 0)]
if (immutable) {
directives.push('immutable')
}
ctx.set('Cache-Control', directives.join(','))
}
ctx.type = type(path)
ctx.body = fs.createReadStream(path)
return path;
return path
}

@@ -143,8 +153,8 @@

function isHidden(root, path) {
path = path.substr(root.length).split(sep);
for(let i = 0; i < path.length; i++) {
if(path[i][0] === '.') return true;
function isHidden (root, path) {
path = path.substr(root.length).split(sep)
for (let i = 0; i < path.length; i++) {
if (path[i][0] === '.') return true
}
return false;
return false
}

@@ -156,4 +166,4 @@

function type(file) {
return extname(basename(file, '.gz'));
function type (file) {
return extname(basename(file, '.gz'))
}

@@ -165,8 +175,8 @@

function decode(path) {
function decode (path) {
try {
return decodeURIComponent(path);
return decodeURIComponent(path)
} catch (err) {
return -1;
return -1
}
}

@@ -5,3 +5,3 @@ {

"repository": "koajs/send",
"version": "4.0.0",
"version": "4.1.0",
"keywords": [

@@ -17,2 +17,9 @@ "koa",

"devDependencies": {
"eslint": "^3.19.0",
"eslint-config-standard": "^10.2.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-node": "^4.2.2",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1",
"iltorb": "^1.2.1",
"istanbul": "0",

@@ -32,2 +39,3 @@ "koa": "2",

"scripts": {
"lint": "eslint --fix .",
"test": "mocha --require should --reporter spec",

@@ -34,0 +42,0 @@ "test-cov": "istanbul cover ./node_modules/.bin/_mocha -- --require should",

@@ -22,5 +22,7 @@

- `maxage` Browser cache max-age in milliseconds. (defaults to `0`)
- `immutable` Tell the browser the resource is immutable and can be cached indefinitely (defaults to `false`)
- `hidden` Allow transfer of hidden files. (defaults to `false`)
- [`root`](#root-path) Root directory to restrict file access
- `gzip` Try to serve the gzipped version of a file automatically when `gzip` is supported by a client and if the requested file with `.gz` extension exists. defaults to true.
- `brotli` Try to serve the brotli version of a file automatically when `brotli` is supported by a client and if the requested file with `.br` extension exists. defaults to true.
- `format` If not `false` (defaults to `true`), format the path to serve static file servers and not require a trailing slash for directories, so that you can do both `/directory` and `/directory/`

@@ -27,0 +29,0 @@ - [`setHeaders`](#setheaders) Function to set custom headers on response.

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