PageSourceSearch

https://www.sae.edu/wp-content/themes/sae-2021/resources/js/urijs.js

js sae.edu collected 2026-09-24 08:29:32 UTC 66,994 bytes, 2,358 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.