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

Commit

Permalink
url: Support for IPv6 addresses in URLs.
Browse files Browse the repository at this point in the history
Fixes #1138, #2610.
  • Loading branch information
morkai authored and koichik committed Jan 31, 2012
1 parent 26bfb6b commit a94ffda
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 26 deletions.
75 changes: 49 additions & 26 deletions lib/url.js
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 @@ -323,20 +338,28 @@ function urlFormat(obj) {
}

var protocol = obj.protocol || '',
host = (obj.host !== undefined) ? auth + obj.host :
obj.hostname !== undefined ? (
auth + obj.hostname +
(obj.port ? ':' + obj.port : '')
) :
false,
pathname = obj.pathname || '',
query = obj.query &&
((typeof obj.query === 'object' &&
Object.keys(obj.query).length) ?
querystring.stringify(obj.query) :
'') || '',
search = obj.search || (query && ('?' + query)) || '',
hash = obj.hash || '';
hash = obj.hash || '',
host = false,
query = '';

if (obj.host !== undefined) {
host = auth + obj.host;
} else if (obj.hostname !== undefined) {
host = auth + (obj.hostname.indexOf(':') === -1 ?
obj.hostname :
'[' + obj.hostname + ']');
if (obj.port) {
host += ':' + obj.port;
}
}

if (obj.query && typeof obj.query === 'object' &&
Object.keys(obj.query).length) {
query = querystring.stringify(obj.query);
}

var search = obj.search || (query && ('?' + query)) || '';

if (protocol && protocol.substr(-1) !== ':') protocol += ':';

Expand Down
65 changes: 65 additions & 0 deletions test/simple/test-url.js
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

1 comment on commit a94ffda

@isaacs
Copy link

@isaacs isaacs commented on a94ffda Jan 31, 2012

Choose a reason for hiding this comment

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

Awesome. Thanks!

Please sign in to comment.