Skip to content

Commit

Permalink
fix(parse): throw on invalid input (#345)
Browse files Browse the repository at this point in the history
validate hostname and port
  • Loading branch information
konstantinblaesi authored and rodneyrehm committed Aug 8, 2017
1 parent d7110bd commit 0657ad8
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 19 deletions.
61 changes: 51 additions & 10 deletions src/URI.js
Expand Up @@ -240,10 +240,16 @@
ws: '80',
wss: '443'
};
// list of protocols which always require a hostname
URI.hostProtocols = [
'http',
'https'
];

// allowed hostname characters according to RFC 3986
// ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded
// I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . -
URI.invalid_hostname_characters = /[^a-zA-Z0-9\.-]/;
URI.invalid_hostname_characters = /[^a-zA-Z0-9\.\-:]/;
// map DOM Elements to their URI attribute
URI.domAttributes = {
'a': 'href',
Expand Down Expand Up @@ -575,6 +581,12 @@
string = '/' + string;
}

URI.ensureValidHostname(parts.hostname, parts.protocol);

if (parts.port) {
URI.ensureValidPort(parts.port);
}

return string.substring(pos) || '/';
};
URI.parseAuthority = function(string, parts) {
Expand Down Expand Up @@ -1015,22 +1027,44 @@
return string;
};

URI.ensureValidHostname = function(v) {
URI.ensureValidHostname = function(v, protocol) {
// Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986)
// they are not part of DNS and therefore ignored by URI.js

if (v.match(URI.invalid_hostname_characters)) {
var hasHostname = !!v; // not null and not an empty string
var hasProtocol = !!protocol;
var rejectEmptyHostname = false;

if (hasProtocol) {
rejectEmptyHostname = arrayContains(URI.hostProtocols, protocol);
}

if (rejectEmptyHostname && !hasHostname) {
throw new TypeError('Hostname cannot be empty, if protocol is ' + protocol);
} else if (v && v.match(URI.invalid_hostname_characters)) {
// test punycode
if (!punycode) {
throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-] and Punycode.js is not available');
}

if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) {
throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.:-]');
}
}
};

URI.ensureValidPort = function (v) {
if (!v) {
return;
}

var port = Number(v);
if (Number.isInteger(port) && (port > 0) && (port < 65536)) {
return;
}

throw new TypeError('Port "' + v + '" is not a valid port');
};

// noConflict
URI.noConflict = function(removeAll) {
if (removeAll) {
Expand Down Expand Up @@ -1288,9 +1322,7 @@
v = v.substring(1);
}

if (v.match(/[^0-9]/)) {
throw new TypeError('Port "' + v + '" contains characters other than [0-9]');
}
URI.ensureValidPort(v);
}
}
return _port.call(this, v, build);
Expand All @@ -1308,6 +1340,7 @@
}

v = x.hostname;
URI.ensureValidHostname(v, this._parts.protocol);
}
return _hostname.call(this, v, build);
};
Expand Down Expand Up @@ -1426,8 +1459,12 @@
v += '.';
}

if (v.indexOf(':') !== -1) {
throw new TypeError('Domains cannot contain colons');
}

if (v) {
URI.ensureValidHostname(v);
URI.ensureValidHostname(v, this._parts.protocol);
}

this._parts.hostname = this._parts.hostname.replace(replace, v);
Expand Down Expand Up @@ -1466,7 +1503,11 @@
throw new TypeError('cannot set domain empty');
}

URI.ensureValidHostname(v);
if (v.indexOf(':') !== -1) {
throw new TypeError('Domains cannot contain colons');
}

URI.ensureValidHostname(v, this._parts.protocol);

if (!this._parts.hostname || this.is('IP')) {
this._parts.hostname = v;
Expand Down
36 changes: 27 additions & 9 deletions test/test.js
Expand Up @@ -124,6 +124,26 @@
ok(u instanceof URI, 'instanceof URI');
ok(u._parts.hostname !== undefined, 'host undefined');
});
test('function URI(string) with invalid port "port" throws', function () {
raises(function () {
new URI('http://example.org:port');
}, TypeError, "throws TypeError");
});
test('function URI(string) with invalid port "0" throws', function () {
raises(function () {
new URI('http://example.org:0');
}, TypeError, "throws TypeError");
});
test('function URI(string) with invalid port "65536" throws', function () {
raises(function () {
new URI('http://example.org:65536');
}, TypeError, "throws TypeError");
});
test('function URI(string) with protocol and without hostname should throw', function () {
raises(function () {
new URI('http://');
}, TypeError, "throws TypeError");
});
test('new URI(string, string)', function() {
// see http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor
var u = new URI('../foobar.html', 'http://example.org/hello/world.html');
Expand Down Expand Up @@ -223,13 +243,16 @@
equal(u.hostname(), 'abc.foobar.lala', 'hostname changed');
equal(u+'', 'http://abc.foobar.lala/foo.html', 'hostname changed url');

u.hostname('');
equal(u.hostname(), '', 'hostname removed');
equal(u+'', 'http:///foo.html', 'hostname removed url');

raises(function() {
u.hostname('foo\\bar.com');
}, TypeError, 'Failing backslash detection in hostname');

raises(function() {
u.hostname('');
}, TypeError, "Trying to set an empty hostname with http(s) protocol throws a TypeError");
raises(function() {
u.hostname(null);
}, TypeError, "Trying to set hostname to null with http(s) protocol throws a TypeError");
});
test('port', function() {
var u = new URI('http://example.org/foo.html');
Expand Down Expand Up @@ -1338,11 +1361,6 @@
url: 'file:///C:/skyclan/snipkit',
base: 'http://example.com/foo/bar',
result: 'file:///C:/skyclan/snipkit'
}, {
name: 'absolute passthru - generic empty-hostname - urljoin (#328)',
url: 'http:///foo',
base: 'http://example.com/foo/bar',
result: 'http:///foo'
}, {
name: 'file paths - urljoin',
url: 'anotherFile',
Expand Down

0 comments on commit 0657ad8

Please sign in to comment.