/*! * uri.js - mutating urls * * version: 1.19.1 * * author: rodney rehm * web: http://medialize.github.io/uri.js/ * * licensed under * mit license http://www.opensource.org/licenses/mit-license * */ (function (root, factory) { 'use strict'; // https://github.com/umdjs/umd/blob/master/returnexports.js if (typeof module === 'object' && module.exports) { // node module.exports = factory(require('./punycode'), require('./ipv6'), require('./secondleveldomains')); } else if (typeof define === 'function' && define.amd) { // amd. register as an anonymous module. define(['./punycode', './ipv6', './secondleveldomains'], factory); } else { // browser globals (root is window) root.uri = factory(root.punycode, root.ipv6, root.secondleveldomains, root); } }(this, function (punycode, ipv6, sld, root) { 'use strict'; /*global location, escape, unescape */ // fixme: v2.0.0 renamce non-camelcase properties to uppercase /*jshint camelcase: false */ // save current uri variable, if any var _uri = root && root.uri; function uri(url, base) { var _urlsupplied = arguments.length >= 1; var _basesupplied = arguments.length >= 2; // allow instantiation without the 'new' keyword if (!(this instanceof uri)) { if (_urlsupplied) { if (_basesupplied) { return new uri(url, base); } return new uri(url); } return new uri(); } if (url === undefined) { if (_urlsupplied) { throw new typeerror('undefined is not a valid argument for uri'); } if (typeof location !== 'undefined') { url = location.href + ''; } else { url = ''; } } if (url === null) { if (_urlsupplied) { throw new typeerror('null is not a valid argument for uri'); } } this.href(url); // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/overview.html#constructor if (base !== undefined) { return this.absoluteto(base); } return this; } function isinteger(value) { return /^[0-9]+$/.test(value); } uri.version = '1.19.1'; var p = uri.prototype; var hasown = object.prototype.hasownproperty; function escaperegex(string) { // https://github.com/medialize/uri.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963 return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); } function gettype(value) { // ie8 doesn't return [object undefined] but [object object] for undefined value if (value === undefined) { return 'undefined'; } return string(object.prototype.tostring.call(value)).slice(8, -1); } function isarray(obj) { return gettype(obj) === 'array'; } function filterarrayvalues(data, value) { var lookup = {}; var i, length; if (gettype(value) === 'regexp') { lookup = null; } else if (isarray(value)) { for (i = 0, length = value.length; i < length; i++) { lookup[value[i]] = true; } } else { lookup[value] = true; } for (i = 0, length = data.length; i < length; i++) { /*jshint laxbreak: true */ var _match = lookup && lookup[data[i]] !== undefined || !lookup && value.test(data[i]); /*jshint laxbreak: false */ if (_match) { data.splice(i, 1); length--; i--; } } return data; } function arraycontains(list, value) { var i, length; // value may be string, number, array, regexp if (isarray(value)) { // note: this can be optimized to o(n) (instead of current o(m * n)) for (i = 0, length = value.length; i < length; i++) { if (!arraycontains(list, value[i])) { return false; } } return true; } var _type = gettype(value); for (i = 0, length = list.length; i < length; i++) { if (_type === 'regexp') { if (typeof list[i] === 'string' && list[i].match(value)) { return true; } } else if (list[i] === value) { return true; } } return false; } function arraysequal(one, two) { if (!isarray(one) || !isarray(two)) { return false; } // arrays can't be equal if they have different amount of content if (one.length !== two.length) { return false; } one.sort(); two.sort(); for (var i = 0, l = one.length; i < l; i++) { if (one[i] !== two[i]) { return false; } } return true; } function trimslashes(text) { var trim_expression = /^\/+|\/+$/g; return text.replace(trim_expression, ''); } uri._parts = function() { return { protocol: null, username: null, password: null, hostname: null, urn: null, port: null, path: null, query: null, fragment: null, // state preventinvalidhostname: uri.preventinvalidhostname, duplicatequeryparameters: uri.duplicatequeryparameters, escapequeryspace: uri.escapequeryspace }; }; // state: throw on invalid hostname // see https://github.com/medialize/uri.js/pull/345 // and https://github.com/medialize/uri.js/issues/354 uri.preventinvalidhostname = false; // state: allow duplicate query parameters (a=1&a=1) uri.duplicatequeryparameters = false; // state: replaces + with %20 (space in query strings) uri.escapequeryspace = true; // static properties uri.protocol_expression = /^[a-z][a-z0-9.+-]*$/i; uri.idn_expression = /[^a-z0-9\._-]/i; uri.punycode_expression = /(xn--)/i; // well, 333.444.555.666 matches, but it sure ain't no ipv4 - do we care? uri.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; // credits to rich brown // source: http://forums.intermapper.com/viewtopic.php?p=1096#1096 // specification: http://www.ietf.org/rfc/rfc4291.txt uri.ip6_expression = /^\s*((([0-9a-fa-f]{1,4}:){7}([0-9a-fa-f]{1,4}|:))|(([0-9a-fa-f]{1,4}:){6}(:[0-9a-fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-fa-f]{1,4}:){5}(((:[0-9a-fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-fa-f]{1,4}:){4}(((:[0-9a-fa-f]{1,4}){1,3})|((:[0-9a-fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-fa-f]{1,4}:){3}(((:[0-9a-fa-f]{1,4}){1,4})|((:[0-9a-fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-fa-f]{1,4}:){2}(((:[0-9a-fa-f]{1,4}){1,5})|((:[0-9a-fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-fa-f]{1,4}:){1}(((:[0-9a-fa-f]{1,4}){1,6})|((:[0-9a-fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-fa-f]{1,4}){1,7})|((:[0-9a-fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/; // expression used is "gruber revised" (@gruber v2) determined to be the // best solution in a regex-golf we did a couple of ages ago at // * http://mathiasbynens.be/demo/url-regex // * http://rodneyrehm.de/t/url-regex.html uri.find_uri_expression = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig; uri.finduri = { // valid "scheme://" or "www." start: /\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi, // everything up to the next whitespace end: /[\s\r\n]|$/, // trim trailing punctuation captured by end regexp trim: /[`!()\[\]{};:'".,<>?«»“”„‘’]+$/, // balanced parens inclusion (), [], {}, <> parens: /(\([^\)]*\)|\[[^\]]*\]|\{[^}]*\}|<[^>]*>)/g, }; // http://www.iana.org/assignments/uri-schemes.html // http://en.wikipedia.org/wiki/list_of_tcp_and_udp_port_numbers#well-known_ports uri.defaultports = { http: '80', https: '443', ftp: '21', gopher: '70', 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\.\-:_]/; // map dom elements to their uri attribute uri.domattributes = { 'a': 'href', 'blockquote': 'cite', 'link': 'href', 'base': 'href', 'script': 'src', 'form': 'action', 'img': 'src', 'area': 'href', 'iframe': 'src', 'embed': 'src', 'source': 'src', 'track': 'src', 'input': 'src', // but only if type="image" 'audio': 'src', 'video': 'src' }; uri.getdomattribute = function(node) { if (!node || !node.nodename) { return undefined; } var nodename = node.nodename.tolowercase(); // should only expose src for type="image" if (nodename === 'input' && node.type !== 'image') { return undefined; } return uri.domattributes[nodename]; }; function escapefordumbfirefox36(value) { // https://github.com/medialize/uri.js/issues/91 return escape(value); } // encoding / decoding according to rfc3986 function strictencodeuricomponent(string) { // see https://developer.mozilla.org/en-us/docs/javascript/reference/global_objects/encodeuricomponent return encodeuricomponent(string) .replace(/[!'()*]/g, escapefordumbfirefox36) .replace(/\*/g, '%2a'); } uri.encode = strictencodeuricomponent; uri.decode = decodeuricomponent; uri.iso8859 = function() { uri.encode = escape; uri.decode = unescape; }; uri.unicode = function() { uri.encode = strictencodeuricomponent; uri.decode = decodeuricomponent; }; uri.characters = { pathname: { encode: { // rfc3986 2.1: for consistency, uri producers and normalizers should // use uppercase hexadecimal digits for all percent-encodings. expression: /%(24|26|2b|2c|3b|3d|3a|40)/ig, map: { // -._~!'()* '%24': '$', '%26': '&', '%2b': '+', '%2c': ',', '%3b': ';', '%3d': '=', '%3a': ':', '%40': '@' } }, decode: { expression: /[\/\?#]/g, map: { '/': '%2f', '?': '%3f', '#': '%23' } } }, reserved: { encode: { // rfc3986 2.1: for consistency, uri producers and normalizers should // use uppercase hexadecimal digits for all percent-encodings. expression: /%(21|23|24|26|27|28|29|2a|2b|2c|2f|3a|3b|3d|3f|40|5b|5d)/ig, map: { // gen-delims '%3a': ':', '%2f': '/', '%3f': '?', '%23': '#', '%5b': '[', '%5d': ']', '%40': '@', // sub-delims '%21': '!', '%24': '$', '%26': '&', '%27': '\'', '%28': '(', '%29': ')', '%2a': '*', '%2b': '+', '%2c': ',', '%3b': ';', '%3d': '=' } } }, urnpath: { // the characters under `encode` are the characters called out by rfc 2141 as being acceptable // for usage in a urn. rfc2141 also calls out "-", ".", and "_" as acceptable characters, but // these aren't encoded by encodeuricomponent, so we don't have to call them out here. also // note that the colon character is not featured in the encoding map; this is because uri.js // gives the colons in urns semantic meaning as the delimiters of path segements, and so it // should not appear unencoded in a segment itself. // see also the note above about rfc3986 and capitalalized hex digits. encode: { expression: /%(21|24|27|28|29|2a|2b|2c|3b|3d|40)/ig, map: { '%21': '!', '%24': '$', '%27': '\'', '%28': '(', '%29': ')', '%2a': '*', '%2b': '+', '%2c': ',', '%3b': ';', '%3d': '=', '%40': '@' } }, // these characters are the characters called out by rfc2141 as "reserved" characters that // should never appear in a urn, plus the colon character (see note above). decode: { expression: /[\/\?#:]/g, map: { '/': '%2f', '?': '%3f', '#': '%23', ':': '%3a' } } } }; uri.encodequery = function(string, escapequeryspace) { var escaped = uri.encode(string + ''); if (escapequeryspace === undefined) { escapequeryspace = uri.escapequeryspace; } return escapequeryspace ? escaped.replace(/%20/g, '+') : escaped; }; uri.decodequery = function(string, escapequeryspace) { string += ''; if (escapequeryspace === undefined) { escapequeryspace = uri.escapequeryspace; } try { return uri.decode(escapequeryspace ? string.replace(/\+/g, '%20') : string); } catch(e) { // we're not going to mess with weird encodings, // give up and return the undecoded original string // see https://github.com/medialize/uri.js/issues/87 // see https://github.com/medialize/uri.js/issues/92 return string; } }; // generate encode/decode path functions var _parts = {'encode':'encode', 'decode':'decode'}; var _part; var generateaccessor = function(_group, _part) { return function(string) { try { return uri[_part](string + '').replace(uri.characters[_group][_part].expression, function(c) { return uri.characters[_group][_part].map[c]; }); } catch (e) { // we're not going to mess with weird encodings, // give up and return the undecoded original string // see https://github.com/medialize/uri.js/issues/87 // see https://github.com/medialize/uri.js/issues/92 return string; } }; }; for (_part in _parts) { uri[_part + 'pathsegment'] = generateaccessor('pathname', _parts[_part]); uri[_part + 'urnpathsegment'] = generateaccessor('urnpath', _parts[_part]); } var generatesegmentedpathfunction = function(_sep, _codingfuncname, _innercodingfuncname) { return function(string) { // why pass in names of functions, rather than the function objects themselves? the // definitions of some functions (but in particular, uri.decode) will occasionally change due // to uri.js having iso8859 and unicode modes. passing in the name and getting it will ensure // that the functions we use here are "fresh". var actualcodingfunc; if (!_innercodingfuncname) { actualcodingfunc = uri[_codingfuncname]; } else { actualcodingfunc = function(string) { return uri[_codingfuncname](uri[_innercodingfuncname](string)); }; } var segments = (string + '').split(_sep); for (var i = 0, length = segments.length; i < length; i++) { segments[i] = actualcodingfunc(segments[i]); } return segments.join(_sep); }; }; // this takes place outside the above loop because we don't want, e.g., encodeurnpath functions. uri.decodepath = generatesegmentedpathfunction('/', 'decodepathsegment'); uri.decodeurnpath = generatesegmentedpathfunction(':', 'decodeurnpathsegment'); uri.recodepath = generatesegmentedpathfunction('/', 'encodepathsegment', 'decode'); uri.recodeurnpath = generatesegmentedpathfunction(':', 'encodeurnpathsegment', 'decode'); uri.encodereserved = generateaccessor('reserved', 'encode'); uri.parse = function(string, parts) { var pos; if (!parts) { parts = { preventinvalidhostname: uri.preventinvalidhostname }; } // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment] // extract fragment pos = string.indexof('#'); if (pos > -1) { // escaping? parts.fragment = string.substring(pos + 1) || null; string = string.substring(0, pos); } // extract query pos = string.indexof('?'); if (pos > -1) { // escaping? parts.query = string.substring(pos + 1) || null; string = string.substring(0, pos); } // extract protocol if (string.substring(0, 2) === '//') { // relative-scheme parts.protocol = null; string = string.substring(2); // extract "user:pass@host:port" string = uri.parseauthority(string, parts); } else { pos = string.indexof(':'); if (pos > -1) { parts.protocol = string.substring(0, pos) || null; if (parts.protocol && !parts.protocol.match(uri.protocol_expression)) { // : may be within the path parts.protocol = undefined; } else if (string.substring(pos + 1, pos + 3) === '//') { string = string.substring(pos + 3); // extract "user:pass@host:port" string = uri.parseauthority(string, parts); } else { string = string.substring(pos + 1); parts.urn = true; } } } // what's left must be the path parts.path = string; // and we're done return parts; }; uri.parsehost = function(string, parts) { if (!string) { string = ''; } // copy chrome, ie, opera backslash-handling behavior. // back slashes before the query string get converted to forward slashes // see: https://github.com/joyent/node/blob/386fd24f49b0e9d1a8a076592a404168faeecc34/lib/url.js#l115-l124 // see: https://code.google.com/p/chromium/issues/detail?id=25916 // https://github.com/medialize/uri.js/pull/233 string = string.replace(/\\/g, '/'); // extract host:port var pos = string.indexof('/'); var bracketpos; var t; if (pos === -1) { pos = string.length; } if (string.charat(0) === '[') { // ipv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6 // i claim most client software breaks on ipv6 anyways. to simplify things, uri only accepts // ipv6+port in the format [2001:db8::1]:80 (for the time being) bracketpos = string.indexof(']'); parts.hostname = string.substring(1, bracketpos) || null; parts.port = string.substring(bracketpos + 2, pos) || null; if (parts.port === '/') { parts.port = null; } } else { var firstcolon = string.indexof(':'); var firstslash = string.indexof('/'); var nextcolon = string.indexof(':', firstcolon + 1); if (nextcolon !== -1 && (firstslash === -1 || nextcolon < firstslash)) { // ipv6 host contains multiple colons - but no port // this notation is actually not allowed by rfc 3986, but we're a liberal parser parts.hostname = string.substring(0, pos) || null; parts.port = null; } else { t = string.substring(0, pos).split(':'); parts.hostname = t[0] || null; parts.port = t[1] || null; } } if (parts.hostname && string.substring(pos).charat(0) !== '/') { pos++; string = '/' + string; } if (parts.preventinvalidhostname) { uri.ensurevalidhostname(parts.hostname, parts.protocol); } if (parts.port) { uri.ensurevalidport(parts.port); } return string.substring(pos) || '/'; }; uri.parseauthority = function(string, parts) { string = uri.parseuserinfo(string, parts); return uri.parsehost(string, parts); }; uri.parseuserinfo = function(string, parts) { // extract username:password var firstslash = string.indexof('/'); var pos = string.lastindexof('@', firstslash > -1 ? firstslash : string.length - 1); var t; // authority@ must come before /path if (pos > -1 && (firstslash === -1 || pos < firstslash)) { t = string.substring(0, pos).split(':'); parts.username = t[0] ? uri.decode(t[0]) : null; t.shift(); parts.password = t[0] ? uri.decode(t.join(':')) : null; string = string.substring(pos + 1); } else { parts.username = null; parts.password = null; } return string; }; uri.parsequery = function(string, escapequeryspace) { if (!string) { return {}; } // throw out the funky business - "?"[name"="value"&"]+ string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, ''); if (!string) { return {}; } var items = {}; var splits = string.split('&'); var length = splits.length; var v, name, value; for (var i = 0; i < length; i++) { v = splits[i].split('='); name = uri.decodequery(v.shift(), escapequeryspace); // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/overview.html#collect-url-parameters value = v.length ? uri.decodequery(v.join('='), escapequeryspace) : null; if (hasown.call(items, name)) { if (typeof items[name] === 'string' || items[name] === null) { items[name] = [items[name]]; } items[name].push(value); } else { items[name] = value; } } return items; }; uri.build = function(parts) { var t = ''; if (parts.protocol) { t += parts.protocol + ':'; } if (!parts.urn && (t || parts.hostname)) { t += '//'; } t += (uri.buildauthority(parts) || ''); if (typeof parts.path === 'string') { if (parts.path.charat(0) !== '/' && typeof parts.hostname === 'string') { t += '/'; } t += parts.path; } if (typeof parts.query === 'string' && parts.query) { t += '?' + parts.query; } if (typeof parts.fragment === 'string' && parts.fragment) { t += '#' + parts.fragment; } return t; }; uri.buildhost = function(parts) { var t = ''; if (!parts.hostname) { return ''; } else if (uri.ip6_expression.test(parts.hostname)) { t += '[' + parts.hostname + ']'; } else { t += parts.hostname; } if (parts.port) { t += ':' + parts.port; } return t; }; uri.buildauthority = function(parts) { return uri.builduserinfo(parts) + uri.buildhost(parts); }; uri.builduserinfo = function(parts) { var t = ''; if (parts.username) { t += uri.encode(parts.username); } if (parts.password) { t += ':' + uri.encode(parts.password); } if (t) { t += '@'; } return t; }; uri.buildquery = function(data, duplicatequeryparameters, escapequeryspace) { // according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html // being »-._~!$&'()*+,;=:@/?« %hex and alnum are allowed // the rfc explicitly states ?/foo being a valid use case, no mention of parameter syntax! // uri.js treats the query string as being application/x-www-form-urlencoded // see http://www.w3.org/tr/rec-html40/interact/forms.html#form-content-type var t = ''; var unique, key, i, length; for (key in data) { if (hasown.call(data, key) && key) { if (isarray(data[key])) { unique = {}; for (i = 0, length = data[key].length; i < length; i++) { if (data[key][i] !== undefined && unique[data[key][i] + ''] === undefined) { t += '&' + uri.buildqueryparameter(key, data[key][i], escapequeryspace); if (duplicatequeryparameters !== true) { unique[data[key][i] + ''] = true; } } } } else if (data[key] !== undefined) { t += '&' + uri.buildqueryparameter(key, data[key], escapequeryspace); } } } return t.substring(1); }; uri.buildqueryparameter = function(name, value, escapequeryspace) { // http://www.w3.org/tr/rec-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded // don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/overview.html#url-parameter-serialization return uri.encodequery(name, escapequeryspace) + (value !== null ? '=' + uri.encodequery(value, escapequeryspace) : ''); }; uri.addquery = function(data, name, value) { if (typeof name === 'object') { for (var key in name) { if (hasown.call(name, key)) { uri.addquery(data, key, name[key]); } } } else if (typeof name === 'string') { if (data[name] === undefined) { data[name] = value; return; } else if (typeof data[name] === 'string') { data[name] = [data[name]]; } if (!isarray(value)) { value = [value]; } data[name] = (data[name] || []).concat(value); } else { throw new typeerror('uri.addquery() accepts an object, string as the name parameter'); } }; uri.setquery = function(data, name, value) { if (typeof name === 'object') { for (var key in name) { if (hasown.call(name, key)) { uri.setquery(data, key, name[key]); } } } else if (typeof name === 'string') { data[name] = value === undefined ? null : value; } else { throw new typeerror('uri.setquery() accepts an object, string as the name parameter'); } }; uri.removequery = function(data, name, value) { var i, length, key; if (isarray(name)) { for (i = 0, length = name.length; i < length; i++) { data[name[i]] = undefined; } } else if (gettype(name) === 'regexp') { for (key in data) { if (name.test(key)) { data[key] = undefined; } } } else if (typeof name === 'object') { for (key in name) { if (hasown.call(name, key)) { uri.removequery(data, key, name[key]); } } } else if (typeof name === 'string') { if (value !== undefined) { if (gettype(value) === 'regexp') { if (!isarray(data[name]) && value.test(data[name])) { data[name] = undefined; } else { data[name] = filterarrayvalues(data[name], value); } } else if (data[name] === string(value) && (!isarray(value) || value.length === 1)) { data[name] = undefined; } else if (isarray(data[name])) { data[name] = filterarrayvalues(data[name], value); } } else { data[name] = undefined; } } else { throw new typeerror('uri.removequery() accepts an object, string, regexp as the first parameter'); } }; uri.hasquery = function(data, name, value, withinarray) { switch (gettype(name)) { case 'string': // nothing to do here break; case 'regexp': for (var key in data) { if (hasown.call(data, key)) { if (name.test(key) && (value === undefined || uri.hasquery(data, key, value))) { return true; } } } return false; case 'object': for (var _key in name) { if (hasown.call(name, _key)) { if (!uri.hasquery(data, _key, name[_key])) { return false; } } } return true; default: throw new typeerror('uri.hasquery() accepts a string, regular expression or object as the name parameter'); } switch (gettype(value)) { case 'undefined': // true if exists (but may be empty) return name in data; // data[name] !== undefined; case 'boolean': // true if exists and non-empty var _booly = boolean(isarray(data[name]) ? data[name].length : data[name]); return value === _booly; case 'function': // allow complex comparison return !!value(data[name], name, data); case 'array': if (!isarray(data[name])) { return false; } var op = withinarray ? arraycontains : arraysequal; return op(data[name], value); case 'regexp': if (!isarray(data[name])) { return boolean(data[name] && data[name].match(value)); } if (!withinarray) { return false; } return arraycontains(data[name], value); case 'number': value = string(value); /* falls through */ case 'string': if (!isarray(data[name])) { return data[name] === value; } if (!withinarray) { return false; } return arraycontains(data[name], value); default: throw new typeerror('uri.hasquery() accepts undefined, boolean, string, number, regexp, function as the value parameter'); } }; uri.joinpaths = function() { var input = []; var segments = []; var nonemptysegments = 0; for (var i = 0; i < arguments.length; i++) { var url = new uri(arguments[i]); input.push(url); var _segments = url.segment(); for (var s = 0; s < _segments.length; s++) { if (typeof _segments[s] === 'string') { segments.push(_segments[s]); } if (_segments[s]) { nonemptysegments++; } } } if (!segments.length || !nonemptysegments) { return new uri(''); } var uri = new uri('').segment(segments); if (input[0].path() === '' || input[0].path().slice(0, 1) === '/') { uri.path('/' + uri.path()); } return uri.normalize(); }; uri.commonpath = function(one, two) { var length = math.min(one.length, two.length); var pos; // find first non-matching character for (pos = 0; pos < length; pos++) { if (one.charat(pos) !== two.charat(pos)) { pos--; break; } } if (pos < 1) { return one.charat(0) === two.charat(0) && one.charat(0) === '/' ? '/' : ''; } // revert to last / if (one.charat(pos) !== '/' || two.charat(pos) !== '/') { pos = one.substring(0, pos).lastindexof('/'); } return one.substring(0, pos + 1); }; uri.withinstring = function(string, callback, options) { options || (options = {}); var _start = options.start || uri.finduri.start; var _end = options.end || uri.finduri.end; var _trim = options.trim || uri.finduri.trim; var _parens = options.parens || uri.finduri.parens; var _attributeopen = /[a-z0-9-]=["']?$/i; _start.lastindex = 0; while (true) { var match = _start.exec(string); if (!match) { break; } var start = match.index; if (options.ignorehtml) { // attribut(e=["']?$) var attributeopen = string.slice(math.max(start - 3, 0), start); if (attributeopen && _attributeopen.test(attributeopen)) { continue; } } var end = start + string.slice(start).search(_end); var slice = string.slice(start, end); // make sure we include well balanced parens var parensend = -1; while (true) { var parensmatch = _parens.exec(slice); if (!parensmatch) { break; } var parensmatchend = parensmatch.index + parensmatch[0].length; parensend = math.max(parensend, parensmatchend); } if (parensend > -1) { slice = slice.slice(0, parensend) + slice.slice(parensend).replace(_trim, ''); } else { slice = slice.replace(_trim, ''); } if (slice.length <= match[0].length) { // the extract only contains the starting marker of a uri, // e.g. "www" or "http://" continue; } if (options.ignore && options.ignore.test(slice)) { continue; } end = start + slice.length; var result = callback(slice, start, end, string); if (result === undefined) { _start.lastindex = end; continue; } result = string(result); string = string.slice(0, start) + result + string.slice(end); _start.lastindex = start + result.length; } _start.lastindex = 0; return string; }; 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 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.-:_]'); } } }; uri.ensurevalidport = function (v) { if (!v) { return; } var port = number(v); if (isinteger(port) && (port > 0) && (port < 65536)) { return; } throw new typeerror('port "' + v + '" is not a valid port'); }; // noconflict uri.noconflict = function(removeall) { if (removeall) { var unconflicted = { uri: this.noconflict() }; if (root.uritemplate && typeof root.uritemplate.noconflict === 'function') { unconflicted.uritemplate = root.uritemplate.noconflict(); } if (root.ipv6 && typeof root.ipv6.noconflict === 'function') { unconflicted.ipv6 = root.ipv6.noconflict(); } if (root.secondleveldomains && typeof root.secondleveldomains.noconflict === 'function') { unconflicted.secondleveldomains = root.secondleveldomains.noconflict(); } return unconflicted; } else if (root.uri === this) { root.uri = _uri; } return this; }; p.build = function(deferbuild) { if (deferbuild === true) { this._deferred_build = true; } else if (deferbuild === undefined || this._deferred_build) { this._string = uri.build(this._parts); this._deferred_build = false; } return this; }; p.clone = function() { return new uri(this); }; p.valueof = p.tostring = function() { return this.build(false)._string; }; function generatesimpleaccessor(_part){ return function(v, build) { if (v === undefined) { return this._parts[_part] || ''; } else { this._parts[_part] = v || null; this.build(!build); return this; } }; } function generateprefixaccessor(_part, _key){ return function(v, build) { if (v === undefined) { return this._parts[_part] || ''; } else { if (v !== null) { v = v + ''; if (v.charat(0) === _key) { v = v.substring(1); } } this._parts[_part] = v; this.build(!build); return this; } }; } p.protocol = generatesimpleaccessor('protocol'); p.username = generatesimpleaccessor('username'); p.password = generatesimpleaccessor('password'); p.hostname = generatesimpleaccessor('hostname'); p.port = generatesimpleaccessor('port'); p.query = generateprefixaccessor('query', '?'); p.fragment = generateprefixaccessor('fragment', '#'); p.search = function(v, build) { var t = this.query(v, build); return typeof t === 'string' && t.length ? ('?' + t) : t; }; p.hash = function(v, build) { var t = this.fragment(v, build); return typeof t === 'string' && t.length ? ('#' + t) : t; }; p.pathname = function(v, build) { if (v === undefined || v === true) { var res = this._parts.path || (this._parts.hostname ? '/' : ''); return v ? (this._parts.urn ? uri.decodeurnpath : uri.decodepath)(res) : res; } else { if (this._parts.urn) { this._parts.path = v ? uri.recodeurnpath(v) : ''; } else { this._parts.path = v ? uri.recodepath(v) : '/'; } this.build(!build); return this; } }; p.path = p.pathname; p.href = function(href, build) { var key; if (href === undefined) { return this.tostring(); } this._string = ''; this._parts = uri._parts(); var _uri = href instanceof uri; var _object = typeof href === 'object' && (href.hostname || href.path || href.pathname); if (href.nodename) { var attribute = uri.getdomattribute(href); href = href[attribute] || ''; _object = false; } // window.location is reported to be an object, but it's not the sort // of object we're looking for: // * location.protocol ends with a colon // * location.query != object.search // * location.hash != object.fragment // simply serializing the unknown object should do the trick // (for location, not for everything...) if (!_uri && _object && href.pathname !== undefined) { href = href.tostring(); } if (typeof href === 'string' || href instanceof string) { this._parts = uri.parse(string(href), this._parts); } else if (_uri || _object) { var src = _uri ? href._parts : href; for (key in src) { if (key === 'query') { continue; } if (hasown.call(this._parts, key)) { this._parts[key] = src[key]; } } if (src.query) { this.query(src.query, false); } } else { throw new typeerror('invalid input'); } this.build(!build); return this; }; // identification accessors p.is = function(what) { var ip = false; var ip4 = false; var ip6 = false; var name = false; var sld = false; var idn = false; var punycode = false; var relative = !this._parts.urn; if (this._parts.hostname) { relative = false; ip4 = uri.ip4_expression.test(this._parts.hostname); ip6 = uri.ip6_expression.test(this._parts.hostname); ip = ip4 || ip6; name = !ip; sld = name && sld && sld.has(this._parts.hostname); idn = name && uri.idn_expression.test(this._parts.hostname); punycode = name && uri.punycode_expression.test(this._parts.hostname); } switch (what.tolowercase()) { case 'relative': return relative; case 'absolute': return !relative; // hostname identification case 'domain': case 'name': return name; case 'sld': return sld; case 'ip': return ip; case 'ip4': case 'ipv4': case 'inet4': return ip4; case 'ip6': case 'ipv6': case 'inet6': return ip6; case 'idn': return idn; case 'url': return !this._parts.urn; case 'urn': return !!this._parts.urn; case 'punycode': return punycode; } return null; }; // component specific input validation var _protocol = p.protocol; var _port = p.port; var _hostname = p.hostname; p.protocol = function(v, build) { if (v) { // accept trailing :// v = v.replace(/:(\/\/)?$/, ''); if (!v.match(uri.protocol_expression)) { throw new typeerror('protocol "' + v + '" contains characters other than [a-z0-9.+-] or doesn\'t start with [a-z]'); } } return _protocol.call(this, v, build); }; p.scheme = p.protocol; p.port = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v !== undefined) { if (v === 0) { v = null; } if (v) { v += ''; if (v.charat(0) === ':') { v = v.substring(1); } uri.ensurevalidport(v); } } return _port.call(this, v, build); }; p.hostname = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v !== undefined) { var x = { preventinvalidhostname: this._parts.preventinvalidhostname }; var res = uri.parsehost(v, x); if (res !== '/') { throw new typeerror('hostname "' + v + '" contains characters other than [a-z0-9.-]'); } v = x.hostname; if (this._parts.preventinvalidhostname) { uri.ensurevalidhostname(v, this._parts.protocol); } } return _hostname.call(this, v, build); }; // compound accessors p.origin = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v === undefined) { var protocol = this.protocol(); var authority = this.authority(); if (!authority) { return ''; } return (protocol ? protocol + '://' : '') + this.authority(); } else { var origin = uri(v); this .protocol(origin.protocol()) .authority(origin.authority()) .build(!build); return this; } }; p.host = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v === undefined) { return this._parts.hostname ? uri.buildhost(this._parts) : ''; } else { var res = uri.parsehost(v, this._parts); if (res !== '/') { throw new typeerror('hostname "' + v + '" contains characters other than [a-z0-9.-]'); } this.build(!build); return this; } }; p.authority = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v === undefined) { return this._parts.hostname ? uri.buildauthority(this._parts) : ''; } else { var res = uri.parseauthority(v, this._parts); if (res !== '/') { throw new typeerror('hostname "' + v + '" contains characters other than [a-z0-9.-]'); } this.build(!build); return this; } }; p.userinfo = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v === undefined) { var t = uri.builduserinfo(this._parts); return t ? t.substring(0, t.length -1) : t; } else { if (v[v.length-1] !== '@') { v += '@'; } uri.parseuserinfo(v, this._parts); this.build(!build); return this; } }; p.resource = function(v, build) { var parts; if (v === undefined) { return this.path() + this.search() + this.hash(); } parts = uri.parse(v); this._parts.path = parts.path; this._parts.query = parts.query; this._parts.fragment = parts.fragment; this.build(!build); return this; }; // fraction accessors p.subdomain = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } // convenience, return "www" from "www.example.org" if (v === undefined) { if (!this._parts.hostname || this.is('ip')) { return ''; } // grab domain and add another segment var end = this._parts.hostname.length - this.domain().length - 1; return this._parts.hostname.substring(0, end) || ''; } else { var e = this._parts.hostname.length - this.domain().length; var sub = this._parts.hostname.substring(0, e); var replace = new regexp('^' + escaperegex(sub)); if (v && v.charat(v.length - 1) !== '.') { v += '.'; } if (v.indexof(':') !== -1) { throw new typeerror('domains cannot contain colons'); } if (v) { uri.ensurevalidhostname(v, this._parts.protocol); } this._parts.hostname = this._parts.hostname.replace(replace, v); this.build(!build); return this; } }; p.domain = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (typeof v === 'boolean') { build = v; v = undefined; } // convenience, return "example.org" from "www.example.org" if (v === undefined) { if (!this._parts.hostname || this.is('ip')) { return ''; } // if hostname consists of 1 or 2 segments, it must be the domain var t = this._parts.hostname.match(/\./g); if (t && t.length < 2) { return this._parts.hostname; } // grab tld and add another segment var end = this._parts.hostname.length - this.tld(build).length - 1; end = this._parts.hostname.lastindexof('.', end -1) + 1; return this._parts.hostname.substring(end) || ''; } else { if (!v) { throw new typeerror('cannot set domain empty'); } 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; } else { var replace = new regexp(escaperegex(this.domain()) + '$'); this._parts.hostname = this._parts.hostname.replace(replace, v); } this.build(!build); return this; } }; p.tld = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (typeof v === 'boolean') { build = v; v = undefined; } // return "org" from "www.example.org" if (v === undefined) { if (!this._parts.hostname || this.is('ip')) { return ''; } var pos = this._parts.hostname.lastindexof('.'); var tld = this._parts.hostname.substring(pos + 1); if (build !== true && sld && sld.list[tld.tolowercase()]) { return sld.get(this._parts.hostname) || tld; } return tld; } else { var replace; if (!v) { throw new typeerror('cannot set tld empty'); } else if (v.match(/[^a-za-z0-9-]/)) { if (sld && sld.is(v)) { replace = new regexp(escaperegex(this.tld()) + '$'); this._parts.hostname = this._parts.hostname.replace(replace, v); } else { throw new typeerror('tld "' + v + '" contains characters other than [a-z0-9]'); } } else if (!this._parts.hostname || this.is('ip')) { throw new referenceerror('cannot set tld on non-domain host'); } else { replace = new regexp(escaperegex(this.tld()) + '$'); this._parts.hostname = this._parts.hostname.replace(replace, v); } this.build(!build); return this; } }; p.directory = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v === undefined || v === true) { if (!this._parts.path && !this._parts.hostname) { return ''; } if (this._parts.path === '/') { return '/'; } var end = this._parts.path.length - this.filename().length - 1; var res = this._parts.path.substring(0, end) || (this._parts.hostname ? '/' : ''); return v ? uri.decodepath(res) : res; } else { var e = this._parts.path.length - this.filename().length; var directory = this._parts.path.substring(0, e); var replace = new regexp('^' + escaperegex(directory)); // fully qualifier directories begin with a slash if (!this.is('relative')) { if (!v) { v = '/'; } if (v.charat(0) !== '/') { v = '/' + v; } } // directories always end with a slash if (v && v.charat(v.length - 1) !== '/') { v += '/'; } v = uri.recodepath(v); this._parts.path = this._parts.path.replace(replace, v); this.build(!build); return this; } }; p.filename = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (typeof v !== 'string') { if (!this._parts.path || this._parts.path === '/') { return ''; } var pos = this._parts.path.lastindexof('/'); var res = this._parts.path.substring(pos+1); return v ? uri.decodepathsegment(res) : res; } else { var mutateddirectory = false; if (v.charat(0) === '/') { v = v.substring(1); } if (v.match(/\.?\//)) { mutateddirectory = true; } var replace = new regexp(escaperegex(this.filename()) + '$'); v = uri.recodepath(v); this._parts.path = this._parts.path.replace(replace, v); if (mutateddirectory) { this.normalizepath(build); } else { this.build(!build); } return this; } }; p.suffix = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v === undefined || v === true) { if (!this._parts.path || this._parts.path === '/') { return ''; } var filename = this.filename(); var pos = filename.lastindexof('.'); var s, res; if (pos === -1) { return ''; } // suffix may only contain alnum characters (yup, i made this up.) s = filename.substring(pos+1); res = (/^[a-z0-9%]+$/i).test(s) ? s : ''; return v ? uri.decodepathsegment(res) : res; } else { if (v.charat(0) === '.') { v = v.substring(1); } var suffix = this.suffix(); var replace; if (!suffix) { if (!v) { return this; } this._parts.path += '.' + uri.recodepath(v); } else if (!v) { replace = new regexp(escaperegex('.' + suffix) + '$'); } else { replace = new regexp(escaperegex(suffix) + '$'); } if (replace) { v = uri.recodepath(v); this._parts.path = this._parts.path.replace(replace, v); } this.build(!build); return this; } }; p.segment = function(segment, v, build) { var separator = this._parts.urn ? ':' : '/'; var path = this.path(); var absolute = path.substring(0, 1) === '/'; var segments = path.split(separator); if (segment !== undefined && typeof segment !== 'number') { build = v; v = segment; segment = undefined; } if (segment !== undefined && typeof segment !== 'number') { throw new error('bad segment "' + segment + '", must be 0-based integer'); } if (absolute) { segments.shift(); } if (segment < 0) { // allow negative indexes to address from the end segment = math.max(segments.length + segment, 0); } if (v === undefined) { /*jshint laxbreak: true */ return segment === undefined ? segments : segments[segment]; /*jshint laxbreak: false */ } else if (segment === null || segments[segment] === undefined) { if (isarray(v)) { segments = []; // collapse empty elements within array for (var i=0, l=v.length; i < l; i++) { if (!v[i].length && (!segments.length || !segments[segments.length -1].length)) { continue; } if (segments.length && !segments[segments.length -1].length) { segments.pop(); } segments.push(trimslashes(v[i])); } } else if (v || typeof v === 'string') { v = trimslashes(v); if (segments[segments.length -1] === '') { // empty trailing elements have to be overwritten // to prevent results such as /foo//bar segments[segments.length -1] = v; } else { segments.push(v); } } } else { if (v) { segments[segment] = trimslashes(v); } else { segments.splice(segment, 1); } } if (absolute) { segments.unshift(''); } return this.path(segments.join(separator), build); }; p.segmentcoded = function(segment, v, build) { var segments, i, l; if (typeof segment !== 'number') { build = v; v = segment; segment = undefined; } if (v === undefined) { segments = this.segment(segment, v, build); if (!isarray(segments)) { segments = segments !== undefined ? uri.decode(segments) : undefined; } else { for (i = 0, l = segments.length; i < l; i++) { segments[i] = uri.decode(segments[i]); } } return segments; } if (!isarray(v)) { v = (typeof v === 'string' || v instanceof string) ? uri.encode(v) : v; } else { for (i = 0, l = v.length; i < l; i++) { v[i] = uri.encode(v[i]); } } return this.segment(segment, v, build); }; // mutating query string var q = p.query; p.query = function(v, build) { if (v === true) { return uri.parsequery(this._parts.query, this._parts.escapequeryspace); } else if (typeof v === 'function') { var data = uri.parsequery(this._parts.query, this._parts.escapequeryspace); var result = v.call(this, data); this._parts.query = uri.buildquery(result || data, this._parts.duplicatequeryparameters, this._parts.escapequeryspace); this.build(!build); return this; } else if (v !== undefined && typeof v !== 'string') { this._parts.query = uri.buildquery(v, this._parts.duplicatequeryparameters, this._parts.escapequeryspace); this.build(!build); return this; } else { return q.call(this, v, build); } }; p.setquery = function(name, value, build) { var data = uri.parsequery(this._parts.query, this._parts.escapequeryspace); if (typeof name === 'string' || name instanceof string) { data[name] = value !== undefined ? value : null; } else if (typeof name === 'object') { for (var key in name) { if (hasown.call(name, key)) { data[key] = name[key]; } } } else { throw new typeerror('uri.addquery() accepts an object, string as the name parameter'); } this._parts.query = uri.buildquery(data, this._parts.duplicatequeryparameters, this._parts.escapequeryspace); if (typeof name !== 'string') { build = value; } this.build(!build); return this; }; p.addquery = function(name, value, build) { var data = uri.parsequery(this._parts.query, this._parts.escapequeryspace); uri.addquery(data, name, value === undefined ? null : value); this._parts.query = uri.buildquery(data, this._parts.duplicatequeryparameters, this._parts.escapequeryspace); if (typeof name !== 'string') { build = value; } this.build(!build); return this; }; p.removequery = function(name, value, build) { var data = uri.parsequery(this._parts.query, this._parts.escapequeryspace); uri.removequery(data, name, value); this._parts.query = uri.buildquery(data, this._parts.duplicatequeryparameters, this._parts.escapequeryspace); if (typeof name !== 'string') { build = value; } this.build(!build); return this; }; p.hasquery = function(name, value, withinarray) { var data = uri.parsequery(this._parts.query, this._parts.escapequeryspace); return uri.hasquery(data, name, value, withinarray); }; p.setsearch = p.setquery; p.addsearch = p.addquery; p.removesearch = p.removequery; p.hassearch = p.hasquery; // sanitizing urls p.normalize = function() { if (this._parts.urn) { return this .normalizeprotocol(false) .normalizepath(false) .normalizequery(false) .normalizefragment(false) .build(); } return this .normalizeprotocol(false) .normalizehostname(false) .normalizeport(false) .normalizepath(false) .normalizequery(false) .normalizefragment(false) .build(); }; p.normalizeprotocol = function(build) { if (typeof this._parts.protocol === 'string') { this._parts.protocol = this._parts.protocol.tolowercase(); this.build(!build); } return this; }; p.normalizehostname = function(build) { if (this._parts.hostname) { if (this.is('idn') && punycode) { this._parts.hostname = punycode.toascii(this._parts.hostname); } else if (this.is('ipv6') && ipv6) { this._parts.hostname = ipv6.best(this._parts.hostname); } this._parts.hostname = this._parts.hostname.tolowercase(); this.build(!build); } return this; }; p.normalizeport = function(build) { // remove port of it's the protocol's default if (typeof this._parts.protocol === 'string' && this._parts.port === uri.defaultports[this._parts.protocol]) { this._parts.port = null; this.build(!build); } return this; }; p.normalizepath = function(build) { var _path = this._parts.path; if (!_path) { return this; } if (this._parts.urn) { this._parts.path = uri.recodeurnpath(this._parts.path); this.build(!build); return this; } if (this._parts.path === '/') { return this; } _path = uri.recodepath(_path); var _was_relative; var _leadingparents = ''; var _parent, _pos; // handle relative paths if (_path.charat(0) !== '/') { _was_relative = true; _path = '/' + _path; } // handle relative files (as opposed to directories) if (_path.slice(-3) === '/..' || _path.slice(-2) === '/.') { _path += '/'; } // resolve simples _path = _path .replace(/(\/(\.\/)+)|(\/\.$)/g, '/') .replace(/\/{2,}/g, '/'); // remember leading parents if (_was_relative) { _leadingparents = _path.substring(1).match(/^(\.\.\/)+/) || ''; if (_leadingparents) { _leadingparents = _leadingparents[0]; } } // resolve parents while (true) { _parent = _path.search(/\/\.\.(\/|$)/); if (_parent === -1) { // no more ../ to resolve break; } else if (_parent === 0) { // top level cannot be relative, skip it _path = _path.substring(3); continue; } _pos = _path.substring(0, _parent).lastindexof('/'); if (_pos === -1) { _pos = _parent; } _path = _path.substring(0, _pos) + _path.substring(_parent + 3); } // revert to relative if (_was_relative && this.is('relative')) { _path = _leadingparents + _path.substring(1); } this._parts.path = _path; this.build(!build); return this; }; p.normalizepathname = p.normalizepath; p.normalizequery = function(build) { if (typeof this._parts.query === 'string') { if (!this._parts.query.length) { this._parts.query = null; } else { this.query(uri.parsequery(this._parts.query, this._parts.escapequeryspace)); } this.build(!build); } return this; }; p.normalizefragment = function(build) { if (!this._parts.fragment) { this._parts.fragment = null; this.build(!build); } return this; }; p.normalizesearch = p.normalizequery; p.normalizehash = p.normalizefragment; p.iso8859 = function() { // expect unicode input, iso8859 output var e = uri.encode; var d = uri.decode; uri.encode = escape; uri.decode = decodeuricomponent; try { this.normalize(); } finally { uri.encode = e; uri.decode = d; } return this; }; p.unicode = function() { // expect iso8859 input, unicode output var e = uri.encode; var d = uri.decode; uri.encode = strictencodeuricomponent; uri.decode = unescape; try { this.normalize(); } finally { uri.encode = e; uri.decode = d; } return this; }; p.readable = function() { var uri = this.clone(); // removing username, password, because they shouldn't be displayed according to rfc 3986 uri.username('').password('').normalize(); var t = ''; if (uri._parts.protocol) { t += uri._parts.protocol + '://'; } if (uri._parts.hostname) { if (uri.is('punycode') && punycode) { t += punycode.tounicode(uri._parts.hostname); if (uri._parts.port) { t += ':' + uri._parts.port; } } else { t += uri.host(); } } if (uri._parts.hostname && uri._parts.path && uri._parts.path.charat(0) !== '/') { t += '/'; } t += uri.path(true); if (uri._parts.query) { var q = ''; for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) { var kv = (qp[i] || '').split('='); q += '&' + uri.decodequery(kv[0], this._parts.escapequeryspace) .replace(/&/g, '%26'); if (kv[1] !== undefined) { q += '=' + uri.decodequery(kv[1], this._parts.escapequeryspace) .replace(/&/g, '%26'); } } t += '?' + q.substring(1); } t += uri.decodequery(uri.hash(), true); return t; }; // resolving relative and absolute urls p.absoluteto = function(base) { var resolved = this.clone(); var properties = ['protocol', 'username', 'password', 'hostname', 'port']; var basedir, i, p; if (this._parts.urn) { throw new error('urns do not have any generally defined hierarchical components'); } if (!(base instanceof uri)) { base = new uri(base); } if (resolved._parts.protocol) { // directly returns even if this._parts.hostname is empty. return resolved; } else { resolved._parts.protocol = base._parts.protocol; } if (this._parts.hostname) { return resolved; } for (i = 0; (p = properties[i]); i++) { resolved._parts[p] = base._parts[p]; } if (!resolved._parts.path) { resolved._parts.path = base._parts.path; if (!resolved._parts.query) { resolved._parts.query = base._parts.query; } } else { if (resolved._parts.path.substring(-2) === '..') { resolved._parts.path += '/'; } if (resolved.path().charat(0) !== '/') { basedir = base.directory(); basedir = basedir ? basedir : base.path().indexof('/') === 0 ? '/' : ''; resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path; resolved.normalizepath(); } } resolved.build(); return resolved; }; p.relativeto = function(base) { var relative = this.clone().normalize(); var relativeparts, baseparts, common, relativepath, basepath; if (relative._parts.urn) { throw new error('urns do not have any generally defined hierarchical components'); } base = new uri(base).normalize(); relativeparts = relative._parts; baseparts = base._parts; relativepath = relative.path(); basepath = base.path(); if (relativepath.charat(0) !== '/') { throw new error('uri is already relative'); } if (basepath.charat(0) !== '/') { throw new error('cannot calculate a uri relative to another relative uri'); } if (relativeparts.protocol === baseparts.protocol) { relativeparts.protocol = null; } if (relativeparts.username !== baseparts.username || relativeparts.password !== baseparts.password) { return relative.build(); } if (relativeparts.protocol !== null || relativeparts.username !== null || relativeparts.password !== null) { return relative.build(); } if (relativeparts.hostname === baseparts.hostname && relativeparts.port === baseparts.port) { relativeparts.hostname = null; relativeparts.port = null; } else { return relative.build(); } if (relativepath === basepath) { relativeparts.path = ''; return relative.build(); } // determine common sub path common = uri.commonpath(relativepath, basepath); // if the paths have nothing in common, return a relative url with the absolute path. if (!common) { return relative.build(); } var parents = baseparts.path .substring(common.length) .replace(/[^\/]*$/, '') .replace(/.*?\//g, '../'); relativeparts.path = (parents + relativeparts.path.substring(common.length)) || './'; return relative.build(); }; // comparing uris p.equals = function(uri) { var one = this.clone(); var two = new uri(uri); var one_map = {}; var two_map = {}; var checked = {}; var one_query, two_query, key; one.normalize(); two.normalize(); // exact match if (one.tostring() === two.tostring()) { return true; } // extract query string one_query = one.query(); two_query = two.query(); one.query(''); two.query(''); // definitely not equal if not even non-query parts match if (one.tostring() !== two.tostring()) { return false; } // query parameters have the same length, even if they're permuted if (one_query.length !== two_query.length) { return false; } one_map = uri.parsequery(one_query, this._parts.escapequeryspace); two_map = uri.parsequery(two_query, this._parts.escapequeryspace); for (key in one_map) { if (hasown.call(one_map, key)) { if (!isarray(one_map[key])) { if (one_map[key] !== two_map[key]) { return false; } } else if (!arraysequal(one_map[key], two_map[key])) { return false; } checked[key] = true; } } for (key in two_map) { if (hasown.call(two_map, key)) { if (!checked[key]) { // two contains a parameter not present in one return false; } } } return true; }; // state p.preventinvalidhostname = function(v) { this._parts.preventinvalidhostname = !!v; return this; }; p.duplicatequeryparameters = function(v) { this._parts.duplicatequeryparameters = !!v; return this; }; p.escapequeryspace = function(v) { this._parts.escapequeryspace = !!v; return this; }; return uri; }));