uri-js-rails 1.7.4 → 1.10.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2a7679fe260f287fbf4cd1b2e7343099c24d65db
4
+ data.tar.gz: 840a0a784bea3cf14f18bf2e713413d68f855b01
5
+ SHA512:
6
+ metadata.gz: 6096b252b722dd13466b81cbea3f59a386ddac30d7c7d478ee1d1a1df0bb5ad16b55829673bb765ac2f16dae1636f15202fa066d03b2db678f9a942d0f780ecc
7
+ data.tar.gz: bfecfe90b7073d03073724c1f9eae3dbb5f8f9b5b68bc555c2eafdd8a49f29536e54f8843483724b829001d35e38038a0a3c7e519321a9b4563d7f2d8ee1edd3
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  Add this line to your application's Gemfile:
8
8
 
9
- gem 'uri-js-rails'
9
+ gem 'uri-js-rails', :group => :assets
10
10
 
11
11
  And then execute:
12
12
 
@@ -1,7 +1,7 @@
1
1
  module Uri
2
2
  module Js
3
3
  module Rails
4
- VERSION = "1.7.4"
4
+ VERSION = "1.10.2"
5
5
  end
6
6
  end
7
7
  end
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * URI.js - Mutating URLs
3
3
  *
4
- * Version: 1.7.4
4
+ * Version: 1.10.2
5
5
  *
6
6
  * Author: Rodney Rehm
7
7
  * Web: http://medialize.github.com/URI.js/
@@ -11,53 +11,64 @@
11
11
  * GPL v3 http://opensource.org/licenses/GPL-3.0
12
12
  *
13
13
  */
14
+ (function (root, factory) {
15
+ // https://github.com/umdjs/umd/blob/master/returnExports.js
16
+ if (typeof exports === 'object') {
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);
25
+ }
26
+ }(this, function (punycode, IPv6, SLD) {
27
+ "use strict";
14
28
 
15
- (function(undefined) {
29
+ function URI(url, base) {
30
+ // Allow instantiation without the 'new' keyword
31
+ if (!(this instanceof URI)) {
32
+ return new URI(url, base);
33
+ }
16
34
 
17
- var _use_module = typeof module !== "undefined" && module.exports,
18
- _load_module = function(module) {
19
- return _use_module ? require('./' + module) : window[module];
20
- },
21
- punycode = _load_module('punycode'),
22
- IPv6 = _load_module('IPv6'),
23
- SLD = _load_module('SecondLevelDomains'),
24
- URI = function(url, base) {
25
- // Allow instantiation without the 'new' keyword
26
- if (!(this instanceof URI)) {
27
- return new URI(url, base);
28
- }
29
-
30
- if (url === undefined) {
31
- if (typeof location !== 'undefined') {
32
- url = location.href + "";
33
- } else {
34
- url = "";
35
- }
35
+ if (url === undefined) {
36
+ if (typeof location !== 'undefined') {
37
+ url = location.href + "";
38
+ } else {
39
+ url = "";
36
40
  }
41
+ }
37
42
 
38
- this.href(url);
43
+ this.href(url);
39
44
 
40
- // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor
41
- if (base !== undefined) {
42
- return this.absoluteTo(base);
43
- }
45
+ // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor
46
+ if (base !== undefined) {
47
+ return this.absoluteTo(base);
48
+ }
44
49
 
45
- return this;
46
- },
47
- p = URI.prototype;
50
+ return this;
51
+ };
52
+
53
+ var p = URI.prototype;
54
+ var hasOwn = Object.prototype.hasOwnProperty;
48
55
 
49
56
  function escapeRegEx(string) {
50
57
  // https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963
51
58
  return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
52
59
  }
53
60
 
61
+ function getType(value) {
62
+ return String(Object.prototype.toString.call(value)).slice(8, -1);
63
+ }
64
+
54
65
  function isArray(obj) {
55
- return String(Object.prototype.toString.call(obj)) === "[object Array]";
66
+ return getType(obj) === "Array";
56
67
  }
57
68
 
58
69
  function filterArrayValues(data, value) {
59
- var lookup = {},
60
- i, length;
70
+ var lookup = {};
71
+ var i, length;
61
72
 
62
73
  if (isArray(value)) {
63
74
  for (i = 0, length = value.length; i < length; i++) {
@@ -78,6 +89,74 @@ function filterArrayValues(data, value) {
78
89
  return data;
79
90
  }
80
91
 
92
+ function arrayContains(list, value) {
93
+ var i, length;
94
+
95
+ // value may be string, number, array, regexp
96
+ if (isArray(value)) {
97
+ // Note: this can be optimized to O(n) (instead of current O(m * n))
98
+ for (i = 0, length = value.length; i < length; i++) {
99
+ if (!arrayContains(list, value[i])) {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ return true;
105
+ }
106
+
107
+ var _type = getType(value);
108
+ for (i = 0, length = list.length; i < length; i++) {
109
+ if (_type === 'RegExp') {
110
+ if (typeof list[i] === 'string' && list[i].match(value)) {
111
+ return true;
112
+ }
113
+ } else if (list[i] === value) {
114
+ return true;
115
+ }
116
+ }
117
+
118
+ return false;
119
+ }
120
+
121
+ function arraysEqual(one, two) {
122
+ if (!isArray(one) || !isArray(two)) {
123
+ return false;
124
+ }
125
+
126
+ // arrays can't be equal if they have different amount of content
127
+ if (one.length !== two.length) {
128
+ return false;
129
+ }
130
+
131
+ one.sort();
132
+ two.sort();
133
+
134
+ for (var i = 0, l = one.length; i < l; i++) {
135
+ if (one[i] !== two[i]) {
136
+ return false;
137
+ }
138
+ }
139
+
140
+ return true;
141
+ }
142
+
143
+ URI._parts = function() {
144
+ return {
145
+ protocol: null,
146
+ username: null,
147
+ password: null,
148
+ hostname: null,
149
+ urn: null,
150
+ port: null,
151
+ path: null,
152
+ query: null,
153
+ fragment: null,
154
+ // state
155
+ duplicateQueryParameters: URI.duplicateQueryParameters
156
+ };
157
+ };
158
+ // state: allow duplicate query parameters (a=1&a=1)
159
+ URI.duplicateQueryParameters = false;
81
160
  // static properties
82
161
  URI.protocol_expression = /^[a-z][a-z0-9-+-]*$/i;
83
162
  URI.idn_expression = /[^a-z0-9\.-]/i;
@@ -87,7 +166,7 @@ URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
87
166
  // credits to Rich Brown
88
167
  // source: http://forums.intermapper.com/viewtopic.php?p=1096#1096
89
168
  // specification: http://www.ietf.org/rfc/rfc4291.txt
90
- 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*$/ ;
169
+ 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*$/;
91
170
  // gruber revised expression - http://rodneyrehm.de/t/url-regex.html
92
171
  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;
93
172
  // http://www.iana.org/assignments/uri-schemes.html
@@ -95,7 +174,10 @@ URI.find_uri_expression = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]
95
174
  URI.defaultPorts = {
96
175
  http: "80",
97
176
  https: "443",
98
- ftp: "21"
177
+ ftp: "21",
178
+ gopher: "70",
179
+ ws: "80",
180
+ wss: "443"
99
181
  };
100
182
  // allowed hostname characters according to RFC 3986
101
183
  // ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded
@@ -198,15 +280,15 @@ URI.decodePath = function(string) {
198
280
  return segments.join('/');
199
281
  };
200
282
  // generate encode/decode path functions
201
- var _parts = {'encode':'encode', 'decode':'decode'},
202
- _part,
203
- generateAccessor = function(_group, _part){
204
- return function(string) {
205
- return URI[_part](string + "").replace(URI.characters[_group][_part].expression, function(c) {
206
- return URI.characters[_group][_part].map[c];
207
- });
208
- };
283
+ var _parts = {'encode':'encode', 'decode':'decode'};
284
+ var _part;
285
+ var generateAccessor = function(_group, _part) {
286
+ return function(string) {
287
+ return URI[_part](string + "").replace(URI.characters[_group][_part].expression, function(c) {
288
+ return URI.characters[_group][_part].map[c];
289
+ });
209
290
  };
291
+ };
210
292
 
211
293
  for (_part in _parts) {
212
294
  URI[_part + "PathSegment"] = generateAccessor("pathname", _parts[_part]);
@@ -214,8 +296,11 @@ for (_part in _parts) {
214
296
 
215
297
  URI.encodeReserved = generateAccessor("reserved", "encode");
216
298
 
217
- URI.parse = function(string) {
218
- var pos, t, parts = {};
299
+ URI.parse = function(string, parts) {
300
+ var pos, t;
301
+ if (!parts) {
302
+ parts = {};
303
+ }
219
304
  // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment]
220
305
 
221
306
  // extract fragment
@@ -248,6 +333,9 @@ URI.parse = function(string) {
248
333
  if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) {
249
334
  // : may be within the path
250
335
  parts.protocol = undefined;
336
+ } else if (parts.protocol === 'file') {
337
+ // the file scheme: does not contain an authority
338
+ string = string.substring(pos + 3);
251
339
  } else if (string.substring(pos + 1, pos + 3) === '//') {
252
340
  string = string.substring(pos + 3);
253
341
 
@@ -268,18 +356,19 @@ URI.parse = function(string) {
268
356
  };
269
357
  URI.parseHost = function(string, parts) {
270
358
  // extract host:port
271
- var pos = string.indexOf('/'),
272
- t;
359
+ var pos = string.indexOf('/');
360
+ var bracketPos;
361
+ var t;
273
362
 
274
363
  if (pos === -1) {
275
364
  pos = string.length;
276
365
  }
277
366
 
278
- if (string[0] === "[") {
367
+ if (string.charAt(0) === "[") {
279
368
  // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6
280
369
  // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts
281
370
  // IPv6+port in the format [2001:db8::1]:80 (for the time being)
282
- var bracketPos = string.indexOf(']');
371
+ bracketPos = string.indexOf(']');
283
372
  parts.hostname = string.substring(1, bracketPos) || null;
284
373
  parts.port = string.substring(bracketPos+2, pos) || null;
285
374
  } else if (string.indexOf(':') !== string.lastIndexOf(':')) {
@@ -293,7 +382,7 @@ URI.parseHost = function(string, parts) {
293
382
  parts.port = t[1] || null;
294
383
  }
295
384
 
296
- if (parts.hostname && string.substring(pos)[0] !== '/') {
385
+ if (parts.hostname && string.substring(pos).charAt(0) !== '/') {
297
386
  pos++;
298
387
  string = "/" + string;
299
388
  }
@@ -306,15 +395,16 @@ URI.parseAuthority = function(string, parts) {
306
395
  };
307
396
  URI.parseUserinfo = function(string, parts) {
308
397
  // extract username:password
309
- var pos = string.indexOf('@'),
310
- firstSlash = string.indexOf('/'),
311
- t;
398
+ var pos = string.indexOf('@');
399
+ var firstSlash = string.indexOf('/');
400
+ var t;
312
401
 
313
402
  // authority@ must come before /path
314
403
  if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) {
315
404
  t = string.substring(0, pos).split(':');
316
405
  parts.username = t[0] ? URI.decode(t[0]) : null;
317
- parts.password = t[1] ? URI.decode(t[1]) : null;
406
+ t.shift();
407
+ parts.password = t[0] ? URI.decode(t.join(':')) : null;
318
408
  string = string.substring(pos + 1);
319
409
  } else {
320
410
  parts.username = null;
@@ -335,15 +425,16 @@ URI.parseQuery = function(string) {
335
425
  return {};
336
426
  }
337
427
 
338
- var items = {},
339
- splits = string.split('&'),
340
- length = splits.length;
428
+ var items = {};
429
+ var splits = string.split('&');
430
+ var length = splits.length;
431
+ var v, name, value;
341
432
 
342
433
  for (var i = 0; i < length; i++) {
343
- var v = splits[i].split('='),
344
- name = URI.decodeQuery(v.shift()),
345
- // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters
346
- value = v.length ? URI.decodeQuery(v.join('=')) : null;
434
+ v = splits[i].split('=');
435
+ name = URI.decodeQuery(v.shift());
436
+ // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters
437
+ value = v.length ? URI.decodeQuery(v.join('=')) : null;
347
438
 
348
439
  if (items[name]) {
349
440
  if (typeof items[name] === "string") {
@@ -360,7 +451,7 @@ URI.parseQuery = function(string) {
360
451
  };
361
452
 
362
453
  URI.build = function(parts) {
363
- var t = '';
454
+ var t = "";
364
455
 
365
456
  if (parts.protocol) {
366
457
  t += parts.protocol + ":";
@@ -373,27 +464,27 @@ URI.build = function(parts) {
373
464
  t += (URI.buildAuthority(parts) || '');
374
465
 
375
466
  if (typeof parts.path === "string") {
376
- if (parts.path[0] !== '/' && typeof parts.hostname === "string") {
467
+ if (parts.path.charAt(0) !== '/' && typeof parts.hostname === "string") {
377
468
  t += '/';
378
469
  }
379
470
 
380
471
  t += parts.path;
381
472
  }
382
473
 
383
- if (typeof parts.query === "string") {
474
+ if (typeof parts.query === "string" && parts.query) {
384
475
  t += '?' + parts.query;
385
476
  }
386
477
 
387
- if (typeof parts.fragment === "string") {
478
+ if (typeof parts.fragment === "string" && parts.fragment) {
388
479
  t += '#' + parts.fragment;
389
480
  }
390
481
  return t;
391
482
  };
392
483
  URI.buildHost = function(parts) {
393
- var t = '';
484
+ var t = "";
394
485
 
395
486
  if (!parts.hostname) {
396
- return '';
487
+ return "";
397
488
  } else if (URI.ip6_expression.test(parts.hostname)) {
398
489
  if (parts.port) {
399
490
  t += "[" + parts.hostname + "]:" + parts.port;
@@ -415,7 +506,7 @@ URI.buildAuthority = function(parts) {
415
506
  return URI.buildUserinfo(parts) + URI.buildHost(parts);
416
507
  };
417
508
  URI.buildUserinfo = function(parts) {
418
- var t = '';
509
+ var t = "";
419
510
 
420
511
  if (parts.username) {
421
512
  t += URI.encode(parts.username);
@@ -437,11 +528,12 @@ URI.buildQuery = function(data, duplicates) {
437
528
  // see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
438
529
 
439
530
  var t = "";
440
- for (var key in data) {
441
- if (Object.hasOwnProperty.call(data, key) && key) {
531
+ var unique, key, i, length;
532
+ for (key in data) {
533
+ if (hasOwn.call(data, key) && key) {
442
534
  if (isArray(data[key])) {
443
- var unique = {};
444
- for (var i = 0, length = data[key].length; i < length; i++) {
535
+ unique = {};
536
+ for (i = 0, length = data[key].length; i < length; i++) {
445
537
  if (data[key][i] !== undefined && unique[data[key][i] + ""] === undefined) {
446
538
  t += "&" + URI.buildQueryParameter(key, data[key][i]);
447
539
  if (duplicates !== true) {
@@ -466,7 +558,7 @@ URI.buildQueryParameter = function(name, value) {
466
558
  URI.addQuery = function(data, name, value) {
467
559
  if (typeof name === "object") {
468
560
  for (var key in name) {
469
- if (Object.prototype.hasOwnProperty.call(name, key)) {
561
+ if (hasOwn.call(name, key)) {
470
562
  URI.addQuery(data, key, name[key]);
471
563
  }
472
564
  }
@@ -488,13 +580,15 @@ URI.addQuery = function(data, name, value) {
488
580
  }
489
581
  };
490
582
  URI.removeQuery = function(data, name, value) {
583
+ var i, length, key;
584
+
491
585
  if (isArray(name)) {
492
- for (var i = 0, length = name.length; i < length; i++) {
586
+ for (i = 0, length = name.length; i < length; i++) {
493
587
  data[name[i]] = undefined;
494
588
  }
495
589
  } else if (typeof name === "object") {
496
- for (var key in name) {
497
- if (Object.prototype.hasOwnProperty.call(name, key)) {
590
+ for (key in name) {
591
+ if (hasOwn.call(name, key)) {
498
592
  URI.removeQuery(data, key, name[key]);
499
593
  }
500
594
  }
@@ -512,25 +606,92 @@ URI.removeQuery = function(data, name, value) {
512
606
  throw new TypeError("URI.addQuery() accepts an object, string as the first parameter");
513
607
  }
514
608
  };
609
+ URI.hasQuery = function(data, name, value, withinArray) {
610
+ if (typeof name === "object") {
611
+ for (var key in name) {
612
+ if (hasOwn.call(name, key)) {
613
+ if (!URI.hasQuery(data, key, name[key])) {
614
+ return false;
615
+ }
616
+ }
617
+ }
618
+
619
+ return true;
620
+ } else if (typeof name !== "string") {
621
+ throw new TypeError("URI.hasQuery() accepts an object, string as the name parameter");
622
+ }
623
+
624
+ switch (getType(value)) {
625
+ case 'Undefined':
626
+ // true if exists (but may be empty)
627
+ return name in data; // data[name] !== undefined;
628
+
629
+ case 'Boolean':
630
+ // true if exists and non-empty
631
+ var _booly = Boolean(isArray(data[name]) ? data[name].length : data[name]);
632
+ return value === _booly;
633
+
634
+ case 'Function':
635
+ // allow complex comparison
636
+ return !!value(data[name], name, data);
637
+
638
+ case 'Array':
639
+ if (!isArray(data[name])) {
640
+ return false;
641
+ }
642
+
643
+ var op = withinArray ? arrayContains : arraysEqual;
644
+ return op(data[name], value);
645
+
646
+ case 'RegExp':
647
+ if (!isArray(data[name])) {
648
+ return Boolean(data[name] && data[name].match(value));
649
+ }
650
+
651
+ if (!withinArray) {
652
+ return false;
653
+ }
654
+
655
+ return arrayContains(data[name], value);
656
+
657
+ case 'Number':
658
+ value = String(value);
659
+ // omit break;
660
+ case 'String':
661
+ if (!isArray(data[name])) {
662
+ return data[name] === value;
663
+ }
664
+
665
+ if (!withinArray) {
666
+ return false;
667
+ }
668
+
669
+ return arrayContains(data[name], value);
670
+
671
+ default:
672
+ throw new TypeError("URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter");
673
+ }
674
+ };
675
+
515
676
 
516
677
  URI.commonPath = function(one, two) {
517
- var length = Math.min(one.length, two.length),
518
- pos;
678
+ var length = Math.min(one.length, two.length);
679
+ var pos;
519
680
 
520
681
  // find first non-matching character
521
682
  for (pos = 0; pos < length; pos++) {
522
- if (one[pos] !== two[pos]) {
683
+ if (one.charAt(pos) !== two.charAt(pos)) {
523
684
  pos--;
524
685
  break;
525
686
  }
526
687
  }
527
688
 
528
689
  if (pos < 1) {
529
- return one[0] === two[0] && one[0] === '/' ? '/' : '';
690
+ return one.charAt(0) === two.charAt(0) && one.charAt(0) === '/' ? '/' : '';
530
691
  }
531
-
692
+
532
693
  // revert to last /
533
- if (one[pos] !== '/') {
694
+ if (one.charAt(pos) !== '/' || two.charAt(pos) !== '/') {
534
695
  pos = one.substring(0, pos).lastIndexOf('/');
535
696
  }
536
697
 
@@ -577,12 +738,9 @@ p.clone = function() {
577
738
  return new URI(this);
578
739
  };
579
740
 
580
- p.toString = function() {
741
+ p.valueOf = p.toString = function() {
581
742
  return this.build(false)._string;
582
743
  };
583
- p.valueOf = function() {
584
- return this.toString();
585
- };
586
744
 
587
745
  // generate simple accessors
588
746
  _parts = {protocol: 'protocol', username: 'username', password: 'password', hostname: 'hostname', port: 'port'};
@@ -598,7 +756,7 @@ generateAccessor = function(_part){
598
756
  };
599
757
  };
600
758
 
601
- for (_part in _parts) {
759
+ for (_part in _parts) {
602
760
  p[_part] = generateAccessor(_parts[_part]);
603
761
  }
604
762
 
@@ -611,7 +769,7 @@ generateAccessor = function(_part, _key){
611
769
  } else {
612
770
  if (v !== null) {
613
771
  v = v + "";
614
- if (v[0] === _key) {
772
+ if (v.charAt(0) === _key) {
615
773
  v = v.substring(1);
616
774
  }
617
775
  }
@@ -652,54 +810,57 @@ p.pathname = function(v, build) {
652
810
  };
653
811
  p.path = p.pathname;
654
812
  p.href = function(href, build) {
813
+ var key;
814
+
655
815
  if (href === undefined) {
656
816
  return this.toString();
657
- } else {
658
- this._string = "";
659
- this._parts = {
660
- protocol: null,
661
- username: null,
662
- password: null,
663
- hostname: null,
664
- urn: null,
665
- port: null,
666
- path: null,
667
- query: null,
668
- fragment: null
669
- };
670
-
671
- var _URI = href instanceof URI,
672
- _object = typeof href === "object" && (href.hostname || href.path),
673
- key;
674
-
675
- if (typeof href === "string") {
676
- this._parts = URI.parse(href);
677
- } else if (_URI || _object) {
678
- var src = _URI ? href._parts : href;
679
- for (key in src) {
680
- if (Object.hasOwnProperty.call(this._parts, key)) {
681
- this._parts[key] = src[key];
682
- }
817
+ }
818
+
819
+ this._string = "";
820
+ this._parts = URI._parts();
821
+
822
+ var _URI = href instanceof URI;
823
+ var _object = typeof href === "object" && (href.hostname || href.path);
824
+
825
+
826
+ // window.location is reported to be an object, but it's not the sort
827
+ // of object we're looking for:
828
+ // * location.protocol ends with a colon
829
+ // * location.query != object.search
830
+ // * location.hash != object.fragment
831
+ // simply serializing the unknown object should do the trick
832
+ // (for location, not for everything...)
833
+ if (!_URI && _object && href.pathname !== undefined) {
834
+ href = href.toString();
835
+ }
836
+
837
+ if (typeof href === "string") {
838
+ this._parts = URI.parse(href, this._parts);
839
+ } else if (_URI || _object) {
840
+ var src = _URI ? href._parts : href;
841
+ for (key in src) {
842
+ if (hasOwn.call(this._parts, key)) {
843
+ this._parts[key] = src[key];
683
844
  }
684
- } else {
685
- throw new TypeError("invalid input");
686
845
  }
687
-
688
- this.build(!build);
689
- return this;
846
+ } else {
847
+ throw new TypeError("invalid input");
690
848
  }
849
+
850
+ this.build(!build);
851
+ return this;
691
852
  };
692
853
 
693
854
  // identification accessors
694
855
  p.is = function(what) {
695
- var ip = false,
696
- ip4 = false,
697
- ip6 = false,
698
- name = false,
699
- sld = false,
700
- idn = false,
701
- punycode = false,
702
- relative = !this._parts.urn;
856
+ var ip = false;
857
+ var ip4 = false;
858
+ var ip6 = false;
859
+ var name = false;
860
+ var sld = false;
861
+ var idn = false;
862
+ var punycode = false;
863
+ var relative = !this._parts.urn;
703
864
 
704
865
  if (this._parts.hostname) {
705
866
  relative = false;
@@ -757,9 +918,9 @@ p.is = function(what) {
757
918
  };
758
919
 
759
920
  // component specific input validation
760
- var _protocol = p.protocol,
761
- _port = p.port,
762
- _hostname = p.hostname;
921
+ var _protocol = p.protocol;
922
+ var _port = p.port;
923
+ var _hostname = p.hostname;
763
924
 
764
925
  p.protocol = function(v, build) {
765
926
  if (v !== undefined) {
@@ -787,7 +948,7 @@ p.port = function(v, build) {
787
948
 
788
949
  if (v) {
789
950
  v += "";
790
- if (v[0] === ":") {
951
+ if (v.charAt(0) === ":") {
791
952
  v = v.substring(1);
792
953
  }
793
954
 
@@ -811,7 +972,7 @@ p.hostname = function(v, build) {
811
972
  return _hostname.call(this, v, build);
812
973
  };
813
974
 
814
- // combination accessors
975
+ // compound accessors
815
976
  p.host = function(v, build) {
816
977
  if (this._parts.urn) {
817
978
  return v === undefined ? '' : this;
@@ -860,6 +1021,20 @@ p.userinfo = function(v, build) {
860
1021
  return this;
861
1022
  }
862
1023
  };
1024
+ p.resource = function(v, build) {
1025
+ var parts;
1026
+
1027
+ if (v === undefined) {
1028
+ return this.path() + this.search() + this.hash();
1029
+ }
1030
+
1031
+ parts = URI.parse(v);
1032
+ this._parts.path = parts.path;
1033
+ this._parts.query = parts.query;
1034
+ this._parts.fragment = parts.fragment;
1035
+ this.build(!build);
1036
+ return this;
1037
+ };
863
1038
 
864
1039
  // fraction accessors
865
1040
  p.subdomain = function(v, build) {
@@ -877,11 +1052,11 @@ p.subdomain = function(v, build) {
877
1052
  var end = this._parts.hostname.length - this.domain().length - 1;
878
1053
  return this._parts.hostname.substring(0, end) || "";
879
1054
  } else {
880
- var e = this._parts.hostname.length - this.domain().length,
881
- sub = this._parts.hostname.substring(0, e),
882
- replace = new RegExp('^' + escapeRegEx(sub));
1055
+ var e = this._parts.hostname.length - this.domain().length;
1056
+ var sub = this._parts.hostname.substring(0, e);
1057
+ var replace = new RegExp('^' + escapeRegEx(sub));
883
1058
 
884
- if (v && v[v.length - 1] !== '.') {
1059
+ if (v && v.charAt(v.length - 1) !== '.') {
885
1060
  v += ".";
886
1061
  }
887
1062
 
@@ -954,8 +1129,8 @@ p.tld = function(v, build) {
954
1129
  return "";
955
1130
  }
956
1131
 
957
- var pos = this._parts.hostname.lastIndexOf('.'),
958
- tld = this._parts.hostname.substring(pos + 1);
1132
+ var pos = this._parts.hostname.lastIndexOf('.');
1133
+ var tld = this._parts.hostname.substring(pos + 1);
959
1134
 
960
1135
  if (build !== true && SLD && SLD.list[tld.toLowerCase()]) {
961
1136
  return SLD.get(this._parts.hostname) || tld;
@@ -964,6 +1139,7 @@ p.tld = function(v, build) {
964
1139
  return tld;
965
1140
  } else {
966
1141
  var replace;
1142
+
967
1143
  if (!v) {
968
1144
  throw new TypeError("cannot set TLD empty");
969
1145
  } else if (v.match(/[^a-zA-Z0-9-]/)) {
@@ -998,15 +1174,15 @@ p.directory = function(v, build) {
998
1174
  return '/';
999
1175
  }
1000
1176
 
1001
- var end = this._parts.path.length - this.filename().length - 1,
1002
- res = this._parts.path.substring(0, end) || (this._parts.hostname ? "/" : "");
1177
+ var end = this._parts.path.length - this.filename().length - 1;
1178
+ var res = this._parts.path.substring(0, end) || (this._parts.hostname ? "/" : "");
1003
1179
 
1004
1180
  return v ? URI.decodePath(res) : res;
1005
1181
 
1006
1182
  } else {
1007
- var e = this._parts.path.length - this.filename().length,
1008
- directory = this._parts.path.substring(0, e),
1009
- replace = new RegExp('^' + escapeRegEx(directory));
1183
+ var e = this._parts.path.length - this.filename().length;
1184
+ var directory = this._parts.path.substring(0, e);
1185
+ var replace = new RegExp('^' + escapeRegEx(directory));
1010
1186
 
1011
1187
  // fully qualifier directories begin with a slash
1012
1188
  if (!this.is('relative')) {
@@ -1014,13 +1190,13 @@ p.directory = function(v, build) {
1014
1190
  v = '/';
1015
1191
  }
1016
1192
 
1017
- if (v[0] !== '/') {
1193
+ if (v.charAt(0) !== '/') {
1018
1194
  v = "/" + v;
1019
1195
  }
1020
1196
  }
1021
1197
 
1022
1198
  // directories always end with a slash
1023
- if (v && v[v.length - 1] !== '/') {
1199
+ if (v && v.charAt(v.length - 1) !== '/') {
1024
1200
  v += '/';
1025
1201
  }
1026
1202
 
@@ -1040,13 +1216,14 @@ p.filename = function(v, build) {
1040
1216
  return "";
1041
1217
  }
1042
1218
 
1043
- var pos = this._parts.path.lastIndexOf('/'),
1044
- res = this._parts.path.substring(pos+1);
1219
+ var pos = this._parts.path.lastIndexOf('/');
1220
+ var res = this._parts.path.substring(pos+1);
1045
1221
 
1046
1222
  return v ? URI.decodePathSegment(res) : res;
1047
1223
  } else {
1048
1224
  var mutatedDirectory = false;
1049
- if (v[0] === '/') {
1225
+
1226
+ if (v.charAt(0) === '/') {
1050
1227
  v = v.substring(1);
1051
1228
  }
1052
1229
 
@@ -1077,9 +1254,9 @@ p.suffix = function(v, build) {
1077
1254
  return "";
1078
1255
  }
1079
1256
 
1080
- var filename = this.filename(),
1081
- pos = filename.lastIndexOf('.'),
1082
- s, res;
1257
+ var filename = this.filename();
1258
+ var pos = filename.lastIndexOf('.');
1259
+ var s, res;
1083
1260
 
1084
1261
  if (pos === -1) {
1085
1262
  return "";
@@ -1090,12 +1267,12 @@ p.suffix = function(v, build) {
1090
1267
  res = (/^[a-z0-9%]+$/i).test(s) ? s : "";
1091
1268
  return v ? URI.decodePathSegment(res) : res;
1092
1269
  } else {
1093
- if (v[0] === '.') {
1270
+ if (v.charAt(0) === '.') {
1094
1271
  v = v.substring(1);
1095
1272
  }
1096
1273
 
1097
- var suffix = this.suffix(),
1098
- replace;
1274
+ var suffix = this.suffix();
1275
+ var replace;
1099
1276
 
1100
1277
  if (!suffix) {
1101
1278
  if (!v) {
@@ -1119,10 +1296,10 @@ p.suffix = function(v, build) {
1119
1296
  }
1120
1297
  };
1121
1298
  p.segment = function(segment, v, build) {
1122
- var separator = this._parts.urn ? ':' : '/',
1123
- path = this.path(),
1124
- absolute = path.substring(0, 1) === '/',
1125
- segments = path.split(separator);
1299
+ var separator = this._parts.urn ? ':' : '/';
1300
+ var path = this.path();
1301
+ var absolute = path.substring(0, 1) === '/';
1302
+ var segments = path.split(separator);
1126
1303
 
1127
1304
  if (typeof segment !== 'number') {
1128
1305
  build = v;
@@ -1179,18 +1356,47 @@ var q = p.query;
1179
1356
  p.query = function(v, build) {
1180
1357
  if (v === true) {
1181
1358
  return URI.parseQuery(this._parts.query);
1359
+ } else if (typeof v === "function") {
1360
+ var data = URI.parseQuery(this._parts.query);
1361
+ var result = v.call(this, data);
1362
+ this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters);
1363
+ this.build(!build);
1364
+ return this;
1182
1365
  } else if (v !== undefined && typeof v !== "string") {
1183
- this._parts.query = URI.buildQuery(v);
1366
+ this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters);
1184
1367
  this.build(!build);
1185
1368
  return this;
1186
1369
  } else {
1187
1370
  return q.call(this, v, build);
1188
1371
  }
1189
1372
  };
1373
+ p.setQuery = function(name, value, build) {
1374
+ var data = URI.parseQuery(this._parts.query);
1375
+
1376
+ if (typeof name === "object") {
1377
+ for (var key in name) {
1378
+ if (hasOwn.call(name, key)) {
1379
+ data[key] = name[key];
1380
+ }
1381
+ }
1382
+ } else if (typeof name === "string") {
1383
+ data[name] = value !== undefined ? value : null;
1384
+ } else {
1385
+ throw new TypeError("URI.addQuery() accepts an object, string as the name parameter");
1386
+ }
1387
+
1388
+ this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters);
1389
+ if (typeof name !== "string") {
1390
+ build = value;
1391
+ }
1392
+
1393
+ this.build(!build);
1394
+ return this;
1395
+ };
1190
1396
  p.addQuery = function(name, value, build) {
1191
1397
  var data = URI.parseQuery(this._parts.query);
1192
- URI.addQuery(data, name, value);
1193
- this._parts.query = URI.buildQuery(data);
1398
+ URI.addQuery(data, name, value === undefined ? null : value);
1399
+ this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters);
1194
1400
  if (typeof name !== "string") {
1195
1401
  build = value;
1196
1402
  }
@@ -1201,7 +1407,7 @@ p.addQuery = function(name, value, build) {
1201
1407
  p.removeQuery = function(name, value, build) {
1202
1408
  var data = URI.parseQuery(this._parts.query);
1203
1409
  URI.removeQuery(data, name, value);
1204
- this._parts.query = URI.buildQuery(data);
1410
+ this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters);
1205
1411
  if (typeof name !== "string") {
1206
1412
  build = value;
1207
1413
  }
@@ -1209,8 +1415,14 @@ p.removeQuery = function(name, value, build) {
1209
1415
  this.build(!build);
1210
1416
  return this;
1211
1417
  };
1418
+ p.hasQuery = function(name, value, withinArray) {
1419
+ var data = URI.parseQuery(this._parts.query);
1420
+ return URI.hasQuery(data, name, value, withinArray);
1421
+ };
1422
+ p.setSearch = p.setQuery;
1212
1423
  p.addSearch = p.addQuery;
1213
1424
  p.removeSearch = p.removeQuery;
1425
+ p.hasSearch = p.hasQuery;
1214
1426
 
1215
1427
  // sanitizing URLs
1216
1428
  p.normalize = function() {
@@ -1271,14 +1483,14 @@ p.normalizePath = function(build) {
1271
1483
  return this;
1272
1484
  }
1273
1485
 
1274
- var _was_relative,
1275
- _was_relative_prefix,
1276
- _path = this._parts.path,
1277
- _parent, _pos;
1486
+ var _was_relative;
1487
+ var _was_relative_prefix;
1488
+ var _path = this._parts.path;
1489
+ var _parent, _pos;
1278
1490
 
1279
1491
  // handle relative paths
1280
- if (_path[0] !== '/') {
1281
- if (_path[0] === '.') {
1492
+ if (_path.charAt(0) !== '/') {
1493
+ if (_path.charAt(0) === '.') {
1282
1494
  _was_relative_prefix = _path.substring(0, _path.indexOf('/'));
1283
1495
  }
1284
1496
  _was_relative = true;
@@ -1306,11 +1518,7 @@ p.normalizePath = function(build) {
1306
1518
  }
1307
1519
  // revert to relative
1308
1520
  if (_was_relative && this.is('relative')) {
1309
- if (_was_relative_prefix){
1310
- _path = _was_relative_prefix + _path;
1311
- } else {
1312
- _path = _path.substring(1);
1313
- }
1521
+ _path = _path.substring(1);
1314
1522
  }
1315
1523
 
1316
1524
  _path = URI.recodePath(_path);
@@ -1345,8 +1553,8 @@ p.normalizeHash = p.normalizeFragment;
1345
1553
 
1346
1554
  p.iso8859 = function() {
1347
1555
  // expect unicode input, iso8859 output
1348
- var e = URI.encode,
1349
- d = URI.decode;
1556
+ var e = URI.encode;
1557
+ var d = URI.decode;
1350
1558
 
1351
1559
  URI.encode = escape;
1352
1560
  URI.decode = decodeURIComponent;
@@ -1358,8 +1566,8 @@ p.iso8859 = function() {
1358
1566
 
1359
1567
  p.unicode = function() {
1360
1568
  // expect iso8859 input, unicode output
1361
- var e = URI.encode,
1362
- d = URI.decode;
1569
+ var e = URI.encode;
1570
+ var d = URI.decode;
1363
1571
 
1364
1572
  URI.encode = strictEncodeURIComponent;
1365
1573
  URI.decode = unescape;
@@ -1389,7 +1597,7 @@ p.readable = function() {
1389
1597
  }
1390
1598
  }
1391
1599
 
1392
- if (uri._parts.hostname && uri._parts.path && uri._parts.path[0] !== '/') {
1600
+ if (uri._parts.hostname && uri._parts.path && uri._parts.path.charAt(0) !== '/') {
1393
1601
  t += '/';
1394
1602
  }
1395
1603
 
@@ -1415,21 +1623,25 @@ p.readable = function() {
1415
1623
 
1416
1624
  // resolving relative and absolute URLs
1417
1625
  p.absoluteTo = function(base) {
1418
- var resolved = this.clone(),
1419
- properties = ['protocol', 'username', 'password', 'hostname', 'port'],
1420
- basedir, i, p;
1626
+ var resolved = this.clone();
1627
+ var properties = ['protocol', 'username', 'password', 'hostname', 'port'];
1628
+ var basedir, i, p;
1421
1629
 
1422
1630
  if (this._parts.urn) {
1423
1631
  throw new Error('URNs do not have any generally defined hierachical components');
1424
1632
  }
1425
1633
 
1426
- if (this._parts.hostname) {
1427
- return resolved;
1428
- }
1429
-
1430
1634
  if (!(base instanceof URI)) {
1431
1635
  base = new URI(base);
1432
1636
  }
1637
+
1638
+ if (!resolved._parts.protocol) {
1639
+ resolved._parts.protocol = base._parts.protocol;
1640
+ }
1641
+
1642
+ if (this._parts.hostname) {
1643
+ return resolved;
1644
+ }
1433
1645
 
1434
1646
  for (i = 0, p; p = properties[i]; i++) {
1435
1647
  resolved._parts[p] = base._parts[p];
@@ -1442,7 +1654,7 @@ p.absoluteTo = function(base) {
1442
1654
  }
1443
1655
  }
1444
1656
 
1445
- if (resolved.path()[0] !== '/') {
1657
+ if (resolved.path().charAt(0) !== '/') {
1446
1658
  basedir = base.directory();
1447
1659
  resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path;
1448
1660
  resolved.normalizePath();
@@ -1452,12 +1664,11 @@ p.absoluteTo = function(base) {
1452
1664
  return resolved;
1453
1665
  };
1454
1666
  p.relativeTo = function(base) {
1455
- var relative = this.clone(),
1456
- properties = ['protocol', 'username', 'password', 'hostname', 'port'],
1457
- common,
1458
- _base;
1667
+ var relative = this.clone();
1668
+ var properties = ['protocol', 'username', 'password', 'hostname', 'port'];
1669
+ var common, _base, _this, _base_diff, _this_diff;
1459
1670
 
1460
- if (this._parts.urn) {
1671
+ if (relative._parts.urn) {
1461
1672
  throw new Error('URNs do not have any generally defined hierachical components');
1462
1673
  }
1463
1674
 
@@ -1465,49 +1676,69 @@ p.relativeTo = function(base) {
1465
1676
  base = new URI(base);
1466
1677
  }
1467
1678
 
1468
- if (this.path()[0] !== '/' || base.path()[0] !== '/') {
1679
+ if (relative.path().charAt(0) !== '/' || base.path().charAt(0) !== '/') {
1469
1680
  throw new Error('Cannot calculate common path from non-relative URLs');
1470
1681
  }
1471
1682
 
1683
+ // determine common sub path
1472
1684
  common = URI.commonPath(relative.path(), base.path());
1473
- _base = base.directory();
1474
-
1685
+
1686
+ // relative paths don't have authority
1475
1687
  for (var i = 0, p; p = properties[i]; i++) {
1476
1688
  relative._parts[p] = null;
1477
1689
  }
1478
1690
 
1479
- if (!common || common === '/') {
1691
+ // no relation if there's nothing in common
1692
+ if (common === '/') {
1480
1693
  return relative;
1694
+ } else if (!common) {
1695
+ // there's absolutely nothing in common here
1696
+ return this.clone();
1481
1697
  }
1698
+
1699
+ _base = base.directory();
1700
+ _this = relative.directory();
1482
1701
 
1702
+ // base and this are on the same level
1703
+ if (_base === _this) {
1704
+ relative._parts.path = relative.filename();
1705
+ return relative.build();
1706
+ }
1707
+
1708
+ _base_diff = _base.substring(common.length);
1709
+ _this_diff = _this.substring(common.length);
1710
+
1711
+ // this is a descendant of base
1483
1712
  if (_base + '/' === common) {
1484
- relative._parts.path = './' + relative.filename();
1485
- } else {
1486
- var parents = '../',
1487
- _common = new RegExp('^' + escapeRegEx(common)),
1488
- _parents = _base.replace(_common, '/').match(/\//g).length -1;
1489
-
1490
- while (_parents--) {
1491
- parents += '../';
1713
+ if (_this_diff) {
1714
+ _this_diff += '/';
1492
1715
  }
1716
+
1717
+ relative._parts.path = _this_diff + relative.filename();
1718
+ return relative.build();
1719
+ }
1720
+
1721
+ // this is a descendant of base
1722
+ var parents = '../';
1723
+ var _common = new RegExp('^' + escapeRegEx(common));
1724
+ var _parents = _base.replace(_common, '/').match(/\//g).length -1;
1493
1725
 
1494
- relative._parts.path = relative._parts.path.replace(_common, parents);
1726
+ while (_parents--) {
1727
+ parents += '../';
1495
1728
  }
1496
1729
 
1497
- relative.build();
1498
- return relative;
1730
+ relative._parts.path = relative._parts.path.replace(_common, parents);
1731
+ return relative.build();
1499
1732
  };
1500
1733
 
1501
1734
  // comparing URIs
1502
1735
  p.equals = function(uri) {
1503
- var one = this.clone(),
1504
- two = new URI(uri),
1505
- one_map = {},
1506
- two_map = {},
1507
- checked = {},
1508
- one_query,
1509
- two_query,
1510
- key;
1736
+ var one = this.clone();
1737
+ var two = new URI(uri);
1738
+ var one_map = {};
1739
+ var two_map = {};
1740
+ var checked = {};
1741
+ var one_query, two_query, key;
1511
1742
 
1512
1743
  one.normalize();
1513
1744
  two.normalize();
@@ -1537,29 +1768,13 @@ p.equals = function(uri) {
1537
1768
  two_map = URI.parseQuery(two_query);
1538
1769
 
1539
1770
  for (key in one_map) {
1540
- if (Object.prototype.hasOwnProperty.call(one_map, key)) {
1771
+ if (hasOwn.call(one_map, key)) {
1541
1772
  if (!isArray(one_map[key])) {
1542
1773
  if (one_map[key] !== two_map[key]) {
1543
1774
  return false;
1544
1775
  }
1545
- } else {
1546
- if (!isArray(two_map[key])) {
1547
- return false;
1548
- }
1549
-
1550
- // arrays can't be equal if they have different amount of content
1551
- if (one_map[key].length !== two_map[key].length) {
1552
- return false;
1553
- }
1554
-
1555
- one_map[key].sort();
1556
- two_map[key].sort();
1557
-
1558
- for (var i = 0, l = one_map[key].length; i < l; i++) {
1559
- if (one_map[key][i] !== two_map[key][i]) {
1560
- return false;
1561
- }
1562
- }
1776
+ } else if (!arraysEqual(one_map[key], two_map[key])) {
1777
+ return false;
1563
1778
  }
1564
1779
 
1565
1780
  checked[key] = true;
@@ -1567,7 +1782,7 @@ p.equals = function(uri) {
1567
1782
  }
1568
1783
 
1569
1784
  for (key in two_map) {
1570
- if (Object.prototype.hasOwnProperty.call(two_map, key)) {
1785
+ if (hasOwn.call(two_map, key)) {
1571
1786
  if (!checked[key]) {
1572
1787
  // two contains a parameter not present in one
1573
1788
  return false;
@@ -1578,9 +1793,11 @@ p.equals = function(uri) {
1578
1793
  return true;
1579
1794
  };
1580
1795
 
1581
- (typeof module !== 'undefined' && module.exports
1582
- ? module.exports = URI
1583
- : window.URI = URI
1584
- );
1796
+ // state
1797
+ p.duplicateQueryParameters = function(v) {
1798
+ this._parts.duplicateQueryParameters = !!v;
1799
+ return this;
1800
+ };
1585
1801
 
1586
- })();
1802
+ return URI;
1803
+ }));