Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make constructor throw on invalid ports. #345

Merged
merged 6 commits into from Aug 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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