1/*! 2 * URI.js - Mutating URLs 3 * 4 * Version: 1.19.10 5 * 6 * Author: Rodney Rehm 7 * Web: http://medialize.github.io/URI.js/ 8 * 9 * Licensed under 10 * MIT License http://www.opensource.org/licenses/mit-license 11 * 12 */ 13(function (root, factory) { 14 'use strict'; 15 // https://github.com/umdjs/umd/blob/master/returnExports.js 16 if (typeof module === 'object' && module.exports) { 17 // Node 18 module.exports = factory(require('./punycode'), require('./IPv6'), require('./SecondLevelDomains')); 19 } else if (typeof define === 'function' && define.amd) { 20 // AMD. Register as an anonymous module. 21 define(['./punycode', './IPv6', './SecondLevelDomains'], factory); 22 } else { 23 // Browser globals (root is window) 24 root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains, root); 25 } 26}(this, function (punycode, IPv6, SLD, root) { 27 'use strict'; 28 /*global location, escape, unescape */ 29 // FIXME: v2.0.0 renamce non-camelCase properties to uppercase 30 /*jshint camelcase: false */ 31 32 // save current URI variable, if any 33 var _URI = root && root.URI; 34 35 function URI(url, base) { 36 var _urlSupplied = arguments.length >= 1; 37 var _baseSupplied = arguments.length >= 2; 38 39 // Allow instantiation without the 'new' keyword 40 if (!(this instanceof URI)) { 41 if (_urlSupplied) { 42 if (_baseSupplied) { 43 return new URI(url, base); 44 } 45 46 return new URI(url); 47 } 48 49 return new URI(); 50 } 51 52 if (url === undefined) { 53 if (_urlSupplied) { 54 throw new TypeError('undefined is not a valid argument for URI'); 55 } 56 57 if (typeof location !== 'undefined') { 58 url = location.href + ''; 59 } else { 60 url = ''; 61 } 62 } 63 64 if (url === null) { 65 if (_urlSupplied) { 66 throw new TypeError('null is not a valid argument for URI'); 67 } 68 } 69 70 this.href(url); 71 72 // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor 73 if (base !== undefined) { 74 return this.absoluteTo(base); 75 } 76 77 return this; 78 } 79 80 function isInteger(value) { 81 return /^[0-9]+$/.test(value); 82 } 83 84 URI.version = '1.19.10'; 85 86 var p = URI.prototype; 87 var hasOwn = Object.prototype.hasOwnProperty; 88 89 function escapeRegEx(string) { 90 // https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963 91 return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); 92 } 93 94 function getType(value) { 95 // IE8 doesn't return [Object Undefined] but [Object Object] for undefined value 96 if (value === undefined) { 97 return 'Undefined'; 98 } 99 100 return String(Object.prototype.toString.call(value)).slice(8, -1); 101 } 102 103 function isArray(obj) { 104 return getType(obj) === 'Array'; 105 } 106 107 function filterArrayValues(data, value) { 108 var lookup = {}; 109 var i, length; 110 111 if (getType(value) === 'RegExp') { 112 lookup = null; 113 } else if (isArray(value)) { 114 for (i = 0, length = value.length; i < length; i++) { 115 lookup[value[i]] = true; 116 } 117 } else { 118 lookup[value] = true; 119 } 120 121 for (i = 0, length = data.length; i < length; i++) { 122 /*jshint laxbreak: true */ 123 var _match = lookup && lookup[data[i]] !== undefined 124 || !lookup && value.test(data[i]); 125 /*jshint laxbreak: false */ 126 if (_match) { 127 data.splice(i, 1); 128 length--; 129 i--; 130 } 131 } 132 133 return data; 134 } 135 136 function arrayContains(list, value) { 137 var i, length; 138 139 // value may be string, number, array, regexp 140 if (isArray(value)) { 141 // Note: this can be optimized to O(n) (instead of current O(m * n)) 142 for (i = 0, length = value.length; i < length; i++) { 143 if (!arrayContains(list, value[i])) { 144 return false; 145 } 146 } 147 148 return true; 149 } 150 151 var _type = getType(value); 152 for (i = 0, length = list.length; i < length; i++) { 153 if (_type === 'RegExp') { 154 if (typeof list[i] === 'string' && list[i].match(value)) { 155 return true; 156 } 157 } else if (list[i] === value) { 158 return true; 159 } 160 } 161 162 return false;
163 } 164 165 function arraysEqual(one, two) { 166 if (!isArray(one) || !isArray(two)) { 167 return false; 168 } 169 170 // arrays can't be equal if they have different amount of content 171 if (one.length !== two.length) { 172 return false; 173 } 174 175 one.sort(); 176 two.sort(); 177 178 for (var i = 0, l = one.length; i < l; i++) { 179 if (one[i] !== two[i]) { 180 return false; 181 } 182 } 183 184 return true; 185 } 186 187 function trimSlashes(text) { 188 var trim_expression = /^\/+|\/+$/g; 189 return text.replace(trim_expression, ''); 190 } 191 192 URI._parts = function() { 193 return { 194 protocol: null, 195 username: null, 196 password: null, 197 hostname: null, 198 urn: null, 199 port: null, 200 path: null, 201 query: null, 202 fragment: null, 203 // state 204 preventInvalidHostname: URI.preventInvalidHostname, 205 duplicateQueryParameters: URI.duplicateQueryParameters, 206 escapeQuerySpace: URI.escapeQuerySpace 207 }; 208 }; 209 // state: throw on invalid hostname 210 // see https://github.com/medialize/URI.js/pull/345 211 // and https://github.com/medialize/URI.js/issues/354 212 URI.preventInvalidHostname = false; 213 // state: allow duplicate query parameters (a=1&a=1) 214 URI.duplicateQueryParameters = false; 215 // state: replaces + with %20 (space in query strings) 216 URI.escapeQuerySpace = true; 217 // static properties 218 URI.protocol_expression = /^[a-z][a-z0-9.+-]*$/i; 219 URI.idn_expression = /[^a-z0-9\._-]/i; 220 URI.punycode_expression = /(xn--)/i; 221 // well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care? 222 URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; 223 // credits to Rich Brown 224 // source: http://forums.intermapper.com/viewtopic.php?p=1096#1096 225 // specification: http://www.ietf.org/rfc/rfc4291.txt 226 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*$/; 227 // expression used is "gruber revised" (@gruber v2) determined to be the 228 // best solution in a regex-golf we did a couple of ages ago at 229 // * http://mathiasbynens.be/demo/url-regex 230 // * http://rodneyrehm.de/t/url-regex.html 231 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; 232 URI.findUri = { 233 // valid "scheme://" or "www." 234 start: /\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi, 235 // everything up to the next whitespace 236 end: /[\s\r\n]|$/, 237 // trim trailing punctuation captured by end RegExp 238 trim: /[`!()\[\]{};:'".,<>?«»âââââ]+$/, 239 // balanced parens inclusion (), [], {}, <> 240 parens: /(\([^\)]*\)|\[[^\]]*\]|\{[^}]*\}|<[^>]*>)/g, 241 }; 242 URI.leading_whitespace_expression = /^[\x00-\x20\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/ 243 // http://www.iana.org/assignments/uri-schemes.html 244 // http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports 245 URI.defaultPorts = { 246 http: '80', 247 https: '443', 248 ftp: '21', 249 gopher: '70', 250 ws: '80', 251 wss: '443' 252 }; 253 // list of protocols which always require a hostname 254 URI.hostProtocols = [ 255 'http', 256 'https' 257 ]; 258 259 // allowed hostname characters according to RFC 3986 260 // ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded 261 // I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . - _ 262 URI.invalid_hostname_characters = /[^a-zA-Z0-9\.\-:_]/; 263 // map DOM Elements to their URI attribute 264 URI.domAttributes = { 265 'a': 'href', 266 'blockquote': 'cite', 267 'link': 'href', 268 'base': 'href', 269 'script': 'src', 270 'form': 'action', 271 'img': 'src', 272 'area': 'href', 273 'iframe': 'src', 274 'embed': 'src', 275 'source': 'src', 276 'track': 'src', 277 'input': 'src', // but only if type="image" 278 'audio': 'src', 279 'video': 'src' 280 }; 281 URI.getDomAttribute = function(node) { 282 if (!node || !node.nodeName) { 283 return undefined; 284 } 285 286 var nodeName = node.nodeName.toLowerCase(); 287 // <input> should only expose src for type="image"
288 if (nodeName === 'input' && node.type !== 'image') { 289 return undefined; 290 } 291 292 return URI.domAttributes[nodeName]; 293 }; 294 295 function escapeForDumbFirefox36(value) { 296 // https://github.com/medialize/URI.js/issues/91 297 return escape(value); 298 } 299 300 // encoding / decoding according to RFC3986 301 function strictEncodeURIComponent(string) { 302 // see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent 303 return encodeURIComponent(string) 304 .replace(/[!'()*]/g, escapeForDumbFirefox36) 305 .replace(/\*/g, '%2A'); 306 } 307 URI.encode = strictEncodeURIComponent; 308 URI.decode = decodeURIComponent; 309 URI.iso8859 = function() { 310 URI.encode = escape; 311 URI.decode = unescape; 312 }; 313 URI.unicode = function() { 314 URI.encode = strictEncodeURIComponent; 315 URI.decode = decodeURIComponent; 316 }; 317 URI.characters = { 318 pathname: { 319 encode: { 320 // RFC3986 2.1: For consistency, URI producers and normalizers should 321 // use uppercase hexadecimal digits for all percent-encodings. 322 expression: /%(24|26|2B|2C|3B|3D|3A|40)/ig, 323 map: { 324 // -._~!'()* 325 '%24': '$', 326 '%26': '&', 327 '%2B': '+', 328 '%2C': ',', 329 '%3B': ';', 330 '%3D': '=', 331 '%3A': ':', 332 '%40': '@' 333 } 334 }, 335 decode: { 336 expression: /[\/\?#]/g, 337 map: { 338 '/': '%2F', 339 '?': '%3F', 340 '#': '%23' 341 } 342 } 343 }, 344 reserved: { 345 encode: { 346 // RFC3986 2.1: For consistency, URI producers and normalizers should 347 // use uppercase hexadecimal digits for all percent-encodings. 348 expression: /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig, 349 map: { 350 // gen-delims 351 '%3A': ':', 352 '%2F': '/', 353 '%3F': '?', 354 '%23': '#', 355 '%5B': '[', 356 '%5D': ']', 357 '%40': '@', 358 // sub-delims 359 '%21': '!', 360 '%24': '$', 361 '%26': '&', 362 '%27': '\'', 363 '%28': '(', 364 '%29': ')', 365 '%2A': '*', 366 '%2B': '+', 367 '%2C': ',', 368 '%3B': ';', 369 '%3D': '=' 370 } 371 } 372 }, 373 urnpath: { 374 // The characters under `encode` are the characters called out by RFC 2141 as being acceptable 375 // for usage in a URN. RFC2141 also calls out "-", ".", and "_" as acceptable characters, but 376 // these aren't encoded by encodeURIComponent, so we don't have to call them out here. Also 377 // note that the colon character is not featured in the encoding map; this is because URI.js 378 // gives the colons in URNs semantic meaning as the delimiters of path segements, and so it 379 // should not appear unencoded in a segment itself. 380 // See also the note above about RFC3986 and capitalalized hex digits. 381 encode: { 382 expression: /%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig, 383 map: { 384 '%21': '!', 385 '%24': '$', 386 '%27': '\'', 387 '%28': '(', 388 '%29': ')', 389 '%2A': '*', 390 '%2B': '+', 391 '%2C': ',', 392 '%3B': ';', 393 '%3D': '=', 394 '%40': '@' 395 } 396 }, 397 // These characters are the characters called out by RFC2141 as "reserved" characters that 398 // should never appear in a URN, plus the colon character (see note above). 399 decode: { 400 expression: /[\/\?#:]/g, 401 map: { 402 '/': '%2F', 403 '?': '%3F', 404 '#': '%23', 405 ':': '%3A' 406 } 407 } 408 } 409 }; 410 URI.encodeQuery = function(string, escapeQuerySpace) { 411 var escaped = URI.encode(string + ''); 412 if (escapeQuerySpace === undefined) { 413 escapeQuerySpace = URI.escapeQuerySpace; 414 } 415 416 return escapeQuerySpace ? escaped.replace(/%20/g, ' ') : escaped; 417 }; 418 URI.decodeQuery = function(string, escapeQuerySpace) { 419 string += ''; 420 if (escapeQuerySpace === undefined) { 421 escapeQuerySpace = URI.escapeQuerySpace; 422 } 423 424 try { 425 return URI.decode(escapeQuerySpace ? string.replace(/\+/g, '%20') : string); 426 } catch(e) { 427 // we're not going to mess with weird encodings, 428 // give up and return the undecoded original string 429 // see https://github.com/medialize/URI.js/issues/87 430 // see https://github.com/medialize/URI.js/issues/92 431 return string; 432 } 433 }; 434 // generate encode/decode path functions 435 var _parts = {'encode':'encode', 'decode':'decode'}; 436 var _part; 437 var generateAccessor = function(_group, _part) { 438 return function(string) {
439 try { 440 return URI[_part](string + '').replace(URI.characters[_group][_part].expression, function(c) { 441 return URI.characters[_group][_part].map[c]; 442 }); 443 } catch (e) { 444 // we're not going to mess with weird encodings, 445 // give up and return the undecoded original string 446 // see https://github.com/medialize/URI.js/issues/87 447 // see https://github.com/medialize/URI.js/issues/92 448 return string; 449 } 450 }; 451 }; 452 453 for (_part in _parts) { 454 URI[_part + 'PathSegment'] = generateAccessor('pathname', _parts[_part]); 455 URI[_part + 'UrnPathSegment'] = generateAccessor('urnpath', _parts[_part]); 456 } 457 458 var generateSegmentedPathFunction = function(_sep, _codingFuncName, _innerCodingFuncName) { 459 return function(string) { 460 // Why pass in names of functions, rather than the function objects themselves? The 461 // definitions of some functions (but in particular, URI.decode) will occasionally change due 462 // to URI.js having ISO8859 and Unicode modes. Passing in the name and getting it will ensure 463 // that the functions we use here are "fresh". 464 var actualCodingFunc; 465 if (!_innerCodingFuncName) { 466 actualCodingFunc = URI[_codingFuncName]; 467 } else { 468 actualCodingFunc = function(string) { 469 return URI[_codingFuncName](URI[_innerCodingFuncName](string)); 470 }; 471 } 472 473 var segments = (string + '').split(_sep); 474 475 for (var i = 0, length = segments.length; i < length; i++) { 476 segments[i] = actualCodingFunc(segments[i]); 477 } 478 479 return segments.join(_sep); 480 }; 481 }; 482 483 // This takes place outside the above loop because we don't want, e.g., encodeUrnPath functions. 484 URI.decodePath = generateSegmentedPathFunction('/', 'decodePathSegment'); 485 URI.decodeUrnPath = generateSegmentedPathFunction(':', 'decodeUrnPathSegment'); 486 URI.recodePath = generateSegmentedPathFunction('/', 'encodePathSegment', 'decode'); 487 URI.recodeUrnPath = generateSegmentedPathFunction(':', 'encodeUrnPathSegment', 'decode'); 488 489 URI.encodeReserved = generateAccessor('reserved', 'encode'); 490 491 URI.parse = function(string, parts) { 492 var pos; 493 if (!parts) { 494 parts = { 495 preventInvalidHostname: URI.preventInvalidHostname 496 }; 497 } 498 499 string = string.replace(URI.leading_whitespace_expression, '') 500 501 // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment] 502 503 // extract fragment 504 pos = string.indexOf('#'); 505 if (pos > -1) { 506 // escaping? 507 parts.fragment = string.substring(pos + 1) || null; 508 string = string.substring(0, pos); 509 } 510 511 // extract query 512 pos = string.indexOf('?'); 513 if (pos > -1) { 514 // escaping? 515 parts.query = string.substring(pos + 1) || null; 516 string = string.substring(0, pos); 517 } 518 519 // slashes and backslashes have lost all meaning for the web protocols (https, http, wss, ws) 520 string = string.replace(/^(https?|ftp|wss?)?:+[/\\]*/i, '$1://'); 521 522 // extract protocol 523 if (string.substring(0, 2) === '//') { 524 // relative-scheme 525 parts.protocol = null; 526 string = string.substring(2); 527 // extract "user:pass@host:port" 528 string = URI.parseAuthority(string, parts); 529 } else { 530 pos = string.indexOf(':'); 531 if (pos > -1) { 532 parts.protocol = string.substring(0, pos) || null;
533 if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) { 534 // : may be within the path 535 parts.protocol = undefined; 536 } else if (string.substring(pos + 1, pos + 3).replace(/\\/g, '/') === '//') { 537 string = string.substring(pos + 3); 538 539 // extract "user:pass@host:port" 540 string = URI.parseAuthority(string, parts); 541 } else { 542 string = string.substring(pos + 1); 543 parts.urn = true; 544 } 545 } 546 } 547 548 // what's left must be the path 549 parts.path = string; 550 551 // and we're done 552 return parts; 553 }; 554 URI.parseHost = function(string, parts) { 555 if (!string) { 556 string = ''; 557 } 558 559 // Copy chrome, IE, opera backslash-handling behavior. 560 // Back slashes before the query string get converted to forward slashes 561 // See: https://github.com/joyent/node/blob/386fd24f49b0e9d1a8a076592a404168faeecc34/lib/url.js#L115-L124 562 // See: https://code.google.com/p/chromium/issues/detail?id=25916 563 // https://github.com/medialize/URI.js/pull/233 564 string = string.replace(/\\/g, '/'); 565 566 // extract host:port 567 var pos = string.indexOf('/'); 568 var bracketPos; 569 var t; 570 571 if (pos === -1) { 572 pos = string.length; 573 } 574 575 if (string.charAt(0) === '[') { 576 // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6 577 // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts 578 // IPv6+port in the format [2001:db8::1]:80 (for the time being) 579 bracketPos = string.indexOf(']'); 580 parts.hostname = string.substring(1, bracketPos) || null; 581 parts.port = string.substring(bracketPos + 2, pos) || null; 582 if (parts.port === '/') { 583 parts.port = null; 584 } 585 } else { 586 var firstColon = string.indexOf(':'); 587 var firstSlash = string.indexOf('/'); 588 var nextColon = string.indexOf(':', firstColon + 1); 589 if (nextColon !== -1 && (firstSlash === -1 || nextColon < firstSlash)) { 590 // IPv6 host contains multiple colons - but no port 591 // this notation is actually not allowed by RFC 3986, but we're a liberal parser 592 parts.hostname = string.substring(0, pos) || null; 593 parts.port = null; 594 } else { 595 t = string.substring(0, pos).split(':'); 596 parts.hostname = t[0] || null; 597 parts.port = t[1] || null; 598 } 599 } 600 601 if (parts.hostname && string.substring(pos).charAt(0) !== '/') { 602 pos++; 603 string = '/' + string; 604 } 605 606 if (parts.preventInvalidHostname) { 607 URI.ensureValidHostname(parts.hostname, parts.protocol); 608 } 609 610 if (parts.port) { 611 URI.ensureValidPort(parts.port); 612 } 613 614 return string.substring(pos) || '/'; 615 }; 616 URI.parseAuthority = function(string, parts) { 617 string = URI.parseUserinfo(string, parts); 618 return URI.parseHost(string, parts); 619 }; 620 URI.parseUserinfo = function(string, parts) { 621 // extract username:password 622 var _string = string 623 var firstBackSlash = string.indexOf('\\'); 624 if (firstBackSlash !== -1) { 625 string = string.replace(/\\/g, '/') 626 } 627 var firstSlash = string.indexOf('/'); 628 var pos = string.lastIndexOf('@', firstSlash > -1 ? firstSlash : string.length - 1); 629 var t; 630 631 // authority@ must come before /path or \path 632 if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) { 633 t = string.substring(0, pos).split(':'); 634 parts.username = t[0] ? URI.decode(t[0]) : null; 635 t.shift(); 636 parts.password = t[0] ? URI.decode(t.join(':')) : null; 637 string = _string.substring(pos + 1); 638 } else { 639 parts.username = null; 640 parts.password = null; 641 } 642 643 return string; 644 }; 645 URI.parseQuery = function(string, escapeQuerySpace) { 646 if (!string) { 647 return {}; 648 } 649 650 // throw out the funky business - "?"[name"="value"&"]+ 651 string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, ''); 652 653 if (!string) { 654 return {}; 655 } 656 657 var items = {}; 658 var splits = string.split('&'); 659 var length = splits.length; 660 var v, name, value; 661 662 for (var i = 0; i < length; i++) { 663 v = splits[i].split('='); 664 name = URI.decodeQuery(v.shift(), escapeQuerySpace); 665 // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters 666 value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null; 667 668 if (name === '__proto__') { 669 // ignore attempt at exploiting JavaScript internals 670 continue; 671 } else if (hasOwn.call(items, name)) { 672 if (typeof items[name] === 'string' || items[name] === null) { 673 items[name] = [items[name]]; 674 } 675 676 items[name].push(value); 677 } else { 678 items[name] = value; 679 } 680 } 681 682 return items; 683 }; 684 685 URI.build = function(parts) { 686 var t = ''; 687 var requireAbsolutePath = false 688 689 if (parts.protocol) { 690 t += parts.protocol + ':'; 691 } 692 693 if (!parts.urn && (t || parts.hostname)) { 694 t += '//'; 695 requireAbsolutePath = true 696 } 697 698 t += (URI.buildAuthority(parts) || ''); 699 700 if (typeof parts.path === 'string') {
701 if (parts.path.charAt(0) !== '/' && requireAbsolutePath) { 702 t += '/'; 703 } 704 705 t += parts.path; 706 } 707 708 if (typeof parts.query === 'string' && parts.query) { 709 t += '?' + parts.query; 710 } 711 712 if (typeof parts.fragment === 'string' && parts.fragment) { 713 t += '#' + parts.fragment; 714 } 715 return t; 716 }; 717 URI.buildHost = function(parts) { 718 var t = ''; 719 720 if (!parts.hostname) { 721 return ''; 722 } else if (URI.ip6_expression.test(parts.hostname)) { 723 t += '[' + parts.hostname + ']'; 724 } else { 725 t += parts.hostname; 726 } 727 728 if (parts.port) { 729 t += ':' + parts.port; 730 } 731 732 return t; 733 }; 734 URI.buildAuthority = function(parts) { 735 return URI.buildUserinfo(parts) + URI.buildHost(parts); 736 }; 737 URI.buildUserinfo = function(parts) { 738 var t = ''; 739 740 if (parts.username) { 741 t += URI.encode(parts.username); 742 } 743 744 if (parts.password) { 745 t += ':' + URI.encode(parts.password); 746 } 747 748 if (t) { 749 t += '@'; 750 } 751 752 return t; 753 }; 754 URI.buildQuery = function(data, duplicateQueryParameters, escapeQuerySpace) { 755 // according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html 756 // being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed 757 // the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax! 758 // URI.js treats the query string as being application/x-www-form-urlencoded 759 // see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type 760 761 var t = ''; 762 var unique, key, i, length; 763 for (key in data) { 764 if (key === '__proto__') { 765 // ignore attempt at exploiting JavaScript internals 766 continue; 767 } else if (hasOwn.call(data, key)) { 768 if (isArray(data[key])) { 769 unique = {}; 770 for (i = 0, length = data[key].length; i < length; i++) { 771 if (data[key][i] !== undefined && unique[data[key][i] + ''] === undefined) { 772 t += '&' + URI.buildQueryParameter(key, data[key][i], escapeQuerySpace); 773 if (duplicateQueryParameters !== true) { 774 unique[data[key][i] + ''] = true; 775 } 776 } 777 } 778 } else if (data[key] !== undefined) { 779 t += '&' + URI.buildQueryParameter(key, data[key], escapeQuerySpace); 780 } 781 } 782 } 783 784 return t.substring(1); 785 }; 786 URI.buildQueryParameter = function(name, value, escapeQuerySpace) { 787 // http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded 788 // don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization 789 return URI.encodeQuery(name, escapeQuerySpace) + (value !== null ? '=' + URI.encodeQuery(value, escapeQuerySpace) : ''); 790 }; 791 792 URI.addQuery = function(data, name, value) { 793 if (typeof name === 'object') { 794 for (var key in name) { 795 if (hasOwn.call(name, key)) { 796 URI.addQuery(data, key, name[key]); 797 } 798 } 799 } else if (typeof name === 'string') { 800 if (data[name] === undefined) { 801 data[name] = value; 802 return; 803 } else if (typeof data[name] === 'string') { 804 data[name] = [data[name]]; 805 } 806 807 if (!isArray(value)) { 808 value = [value]; 809 } 810 811 data[name] = (data[name] || []).concat(value); 812 } else { 813 throw new TypeError('URI.addQuery() accepts an object, string as the name parameter'); 814 } 815 }; 816 817 URI.setQuery = function(data, name, value) { 818 if (typeof name === 'object') { 819 for (var key in name) { 820 if (hasOwn.call(name, key)) { 821 URI.setQuery(data, key, name[key]); 822 } 823 } 824 } else if (typeof name === 'string') { 825 data[name] = value === undefined ? null : value; 826 } else { 827 throw new TypeError('URI.setQuery() accepts an object, string as the name parameter'); 828 } 829 }; 830 831 URI.removeQuery = function(data, name, value) { 832 var i, length, key; 833 834 if (isArray(name)) { 835 for (i = 0, length = name.length; i < length; i++) { 836 data[name[i]] = undefined; 837 } 838 } else if (getType(name) === 'RegExp') { 839 for (key in data) { 840 if (name.test(key)) { 841 data[key] = undefined; 842 } 843 } 844 } else if (typeof name === 'object') { 845 for (key in name) { 846 if (hasOwn.call(name, key)) { 847 URI.removeQuery(data, key, name[key]); 848 } 849 } 850 } else if (typeof name === 'string') { 851 if (value !== undefined) { 852 if (getType(value) === 'RegExp') { 853 if (!isArray(data[name]) && value.test(data[name])) { 854 data[name] = undefined; 855 } else { 856 data[name] = filterArrayValues(data[name], value); 857 } 858 } else if (data[name] === String(value) && (!isArray(value) || value.length === 1)) { 859 data[name] = undefined; 860 } else if (isArray(data[name])) { 861 data[name] = filterArrayValues(data[name], value); 862 } 863 } else { 864 data[name] = undefined; 865 } 866 } else { 867 throw new TypeError('URI.removeQuery() accepts an object, string, RegExp as the first parameter'); 868 } 869 }; 870 URI.hasQuery = function(data, name, value, withinArray) { 871 switch (getType(name)) { 872 case 'String': 873 // Nothing to do here 874 break; 875 876 case 'RegExp': 877 for (var key in data) {
878 if (hasOwn.call(data, key)) { 879 if (name.test(key) && (value === undefined || URI.hasQuery(data, key, value))) { 880 return true; 881 } 882 } 883 } 884 885 return false; 886 887 case 'Object': 888 for (var _key in name) { 889 if (hasOwn.call(name, _key)) { 890 if (!URI.hasQuery(data, _key, name[_key])) { 891 return false; 892 } 893 } 894 } 895 896 return true; 897 898 default: 899 throw new TypeError('URI.hasQuery() accepts a string, regular expression or object as the name parameter'); 900 } 901 902 switch (getType(value)) { 903 case 'Undefined': 904 // true if exists (but may be empty) 905 return name in data; // data[name] !== undefined; 906 907 case 'Boolean': 908 // true if exists and non-empty 909 var _booly = Boolean(isArray(data[name]) ? data[name].length : data[name]); 910 return value === _booly; 911 912 case 'Function': 913 // allow complex comparison 914 return !!value(data[name], name, data); 915 916 case 'Array': 917 if (!isArray(data[name])) { 918 return false; 919 } 920 921 var op = withinArray ? arrayContains : arraysEqual; 922 return op(data[name], value); 923 924 case 'RegExp': 925 if (!isArray(data[name])) { 926 return Boolean(data[name] && data[name].match(value)); 927 } 928 929 if (!withinArray) { 930 return false; 931 } 932 933 return arrayContains(data[name], value); 934 935 case 'Number': 936 value = String(value); 937 /* falls through */ 938 case 'String': 939 if (!isArray(data[name])) { 940 return data[name] === value; 941 } 942 943 if (!withinArray) { 944 return false; 945 } 946 947 return arrayContains(data[name], value); 948 949 default: 950 throw new TypeError('URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter'); 951 } 952 }; 953 954 955 URI.joinPaths = function() { 956 var input = []; 957 var segments = []; 958 var nonEmptySegments = 0; 959 960 for (var i = 0; i < arguments.length; i++) { 961 var url = new URI(arguments[i]); 962 input.push(url); 963 var _segments = url.segment(); 964 for (var s = 0; s < _segments.length; s++) { 965 if (typeof _segments[s] === 'string') { 966 segments.push(_segments[s]); 967 } 968 969 if (_segments[s]) { 970 nonEmptySegments++; 971 } 972 } 973 } 974 975 if (!segments.length || !nonEmptySegments) { 976 return new URI(''); 977 } 978 979 var uri = new URI('').segment(segments); 980 981 if (input[0].path() === '' || input[0].path().slice(0, 1) === '/') { 982 uri.path('/' + uri.path()); 983 } 984 985 return uri.normalize(); 986 }; 987 988 URI.commonPath = function(one, two) { 989 var length = Math.min(one.length, two.length); 990 var pos; 991 992 // find first non-matching character 993 for (pos = 0; pos < length; pos++) { 994 if (one.charAt(pos) !== two.charAt(pos)) { 995 pos--; 996 break; 997 } 998 } 999 1000 if (pos < 1) { 1001 return one.charAt(0) === two.charAt(0) && one.charAt(0) === '/' ? '/' : ''; 1002 } 1003 1004 // revert to last / 1005 if (one.charAt(pos) !== '/' || two.charAt(pos) !== '/') { 1006 pos = one.substring(0, pos).lastIndexOf('/'); 1007 } 1008 1009 return one.substring(0, pos + 1); 1010 }; 1011 1012 URI.withinString = function(string, callback, options) { 1013 options || (options = {}); 1014 var _start = options.start || URI.findUri.start; 1015 var _end = options.end || URI.findUri.end; 1016 var _trim = options.trim || URI.findUri.trim; 1017 var _parens = options.parens || URI.findUri.parens; 1018 var _attributeOpen = /[a-z0-9-]=["']?$/i; 1019 1020 _start.lastIndex = 0; 1021 while (true) { 1022 var match = _start.exec(string); 1023 if (!match) { 1024 break; 1025 } 1026 1027 var start = match.index; 1028 if (options.ignoreHtml) { 1029 // attribut(e=["']?$) 1030 var attributeOpen = string.slice(Math.max(start - 3, 0), start); 1031 if (attributeOpen && _attributeOpen.test(attributeOpen)) { 1032 continue; 1033 } 1034 } 1035 1036 var end = start + string.slice(start).search(_end); 1037 var slice = string.slice(start, end); 1038 // make sure we include well balanced parens 1039 var parensEnd = -1; 1040 while (true) { 1041 var parensMatch = _parens.exec(slice); 1042 if (!parensMatch) { 1043 break; 1044 } 1045 1046 var parensMatchEnd = parensMatch.index + parensMatch[0].length; 1047 parensEnd = Math.max(parensEnd, parensMatchEnd); 1048 } 1049 1050 if (parensEnd > -1) { 1051 slice = slice.slice(0, parensEnd) + slice.slice(parensEnd).replace(_trim, ''); 1052 } else { 1053 slice = slice.replace(_trim, ''); 1054 } 1055 1056 if (slice.length <= match[0].length) { 1057 // the extract only contains the starting marker of a URI, 1058 // e.g. "www" or "http://" 1059 continue; 1060 } 1061 1062 if (options.ignore && options.ignore.test(slice)) { 1063 continue; 1064 } 1065 1066 end = start + slice.length; 1067 var result = callback(slice, start, end, string); 1068 if (result === undefined) { 1069 _start.lastIndex = end; 1070 continue; 1071 } 1072 1073 result = String(result);
1074 string = string.slice(0, start) + result + string.slice(end); 1075 _start.lastIndex = start + result.length; 1076 } 1077 1078 _start.lastIndex = 0; 1079 return string; 1080 }; 1081 1082 URI.ensureValidHostname = function(v, protocol) { 1083 // Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986) 1084 // they are not part of DNS and therefore ignored by URI.js 1085 1086 var hasHostname = !!v; // not null and not an empty string 1087 var hasProtocol = !!protocol; 1088 var rejectEmptyHostname = false; 1089 1090 if (hasProtocol) { 1091 rejectEmptyHostname = arrayContains(URI.hostProtocols, protocol); 1092 } 1093 1094 if (rejectEmptyHostname && !hasHostname) { 1095 throw new TypeError('Hostname cannot be empty, if protocol is ' + protocol); 1096 } else if (v && v.match(URI.invalid_hostname_characters)) { 1097 // test punycode 1098 if (!punycode) { 1099 throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_] and Punycode.js is not available'); 1100 } 1101 if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) { 1102 throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_]'); 1103 } 1104 } 1105 }; 1106 1107 URI.ensureValidPort = function (v) { 1108 if (!v) { 1109 return; 1110 } 1111 1112 var port = Number(v); 1113 if (isInteger(port) && (port > 0) && (port < 65536)) { 1114 return; 1115 } 1116 1117 throw new TypeError('Port "' + v + '" is not a valid port'); 1118 }; 1119 1120 // noConflict 1121 URI.noConflict = function(removeAll) { 1122 if (removeAll) { 1123 var unconflicted = { 1124 URI: this.noConflict() 1125 }; 1126 1127 if (root.URITemplate && typeof root.URITemplate.noConflict === 'function') { 1128 unconflicted.URITemplate = root.URITemplate.noConflict(); 1129 } 1130 1131 if (root.IPv6 && typeof root.IPv6.noConflict === 'function') { 1132 unconflicted.IPv6 = root.IPv6.noConflict(); 1133 } 1134 1135 if (root.SecondLevelDomains && typeof root.SecondLevelDomains.noConflict === 'function') { 1136 unconflicted.SecondLevelDomains = root.SecondLevelDomains.noConflict(); 1137 } 1138 1139 return unconflicted; 1140 } else if (root.URI === this) { 1141 root.URI = _URI; 1142 } 1143 1144 return this; 1145 }; 1146 1147 p.build = function(deferBuild) { 1148 if (deferBuild === true) { 1149 this._deferred_build = true; 1150 } else if (deferBuild === undefined || this._deferred_build) { 1151 this._string = URI.build(this._parts); 1152 this._deferred_build = false; 1153 } 1154 1155 return this; 1156 }; 1157 1158 p.clone = function() { 1159 return new URI(this); 1160 }; 1161 1162 p.valueOf = p.toString = function() { 1163 return this.build(false)._string; 1164 }; 1165 1166 1167 function generateSimpleAccessor(_part){ 1168 return function(v, build) { 1169 if (v === undefined) { 1170 return this._parts[_part] || ''; 1171 } else { 1172 this._parts[_part] = v || null; 1173 this.build(!build); 1174 return this; 1175 } 1176 }; 1177 } 1178 1179 function generatePrefixAccessor(_part, _key){ 1180 return function(v, build) { 1181 if (v === undefined) { 1182 return this._parts[_part] || ''; 1183 } else { 1184 if (v !== null) { 1185 v = v + ''; 1186 if (v.charAt(0) === _key) { 1187 v = v.substring(1); 1188 } 1189 } 1190 1191 this._parts[_part] = v; 1192 this.build(!build); 1193 return this; 1194 } 1195 }; 1196 } 1197 1198 p.protocol = generateSimpleAccessor('protocol'); 1199 p.username = generateSimpleAccessor('username'); 1200 p.password = generateSimpleAccessor('password'); 1201 p.hostname = generateSimpleAccessor('hostname'); 1202 p.port = generateSimpleAccessor('port'); 1203 p.query = generatePrefixAccessor('query', '?'); 1204 p.fragment = generatePrefixAccessor('fragment', '#'); 1205 1206 p.search = function(v, build) { 1207 var t = this.query(v, build); 1208 return typeof t === 'string' && t.length ? ('?' + t) : t; 1209 }; 1210 p.hash = function(v, build) { 1211 var t = this.fragment(v, build); 1212 return typeof t === 'string' && t.length ? ('#' + t) : t; 1213 }; 1214 1215 p.pathname = function(v, build) { 1216 if (v === undefined || v === true) { 1217 var res = this._parts.path || (this._parts.hostname ? '/' : ''); 1218 return v ? (this._parts.urn ? URI.decodeUrnPath : URI.decodePath)(res) : res; 1219 } else { 1220 if (this._parts.urn) { 1221 this._parts.path = v ? URI.recodeUrnPath(v) : ''; 1222 } else { 1223 this._parts.path = v ? URI.recodePath(v) : '/'; 1224 } 1225 this.build(!build); 1226 return this; 1227 } 1228 }; 1229 p.path = p.pathname; 1230 p.href = function(href, build) { 1231 var key; 1232 1233 if (href === undefined) { 1234 return this.toString(); 1235 } 1236 1237 this._string = ''; 1238 this._parts = URI._parts(); 1239 1240 var _URI = href instanceof URI; 1241 var _object = typeof href === 'object' && (href.hostname || href.path || href.pathname); 1242 if (href.nodeName) { 1243 var attribute = URI.getDomAttribute(href); 1244 href = href[attribute] || ''; 1245 _object = false;
1246 } 1247 1248 // window.location is reported to be an object, but it's not the sort 1249 // of object we're looking for: 1250 // * location.protocol ends with a colon 1251 // * location.query != object.search 1252 // * location.hash != object.fragment 1253 // simply serializing the unknown object should do the trick 1254 // (for location, not for everything...) 1255 if (!_URI && _object && href.pathname !== undefined) { 1256 href = href.toString(); 1257 } 1258 1259 if (typeof href === 'string' || href instanceof String) { 1260 this._parts = URI.parse(String(href), this._parts); 1261 } else if (_URI || _object) { 1262 var src = _URI ? href._parts : href; 1263 for (key in src) { 1264 if (key === 'query') { continue; } 1265 if (hasOwn.call(this._parts, key)) { 1266 this._parts[key] = src[key]; 1267 } 1268 } 1269 if (src.query) { 1270 this.query(src.query, false); 1271 } 1272 } else { 1273 throw new TypeError('invalid input'); 1274 } 1275 1276 this.build(!build); 1277 return this; 1278 }; 1279 1280 // identification accessors 1281 p.is = function(what) { 1282 var ip = false; 1283 var ip4 = false; 1284 var ip6 = false; 1285 var name = false; 1286 var sld = false; 1287 var idn = false; 1288 var punycode = false; 1289 var relative = !this._parts.urn; 1290 1291 if (this._parts.hostname) { 1292 relative = false; 1293 ip4 = URI.ip4_expression.test(this._parts.hostname); 1294 ip6 = URI.ip6_expression.test(this._parts.hostname); 1295 ip = ip4 || ip6; 1296 name = !ip; 1297 sld = name && SLD && SLD.has(this._parts.hostname); 1298 idn = name && URI.idn_expression.test(this._parts.hostname); 1299 punycode = name && URI.punycode_expression.test(this._parts.hostname); 1300 } 1301 1302 switch (what.toLowerCase()) { 1303 case 'relative': 1304 return relative; 1305 1306 case 'absolute': 1307 return !relative; 1308 1309 // hostname identification 1310 case 'domain': 1311 case 'name': 1312 return name; 1313 1314 case 'sld': 1315 return sld; 1316 1317 case 'ip': 1318 return ip; 1319 1320 case 'ip4': 1321 case 'ipv4': 1322 case 'inet4': 1323 return ip4; 1324 1325 case 'ip6': 1326 case 'ipv6': 1327 case 'inet6': 1328 return ip6; 1329 1330 case 'idn': 1331 return idn; 1332 1333 case 'url': 1334 return !this._parts.urn; 1335 1336 case 'urn': 1337 return !!this._parts.urn; 1338 1339 case 'punycode': 1340 return punycode; 1341 } 1342 1343 return null; 1344 }; 1345 1346 // component specific input validation 1347 var _protocol = p.protocol; 1348 var _port = p.port; 1349 var _hostname = p.hostname; 1350 1351 p.protocol = function(v, build) { 1352 if (v) { 1353 // accept trailing :// 1354 v = v.replace(/:(\/\/)?$/, ''); 1355 1356 if (!v.match(URI.protocol_expression)) { 1357 throw new TypeError('Protocol "' + v + '" contains characters other than [A-Z0-9.+-] or doesn\'t start with [A-Z]'); 1358 } 1359 } 1360 1361 return _protocol.call(this, v, build); 1362 }; 1363 p.scheme = p.protocol; 1364 p.port = function(v, build) { 1365 if (this._parts.urn) { 1366 return v === undefined ? '' : this; 1367 } 1368 1369 if (v !== undefined) { 1370 if (v === 0) { 1371 v = null; 1372 } 1373 1374 if (v) { 1375 v += ''; 1376 if (v.charAt(0) === ':') { 1377 v = v.substring(1); 1378 } 1379 1380 URI.ensureValidPort(v); 1381 } 1382 } 1383 return _port.call(this, v, build); 1384 }; 1385 p.hostname = function(v, build) { 1386 if (this._parts.urn) { 1387 return v === undefined ? '' : this; 1388 } 1389 1390 if (v !== undefined) { 1391 var x = { preventInvalidHostname: this._parts.preventInvalidHostname }; 1392 var res = URI.parseHost(v, x); 1393 if (res !== '/') { 1394 throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); 1395 } 1396 1397 v = x.hostname; 1398 if (this._parts.preventInvalidHostname) { 1399 URI.ensureValidHostname(v, this._parts.protocol); 1400 } 1401 } 1402 1403 return _hostname.call(this, v, build); 1404 }; 1405 1406 // compound accessors 1407 p.origin = function(v, build) { 1408 if (this._parts.urn) { 1409 return v === undefined ? '' : this; 1410 } 1411 1412 if (v === undefined) { 1413 var protocol = this.protocol(); 1414 var authority = this.authority(); 1415 if (!authority) { 1416 return ''; 1417 } 1418 1419 return (protocol ? protocol + '://' : '') + this.authority(); 1420 } else { 1421 var origin = URI(v); 1422 this 1423 .protocol(origin.protocol()) 1424 .authority(origin.authority()) 1425 .build(!build); 1426 return this; 1427 } 1428 }; 1429 p.host = function(v, build) { 1430 if (this._parts.urn) { 1431 return v === undefined ? '' : this; 1432 } 1433 1434 if (v === undefined) { 1435 return this._parts.hostname ? URI.buildHost(this._parts) : ''; 1436 } else { 1437 var res = URI.parseHost(v, this._parts); 1438 if (res !== '/') { 1439 throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); 1440 } 1441 1442 this.build(!build); 1443 return this; 1444 } 1445 }; 1446 p.authority = function(v, build) { 1447 if (this._parts.urn) { 1448 return v === undefined ? '' : this; 1449 } 1450 1451 if (v === undefined) { 1452 return this._parts.hostname ? URI.buildAuthority(this._parts) : ''; 1453 } else { 1454 var res = URI.parseAuthority(v, this._parts); 1455 if (res !== '/') { 1456 throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); 1457 } 1458 1459 this.build(!build); 1460 return this; 1461 } 1462 }; 1463 p.userinfo = function(v, build) { 1464 if (this._parts.urn) { 1465 return v === undefined ? '' : this; 1466 } 1467 1468 if (v === undefined) { 1469 var t = URI.buildUserinfo(this._parts); 1470 return t ? t.substring(0, t.length -1) : t; 1471 } else { 1472 if (v[v.length-1] !== '@') { 1473 v += '@'; 1474 } 1475 1476 URI.parseUserinfo(v, this._parts); 1477 this.build(!build); 1478 return this; 1479 } 1480 }; 1481 p.resource = function(v, build) { 1482 var parts; 1483 1484 if (v === undefined) { 1485 return this.path() + this.search() + this.hash(); 1486 } 1487 1488 parts = URI.parse(v); 1489 this._parts.path = parts.path; 1490 this._parts.query = parts.query; 1491 this._parts.fragment = parts.fragment; 1492 this.build(!build); 1493 return this; 1494 }; 1495 1496 // fraction accessors 1497 p.subdomain = function(v, build) { 1498 if (this._parts.urn) { 1499 return v === undefined ? '' : this; 1500 } 1501 1502 // convenience, return "www" from "www.example.org" 1503 if (v === undefined) { 1504 if (!this._parts.hostname || this.is('IP')) { 1505 return ''; 1506 } 1507 1508 // grab domain and add another segment 1509 var end = this._parts.hostname.length - this.domain().length - 1; 1510 return this._parts.hostname.substring(0, end) || ''; 1511 } else { 1512 var e = this._parts.hostname.length - this.domain().length; 1513 var sub = this._parts.hostname.substring(0, e); 1514 var replace = new RegExp('^' + escapeRegEx(sub)); 1515 1516 if (v && v.charAt(v.length - 1) !== '.') { 1517 v += '.'; 1518 } 1519 1520 if (v.indexOf(':') !== -1) { 1521 throw new TypeError('Domains cannot contain colons'); 1522 } 1523 1524 if (v) { 1525 URI.ensureValidHostname(v, this._parts.protocol); 1526 } 1527 1528 this._parts.hostname = this._parts.hostname.replace(replace, v); 1529 this.build(!build); 1530 return this; 1531 } 1532 }; 1533 p.domain = function(v, build) { 1534 if (this._parts.urn) { 1535 return v === undefined ? '' : this; 1536 } 1537 1538 if (typeof v === 'boolean') { 1539 build = v; 1540 v = undefined; 1541 } 1542 1543 // convenience, return "example.org" from "www.example.org" 1544 if (v === undefined) { 1545 if (!this._parts.hostname || this.is('IP')) { 1546 return ''; 1547 } 1548 1549 // if hostname consists of 1 or 2 segments, it must be the domain 1550 var t = this._parts.hostname.match(/\./g); 1551 if (t && t.length < 2) { 1552 return this._parts.hostname; 1553 } 1554 1555 // grab tld and add another segment 1556 var end = this._parts.hostname.length - this.tld(build).length - 1; 1557 end = this._parts.hostname.lastIndexOf('.', end -1) + 1; 1558 return this._parts.hostname.substring(end) || ''; 1559 } else { 1560 if (!v) { 1561 throw new TypeError('cannot set domain empty'); 1562 } 1563 1564 if (v.indexOf(':') !== -1) { 1565 throw new TypeError('Domains cannot contain colons'); 1566 } 1567 1568 URI.ensureValidHostname(v, this._parts.protocol); 1569 1570 if (!this._parts.hostname || this.is('IP')) { 1571 this._parts.hostname = v; 1572 } else { 1573 var replace = new RegExp(escapeRegEx(this.domain()) + '$'); 1574 this._parts.hostname = this._parts.hostname.replace(replace, v); 1575 } 1576 1577 this.build(!build); 1578 return this; 1579 } 1580 }; 1581 p.tld = function(v, build) { 1582 if (this._parts.urn) { 1583 return v === undefined ? '' : this; 1584 } 1585 1586 if (typeof v === 'boolean') { 1587 build = v; 1588 v = undefined; 1589 } 1590 1591 // return "org" from "www.example.org" 1592 if (v === undefined) { 1593 if (!this._parts.hostname || this.is('IP')) { 1594 return ''; 1595 } 1596 1597 var pos = this._parts.hostname.lastIndexOf('.'); 1598 var tld = this._parts.hostname.substring(pos + 1); 1599 1600 if (build !== true && SLD && SLD.list[tld.toLowerCase()]) { 1601 return SLD.get(this._parts.hostname) || tld; 1602 } 1603 1604 return tld; 1605 } else { 1606 var replace; 1607 1608 if (!v) { 1609 throw new TypeError('cannot set TLD empty'); 1610 } else if (v.match(/[^a-zA-Z0-9-]/)) { 1611 if (SLD && SLD.is(v)) { 1612 replace = new RegExp(escapeRegEx(this.tld()) + '$'); 1613 this._parts.hostname = this._parts.hostname.replace(replace, v); 1614 } else { 1615 throw new TypeError('TLD "' + v + '" contains characters other than [A-Z0-9]'); 1616 } 1617 } else if (!this._parts.hostname || this.is('IP')) { 1618 throw new ReferenceError('cannot set TLD on non-domain host'); 1619 } else { 1620 replace = new RegExp(escapeRegEx(this.tld()) + '$'); 1621 this._parts.hostname = this._parts.hostname.replace(replace, v); 1622 } 1623 1624 this.build(!build); 1625 return this; 1626 } 1627 }; 1628 p.directory = function(v, build) { 1629 if (this._parts.urn) { 1630 return v === undefined ? '' : this; 1631 } 1632 1633 if (v === undefined || v === true) { 1634 if (!this._parts.path && !this._parts.hostname) { 1635 return ''; 1636 } 1637 1638 if (this._parts.path === '/') { 1639 return '/'; 1640 } 1641 1642 var end = this._parts.path.length - this.filename().length - 1; 1643 var res = this._parts.path.substring(0, end) || (this._parts.hostname ? '/' : ''); 1644 1645 return v ? URI.decodePath(res) : res; 1646 1647 } else { 1648 var e = this._parts.path.length - this.filename().length; 1649 var directory = this._parts.path.substring(0, e); 1650 var replace = new RegExp('^' + escapeRegEx(directory)); 1651 1652 // fully qualifier directories begin with a slash 1653 if (!this.is('relative')) { 1654 if (!v) { 1655 v = '/'; 1656 } 1657 1658 if (v.charAt(0) !== '/') { 1659 v = '/' + v; 1660 } 1661 } 1662 1663 // directories always end with a slash 1664 if (v && v.charAt(v.length - 1) !== '/') { 1665 v += '/'; 1666 } 1667 1668 v = URI.recodePath(v); 1669 this._parts.path = this._parts.path.replace(replace, v); 1670 this.build(!build); 1671 return this; 1672 } 1673 }; 1674 p.filename = function(v, build) { 1675 if (this._parts.urn) { 1676 return v === undefined ? '' : this; 1677 } 1678 1679 if (typeof v !== 'string') { 1680 if (!this._parts.path || this._parts.path === '/') { 1681 return ''; 1682 } 1683 1684 var pos = this._parts.path.lastIndexOf('/'); 1685 var res = this._parts.path.substring(pos+1); 1686 1687 return v ? URI.decodePathSegment(res) : res; 1688 } else { 1689 var mutatedDirectory = false;
1690 1691 if (v.charAt(0) === '/') { 1692 v = v.substring(1); 1693 } 1694 1695 if (v.match(/\.?\//)) { 1696 mutatedDirectory = true; 1697 } 1698 1699 var replace = new RegExp(escapeRegEx(this.filename()) + '$'); 1700 v = URI.recodePath(v); 1701 this._parts.path = this._parts.path.replace(replace, v); 1702 1703 if (mutatedDirectory) { 1704 this.normalizePath(build); 1705 } else { 1706 this.build(!build); 1707 } 1708 1709 return this; 1710 } 1711 }; 1712 p.suffix = function(v, build) { 1713 if (this._parts.urn) { 1714 return v === undefined ? '' : this; 1715 } 1716 1717 if (v === undefined || v === true) { 1718 if (!this._parts.path || this._parts.path === '/') { 1719 return ''; 1720 } 1721 1722 var filename = this.filename(); 1723 var pos = filename.lastIndexOf('.'); 1724 var s, res; 1725 1726 if (pos === -1) { 1727 return ''; 1728 } 1729 1730 // suffix may only contain alnum characters (yup, I made this up.) 1731 s = filename.substring(pos+1); 1732 res = (/^[a-z0-9%]+$/i).test(s) ? s : ''; 1733 return v ? URI.decodePathSegment(res) : res; 1734 } else { 1735 if (v.charAt(0) === '.') { 1736 v = v.substring(1); 1737 } 1738 1739 var suffix = this.suffix(); 1740 var replace; 1741 1742 if (!suffix) { 1743 if (!v) { 1744 return this; 1745 } 1746 1747 this._parts.path += '.' + URI.recodePath(v); 1748 } else if (!v) { 1749 replace = new RegExp(escapeRegEx('.' + suffix) + '$'); 1750 } else { 1751 replace = new RegExp(escapeRegEx(suffix) + '$'); 1752 } 1753 1754 if (replace) { 1755 v = URI.recodePath(v); 1756 this._parts.path = this._parts.path.replace(replace, v); 1757 } 1758 1759 this.build(!build); 1760 return this; 1761 } 1762 }; 1763 p.segment = function(segment, v, build) { 1764 var separator = this._parts.urn ? ':' : '/'; 1765 var path = this.path(); 1766 var absolute = path.substring(0, 1) === '/'; 1767 var segments = path.split(separator); 1768 1769 if (segment !== undefined && typeof segment !== 'number') { 1770 build = v; 1771 v = segment; 1772 segment = undefined; 1773 } 1774 1775 if (segment !== undefined && typeof segment !== 'number') { 1776 throw new Error('Bad segment "' + segment + '", must be 0-based integer'); 1777 } 1778 1779 if (absolute) { 1780 segments.shift(); 1781 } 1782 1783 if (segment < 0) { 1784 // allow negative indexes to address from the end 1785 segment = Math.max(segments.length + segment, 0); 1786 } 1787 1788 if (v === undefined) { 1789 /*jshint laxbreak: true */ 1790 return segment === undefined 1791 ? segments 1792 : segments[segment]; 1793 /*jshint laxbreak: false */ 1794 } else if (segment === null || segments[segment] === undefined) { 1795 if (isArray(v)) { 1796 segments = []; 1797 // collapse empty elements within array 1798 for (var i=0, l=v.length; i < l; i++) { 1799 if (!v[i].length && (!segments.length || !segments[segments.length -1].length)) { 1800 continue; 1801 } 1802 1803 if (segments.length && !segments[segments.length -1].length) { 1804 segments.pop(); 1805 } 1806 1807 segments.push(trimSlashes(v[i])); 1808 } 1809 } else if (v || typeof v === 'string') { 1810 v = trimSlashes(v); 1811 if (segments[segments.length -1] === '') { 1812 // empty trailing elements have to be overwritten 1813 // to prevent results such as /foo//bar 1814 segments[segments.length -1] = v; 1815 } else { 1816 segments.push(v); 1817 } 1818 } 1819 } else { 1820 if (v) { 1821 segments[segment] = trimSlashes(v); 1822 } else { 1823 segments.splice(segment, 1); 1824 } 1825 } 1826 1827 if (absolute) { 1828 segments.unshift(''); 1829 } 1830 1831 return this.path(segments.join(separator), build); 1832 }; 1833 p.segmentCoded = function(segment, v, build) { 1834 var segments, i, l; 1835 1836 if (typeof segment !== 'number') { 1837 build = v; 1838 v = segment; 1839 segment = undefined; 1840 } 1841 1842 if (v === undefined) { 1843 segments = this.segment(segment, v, build); 1844 if (!isArray(segments)) { 1845 segments = segments !== undefined ? URI.decode(segments) : undefined; 1846 } else { 1847 for (i = 0, l = segments.length; i < l; i++) { 1848 segments[i] = URI.decode(segments[i]); 1849 } 1850 } 1851 1852 return segments; 1853 } 1854 1855 if (!isArray(v)) { 1856 v = (typeof v === 'string' || v instanceof String) ? URI.encode(v) : v; 1857 } else { 1858 for (i = 0, l = v.length; i < l; i++) { 1859 v[i] = URI.encode(v[i]); 1860 } 1861 } 1862 1863 return this.segment(segment, v, build); 1864 }; 1865 1866 // mutating query string 1867 var q = p.query; 1868 p.query = function(v, build) { 1869 if (v === true) { 1870 return URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); 1871 } else if (typeof v === 'function') { 1872 var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); 1873 var result = v.call(this, data); 1874 this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); 1875 this.build(!build); 1876 return this; 1877 } else if (v !== undefined && typeof v !== 'string') { 1878 this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); 1879 this.build(!build); 1880 return this; 1881 } else { 1882 return q.call(this, v, build); 1883 } 1884 }; 1885 p.setQuery = function(name, value, build) { 1886 var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); 1887 1888 if (typeof name === 'string' || name instanceof String) { 1889 data[name] = value !== undefined ? value : null; 1890 } else if (typeof name === 'object') { 1891 for (var key in name) { 1892 if (hasOwn.call(name, key)) { 1893 data[key] = name[key]; 1894 } 1895 } 1896 } else { 1897 throw new TypeError('URI.addQuery() accepts an object, string as the name parameter'); 1898 } 1899 1900 this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); 1901 if (typeof name !== 'string') { 1902 build = value; 1903 } 1904 1905 this.build(!build); 1906 return this; 1907 }; 1908 p.addQuery = function(name, value, build) { 1909 var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); 1910 URI.addQuery(data, name, value === undefined ? null : value); 1911 this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); 1912 if (typeof name !== 'string') { 1913 build = value; 1914 } 1915 1916 this.build(!build); 1917 return this; 1918 }; 1919 p.removeQuery = function(name, value, build) { 1920 var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); 1921 URI.removeQuery(data, name, value); 1922 this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); 1923 if (typeof name !== 'string') { 1924 build = value; 1925 } 1926 1927 this.build(!build); 1928 return this; 1929 }; 1930 p.hasQuery = function(name, value, withinArray) { 1931 var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); 1932 return URI.hasQuery(data, name, value, withinArray); 1933 }; 1934 p.setSearch = p.setQuery; 1935 p.addSearch = p.addQuery; 1936 p.removeSearch = p.removeQuery; 1937 p.hasSearch = p.hasQuery; 1938 1939 // sanitizing URLs 1940 p.normalize = function() { 1941 if (this._parts.urn) { 1942 return this 1943 .normalizeProtocol(false) 1944 .normalizePath(false) 1945 .normalizeQuery(false) 1946 .normalizeFragment(false) 1947 .build(); 1948 } 1949 1950 return this 1951 .normalizeProtocol(false) 1952 .normalizeHostname(false) 1953 .normalizePort(false) 1954 .normalizePath(false) 1955 .normalizeQuery(false) 1956 .normalizeFragment(false) 1957 .build(); 1958 }; 1959 p.normalizeProtocol = function(build) { 1960 if (typeof this._parts.protocol === 'string') { 1961 this._parts.protocol = this._parts.protocol.toLowerCase(); 1962 this.build(!build); 1963 } 1964 1965 return this; 1966 }; 1967 p.normalizeHostname = function(build) { 1968 if (this._parts.hostname) { 1969 if (this.is('IDN') && punycode) { 1970 this._parts.hostname = punycode.toASCII(this._parts.hostname); 1971 } else if (this.is('IPv6') && IPv6) { 1972 this._parts.hostname = IPv6.best(this._parts.hostname); 1973 } 1974 1975 this._parts.hostname = this._parts.hostname.toLowerCase(); 1976 this.build(!build); 1977 } 1978 1979 return this; 1980 }; 1981 p.normalizePort = function(build) { 1982 // remove port of it's the protocol's default 1983 if (typeof this._parts.protocol === 'string' && this._parts.port === URI.defaultPorts[this._parts.protocol]) { 1984 this._parts.port = null; 1985 this.build(!build); 1986 } 1987 1988 return this; 1989 };
1990 p.normalizePath = function(build) { 1991 var _path = this._parts.path; 1992 if (!_path) { 1993 return this; 1994 } 1995 1996 if (this._parts.urn) { 1997 this._parts.path = URI.recodeUrnPath(this._parts.path); 1998 this.build(!build); 1999 return this; 2000 } 2001 2002 if (this._parts.path === '/') { 2003 return this; 2004 } 2005 2006 _path = URI.recodePath(_path); 2007 2008 var _was_relative; 2009 var _leadingParents = ''; 2010 var _parent, _pos; 2011 2012 // handle relative paths 2013 if (_path.charAt(0) !== '/') { 2014 _was_relative = true; 2015 _path = '/' + _path; 2016 } 2017 2018 // handle relative files (as opposed to directories) 2019 if (_path.slice(-3) === '/..' || _path.slice(-2) === '/.') { 2020 _path += '/'; 2021 } 2022 2023 // resolve simples 2024 _path = _path 2025 .replace(/(\/(\.\/)+)|(\/\.$)/g, '/') 2026 .replace(/\/{2,}/g, '/'); 2027 2028 // remember leading parents 2029 if (_was_relative) { 2030 _leadingParents = _path.substring(1).match(/^(\.\.\/)+/) || ''; 2031 if (_leadingParents) { 2032 _leadingParents = _leadingParents[0]; 2033 } 2034 } 2035 2036 // resolve parents 2037 while (true) { 2038 _parent = _path.search(/\/\.\.(\/|$)/); 2039 if (_parent === -1) { 2040 // no more ../ to resolve 2041 break; 2042 } else if (_parent === 0) { 2043 // top level cannot be relative, skip it 2044 _path = _path.substring(3); 2045 continue; 2046 } 2047 2048 _pos = _path.substring(0, _parent).lastIndexOf('/'); 2049 if (_pos === -1) { 2050 _pos = _parent; 2051 } 2052 _path = _path.substring(0, _pos) + _path.substring(_parent + 3); 2053 } 2054 2055 // revert to relative 2056 if (_was_relative && this.is('relative')) { 2057 _path = _leadingParents + _path.substring(1); 2058 } 2059 2060 this._parts.path = _path; 2061 this.build(!build); 2062 return this; 2063 }; 2064 p.normalizePathname = p.normalizePath; 2065 p.normalizeQuery = function(build) { 2066 if (typeof this._parts.query === 'string') { 2067 if (!this._parts.query.length) { 2068 this._parts.query = null; 2069 } else { 2070 this.query(URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace)); 2071 } 2072 2073 this.build(!build); 2074 } 2075 2076 return this; 2077 }; 2078 p.normalizeFragment = function(build) { 2079 if (!this._parts.fragment) { 2080 this._parts.fragment = null; 2081 this.build(!build); 2082 } 2083 2084 return this; 2085 }; 2086 p.normalizeSearch = p.normalizeQuery; 2087 p.normalizeHash = p.normalizeFragment; 2088 2089 p.iso8859 = function() { 2090 // expect unicode input, iso8859 output 2091 var e = URI.encode; 2092 var d = URI.decode; 2093 2094 URI.encode = escape; 2095 URI.decode = decodeURIComponent; 2096 try { 2097 this.normalize(); 2098 } finally { 2099 URI.encode = e; 2100 URI.decode = d; 2101 } 2102 return this; 2103 }; 2104 2105 p.unicode = function() { 2106 // expect iso8859 input, unicode output 2107 var e = URI.encode; 2108 var d = URI.decode; 2109 2110 URI.encode = strictEncodeURIComponent; 2111 URI.decode = unescape; 2112 try { 2113 this.normalize(); 2114 } finally { 2115 URI.encode = e; 2116 URI.decode = d; 2117 } 2118 return this; 2119 }; 2120 2121 p.readable = function() { 2122 var uri = this.clone(); 2123 // removing username, password, because they shouldn't be displayed according to RFC 3986 2124 uri.username('').password('').normalize(); 2125 var t = ''; 2126 if (uri._parts.protocol) { 2127 t += uri._parts.protocol + '://'; 2128 } 2129 2130 if (uri._parts.hostname) { 2131 if (uri.is('punycode') && punycode) { 2132 t += punycode.toUnicode(uri._parts.hostname); 2133 if (uri._parts.port) { 2134 t += ':' + uri._parts.port; 2135 } 2136 } else { 2137 t += uri.host(); 2138 } 2139 } 2140 2141 if (uri._parts.hostname && uri._parts.path && uri._parts.path.charAt(0) !== '/') { 2142 t += '/'; 2143 } 2144 2145 t += uri.path(true); 2146 if (uri._parts.query) { 2147 var q = ''; 2148 for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) { 2149 var kv = (qp[i] || '').split('='); 2150 q += '&' + URI.decodeQuery(kv[0], this._parts.escapeQuerySpace) 2151 .replace(/&/g, '%26'); 2152 2153 if (kv[1] !== undefined) { 2154 q += '=' + URI.decodeQuery(kv[1], this._parts.escapeQuerySpace) 2155 .replace(/&/g, '%26'); 2156 } 2157 } 2158 t += '?' + q.substring(1); 2159 } 2160 2161 t += URI.decodeQuery(uri.hash(), true); 2162 return t; 2163 }; 2164 2165 // resolving relative and absolute URLs 2166 p.absoluteTo = function(base) { 2167 var resolved = this.clone(); 2168 var properties = ['protocol', 'username', 'password', 'hostname', 'port']; 2169 var basedir, i, p; 2170 2171 if (this._parts.urn) { 2172 throw new Error('URNs do not have any generally defined hierarchical components'); 2173 } 2174 2175 if (!(base instanceof URI)) { 2176 base = new URI(base); 2177 } 2178 2179 if (resolved._parts.protocol) { 2180 // Directly returns even if this._parts.hostname is empty. 2181 return resolved; 2182 } else { 2183 resolved._parts.protocol = base._parts.protocol; 2184 } 2185 2186 if (this._parts.hostname) { 2187 return resolved; 2188 } 2189 2190 for (i = 0; (p = properties[i]); i++) { 2191 resolved._parts[p] = base._parts[p]; 2192 } 2193 2194 if (!resolved._parts.path) { 2195 resolved._parts.path = base._parts.path;
2196 if (!resolved._parts.query) { 2197 resolved._parts.query = base._parts.query; 2198 } 2199 } else { 2200 if (resolved._parts.path.substring(-2) === '..') { 2201 resolved._parts.path += '/'; 2202 } 2203 2204 if (resolved.path().charAt(0) !== '/') { 2205 basedir = base.directory(); 2206 basedir = basedir ? basedir : base.path().indexOf('/') === 0 ? '/' : ''; 2207 resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path; 2208 resolved.normalizePath(); 2209 } 2210 } 2211 2212 resolved.build(); 2213 return resolved; 2214 }; 2215 p.relativeTo = function(base) { 2216 var relative = this.clone().normalize(); 2217 var relativeParts, baseParts, common, relativePath, basePath; 2218 2219 if (relative._parts.urn) { 2220 throw new Error('URNs do not have any generally defined hierarchical components'); 2221 } 2222 2223 base = new URI(base).normalize(); 2224 relativeParts = relative._parts; 2225 baseParts = base._parts; 2226 relativePath = relative.path(); 2227 basePath = base.path(); 2228 2229 if (relativePath.charAt(0) !== '/') { 2230 throw new Error('URI is already relative'); 2231 } 2232 2233 if (basePath.charAt(0) !== '/') { 2234 throw new Error('Cannot calculate a URI relative to another relative URI'); 2235 } 2236 2237 if (relativeParts.protocol === baseParts.protocol) { 2238 relativeParts.protocol = null; 2239 } 2240 2241 if (relativeParts.username !== baseParts.username || relativeParts.password !== baseParts.password) { 2242 return relative.build(); 2243 } 2244 2245 if (relativeParts.protocol !== null || relativeParts.username !== null || relativeParts.password !== null) { 2246 return relative.build(); 2247 } 2248 2249 if (relativeParts.hostname === baseParts.hostname && relativeParts.port === baseParts.port) { 2250 relativeParts.hostname = null; 2251 relativeParts.port = null; 2252 } else { 2253 return relative.build(); 2254 } 2255 2256 if (relativePath === basePath) { 2257 relativeParts.path = ''; 2258 return relative.build(); 2259 } 2260 2261 // determine common sub path 2262 common = URI.commonPath(relativePath, basePath); 2263 2264 // If the paths have nothing in common, return a relative URL with the absolute path. 2265 if (!common) { 2266 return relative.build(); 2267 } 2268 2269 var parents = baseParts.path 2270 .substring(common.length) 2271 .replace(/[^\/]*$/, '') 2272 .replace(/.*?\//g, '../'); 2273 2274 relativeParts.path = (parents + relativeParts.path.substring(common.length)) || './'; 2275 2276 return relative.build(); 2277 }; 2278 2279 // comparing URIs 2280 p.equals = function(uri) { 2281 var one = this.clone(); 2282 var two = new URI(uri); 2283 var one_map = {}; 2284 var two_map = {}; 2285 var checked = {}; 2286 var one_query, two_query, key; 2287 2288 one.normalize(); 2289 two.normalize(); 2290 2291 // exact match 2292 if (one.toString() === two.toString()) { 2293 return true; 2294 } 2295 2296 // extract query string 2297 one_query = one.query(); 2298 two_query = two.query(); 2299 one.query(''); 2300 two.query(''); 2301 2302 // definitely not equal if not even non-query parts match 2303 if (one.toString() !== two.toString()) { 2304 return false; 2305 } 2306 2307 // query parameters have the same length, even if they're permuted 2308 if (one_query.length !== two_query.length) { 2309 return false; 2310 } 2311 2312 one_map = URI.parseQuery(one_query, this._parts.escapeQuerySpace); 2313 two_map = URI.parseQuery(two_query, this._parts.escapeQuerySpace); 2314 2315 for (key in one_map) { 2316 if (hasOwn.call(one_map, key)) { 2317 if (!isArray(one_map[key])) { 2318 if (one_map[key] !== two_map[key]) { 2319 return false; 2320 } 2321 } else if (!arraysEqual(one_map[key], two_map[key])) { 2322 return false; 2323 } 2324 2325 checked[key] = true; 2326 } 2327 } 2328 2329 for (key in two_map) { 2330 if (hasOwn.call(two_map, key)) { 2331 if (!checked[key]) { 2332 // two contains a parameter not present in one 2333 return false; 2334 } 2335 } 2336 } 2337 2338 return true; 2339 }; 2340 2341 // state 2342 p.preventInvalidHostname = function(v) { 2343 this._parts.preventInvalidHostname = !!v; 2344 return this; 2345 }; 2346 2347 p.duplicateQueryParameters = function(v) { 2348 this._parts.duplicateQueryParameters = !!v; 2349 return this; 2350 }; 2351 2352 p.escapeQuerySpace = function(v) { 2353 this._parts.escapeQuerySpace = !!v; 2354 return this; 2355 }; 2356 2357 return URI; 2358}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.