koa 2.10.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/History.md CHANGED
@@ -1,4 +1,20 @@
1
1
 
2
+ 2.11.0 / 2019-10-28
3
+ ==================
4
+
5
+ **features**
6
+ * [[`422e539`](http://github.com/koajs/koa/commit/422e539e8989e65ba43ecc39ddbaa3c4f755d465)] - feat: support app.proxyIPHeader and app.maxIpsCount to make ctx.ips more security (Yiyu He <<dead_horse@qq.com>>)
7
+ * [[`d48d88e`](http://github.com/koajs/koa/commit/d48d88ee17b780c02123e6d657274cab456e943e)] - feat: implement response.has (#1397) (Konstantin Vyatkin <<tino@vtkn.io>>)
8
+
9
+ **others**
10
+ * [[`4dc56f6`](http://github.com/koajs/koa/commit/4dc56f6d04e8f5fe12ba53a8a776653b3d7b60ed)] - chore: update ESLint and plugins/configs (#1407) (Konstantin Vyatkin <<tino@vtkn.io>>)
11
+ * [[`be7d334`](http://github.com/koajs/koa/commit/be7d334778481639294cdf87f5c359a230aeb65b)] - chore: removes code duplication at handling HEAD method (#1400) (Konstantin Vyatkin <<tino@vtkn.io>>)
12
+ * [[`f155785`](http://github.com/koajs/koa/commit/f155785e2bb42b5ddf0a8156401c6dafdf57ba8b)] - chore: support `writableEnded` (#1402) (Konstantin Vyatkin <<tino@vtkn.io>>)
13
+ * [[`b968688`](http://github.com/koajs/koa/commit/b968688afe2c727ae141f50aa983d481dbc1dbbf)] - chore: add FUNDING.yml (#1403) (Konstantin Vyatkin <<tino@vtkn.io>>)
14
+ * [[`4f96829`](http://github.com/koajs/koa/commit/4f968298f97394e488297ec32c8e927a3a322076)] - chore: remove isJSON in res.length (#1399) (Konstantin Vyatkin <<tino@vtkn.io>>)
15
+ * [[`8be5626`](http://github.com/koajs/koa/commit/8be5626bbb54e6c899a1b71d22411709126d9fea)] - build: enable codecov partial coverage and use bash uploader (#1396) (Konstantin Vyatkin <<tino@vtkn.io>>)
16
+ * [[`ef5c43b`](http://github.com/koajs/koa/commit/ef5c43bcbcf31819e032c3b7ae7654b7f8e9358b)] - chore: use rest params (#1393) (Konstantin Vyatkin <<tino@vtkn.io>>)
17
+
2
18
  2.10.0 / 2019-10-12
3
19
  ==================
4
20
 
@@ -10,7 +10,6 @@ const debug = require('debug')('koa:application');
10
10
  const onFinished = require('on-finished');
11
11
  const response = require('./response');
12
12
  const compose = require('koa-compose');
13
- const isJSON = require('koa-is-json');
14
13
  const context = require('./context');
15
14
  const request = require('./request');
16
15
  const statuses = require('statuses');
@@ -42,6 +41,8 @@ module.exports = class Application extends Emitter {
42
41
  * @param {string[]} [options.keys] Signed cookie keys
43
42
  * @param {boolean} [options.proxy] Trust proxy headers
44
43
  * @param {number} [options.subdomainOffset] Subdomain offset
44
+ * @param {boolean} [options.proxyIpHeader] proxy ip header, default to X-Forwarded-For
45
+ * @param {boolean} [options.maxIpsCount] max ips read from proxy ip header, default to 0 (means infinity)
45
46
  *
46
47
  */
47
48
 
@@ -50,6 +51,8 @@ module.exports = class Application extends Emitter {
50
51
  options = options || {};
51
52
  this.proxy = options.proxy || false;
52
53
  this.subdomainOffset = options.subdomainOffset || 2;
54
+ this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For';
55
+ this.maxIpsCount = options.maxIpsCount || 0;
53
56
  this.env = options.env || process.env.NODE_ENV || 'development';
54
57
  if (options.keys) this.keys = options.keys;
55
58
  this.middleware = [];
@@ -225,9 +228,10 @@ function respond(ctx) {
225
228
  return res.end();
226
229
  }
227
230
 
228
- if ('HEAD' == ctx.method) {
229
- if (!res.headersSent && isJSON(body)) {
230
- ctx.length = Buffer.byteLength(JSON.stringify(body));
231
+ if ('HEAD' === ctx.method) {
232
+ if (!res.headersSent && !ctx.response.has('Content-Length')) {
233
+ const { length } = ctx.response;
234
+ if (Number.isInteger(length)) ctx.length = length;
231
235
  }
232
236
  return res.end();
233
237
  }
package/lib/context.js CHANGED
@@ -193,6 +193,7 @@ delegate(proto, 'response')
193
193
  .method('redirect')
194
194
  .method('remove')
195
195
  .method('vary')
196
+ .method('has')
196
197
  .method('set')
197
198
  .method('append')
198
199
  .method('flushHeaders')
package/lib/request.js CHANGED
@@ -432,10 +432,14 @@ module.exports = {
432
432
 
433
433
  get ips() {
434
434
  const proxy = this.app.proxy;
435
- const val = this.get('X-Forwarded-For');
436
- return proxy && val
435
+ const val = this.get(this.app.proxyIpHeader);
436
+ let ips = proxy && val
437
437
  ? val.split(/\s*,\s*/)
438
438
  : [];
439
+ if (this.app.maxIpsCount > 0) {
440
+ ips = ips.slice(-this.app.maxIpsCount);
441
+ }
442
+ return ips;
439
443
  },
440
444
 
441
445
  /**
@@ -622,15 +626,14 @@ module.exports = {
622
626
  *
623
627
  * this.is('html'); // => false
624
628
  *
625
- * @param {String|Array} types...
629
+ * @param {String|String[]} [type]
630
+ * @param {String[]} [types]
626
631
  * @return {String|false|null}
627
632
  * @api public
628
633
  */
629
634
 
630
- is(types) {
631
- if (!types) return typeis(this.req);
632
- if (!Array.isArray(types)) types = [].slice.call(arguments);
633
- return typeis(this.req, types);
635
+ is(type, ...types) {
636
+ return typeis(this.req, type, ...types);
634
637
  },
635
638
 
636
639
  /**
package/lib/response.js CHANGED
@@ -9,7 +9,6 @@ const contentDisposition = require('content-disposition');
9
9
  const ensureErrorHandler = require('error-inject');
10
10
  const getType = require('cache-content-type');
11
11
  const onFinish = require('on-finished');
12
- const isJSON = require('koa-is-json');
13
12
  const escape = require('escape-html');
14
13
  const typeis = require('type-is').is;
15
14
  const statuses = require('statuses');
@@ -20,6 +19,7 @@ const vary = require('vary');
20
19
  const only = require('only');
21
20
  const util = require('util');
22
21
  const encodeUrl = require('encodeurl');
22
+ const Stream = require('stream');
23
23
 
24
24
  /**
25
25
  * Prototype.
@@ -149,7 +149,7 @@ module.exports = {
149
149
  if (!this._explicitStatus) this.status = 200;
150
150
 
151
151
  // set the content-type only if not yet set
152
- const setType = !this.header['content-type'];
152
+ const setType = !this.has('Content-Type');
153
153
 
154
154
  // string
155
155
  if ('string' == typeof val) {
@@ -201,18 +201,15 @@ module.exports = {
201
201
  */
202
202
 
203
203
  get length() {
204
- const len = this.header['content-length'];
205
- const body = this.body;
206
-
207
- if (null == len) {
208
- if (!body) return;
209
- if ('string' == typeof body) return Buffer.byteLength(body);
210
- if (Buffer.isBuffer(body)) return body.length;
211
- if (isJSON(body)) return Buffer.byteLength(JSON.stringify(body));
212
- return;
204
+ if (this.has('Content-Length')) {
205
+ return parseInt(this.get('Content-Length'), 10) || 0;
213
206
  }
214
207
 
215
- return Math.trunc(len) || 0;
208
+ const { body } = this;
209
+ if (!body || body instanceof Stream) return undefined;
210
+ if ('string' === typeof body) return Buffer.byteLength(body);
211
+ if (Buffer.isBuffer(body)) return body.length;
212
+ return Buffer.byteLength(JSON.stringify(body));
216
213
  },
217
214
 
218
215
  /**
@@ -389,16 +386,14 @@ module.exports = {
389
386
  * Check whether the response is one of the listed types.
390
387
  * Pretty much the same as `this.request.is()`.
391
388
  *
392
- * @param {String|Array} types...
389
+ * @param {String|String[]} [type]
390
+ * @param {String[]} [types]
393
391
  * @return {String|false}
394
392
  * @api public
395
393
  */
396
394
 
397
- is(types) {
398
- const type = this.type;
399
- if (!types) return type || false;
400
- if (!Array.isArray(types)) types = [].slice.call(arguments);
401
- return typeis(type, types);
395
+ is(type, ...types) {
396
+ return typeis(this.type, type, ...types);
402
397
  },
403
398
 
404
399
  /**
@@ -421,6 +416,29 @@ module.exports = {
421
416
  return this.header[field.toLowerCase()] || '';
422
417
  },
423
418
 
419
+ /**
420
+ * Returns true if the header identified by name is currently set in the outgoing headers.
421
+ * The header name matching is case-insensitive.
422
+ *
423
+ * Examples:
424
+ *
425
+ * this.has('Content-Type');
426
+ * // => true
427
+ *
428
+ * this.get('content-type');
429
+ * // => true
430
+ *
431
+ * @param {String} field
432
+ * @return {boolean}
433
+ * @api public
434
+ */
435
+ has(field) {
436
+ return typeof this.res.hasHeader === 'function'
437
+ ? this.res.hasHeader(field)
438
+ // Node < 7.7
439
+ : field.toLowerCase() in this.headers;
440
+ },
441
+
424
442
  /**
425
443
  * Set header `field` to `val`, or pass
426
444
  * an object of header fields.
@@ -502,7 +520,11 @@ module.exports = {
502
520
 
503
521
  get writable() {
504
522
  // can't write any more after response finished
505
- if (this.res.finished) return false;
523
+ // response.writableEnded is available since Node > 12.9
524
+ // https://nodejs.org/api/http.html#http_response_writableended
525
+ // response.finished is undocumented feature of previous Node versions
526
+ // https://stackoverflow.com/questions/16254385/undocumented-response-finished-in-node-js
527
+ if (this.res.writableEnded || this.res.finished) return false;
506
528
 
507
529
  const socket = this.res.socket;
508
530
  // There are already pending outgoing res, but still writable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koa",
3
- "version": "2.10.0",
3
+ "version": "2.11.0",
4
4
  "description": "Koa web app framework",
5
5
  "main": "lib/application.js",
6
6
  "scripts": {
@@ -40,7 +40,6 @@
40
40
  "is-generator-function": "^1.0.7",
41
41
  "koa-compose": "^4.1.0",
42
42
  "koa-convert": "^1.2.0",
43
- "koa-is-json": "^1.0.0",
44
43
  "on-finished": "^2.3.0",
45
44
  "only": "~0.0.2",
46
45
  "parseurl": "^1.3.2",
@@ -50,11 +49,13 @@
50
49
  },
51
50
  "devDependencies": {
52
51
  "egg-bin": "^4.13.0",
53
- "eslint": "^6.0.1",
52
+ "eslint": "^6.5.1",
54
53
  "eslint-config-koa": "^2.0.0",
55
- "eslint-config-standard": "^7.0.1",
56
- "eslint-plugin-promise": "^3.5.0",
57
- "eslint-plugin-standard": "^2.1.1",
54
+ "eslint-config-standard": "^14.1.0",
55
+ "eslint-plugin-import": "^2.18.2",
56
+ "eslint-plugin-node": "^10.0.0",
57
+ "eslint-plugin-promise": "^4.2.1",
58
+ "eslint-plugin-standard": "^4.0.1",
58
59
  "mm": "^2.5.0",
59
60
  "supertest": "^3.1.0"
60
61
  },