Add back support for accepts without using npm accepts
This commit is contained in:
parent
4078dc1182
commit
141d91b216
10 changed files with 517 additions and 21 deletions
|
@ -1 +1,3 @@
|
|||
extends: koa
|
||||
rules:
|
||||
operator-linebreak: [error, before]
|
||||
|
|
79
lib/accepts.js
Normal file
79
lib/accepts.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
const getMimetype = require('./getmimetype');
|
||||
|
||||
module.exports = function accepts(ctx, type, ask) {
|
||||
if (!ctx._accept) {
|
||||
ctx._accept = {};
|
||||
}
|
||||
if (!ctx._accept[type]) {
|
||||
let types = ctx.req.headers[type];
|
||||
let quality = 9999; // Little bit of a hack :)
|
||||
if (types) {
|
||||
types = types.split(',')
|
||||
.map(x => {
|
||||
x = x.trim();
|
||||
let q = quality--;
|
||||
if (x.indexOf('q=') >= 0) {
|
||||
q = parseFloat(x.substr(x.indexOf('q=') + 2)) || 1;
|
||||
x = x.substr(0, x.indexOf(';'));
|
||||
}
|
||||
return [x, q];
|
||||
})
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(x => x[0]);
|
||||
} else {
|
||||
types = [];
|
||||
}
|
||||
|
||||
if (type === 'accept-encoding') {
|
||||
types.push('identity');
|
||||
}
|
||||
|
||||
ctx._accept[type] = types;
|
||||
}
|
||||
|
||||
let can = ctx._accept[type];
|
||||
|
||||
// If empty argument, return all supported can
|
||||
if (ask.length === 0) {
|
||||
return can;
|
||||
}
|
||||
|
||||
// If no supported was sent, return the first ask item
|
||||
if (!can.length) {
|
||||
return ask[0];
|
||||
}
|
||||
|
||||
let parsed = ask.slice();
|
||||
|
||||
if (type === 'accept') {
|
||||
for (let t = 0; t < parsed.length; t++) {
|
||||
parsed[t] = getMimetype(parsed[t]) || parsed[t];
|
||||
}
|
||||
}
|
||||
|
||||
// Loop over the supported can, returning the first
|
||||
// matching ask type.
|
||||
for (let i = 0; i < can.length; i++) {
|
||||
for (let t = 0; t < parsed.length; t++) {
|
||||
// Check if we allow root checking (application/*)
|
||||
if (type === 'accept') {
|
||||
let allowRoot = can[i].indexOf('/*') >= 0;
|
||||
|
||||
// Big if :)
|
||||
if (can[i] === '*/*'
|
||||
|| can[i].indexOf(parsed[t]) >= 0
|
||||
|| (allowRoot
|
||||
&& parsed[t].indexOf('/') >= 0
|
||||
&& can[i].split('/')[0] === parsed[t].split('/')[0]
|
||||
)) {
|
||||
return ask[t];
|
||||
}
|
||||
} else {
|
||||
if (can[i] === parsed[t]) {
|
||||
return ask[t];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
|
@ -195,6 +195,10 @@ delegate(proto, 'response')
|
|||
*/
|
||||
|
||||
delegate(proto, 'request')
|
||||
.method('acceptsLanguages')
|
||||
.method('acceptsEncodings')
|
||||
.method('acceptsCharsets')
|
||||
.method('accepts')
|
||||
.method('get')
|
||||
.method('is')
|
||||
.access('querystring')
|
||||
|
|
25
lib/getmimetype.js
Normal file
25
lib/getmimetype.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
module.exports = function getMimetype(type, includeCharset) {
|
||||
let charset = includeCharset ? '; charset=utf-8' : '';
|
||||
|
||||
if (type.indexOf('json') >= 0 || type.indexOf('css.map') >= 0 || type.indexOf('js.map') >= 0) {
|
||||
return 'application/json' + charset;
|
||||
} else if (type.indexOf('html') >= 0) {
|
||||
return 'text/html' + charset;
|
||||
} else if (type.indexOf('css') >= 0) {
|
||||
return 'text/css' + charset;
|
||||
} else if (type.indexOf('js') >= 0 || type.indexOf('javascript') >= 0) {
|
||||
return 'application/javascript' + charset;
|
||||
} else if (type.indexOf('png') >= 0) {
|
||||
return 'image/png';
|
||||
} else if (type.indexOf('jpg') >= 0) {
|
||||
return 'image/jpeg';
|
||||
} else if (type.indexOf('jpeg') >= 0) {
|
||||
return 'image/jpeg';
|
||||
} else if (type.indexOf('gif') >= 0) {
|
||||
return 'image/gif';
|
||||
} else if (type.indexOf('text') >= 0 || type.indexOf('txt') >= 0) {
|
||||
return 'text/plain' + charset;
|
||||
} else if (type.indexOf('bin') >= 0) {
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
};
|
159
lib/request.js
159
lib/request.js
|
@ -13,6 +13,7 @@ const qs = require('querystring');
|
|||
const typeis = require('type-is');
|
||||
const fresh = require('fresh');
|
||||
const util = require('util');
|
||||
const accepts = require('./accepts');
|
||||
|
||||
const IP = Symbol('context#ip');
|
||||
|
||||
|
@ -472,6 +473,164 @@ module.exports = {
|
|||
.slice(offset);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get accept object.
|
||||
* Lazily memoized.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
accept(type, ) {
|
||||
if (!this._accept) {
|
||||
let types = this.req.headers.accept;
|
||||
if (types) {
|
||||
types = types.split(',')
|
||||
.map(x => {
|
||||
x = x.trim();
|
||||
let q = 1;
|
||||
if (x.indexOf('q=') >= 0) {
|
||||
q = parseFloat(x.substr(x.indexOf('q=') + 2)) || 1;
|
||||
x = x.substr(0, x.indexOf(';'));
|
||||
}
|
||||
return [x, q];
|
||||
})
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(x => x[0]);
|
||||
} else {
|
||||
types = [];
|
||||
}
|
||||
|
||||
this._accept = {
|
||||
types: types
|
||||
};
|
||||
}
|
||||
return this._accept;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
let types = [...args];
|
||||
|
||||
// If passed an array, grab it
|
||||
if (types[0] instanceof Array) {
|
||||
types = types[0];
|
||||
}
|
||||
|
||||
return accepts(this, 'accept', types);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
let types = [...args];
|
||||
|
||||
// If passed an array, grab it
|
||||
if (types[0] instanceof Array) {
|
||||
types = types[0];
|
||||
}
|
||||
|
||||
return accepts(this, 'accept-encoding', types);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
let types = [...args];
|
||||
|
||||
// If passed an array, grab it
|
||||
if (types[0] instanceof Array) {
|
||||
types = types[0];
|
||||
}
|
||||
|
||||
return accepts(this, 'accept-charset', types);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
let types = [...args];
|
||||
|
||||
// If passed an array, grab it
|
||||
if (types[0] instanceof Array) {
|
||||
types = types[0];
|
||||
}
|
||||
|
||||
return accepts(this, 'accept-language', types);
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the incoming request contains the "Content-Type"
|
||||
* header field, and it contains any of the give mime `type`s.
|
||||
|
|
|
@ -13,6 +13,7 @@ const typeis = require('type-is').is;
|
|||
const statuses = require('./statuses');
|
||||
const assert = require('assert');
|
||||
const extname = require('path').extname;
|
||||
const getMimetype = require('./getmimetype');
|
||||
const util = require('util');
|
||||
|
||||
/**
|
||||
|
@ -344,29 +345,14 @@ module.exports = {
|
|||
type += '; charset=utf-8';
|
||||
}
|
||||
this.set('Content-Type', type);
|
||||
} else if (type.indexOf('json') >= 0 || type.indexOf('css.map') >= 0 || type.indexOf('js.map') >= 0) {
|
||||
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') >= 0) {
|
||||
this.set('Content-Type', 'text/plain; charset=utf-8');
|
||||
} else if (type.indexOf('bin') >= 0) {
|
||||
this.set('Content-Type', 'application/octet-stream');
|
||||
} else {
|
||||
let mimetype = getMimetype(type, true);
|
||||
if (mimetype) {
|
||||
this.set('Content-Type', mimetype);
|
||||
} else {
|
||||
this.remove('Content-Type');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
94
test/request/accepts.js
Normal file
94
test/request/accepts.js
Normal file
|
@ -0,0 +1,94 @@
|
|||
|
||||
'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);
|
||||
});
|
||||
});
|
||||
});
|
52
test/request/acceptsCharsets.js
Normal file
52
test/request/acceptsCharsets.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
|
||||
'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');
|
||||
});
|
||||
});
|
||||
});
|
43
test/request/acceptsEncodings.js
Normal file
43
test/request/acceptsEncodings.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
|
||||
'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');
|
||||
});
|
||||
});
|
||||
});
|
52
test/request/acceptsLanguages.js
Normal file
52
test/request/acceptsLanguages.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
|
||||
'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');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue