Browse Source

url: Support for IPv6 addresses in URLs.

Fixes #1138, #2610.
v0.7.4-release
Łukasz Walukiewicz 13 years ago
committed by koichik
parent
commit
a94ffdaec5
  1. 75
      lib/url.js
  2. 65
      test/simple/test-url.js

75
lib/url.js

@ -35,7 +35,7 @@ var protocolPattern = /^([a-z0-9.+-]+:)/i,
// RFC 2396: characters reserved for delimiting URLs. // RFC 2396: characters reserved for delimiting URLs.
delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'], delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'],
// RFC 2396: characters not allowed for various reasons. // 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. // Allowed by RFCs, but cause of XSS attacks. Always escape these.
autoEscape = ['\''], autoEscape = ['\''],
// Characters that are never ever allowed in a hostname. // Characters that are never ever allowed in a hostname.
@ -179,10 +179,15 @@ function urlParse(url, parseQueryString, slashesDenoteHost) {
// so even if it's empty, it has to be present. // so even if it's empty, it has to be present.
out.hostname = out.hostname || ''; 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. // validate a little.
if (out.hostname.length > hostnameMaxLen) { if (out.hostname.length > hostnameMaxLen) {
out.hostname = ''; out.hostname = '';
} else { } else if (!ipv6Hostname) {
var hostparts = out.hostname.split(/\./); var hostparts = out.hostname.split(/\./);
for (var i = 0, l = hostparts.length; i < l; i++) { for (var i = 0, l = hostparts.length; i < l; i++) {
var part = hostparts[i]; var part = hostparts[i];
@ -221,22 +226,32 @@ function urlParse(url, parseQueryString, slashesDenoteHost) {
// hostnames are always lower case. // hostnames are always lower case.
out.hostname = out.hostname.toLowerCase(); out.hostname = out.hostname.toLowerCase();
// IDNA Support: Returns a puny coded representation of "domain". if (!ipv6Hostname) {
// It only converts the part of the domain name that // IDNA Support: Returns a puny coded representation of "domain".
// has non ASCII characters. I.e. it dosent matter if // It only converts the part of the domain name that
// you call it with a domain that already is in ASCII. // has non ASCII characters. I.e. it dosent matter if
var domainArray = out.hostname.split('.'); // you call it with a domain that already is in ASCII.
var newOut = []; var domainArray = out.hostname.split('.');
for (var i = 0; i < domainArray.length; ++i) { var newOut = [];
var s = domainArray[i]; for (var i = 0; i < domainArray.length; ++i) {
newOut.push(s.match(/[^A-Za-z0-9_-]/) ? var s = domainArray[i];
'xn--' + punycode.encode(s) : s); 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.host = (out.hostname || '') +
((out.port) ? ':' + out.port : ''); ((out.port) ? ':' + out.port : '');
out.href += out.host; 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. // now rest is set to the post-host stuff.
@ -323,20 +338,28 @@ function urlFormat(obj) {
} }
var protocol = obj.protocol || '', 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 || '', pathname = obj.pathname || '',
query = obj.query && hash = obj.hash || '',
((typeof obj.query === 'object' && host = false,
Object.keys(obj.query).length) ? query = '';
querystring.stringify(obj.query) :
'') || '', if (obj.host !== undefined) {
search = obj.search || (query && ('?' + query)) || '', host = auth + obj.host;
hash = obj.hash || ''; } 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 += ':'; if (protocol && protocol.substr(-1) !== ':') protocol += ':';

65
test/simple/test-url.js

@ -476,6 +476,55 @@ var parseTests = {
'href': 'www.example.com', 'href': 'www.example.com',
'pathname': 'www.example.com', 'pathname': 'www.example.com',
'path': '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'
} }
}; };
@ -650,6 +699,22 @@ var formatTests = {
'hostname': 'foo', 'hostname': 'foo',
'protocol': 'dot.test:', 'protocol': 'dot.test:',
'pathname': '/bar' '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) { for (var u in formatTests) {

Loading…
Cancel
Save