koa 2.8.1 → 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,45 @@
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
+
18
+ 2.10.0 / 2019-10-12
19
+ ==================
20
+
21
+ **features**
22
+ * [[`d7f7f77`](http://github.com/koajs/koa/commit/d7f7f77689e2eaef050686be2bdf3e72881a79ac)] - feat: support sameSite=none cookies (bump cookies dependency) (#1390) (Filip Skokan <<panva.ip@gmail.com>>)
23
+
24
+ 2.9.0 / 2019-10-12
25
+ ==================
26
+
27
+ **features**
28
+ * [[`2d1c598`](http://github.com/koajs/koa/commit/2d1c5981869e0fe6f5bc71b5c5582accfd125cc6)] - feat: export HttpError from http-errors library (Micheal Hill <<micheal.hill@trunkplatform.com>>)
29
+
30
+ **others**
31
+ * [[`cf70dbc`](http://github.com/koajs/koa/commit/cf70dbc6d2ba62bf1eb12b563dd5ecd27af6e2be)] - Chore: Use https in readme (#1389) (谭九鼎 <<109224573@qq.com>>)
32
+
33
+ 2.8.2 / 2019-09-28
34
+ ==================
35
+
36
+ **fixes**
37
+ * [[`54e8fab`](http://github.com/koajs/koa/commit/54e8fab3e3d907bbb264caf3e28a24773d0d6fdb)] - fix: encode redirect url if not already encoded (#1384) (fengmk2 <<fengmk2@gmail.com>>)
38
+
39
+ **others**
40
+ * [[`817b498`](http://github.com/koajs/koa/commit/817b49830571b45a8aec6b1fc1525434f5798c58)] - test: fix body test (#1375) (Robert Nagy <<ronagy@icloud.com>>)
41
+ * [[`f75d445`](http://github.com/koajs/koa/commit/f75d4455359ecdf30eeb676e2c7f31d4cf7b42ed)] - test: fix end after end (#1374) (Robert Nagy <<ronagy@icloud.com>>)
42
+
2
43
  2.8.1 / 2019-08-19
3
44
  ==================
4
45
 
package/Readme.md CHANGED
@@ -41,7 +41,7 @@ app.listen(3000);
41
41
 
42
42
  - [Kick-Off-Koa](https://github.com/koajs/kick-off-koa) - An intro to Koa via a set of self-guided workshops.
43
43
  - [Workshop](https://github.com/koajs/workshop) - A workshop to learn the basics of Koa, Express' spiritual successor.
44
- - [Introduction Screencast](http://knowthen.com/episode-3-koajs-quickstart-guide/) - An introduction to installing and getting started with Koa
44
+ - [Introduction Screencast](https://knowthen.com/episode-3-koajs-quickstart-guide/) - An introduction to installing and getting started with Koa
45
45
 
46
46
 
47
47
  ## Middleware
@@ -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');
@@ -21,6 +20,7 @@ const http = require('http');
21
20
  const only = require('only');
22
21
  const convert = require('koa-convert');
23
22
  const deprecate = require('depd')('koa');
23
+ const { HttpError } = require('http-errors');
24
24
 
25
25
  /**
26
26
  * Expose `Application` class.
@@ -41,6 +41,8 @@ module.exports = class Application extends Emitter {
41
41
  * @param {string[]} [options.keys] Signed cookie keys
42
42
  * @param {boolean} [options.proxy] Trust proxy headers
43
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)
44
46
  *
45
47
  */
46
48
 
@@ -49,6 +51,8 @@ module.exports = class Application extends Emitter {
49
51
  options = options || {};
50
52
  this.proxy = options.proxy || false;
51
53
  this.subdomainOffset = options.subdomainOffset || 2;
54
+ this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For';
55
+ this.maxIpsCount = options.maxIpsCount || 0;
52
56
  this.env = options.env || process.env.NODE_ENV || 'development';
53
57
  if (options.keys) this.keys = options.keys;
54
58
  this.middleware = [];
@@ -224,9 +228,10 @@ function respond(ctx) {
224
228
  return res.end();
225
229
  }
226
230
 
227
- if ('HEAD' == ctx.method) {
228
- if (!res.headersSent && isJSON(body)) {
229
- 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;
230
235
  }
231
236
  return res.end();
232
237
  }
@@ -257,3 +262,9 @@ function respond(ctx) {
257
262
  }
258
263
  res.end(body);
259
264
  }
265
+
266
+ /**
267
+ * Make HttpError available to consumers of the library so that consumers don't
268
+ * have a direct dependency upon `http-errors`
269
+ */
270
+ module.exports.HttpError = HttpError;
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');
@@ -19,6 +18,8 @@ const extname = require('path').extname;
19
18
  const vary = require('vary');
20
19
  const only = require('only');
21
20
  const util = require('util');
21
+ const encodeUrl = require('encodeurl');
22
+ const Stream = require('stream');
22
23
 
23
24
  /**
24
25
  * Prototype.
@@ -148,7 +149,7 @@ module.exports = {
148
149
  if (!this._explicitStatus) this.status = 200;
149
150
 
150
151
  // set the content-type only if not yet set
151
- const setType = !this.header['content-type'];
152
+ const setType = !this.has('Content-Type');
152
153
 
153
154
  // string
154
155
  if ('string' == typeof val) {
@@ -200,18 +201,15 @@ module.exports = {
200
201
  */
201
202
 
202
203
  get length() {
203
- const len = this.header['content-length'];
204
- const body = this.body;
205
-
206
- if (null == len) {
207
- if (!body) return;
208
- if ('string' == typeof body) return Buffer.byteLength(body);
209
- if (Buffer.isBuffer(body)) return body.length;
210
- if (isJSON(body)) return Buffer.byteLength(JSON.stringify(body));
211
- return;
204
+ if (this.has('Content-Length')) {
205
+ return parseInt(this.get('Content-Length'), 10) || 0;
212
206
  }
213
207
 
214
- 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));
215
213
  },
216
214
 
217
215
  /**
@@ -260,7 +258,7 @@ module.exports = {
260
258
  redirect(url, alt) {
261
259
  // location
262
260
  if ('back' == url) url = this.ctx.get('Referrer') || alt || '/';
263
- this.set('Location', url);
261
+ this.set('Location', encodeUrl(url));
264
262
 
265
263
  // status
266
264
  if (!statuses.redirect[this.status]) this.status = 302;
@@ -388,16 +386,14 @@ module.exports = {
388
386
  * Check whether the response is one of the listed types.
389
387
  * Pretty much the same as `this.request.is()`.
390
388
  *
391
- * @param {String|Array} types...
389
+ * @param {String|String[]} [type]
390
+ * @param {String[]} [types]
392
391
  * @return {String|false}
393
392
  * @api public
394
393
  */
395
394
 
396
- is(types) {
397
- const type = this.type;
398
- if (!types) return type || false;
399
- if (!Array.isArray(types)) types = [].slice.call(arguments);
400
- return typeis(type, types);
395
+ is(type, ...types) {
396
+ return typeis(this.type, type, ...types);
401
397
  },
402
398
 
403
399
  /**
@@ -420,6 +416,29 @@ module.exports = {
420
416
  return this.header[field.toLowerCase()] || '';
421
417
  },
422
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
+
423
442
  /**
424
443
  * Set header `field` to `val`, or pass
425
444
  * an object of header fields.
@@ -501,7 +520,11 @@ module.exports = {
501
520
 
502
521
  get writable() {
503
522
  // can't write any more after response finished
504
- 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;
505
528
 
506
529
  const socket = this.res.socket;
507
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.8.1",
3
+ "version": "2.11.0",
4
4
  "description": "Koa web app framework",
5
5
  "main": "lib/application.js",
6
6
  "scripts": {
@@ -26,11 +26,12 @@
26
26
  "cache-content-type": "^1.0.0",
27
27
  "content-disposition": "~0.5.2",
28
28
  "content-type": "^1.0.4",
29
- "cookies": "~0.7.1",
29
+ "cookies": "~0.8.0",
30
30
  "debug": "~3.1.0",
31
31
  "delegates": "^1.0.0",
32
32
  "depd": "^1.1.2",
33
33
  "destroy": "^1.0.4",
34
+ "encodeurl": "^1.0.2",
34
35
  "error-inject": "^1.0.0",
35
36
  "escape-html": "^1.0.3",
36
37
  "fresh": "~0.5.2",
@@ -39,7 +40,6 @@
39
40
  "is-generator-function": "^1.0.7",
40
41
  "koa-compose": "^4.1.0",
41
42
  "koa-convert": "^1.2.0",
42
- "koa-is-json": "^1.0.0",
43
43
  "on-finished": "^2.3.0",
44
44
  "only": "~0.0.2",
45
45
  "parseurl": "^1.3.2",
@@ -49,11 +49,13 @@
49
49
  },
50
50
  "devDependencies": {
51
51
  "egg-bin": "^4.13.0",
52
- "eslint": "^6.0.1",
52
+ "eslint": "^6.5.1",
53
53
  "eslint-config-koa": "^2.0.0",
54
- "eslint-config-standard": "^7.0.1",
55
- "eslint-plugin-promise": "^3.5.0",
56
- "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",
57
59
  "mm": "^2.5.0",
58
60
  "supertest": "^3.1.0"
59
61
  },