Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

Support for literal IPv6 addresses in URLs. #2610

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 31 additions & 15 deletions lib/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ var protocolPattern = /^([a-z0-9.+-]+:)/i,
// RFC 2396: characters reserved for delimiting URLs.
delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'],
// RFC 2396: characters not allowed for various reasons.
unwise = ['{', '}', '|', '\\', '^', '~', '[', ']', '`'].concat(delims),
unwise = ['{', '}', '|', '\\', '^', '~', '`'].concat(delims),
// Allowed by RFCs, but cause of XSS attacks. Always escape these.
autoEscape = ['\''],
// Characters that are never ever allowed in a hostname.
Expand Down Expand Up @@ -179,10 +179,15 @@ function urlParse(url, parseQueryString, slashesDenoteHost) {
// so even if it's empty, it has to be present.
out.hostname = out.hostname || '';

// if hostname begins with [ and ends with ]
// assume that it's an IPv6 address.
var ipv6Hostname = out.hostname[0] === '[' &&
out.hostname[out.hostname.length - 1] === ']';

// validate a little.
if (out.hostname.length > hostnameMaxLen) {
out.hostname = '';
} else {
} else if (!ipv6Hostname) {
var hostparts = out.hostname.split(/\./);
for (var i = 0, l = hostparts.length; i < l; i++) {
var part = hostparts[i];
Expand Down Expand Up @@ -221,22 +226,32 @@ function urlParse(url, parseQueryString, slashesDenoteHost) {
// hostnames are always lower case.
out.hostname = out.hostname.toLowerCase();

// IDNA Support: Returns a puny coded representation of "domain".
// It only converts the part of the domain name that
// has non ASCII characters. I.e. it dosent matter if
// you call it with a domain that already is in ASCII.
var domainArray = out.hostname.split('.');
var newOut = [];
for (var i = 0; i < domainArray.length; ++i) {
var s = domainArray[i];
newOut.push(s.match(/[^A-Za-z0-9_-]/) ?
'xn--' + punycode.encode(s) : s);
if (!ipv6Hostname) {
// IDNA Support: Returns a puny coded representation of "domain".
// It only converts the part of the domain name that
// has non ASCII characters. I.e. it dosent matter if
// you call it with a domain that already is in ASCII.
var domainArray = out.hostname.split('.');
var newOut = [];
for (var i = 0; i < domainArray.length; ++i) {
var s = domainArray[i];
newOut.push(s.match(/[^A-Za-z0-9_-]/) ?
'xn--' + punycode.encode(s) : s);
}
out.hostname = newOut.join('.');
}
out.hostname = newOut.join('.');

out.host = (out.hostname || '') +
((out.port) ? ':' + out.port : '');
out.href += out.host;

// strip [ and ] from the hostname
if (ipv6Hostname) {
out.hostname = out.hostname.substr(1, out.hostname.length - 2);
if (rest[0] !== '/') {
rest = '/' + rest;
}
}
}

// now rest is set to the post-host stuff.
Expand Down Expand Up @@ -325,8 +340,9 @@ function urlFormat(obj) {
var protocol = obj.protocol || '',
host = (obj.host !== undefined) ? auth + obj.host :
obj.hostname !== undefined ? (
auth + obj.hostname +
(obj.port ? ':' + obj.port : '')
auth + (obj.hostname.indexOf(':') === -1 ? obj.hostname :
('[' + obj.hostname + ']')
) + (obj.port ? ':' + obj.port : '')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you break this out into separate if statements?

) :
false,
pathname = obj.pathname || '',
Expand Down
65 changes: 65 additions & 0 deletions test/simple/test-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,55 @@ var parseTests = {
'href': 'www.example.com',
'pathname': 'www.example.com',
'path': 'www.example.com'
},
// ipv6 support
'[fe80::1]': {
'href': '[fe80::1]',
'pathname': '[fe80::1]',
'path': '[fe80::1]'
},
'coap://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]': {
'protocol': 'coap:',
'slashes': true,
'host': '[fedc:ba98:7654:3210:fedc:ba98:7654:3210]',
'hostname': 'fedc:ba98:7654:3210:fedc:ba98:7654:3210',
'href': 'coap://[fedc:ba98:7654:3210:fedc:ba98:7654:3210]/',
'pathname': '/',
'path': '/'
},
'coap://[1080:0:0:0:8:800:200C:417A]:61616/': {
'protocol': 'coap:',
'slashes': true,
'host': '[1080:0:0:0:8:800:200c:417a]:61616',
'port': '61616',
'hostname': '1080:0:0:0:8:800:200c:417a',
'href': 'coap://[1080:0:0:0:8:800:200c:417a]:61616/',
'pathname': '/',
'path': '/'
},
'http://user:password@[3ffe:2a00:100:7031::1]:8080': {
'protocol': 'http:',
'slashes': true,
'auth': 'user:password',
'host': '[3ffe:2a00:100:7031::1]:8080',
'port': '8080',
'hostname': '3ffe:2a00:100:7031::1',
'href': 'http://user:password@[3ffe:2a00:100:7031::1]:8080/',
'pathname': '/',
'path': '/'
},
'coap://u:p@[::192.9.5.5]:61616/.well-known/r?n=Temperature': {
'protocol': 'coap:',
'slashes': true,
'auth': 'u:p',
'host': '[::192.9.5.5]:61616',
'port': '61616',
'hostname': '::192.9.5.5',
'href': 'coap://u:p@[::192.9.5.5]:61616/.well-known/r?n=Temperature',
'search': '?n=Temperature',
'query': 'n=Temperature',
'pathname': '/.well-known/r',
'path': '/.well-known/r?n=Temperature'
}
};

Expand Down Expand Up @@ -650,6 +699,22 @@ var formatTests = {
'hostname': 'foo',
'protocol': 'dot.test:',
'pathname': '/bar'
},
// ipv6 support
'coap:u:p@[::1]:61616/.well-known/r?n=Temperature': {
'href': 'coap:u:p@[::1]:61616/.well-known/r?n=Temperature',
'protocol': 'coap:',
'auth': 'u:p',
'hostname': '::1',
'port': '61616',
'pathname': '/.well-known/r',
'search': 'n=Temperature'
},
'coap:[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616/s/stopButton': {
'href': 'coap:[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616/s/stopButton',
'protocol': 'coap',
'host': '[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616',
'pathname': '/s/stopButton'
}
};
for (var u in formatTests) {
Expand Down