Remove accepts, cache-content-type and content-type.

Replace content-disposition with one that doesn’t use safe-buffer
This commit is contained in:
Jonatan Nilsson 2019-09-29 19:17:14 +00:00
parent a0d2816cba
commit 2ef7846b5f
13 changed files with 31 additions and 604 deletions

View file

@ -103,17 +103,6 @@ Koa's `Request` object provides helpful methods for working with
http requests which delegate to an [IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
from the node `http` module.
Here is an example of checking that a requesting client supports xml.
```js
app.use(async (ctx, next) => {
ctx.assert(ctx.request.accepts('xml'), 406);
// equivalent to:
// if (!ctx.request.accepts('xml')) ctx.throw(406);
await next();
});
```
Koa provides a `Response` object as the `response` property of the `Context`.
Koa's `Response` object provides helpful methods for working with
http responses which delegate to a [ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse)
@ -136,8 +125,7 @@ app.use(async (ctx, next) => {
```
The `Context` object also provides shortcuts for methods on its `request` and `response`. In the prior
examples, `ctx.type` can be used instead of `ctx.response.type` and `ctx.accepts` can be used
instead of `ctx.request.accepts`.
examples, `ctx.type` can be used instead of `ctx.response.type`.
For more information on `Request`, `Response` and `Context`, see the [Request API Reference](docs/api/request.md),
[Response API Reference](docs/api/response.md) and [Context API Reference](docs/api/context.md).

View file

@ -172,10 +172,6 @@ Koa uses [http-assert](https://github.com/jshttp/http-assert) for assertions.
- `ctx.ips`
- `ctx.subdomains`
- `ctx.is()`
- `ctx.accepts()`
- `ctx.acceptsEncodings()`
- `ctx.acceptsCharsets()`
- `ctx.acceptsLanguages()`
- `ctx.get()`
## Response aliases

View file

@ -241,141 +241,6 @@ if (ctx.is('image/*')) {
}
```
### Content Negotiation
Koa's `request` object includes helpful content negotiation utilities powered by [accepts](http://github.com/expressjs/accepts) and [negotiator](https://github.com/federomero/negotiator). These utilities are:
- `request.accepts(types)`
- `request.acceptsEncodings(types)`
- `request.acceptsCharsets(charsets)`
- `request.acceptsLanguages(langs)`
If no types are supplied, __all__ acceptable types are returned.
If multiple types are supplied, the best match will be returned. If no matches are found, a `false` is returned, and you should send a `406 "Not Acceptable"` response to the client.
In the case of missing accept headers where any type is acceptable, the first type will be returned. Thus, the order of types you supply is important.
### request.accepts(types)
Check if the given `type(s)` is acceptable, returning the best match when true, otherwise `false`. The `type` value may be one or more mime type string
such as "application/json", the extension name
such as "json", or an array `["json", "html", "text/plain"]`.
```js
// Accept: text/html
ctx.accepts('html');
// => "html"
// Accept: text/*, application/json
ctx.accepts('html');
// => "html"
ctx.accepts('text/html');
// => "text/html"
ctx.accepts('json', 'text');
// => "json"
ctx.accepts('application/json');
// => "application/json"
// Accept: text/*, application/json
ctx.accepts('image/png');
ctx.accepts('png');
// => false
// Accept: text/*;q=.5, application/json
ctx.accepts(['html', 'json']);
ctx.accepts('html', 'json');
// => "json"
// No Accept header
ctx.accepts('html', 'json');
// => "html"
ctx.accepts('json', 'html');
// => "json"
```
You may call `ctx.accepts()` as many times as you like,
or use a switch:
```js
switch (ctx.accepts('json', 'html', 'text')) {
case 'json': break;
case 'html': break;
case 'text': break;
default: ctx.throw(406, 'json, html, or text only');
}
```
### request.acceptsEncodings(encodings)
Check if `encodings` are acceptable, returning the best match when true, otherwise `false`. Note that you should include `identity` as one of the encodings!
```js
// Accept-Encoding: gzip
ctx.acceptsEncodings('gzip', 'deflate', 'identity');
// => "gzip"
ctx.acceptsEncodings(['gzip', 'deflate', 'identity']);
// => "gzip"
```
When no arguments are given all accepted encodings
are returned as an array:
```js
// Accept-Encoding: gzip, deflate
ctx.acceptsEncodings();
// => ["gzip", "deflate", "identity"]
```
Note that the `identity` encoding (which means no encoding) could be unacceptable if the client explicitly sends `identity;q=0`. Although this is an edge case, you should still handle the case where this method returns `false`.
### request.acceptsCharsets(charsets)
Check if `charsets` are acceptable, returning
the best match when true, otherwise `false`.
```js
// Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5
ctx.acceptsCharsets('utf-8', 'utf-7');
// => "utf-8"
ctx.acceptsCharsets(['utf-7', 'utf-8']);
// => "utf-8"
```
When no arguments are given all accepted charsets
are returned as an array:
```js
// Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5
ctx.acceptsCharsets();
// => ["utf-8", "utf-7", "iso-8859-1"]
```
### request.acceptsLanguages(langs)
Check if `langs` are acceptable, returning
the best match when true, otherwise `false`.
```js
// Accept-Language: en;q=0.8, es, pt
ctx.acceptsLanguages('es', 'en');
// => "es"
ctx.acceptsLanguages(['en', 'es']);
// => "es"
```
When no arguments are given all accepted languages
are returned as an array:
```js
// Accept-Language: en;q=0.8, es, pt
ctx.acceptsLanguages();
// => ["es", "pt", "en"]
```
### request.idempotent
Check if the request is idempotent.

View file

@ -211,10 +211,6 @@ delegate(proto, 'response')
*/
delegate(proto, 'request')
.method('acceptsLanguages')
.method('acceptsEncodings')
.method('acceptsCharsets')
.method('accepts')
.method('get')
.method('is')
.access('querystring')

View file

@ -7,8 +7,6 @@
const URL = require('url').URL;
const net = require('net');
const accepts = require('accepts');
const contentType = require('content-type');
const stringify = require('url').format;
const parse = require('parseurl');
const qs = require('querystring');
@ -357,22 +355,6 @@ module.exports = {
return this.req.socket;
},
/**
* Get the charset when present or undefined.
*
* @return {String}
* @api public
*/
get charset() {
try {
const { parameters } = contentType.parse(this.req);
return parameters.charset || '';
} catch (e) {
return '';
}
},
/**
* Return parsed Content-Length when present.
*
@ -484,123 +466,6 @@ module.exports = {
.slice(offset);
},
/**
* Get accept object.
* Lazily memoized.
*
* @return {Object}
* @api private
*/
get accept() {
return this._accept || (this._accept = accepts(this.req));
},
/**
* Set accept object.
*
* @param {Object}
* @api private
*/
set accept(obj) {
this._accept = obj;
},
/**
* Check if the given `type(s)` is acceptable, returning
* the best match when true, otherwise `false`, in which
* case you should respond with 406 "Not Acceptable".
*
* The `type` value may be a single mime type string
* such as "application/json", the extension name
* such as "json" or an array `["json", "html", "text/plain"]`. When a list
* or array is given the _best_ match, if any is returned.
*
* Examples:
*
* // Accept: text/html
* this.accepts('html');
* // => "html"
*
* // Accept: text/*, application/json
* this.accepts('html');
* // => "html"
* this.accepts('text/html');
* // => "text/html"
* this.accepts('json', 'text');
* // => "json"
* this.accepts('application/json');
* // => "application/json"
*
* // Accept: text/*, application/json
* this.accepts('image/png');
* this.accepts('png');
* // => false
*
* // Accept: text/*;q=.5, application/json
* this.accepts(['html', 'json']);
* this.accepts('html', 'json');
* // => "json"
*
* @param {String|Array} type(s)...
* @return {String|Array|false}
* @api public
*/
accepts(...args) {
return this.accept.types(...args);
},
/**
* Return accepted encodings or best fit based on `encodings`.
*
* Given `Accept-Encoding: gzip, deflate`
* an array sorted by quality is returned:
*
* ['gzip', 'deflate']
*
* @param {String|Array} encoding(s)...
* @return {String|Array}
* @api public
*/
acceptsEncodings(...args) {
return this.accept.encodings(...args);
},
/**
* Return accepted charsets or best fit based on `charsets`.
*
* Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`
* an array sorted by quality is returned:
*
* ['utf-8', 'utf-7', 'iso-8859-1']
*
* @param {String|Array} charset(s)...
* @return {String|Array}
* @api public
*/
acceptsCharsets(...args) {
return this.accept.charsets(...args);
},
/**
* Return accepted languages or best fit based on `langs`.
*
* Given `Accept-Language: en;q=0.8, es, pt`
* an array sorted by quality is returned:
*
* ['es', 'pt', 'en']
*
* @param {String|Array} lang(s)...
* @return {Array|String}
* @api public
*/
acceptsLanguages(...args) {
return this.accept.languages(...args);
},
/**
* Check if the incoming request contains the "Content-Type"
* header field, and it contains any of the give mime `type`s.

View file

@ -7,7 +7,6 @@
const contentDisposition = require('content-disposition');
const ensureErrorHandler = require('error-inject');
const getType = require('cache-content-type');
const onFinish = require('on-finished');
const isJSON = require('koa-is-json');
const escape = require('escape-html');
@ -267,7 +266,7 @@ module.exports = {
if (!statuses.redirect[this.status]) this.status = 302;
// html
if (this.ctx.accepts('html')) {
if (this.ctx.headers.accept && this.ctx.headers.accept.indexOf('html') >= 0) {
url = escape(url);
this.type = 'text/html; charset=utf-8';
this.body = `Redirecting to <a href="${url}">${url}</a>.`;
@ -307,10 +306,34 @@ module.exports = {
* @api public
*/
set type(type) {
type = getType(type);
if (type) {
set type(orgType) {
let type = orgType
// If full type is specified, pass it straight on.
// Otherwise we do some basic checking for most common
// supported mime types.
if (type.indexOf('/') > 0 || type.indexOf(';') > 0) {
if (type.indexOf(';') === -1 && type.indexOf('text') >= 0) {
type += '; charset=utf-8'
}
this.set('Content-Type', type);
} else if (type.indexOf('json')) {
this.set('Content-Type', 'application/json; charset=utf-8');
} else if (type.indexOf('html') => 0) {
this.set('Content-Type', 'text/html; charset=utf-8');
} else if (type.indexOf('css') => 0) {
this.set('Content-Type', 'text/css; charset=utf-8');
} else if (type.indexOf('js') => 0 || type.indexOf('javascript') => 0) {
this.set('Content-Type', 'application/javascript; charset=utf-8');
} else if (type.indexOf('png') => 0) {
this.set('Content-Type', 'image/png');
} else if (type.indexOf('jpg') => 0) {
this.set('Content-Type', 'image/jpeg');
} else if (type.indexOf('jpeg') => 0) {
this.set('Content-Type', 'image/jpeg');
} else if (type.indexOf('gif') => 0) {
this.set('Content-Type', 'image/gif');
} else if (type.indexOf('text')) {
this.set('Content-Type', 'text/plain; charset=utf-8');
} else {
this.remove('Content-Type');
}

View file

@ -22,10 +22,8 @@
],
"license": "MIT",
"dependencies": {
"accepts": "^1.3.5",
"cache-content-type": "^1.0.0",
"content-disposition": "~0.5.2",
"content-type": "^1.0.4",
"content-disposition": "jharrilim/content-disposition#572383f
",
"cookies": "~0.7.1",
"debug": "~3.1.0",
"delegates": "^1.0.0",

View file

@ -1,27 +0,0 @@
'use strict';
const Accept = require('accepts');
const assert = require('assert');
const context = require('../helpers/context');
describe('ctx.accept', () => {
it('should return an Accept instance', () => {
const ctx = context();
ctx.req.headers.accept = 'application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain';
assert(ctx.accept instanceof Accept);
});
});
describe('ctx.accept=', () => {
it('should replace the accept object', () => {
const ctx = context();
ctx.req.headers.accept = 'text/plain';
assert.deepEqual(ctx.accepts(), ['text/plain']);
const request = context.request();
request.req.headers.accept = 'application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain';
ctx.accept = Accept(request.req);
assert.deepEqual(ctx.accepts(), ['text/html', 'text/plain', 'image/jpeg', 'application/*']);
});
});

View file

@ -1,94 +0,0 @@
'use strict';
const assert = require('assert');
const context = require('../helpers/context');
describe('ctx.accepts(types)', () => {
describe('with no arguments', () => {
describe('when Accept is populated', () => {
it('should return all accepted types', () => {
const ctx = context();
ctx.req.headers.accept = 'application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain';
assert.deepEqual(ctx.accepts(), ['text/html', 'text/plain', 'image/jpeg', 'application/*']);
});
});
});
describe('with no valid types', () => {
describe('when Accept is populated', () => {
it('should return false', () => {
const ctx = context();
ctx.req.headers.accept = 'application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain';
assert.equal(ctx.accepts('image/png', 'image/tiff'), false);
});
});
describe('when Accept is not populated', () => {
it('should return the first type', () => {
const ctx = context();
assert.equal(ctx.accepts('text/html', 'text/plain', 'image/jpeg', 'application/*'), 'text/html');
});
});
});
describe('when extensions are given', () => {
it('should convert to mime types', () => {
const ctx = context();
ctx.req.headers.accept = 'text/plain, text/html';
assert.equal(ctx.accepts('html'), 'html');
assert.equal(ctx.accepts('.html'), '.html');
assert.equal(ctx.accepts('txt'), 'txt');
assert.equal(ctx.accepts('.txt'), '.txt');
assert.equal(ctx.accepts('png'), false);
});
});
describe('when an array is given', () => {
it('should return the first match', () => {
const ctx = context();
ctx.req.headers.accept = 'text/plain, text/html';
assert.equal(ctx.accepts(['png', 'text', 'html']), 'text');
assert.equal(ctx.accepts(['png', 'html']), 'html');
});
});
describe('when multiple arguments are given', () => {
it('should return the first match', () => {
const ctx = context();
ctx.req.headers.accept = 'text/plain, text/html';
assert.equal(ctx.accepts('png', 'text', 'html'), 'text');
assert.equal(ctx.accepts('png', 'html'), 'html');
});
});
describe('when present in Accept as an exact match', () => {
it('should return the type', () => {
const ctx = context();
ctx.req.headers.accept = 'text/plain, text/html';
assert.equal(ctx.accepts('text/html'), 'text/html');
assert.equal(ctx.accepts('text/plain'), 'text/plain');
});
});
describe('when present in Accept as a type match', () => {
it('should return the type', () => {
const ctx = context();
ctx.req.headers.accept = 'application/json, */*';
assert.equal(ctx.accepts('text/html'), 'text/html');
assert.equal(ctx.accepts('text/plain'), 'text/plain');
assert.equal(ctx.accepts('image/png'), 'image/png');
});
});
describe('when present in Accept as a subtype match', () => {
it('should return the type', () => {
const ctx = context();
ctx.req.headers.accept = 'application/json, text/*';
assert.equal(ctx.accepts('text/html'), 'text/html');
assert.equal(ctx.accepts('text/plain'), 'text/plain');
assert.equal(ctx.accepts('image/png'), false);
assert.equal(ctx.accepts('png'), false);
});
});
});

View file

@ -1,52 +0,0 @@
'use strict';
const assert = require('assert');
const context = require('../helpers/context');
describe('ctx.acceptsCharsets()', () => {
describe('with no arguments', () => {
describe('when Accept-Charset is populated', () => {
it('should return accepted types', () => {
const ctx = context();
ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5';
assert.deepEqual(ctx.acceptsCharsets(), ['utf-8', 'utf-7', 'iso-8859-1']);
});
});
});
describe('with multiple arguments', () => {
describe('when Accept-Charset is populated', () => {
describe('if any types match', () => {
it('should return the best fit', () => {
const ctx = context();
ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5';
assert.equal(ctx.acceptsCharsets('utf-7', 'utf-8'), 'utf-8');
});
});
describe('if no types match', () => {
it('should return false', () => {
const ctx = context();
ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5';
assert.equal(ctx.acceptsCharsets('utf-16'), false);
});
});
});
describe('when Accept-Charset is not populated', () => {
it('should return the first type', () => {
const ctx = context();
assert.equal(ctx.acceptsCharsets('utf-7', 'utf-8'), 'utf-7');
});
});
});
describe('with an array', () => {
it('should return the best fit', () => {
const ctx = context();
ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5';
assert.equal(ctx.acceptsCharsets(['utf-7', 'utf-8']), 'utf-8');
});
});
});

View file

@ -1,43 +0,0 @@
'use strict';
const assert = require('assert');
const context = require('../helpers/context');
describe('ctx.acceptsEncodings()', () => {
describe('with no arguments', () => {
describe('when Accept-Encoding is populated', () => {
it('should return accepted types', () => {
const ctx = context();
ctx.req.headers['accept-encoding'] = 'gzip, compress;q=0.2';
assert.deepEqual(ctx.acceptsEncodings(), ['gzip', 'compress', 'identity']);
assert.equal(ctx.acceptsEncodings('gzip', 'compress'), 'gzip');
});
});
describe('when Accept-Encoding is not populated', () => {
it('should return identity', () => {
const ctx = context();
assert.deepEqual(ctx.acceptsEncodings(), ['identity']);
assert.equal(ctx.acceptsEncodings('gzip', 'deflate', 'identity'), 'identity');
});
});
});
describe('with multiple arguments', () => {
it('should return the best fit', () => {
const ctx = context();
ctx.req.headers['accept-encoding'] = 'gzip, compress;q=0.2';
assert.equal(ctx.acceptsEncodings('compress', 'gzip'), 'gzip');
assert.equal(ctx.acceptsEncodings('gzip', 'compress'), 'gzip');
});
});
describe('with an array', () => {
it('should return the best fit', () => {
const ctx = context();
ctx.req.headers['accept-encoding'] = 'gzip, compress;q=0.2';
assert.equal(ctx.acceptsEncodings(['compress', 'gzip']), 'gzip');
});
});
});

View file

@ -1,52 +0,0 @@
'use strict';
const assert = require('assert');
const context = require('../helpers/context');
describe('ctx.acceptsLanguages(langs)', () => {
describe('with no arguments', () => {
describe('when Accept-Language is populated', () => {
it('should return accepted types', () => {
const ctx = context();
ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt';
assert.deepEqual(ctx.acceptsLanguages(), ['es', 'pt', 'en']);
});
});
});
describe('with multiple arguments', () => {
describe('when Accept-Language is populated', () => {
describe('if any types types match', () => {
it('should return the best fit', () => {
const ctx = context();
ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt';
assert.equal(ctx.acceptsLanguages('es', 'en'), 'es');
});
});
describe('if no types match', () => {
it('should return false', () => {
const ctx = context();
ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt';
assert.equal(ctx.acceptsLanguages('fr', 'au'), false);
});
});
});
describe('when Accept-Language is not populated', () => {
it('should return the first type', () => {
const ctx = context();
assert.equal(ctx.acceptsLanguages('es', 'en'), 'es');
});
});
});
describe('with an array', () => {
it('should return the best fit', () => {
const ctx = context();
ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt';
assert.equal(ctx.acceptsLanguages(['es', 'en']), 'es');
});
});
});

View file

@ -1,36 +0,0 @@
'use strict';
const request = require('../helpers/context').request;
const assert = require('assert');
describe('req.charset', () => {
describe('with no content-type present', () => {
it('should return ""', () => {
const req = request();
assert('' === req.charset);
});
});
describe('with charset present', () => {
it('should return ""', () => {
const req = request();
req.header['content-type'] = 'text/plain';
assert('' === req.charset);
});
});
describe('with a charset', () => {
it('should return the charset', () => {
const req = request();
req.header['content-type'] = 'text/plain; charset=utf-8';
assert.equal(req.charset, 'utf-8');
});
it('should return "" if content-type is invalid', () => {
const req = request();
req.header['content-type'] = 'application/json; application/text; charset=utf-8';
assert.equal(req.charset, '');
});
});
});