Skip to content

Commit

Permalink
Use node 7+ WHATWG parser for hostname, fixes #1002 (#1004)
Browse files Browse the repository at this point in the history
* Use node 7+ WHATWG parser for hostname, fixes #1002

* only use URL if host is IPv6, expose parsed URL

* catch invalid URLs, memoize empty obj

* hostname returns empty string when URL throws
  • Loading branch information
fl0w authored and jonathanong committed Jun 20, 2017
1 parent 0125878 commit 327b65c
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 0 deletions.
8 changes: 8 additions & 0 deletions docs/api/request.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ ctx.request.href

Get hostname when present. Supports `X-Forwarded-Host`
when `app.proxy` is __true__, otherwise `Host` is used.

If host is IPv6, Koa delegates parsing to
[WHATWG URL API](https://nodejs.org/dist/latest-v8.x/docs/api/url.html#url_the_whatwg_url_api),
*Note* This may impact performance.

### request.URL

Get WHATWG parsed URL object.

### request.type

Expand Down
1 change: 1 addition & 0 deletions lib/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ delegate(proto, 'request')
.getter('protocol')
.getter('host')
.getter('hostname')
.getter('URL')
.getter('header')
.getter('headers')
.getter('secure')
Expand Down
24 changes: 24 additions & 0 deletions lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Module dependencies.
*/

const URL = require('url').URL;
const net = require('net');
const contentType = require('content-type');
const stringify = require('url').format;
Expand Down Expand Up @@ -264,9 +265,32 @@ module.exports = {
get hostname() {
const host = this.host;
if (!host) return '';
if ('[' == host[0]) return this.URL.hostname || ''; // IPv6
return host.split(':')[0];
},

/**
* Get WHATWG parsed URL.
* Lazily memoized.
*
* @return {URL|Object}
* @api public
*/

get URL() {
if (!this.memoizedURL) {
const protocol = this.protocol;
const host = this.host;
const originalUrl = this.originalUrl || ''; // avoid undefined in template string
try {
this.memoizedURL = new URL(`${protocol}://${host}${originalUrl}`);
} catch (err) {
this.memoizedURL = Object.create(null);
}
}
return this.memoizedURL;
},

/**
* Check if the request is fresh, aka
* Last-Modified and/or the ETag
Expand Down
32 changes: 32 additions & 0 deletions test/request/hostname.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,38 @@ describe('req.hostname', () => {
});
});

describe('with IPv6 in host', () => {
it('should parse localhost void of port', () => {
const req = request();
req.header.host = '[::1]';
assert.equal(req.hostname, '[::1]');
});

it('should parse localhost with port 80', () => {
const req = request();
req.header.host = '[::1]:80';
assert.equal(req.hostname, '[::1]');
});

it('should parse localhost with non special schema port', () => {
const req = request();
req.header.host = '[::1]:1337';
assert.equal(req.hostname, '[::1]');
});

it('should reduce IPv6 with non special schema port, as hostname', () => {
const req = request();
req.header.host = '[2001:cdba:0000:0000:0000:0000:3257:9652]:1337';
assert.equal(req.hostname, '[2001:cdba::3257:9652]');
});

it('should return empty string when invalid', () => {
const req = request();
req.header.host = '[invalidIPv6]';
assert.equal(req.hostname, '');
});
});

describe('when X-Forwarded-Host is present', () => {
describe('and proxy is not trusted', () => {
it('should be ignored', () => {
Expand Down
26 changes: 26 additions & 0 deletions test/request/whatwg-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

'use strict';

const request = require('../helpers/context').request;
const assert = require('assert');

describe('req.URL', () => {
describe('should not throw when', () => {
it('host is void', () => {
const req = request();
assert.doesNotThrow(() => req.URL, TypeError);
});

it('header.host is invalid', () => {
const req = request();
req.header.host = 'invalid host';
assert.doesNotThrow(() => req.URL, TypeError);
});
});

it('should return empty object when invalid', () => {
const req = request();
req.header.host = 'invalid host';
assert.deepStrictEqual(req.URL, Object.create(null));
});
});

0 comments on commit 327b65c

Please sign in to comment.