uri-js-rails 1.7.4 → 1.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ }));