diff --git a/lib/accepts.js b/lib/accepts.js index 0f68167..cad2457 100644 --- a/lib/accepts.js +++ b/lib/accepts.js @@ -1,10 +1,12 @@ const getMimetype = require('./getmimetype'); -module.exports = function accepts(ctx, type, ask) { +module.exports = function accepts(ctx, type, ask, isReq = true) { if (!ctx._accept) { ctx._accept = {}; } - if (!ctx._accept[type]) { + + // We don't need to parse content-type + if (!ctx._accept[type] && type !== 'content-type') { let types = ctx.req.headers[type]; let quality = 9999; // Little bit of a hack :) if (types) { @@ -27,27 +29,56 @@ module.exports = function accepts(ctx, type, ask) { if (type === 'accept-encoding') { types.push('identity'); } - ctx._accept[type] = types; } - let can = ctx._accept[type]; + let can; + + if (type === 'content-type') { + if (isReq) { + // Check if a request has a request body. + // A request with a body __must__ either have `transfer-encoding` + // or `content-length` headers set. + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3 + if (ctx.req.headers['transfer-encoding'] === undefined + && isNaN(ctx.req.headers['content-length'])) { + return null; + } + can = ctx.req.headers[type]; + } else { + can = ctx.type; + } + } else { + can = ctx._accept[type]; + if (!can.length) can = null; + } // If empty argument, return all supported can - if (ask.length === 0) { - return can; + if (ask.length === 0 && can) { + return can || false; } // If no supported was sent, return the first ask item - if (!can.length) { + // unless we're dealing with content-type we need to be smarter. + if (!can) { + if (type === 'content-type') { + return false; + } return ask[0]; } let parsed = ask.slice(); - if (type === 'accept') { + if (type === 'accept' || type === 'content-type') { for (let t = 0; t < parsed.length; t++) { - parsed[t] = getMimetype(parsed[t]) || parsed[t]; + if (parsed[t].startsWith('*/')) { + parsed[t] = parsed[t].substr(2); + } else if (parsed[t].indexOf('/*') < 0) { + parsed[t] = getMimetype(parsed[t]) || parsed[t]; + } + } + if (type === 'content-type') { + can = [can.split(';')[0]]; } } @@ -56,8 +87,9 @@ module.exports = function accepts(ctx, type, ask) { 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; + if (type === 'accept' || type === 'content-type') { + let allowRoot = can[i].indexOf('/*') >= 0 + || parsed[t].indexOf('/*') >= 0; // Big if :) if (can[i] === '*/*' @@ -66,6 +98,12 @@ module.exports = function accepts(ctx, type, ask) { && parsed[t].indexOf('/') >= 0 && can[i].split('/')[0] === parsed[t].split('/')[0] )) { + if (type === 'content-type') { + if (ask[t].indexOf('/') === -1) { + return ask[t]; + } + return can[i]; + } return ask[t]; } } else { diff --git a/lib/application.js b/lib/application.js index cd3d3fb..68794b9 100644 --- a/lib/application.js +++ b/lib/application.js @@ -6,6 +6,10 @@ */ const debug = require('debug-ms')('koa:application'); +const Emitter = require('events'); +const util = require('util'); +const Stream = require('stream'); +const http = require('http'); const onFinished = require('./onfinish'); const response = require('./response'); const compose = require('./compose'); @@ -13,10 +17,6 @@ const isJSON = require('./isjson'); const context = require('./context'); const request = require('./request'); const statuses = require('./statuses'); -const Emitter = require('events'); -const util = require('util'); -const Stream = require('stream'); -const http = require('http'); /** * Expose `Application` class. diff --git a/lib/request.js b/lib/request.js index cdcbc2d..9733892 100644 --- a/lib/request.js +++ b/lib/request.js @@ -8,11 +8,10 @@ const URL = require('url').URL; const net = require('net'); const stringify = require('url').format; -const fastparse = require('./fastparse'); const qs = require('querystring'); -const typeis = require('type-is'); const fresh = require('fresh'); const util = require('util'); +const fastparse = require('./fastparse'); const accepts = require('./accepts'); const IP = Symbol('context#ip'); @@ -473,40 +472,6 @@ 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 @@ -658,9 +623,9 @@ module.exports = { */ is(types) { - if (!types) return typeis(this.req); + if (!types) return accepts(this, 'content-type', []); if (!Array.isArray(types)) types = [].slice.call(arguments); - return typeis(this.req, types); + return accepts(this, 'content-type', types); }, /** diff --git a/lib/response.js b/lib/response.js index 40fffdd..f38bf98 100644 --- a/lib/response.js +++ b/lib/response.js @@ -7,14 +7,14 @@ const ReadStream = require('fs').ReadStream; const contentDisposition = require('content-disposition'); -const onFinish = require('./onfinish'); -const isJSON = require('./isjson'); -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'); +const onFinish = require('./onfinish'); +const isJSON = require('./isjson'); +const statuses = require('./statuses'); +const getMimetype = require('./getmimetype'); +const accepts = require('./accepts'); /** * Prototype. @@ -287,6 +287,8 @@ module.exports = { // html if (this.ctx.headers.accept && this.ctx.headers.accept.indexOf('html') >= 0) { + // Sanitize the url in case developer does something silly like: + // ctx.redirect(ctx.query.goto) or something without sanitizing himself. url = url.replace(/&/g, '&') .replace(//g, '>') @@ -434,10 +436,9 @@ module.exports = { */ is(types) { - const type = this.type; - if (!types) return type || false; + if (!types) return this.type || false; if (!Array.isArray(types)) types = [].slice.call(arguments); - return typeis(type, types); + return accepts(this, 'content-type', types, false); }, /** diff --git a/package.json b/package.json index 490af67..cf62b02 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,7 @@ "content-disposition": "jharrilim/content-disposition#572383f", "debug-ms": "~4.1.2", "fresh": "~0.5.2", - "http-errors-lite": "^2.0.2", - "type-is": "^1.6.16" + "http-errors-lite": "^2.0.2" }, "devDependencies": { "egg-bin": "^4.13.0", diff --git a/test/application/index.js b/test/application/index.js index ba3a5e0..7f88fff 100644 --- a/test/application/index.js +++ b/test/application/index.js @@ -32,8 +32,7 @@ describe('app', () => { ctx.socket.writable = false; ctx.status = 204; // throw if .writeHead or .end is called - ctx.res.writeHead = - ctx.res.end = () => { + ctx.res.writeHead = ctx.res.end = () => { throw new Error('response sent'); }; });