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