Skip to content

Instantly share code, notes, and snippets.

@kakarukeys
Created May 21, 2017 13:51
Show Gist options
  • Save kakarukeys/5381f3975674d81ba37d3bee4cd415a1 to your computer and use it in GitHub Desktop.
Save kakarukeys/5381f3975674d81ba37d3bee4cd415a1 to your computer and use it in GitHub Desktop.
solution2 - URI comparison
function parseURI(uri) {
/* parse <uri> into invidual components (loosely, some errors allowed) */
// see https://regex101.com/r/l96l7F/1 for explanation
var regex = /(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/\/?)?(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?((?:\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?[^?#\/]*)(?:\?([^#]*))?(?:#(.*))?/,
groupNames = [
"fullMatch",
"scheme",
"username",
"password",
"host",
"port",
"path",
"query",
"fragment"
],
result = regex.exec(uri),
uriObj = {};
for (var i = 0; i < result.length; i++) {
uriObj[groupNames[i]] = result[i] || '';
}
return uriObj;
}
function parsePath(path) {
/* return an array of invidual components in <path> */
var resolved = [],
pieces = path.split('/'),
token;
for (var i = 0; i < pieces.length; i++) {
token = decodeURIComponent(pieces[i]);
if (token && token !== '.' && token !== "..") {
resolved.push(token);
} else {
if (token === '..') {
resolved.pop();
}
if (i === pieces.length - 1) {
// last token, push a blank string representing a trailing slash
resolved.push('');
}
}
}
return resolved;
}
function parseQuery(query) {
/* return an array of name, values pair from <query> ordered by name */
var resolvedMap = {},
resolvedArr = [],
pieces = query.split('&'),
token, name, value;
for (var i = 0; i < pieces.length; i++) {
token = pieces[i].split('=');
name = decodeURIComponent(token[0]);
value = decodeURIComponent(token[1] || '');
if (!resolvedMap[name]) {
resolvedMap[name] = [];
resolvedArr.push([name, resolvedMap[name]]);
}
resolvedMap[name].push(value);
}
return resolvedArr.sort();
}
function checkURIs(uri1, uri2) {
/* return whether <uri1> and <uri2> are equivalent */
if (uri1 === uri2) {
return true;
}
var uriObj1 = parseURI(uri1),
uriObj2 = parseURI(uri2),
protocol1 = uriObj1.scheme.toUpperCase() || "HTTP",
protocol2 = uriObj2.scheme.toUpperCase() || "HTTP";
if (protocol1 != "HTTP" || protocol2 != "HTTP") {
throw new RangeError("Comparison involving non-http URI is not yet supported.");
}
return protocol1 === protocol2 &&
decodeURIComponent(uriObj1.username) === decodeURIComponent(uriObj2.username) &&
decodeURIComponent(uriObj1.password) === decodeURIComponent(uriObj2.password) &&
uriObj1.host.toUpperCase() === uriObj2.host.toUpperCase() &&
(uriObj1.port || "80") === (uriObj2.port || "80") &&
decodeURIComponent(uriObj1.fragment) === decodeURIComponent(uriObj2.fragment) &&
JSON.stringify(parsePath(uriObj1.path)) === JSON.stringify(parsePath(uriObj2.path)) &&
JSON.stringify(parseQuery(uriObj1.query)) === JSON.stringify(parseQuery(uriObj2.query));
}
/* unit tests */
try {
checkURIs(
"http://username:passwd123@domain.com/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"ftp://username:passwd123@domain.com/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash")
checkURIs(
"https://username:passwd123@domain.com/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash")
console.assert(false, "compare with non-http URI")
} catch (e) {
if (!(e instanceof RangeError)) {
console.assert(false, "compare with non-http URI")
}
}
var testData = [
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash", "exactly equal"],
["hTtP://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"HtTp://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash", "scheme name case-insensitive"],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"//username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash", "protocol-relative URI"],
["http://john:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://John:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash", "username", false],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:Passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash", "password", false],
["http://username:passwd123@zendesk.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@ZendesK.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash", "host name case-insensitive"],
["http://username:passwd123@zendesk.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@google.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash", "host name", false],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash", "default port"],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:8000/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash", "port", false],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#Hash", "fragment", false],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1", "fragment", false],
["http://username:passwd123@domain.com:80?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/?a=3&b=4&a=1#hash", "path (root)"],
["http://username:passwd123@domain.com:80/?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/abc?a=3&b=4&a=1#hash", "path", false],
["http://username:passwd123@domain.com:80/abc?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/abc/?a=3&b=4&a=1#hash", "path (trailing slash)", false],
["http://username:passwd123@domain.com:80/abc/?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/abc/def.html?a=3&b=4&a=1#hash", "path", false],
["http://username:passwd123@domain.com:80/abc/def.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/abc/def.html/?a=3&b=4&a=1#hash", "path (trailing slash)", false],
["http://username:passwd123@domain.com:80/users/../../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/temp/%7Esmith/home.html?a=3&b=4&a=1#hash", " path traversal"],
["http://username:passwd123@domain.com:80/abc/def/.?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/abc/def/?a=3&b=4&a=1#hash", " path traversal (trailing dot)"],
["http://username:passwd123@domain.com:80/abc/def/..?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/abc/?a=3&b=4&a=1#hash", " path traversal (trailing dot dot)"],
["http://username:passwd123@domain.com:80//users///..//temp////.//%7Esmith////home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash", "path (loose matching)"],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&c=4&a=1#hash", "query name diff", false],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=9&a=1#hash", "query value diff", false],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1&b=5#hash", "query extra value", false],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?b=4&a=3&a=1#hash", "query name ordering"],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=1&b=4&a=3#hash", "query value ordering", false],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b&a=1#hash", "query missing value"],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=null&a=1#hash", "query missing value", false],
["http://user%3Aname@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://user:name@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash", "URI encoding (username) (reserved char)", false],
["http://user%7Ename@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://user~name@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash", "URI encoding (username) (unreserved char)"],
["http://username:passwd%7E123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd~123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash", "URI encoding (password)"],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith%2Fhome.html?a=3&b=4&a=1#hash", "URI encoding (path) (reserved char)", false],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith%40/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./~smith@/home.html?a=3&b=4&a=1#hash", "URI encoding (path)"],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4%26a=1#hash", "URI encoding (query) (reserved char)", false],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=foo?bar~#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=foo%3Fbar%7E#hash", "URI encoding (query)"],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#hash",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1%23hash", "URI encoding (fragment) (reserved char)", false],
["http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#:hash~",
"http://username:passwd123@domain.com:80/users/../temp/./%7Esmith/home.html?a=3&b=4&a=1#%3Ahash%7E", "URI encoding (fragment)"],
["http://www.zendesk.com/product/pricing/",
"http://www.zendesk.com/support/", "common URL without many components", false],
], d;
for (var i = 0; i < testData.length; i++) {
d = testData[i];
if (d[3] === false) {
console.assert(!checkURIs(d[0], d[1]), d[2]);
console.assert(!checkURIs(d[1], d[0]), d[2] + " (args reversed)");
} else {
console.assert(checkURIs(d[0], d[1]), d[2]);
console.assert(checkURIs(d[1], d[0]), d[2] + " (args reversed)");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment