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