PageSourceSearch

https://durmersheim.de/utils/urijs/1.14.1/URI.js?bust=201907311430_202608282118

js durmersheim.de collected 2026-10-02 06:24:26 UTC 57,919 bytes, 2,002 lines download raw bytes

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.