uri-js-rails 1.7.4

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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in uri-js-rails.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Robin Wenglewski
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Uri::Js::Rails
2
+
3
+ [URI.js](https://github.com/medialize/URI.js) for Rails.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'uri-js-rails'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install uri-js-rails
18
+
19
+ ## Usage
20
+
21
+ Add to your application.js manifest:
22
+
23
+ //= require URI
24
+
25
+ **and** if you want to use the jQuery plugin
26
+
27
+ //= require jquery.URI
28
+
29
+
30
+ ## Contributing
31
+
32
+ 1. Fork it
33
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
34
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
35
+ 4. Push to the branch (`git push origin my-new-feature`)
36
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,2 @@
1
+ require 'uri-js-rails/version'
2
+ require 'uri-js-rails/engine'
@@ -0,0 +1,11 @@
1
+ # Configure for Rails 3.1
2
+ module Uri
3
+ module Js
4
+ if defined?(::Rails) and ::Rails.version >= "3.1"
5
+ module Rails
6
+ class Engine < ::Rails::Engine
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Uri
2
+ module Js
3
+ module Rails
4
+ VERSION = "1.7.4"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'uri-js-rails/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "uri-js-rails"
8
+ gem.version = Uri::Js::Rails::VERSION
9
+ gem.authors = ["Robin Wenglewski"]
10
+ gem.email = ["robin@wenglewski.de"]
11
+ gem.description = %q{}
12
+ gem.summary = %q{URI.js for rails}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ end
@@ -0,0 +1,1586 @@
1
+ /*!
2
+ * URI.js - Mutating URLs
3
+ *
4
+ * Version: 1.7.4
5
+ *
6
+ * Author: Rodney Rehm
7
+ * Web: http://medialize.github.com/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
+
15
+ (function(undefined) {
16
+
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
+ }
36
+ }
37
+
38
+ this.href(url);
39
+
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
+ }
44
+
45
+ return this;
46
+ },
47
+ p = URI.prototype;
48
+
49
+ function escapeRegEx(string) {
50
+ // https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963
51
+ return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
52
+ }
53
+
54
+ function isArray(obj) {
55
+ return String(Object.prototype.toString.call(obj)) === "[object Array]";
56
+ }
57
+
58
+ function filterArrayValues(data, value) {
59
+ var lookup = {},
60
+ i, length;
61
+
62
+ if (isArray(value)) {
63
+ for (i = 0, length = value.length; i < length; i++) {
64
+ lookup[value[i]] = true;
65
+ }
66
+ } else {
67
+ lookup[value] = true;
68
+ }
69
+
70
+ for (i = 0, length = data.length; i < length; i++) {
71
+ if (lookup[data[i]] !== undefined) {
72
+ data.splice(i, 1);
73
+ length--;
74
+ i--;
75
+ }
76
+ }
77
+
78
+ return data;
79
+ }
80
+
81
+ // static properties
82
+ URI.protocol_expression = /^[a-z][a-z0-9-+-]*$/i;
83
+ URI.idn_expression = /[^a-z0-9\.-]/i;
84
+ URI.punycode_expression = /(xn--)/i;
85
+ // well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care?
86
+ URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
87
+ // credits to Rich Brown
88
+ // source: http://forums.intermapper.com/viewtopic.php?p=1096#1096
89
+ // 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*$/ ;
91
+ // gruber revised expression - http://rodneyrehm.de/t/url-regex.html
92
+ 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
+ // http://www.iana.org/assignments/uri-schemes.html
94
+ // http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports
95
+ URI.defaultPorts = {
96
+ http: "80",
97
+ https: "443",
98
+ ftp: "21"
99
+ };
100
+ // allowed hostname characters according to RFC 3986
101
+ // ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded
102
+ // I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . -
103
+ URI.invalid_hostname_characters = /[^a-zA-Z0-9\.-]/;
104
+ // encoding / decoding according to RFC3986
105
+ function strictEncodeURIComponent(string) {
106
+ // see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent
107
+ return encodeURIComponent(string)
108
+ .replace(/[!'()*]/g, escape)
109
+ .replace(/\*/g, "%2A");
110
+ }
111
+ URI.encode = strictEncodeURIComponent;
112
+ URI.decode = decodeURIComponent;
113
+ URI.iso8859 = function() {
114
+ URI.encode = escape;
115
+ URI.decode = unescape;
116
+ };
117
+ URI.unicode = function() {
118
+ URI.encode = strictEncodeURIComponent;
119
+ URI.decode = decodeURIComponent;
120
+ };
121
+ URI.characters = {
122
+ pathname: {
123
+ encode: {
124
+ // RFC3986 2.1: For consistency, URI producers and normalizers should
125
+ // use uppercase hexadecimal digits for all percent-encodings.
126
+ expression: /%(24|26|2B|2C|3B|3D|3A|40)/ig,
127
+ map: {
128
+ // -._~!'()*
129
+ "%24": "$",
130
+ "%26": "&",
131
+ "%2B": "+",
132
+ "%2C": ",",
133
+ "%3B": ";",
134
+ "%3D": "=",
135
+ "%3A": ":",
136
+ "%40": "@"
137
+ }
138
+ },
139
+ decode: {
140
+ expression: /[\/\?#]/g,
141
+ map: {
142
+ "/": "%2F",
143
+ "?": "%3F",
144
+ "#": "%23"
145
+ }
146
+ }
147
+ },
148
+ reserved: {
149
+ encode: {
150
+ // RFC3986 2.1: For consistency, URI producers and normalizers should
151
+ // use uppercase hexadecimal digits for all percent-encodings.
152
+ expression: /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,
153
+ map: {
154
+ // gen-delims
155
+ "%3A": ":",
156
+ "%2F": "/",
157
+ "%3F": "?",
158
+ "%23": "#",
159
+ "%5B": "[",
160
+ "%5D": "]",
161
+ "%40": "@",
162
+ // sub-delims
163
+ "%21": "!",
164
+ "%24": "$",
165
+ "%26": "&",
166
+ "%27": "'",
167
+ "%28": "(",
168
+ "%29": ")",
169
+ "%2A": "*",
170
+ "%2B": "+",
171
+ "%2C": ",",
172
+ "%3B": ";",
173
+ "%3D": "="
174
+ }
175
+ }
176
+ }
177
+ };
178
+ URI.encodeQuery = function(string) {
179
+ return URI.encode(string + "").replace(/%20/g, '+');
180
+ };
181
+ URI.decodeQuery = function(string) {
182
+ return URI.decode((string + "").replace(/\+/g, '%20'));
183
+ };
184
+ URI.recodePath = function(string) {
185
+ var segments = (string + "").split('/');
186
+ for (var i = 0, length = segments.length; i < length; i++) {
187
+ segments[i] = URI.encodePathSegment(URI.decode(segments[i]));
188
+ }
189
+
190
+ return segments.join('/');
191
+ };
192
+ URI.decodePath = function(string) {
193
+ var segments = (string + "").split('/');
194
+ for (var i = 0, length = segments.length; i < length; i++) {
195
+ segments[i] = URI.decodePathSegment(segments[i]);
196
+ }
197
+
198
+ return segments.join('/');
199
+ };
200
+ // 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
+ };
209
+ };
210
+
211
+ for (_part in _parts) {
212
+ URI[_part + "PathSegment"] = generateAccessor("pathname", _parts[_part]);
213
+ }
214
+
215
+ URI.encodeReserved = generateAccessor("reserved", "encode");
216
+
217
+ URI.parse = function(string) {
218
+ var pos, t, parts = {};
219
+ // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment]
220
+
221
+ // extract fragment
222
+ pos = string.indexOf('#');
223
+ if (pos > -1) {
224
+ // escaping?
225
+ parts.fragment = string.substring(pos + 1) || null;
226
+ string = string.substring(0, pos);
227
+ }
228
+
229
+ // extract query
230
+ pos = string.indexOf('?');
231
+ if (pos > -1) {
232
+ // escaping?
233
+ parts.query = string.substring(pos + 1) || null;
234
+ string = string.substring(0, pos);
235
+ }
236
+
237
+ // extract protocol
238
+ if (string.substring(0, 2) === '//') {
239
+ // relative-scheme
240
+ parts.protocol = '';
241
+ string = string.substring(2);
242
+ // extract "user:pass@host:port"
243
+ string = URI.parseAuthority(string, parts);
244
+ } else {
245
+ pos = string.indexOf(':');
246
+ if (pos > -1) {
247
+ parts.protocol = string.substring(0, pos);
248
+ if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) {
249
+ // : may be within the path
250
+ parts.protocol = undefined;
251
+ } else if (string.substring(pos + 1, pos + 3) === '//') {
252
+ string = string.substring(pos + 3);
253
+
254
+ // extract "user:pass@host:port"
255
+ string = URI.parseAuthority(string, parts);
256
+ } else {
257
+ string = string.substring(pos + 1);
258
+ parts.urn = true;
259
+ }
260
+ }
261
+ }
262
+
263
+ // what's left must be the path
264
+ parts.path = string;
265
+
266
+ // and we're done
267
+ return parts;
268
+ };
269
+ URI.parseHost = function(string, parts) {
270
+ // extract host:port
271
+ var pos = string.indexOf('/'),
272
+ t;
273
+
274
+ if (pos === -1) {
275
+ pos = string.length;
276
+ }
277
+
278
+ if (string[0] === "[") {
279
+ // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6
280
+ // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts
281
+ // IPv6+port in the format [2001:db8::1]:80 (for the time being)
282
+ var bracketPos = string.indexOf(']');
283
+ parts.hostname = string.substring(1, bracketPos) || null;
284
+ parts.port = string.substring(bracketPos+2, pos) || null;
285
+ } else if (string.indexOf(':') !== string.lastIndexOf(':')) {
286
+ // IPv6 host contains multiple colons - but no port
287
+ // this notation is actually not allowed by RFC 3986, but we're a liberal parser
288
+ parts.hostname = string.substring(0, pos) || null;
289
+ parts.port = null;
290
+ } else {
291
+ t = string.substring(0, pos).split(':');
292
+ parts.hostname = t[0] || null;
293
+ parts.port = t[1] || null;
294
+ }
295
+
296
+ if (parts.hostname && string.substring(pos)[0] !== '/') {
297
+ pos++;
298
+ string = "/" + string;
299
+ }
300
+
301
+ return string.substring(pos) || '/';
302
+ };
303
+ URI.parseAuthority = function(string, parts) {
304
+ string = URI.parseUserinfo(string, parts);
305
+ return URI.parseHost(string, parts);
306
+ };
307
+ URI.parseUserinfo = function(string, parts) {
308
+ // extract username:password
309
+ var pos = string.indexOf('@'),
310
+ firstSlash = string.indexOf('/'),
311
+ t;
312
+
313
+ // authority@ must come before /path
314
+ if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) {
315
+ t = string.substring(0, pos).split(':');
316
+ parts.username = t[0] ? URI.decode(t[0]) : null;
317
+ parts.password = t[1] ? URI.decode(t[1]) : null;
318
+ string = string.substring(pos + 1);
319
+ } else {
320
+ parts.username = null;
321
+ parts.password = null;
322
+ }
323
+
324
+ return string;
325
+ };
326
+ URI.parseQuery = function(string) {
327
+ if (!string) {
328
+ return {};
329
+ }
330
+
331
+ // throw out the funky business - "?"[name"="value"&"]+
332
+ string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, '');
333
+
334
+ if (!string) {
335
+ return {};
336
+ }
337
+
338
+ var items = {},
339
+ splits = string.split('&'),
340
+ length = splits.length;
341
+
342
+ 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;
347
+
348
+ if (items[name]) {
349
+ if (typeof items[name] === "string") {
350
+ items[name] = [items[name]];
351
+ }
352
+
353
+ items[name].push(value);
354
+ } else {
355
+ items[name] = value;
356
+ }
357
+ }
358
+
359
+ return items;
360
+ };
361
+
362
+ URI.build = function(parts) {
363
+ var t = '';
364
+
365
+ if (parts.protocol) {
366
+ t += parts.protocol + ":";
367
+ }
368
+
369
+ if (!parts.urn && (t || parts.hostname)) {
370
+ t += '//';
371
+ }
372
+
373
+ t += (URI.buildAuthority(parts) || '');
374
+
375
+ if (typeof parts.path === "string") {
376
+ if (parts.path[0] !== '/' && typeof parts.hostname === "string") {
377
+ t += '/';
378
+ }
379
+
380
+ t += parts.path;
381
+ }
382
+
383
+ if (typeof parts.query === "string") {
384
+ t += '?' + parts.query;
385
+ }
386
+
387
+ if (typeof parts.fragment === "string") {
388
+ t += '#' + parts.fragment;
389
+ }
390
+ return t;
391
+ };
392
+ URI.buildHost = function(parts) {
393
+ var t = '';
394
+
395
+ if (!parts.hostname) {
396
+ return '';
397
+ } else if (URI.ip6_expression.test(parts.hostname)) {
398
+ if (parts.port) {
399
+ t += "[" + parts.hostname + "]:" + parts.port;
400
+ } else {
401
+ // don't know if we should always wrap IPv6 in []
402
+ // the RFC explicitly says SHOULD, not MUST.
403
+ t += parts.hostname;
404
+ }
405
+ } else {
406
+ t += parts.hostname;
407
+ if (parts.port) {
408
+ t += ':' + parts.port;
409
+ }
410
+ }
411
+
412
+ return t;
413
+ };
414
+ URI.buildAuthority = function(parts) {
415
+ return URI.buildUserinfo(parts) + URI.buildHost(parts);
416
+ };
417
+ URI.buildUserinfo = function(parts) {
418
+ var t = '';
419
+
420
+ if (parts.username) {
421
+ t += URI.encode(parts.username);
422
+
423
+ if (parts.password) {
424
+ t += ':' + URI.encode(parts.password);
425
+ }
426
+
427
+ t += "@";
428
+ }
429
+
430
+ return t;
431
+ };
432
+ URI.buildQuery = function(data, duplicates) {
433
+ // according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html
434
+ // being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed
435
+ // the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax!
436
+ // URI.js treats the query string as being application/x-www-form-urlencoded
437
+ // see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
438
+
439
+ var t = "";
440
+ for (var key in data) {
441
+ if (Object.hasOwnProperty.call(data, key) && key) {
442
+ if (isArray(data[key])) {
443
+ var unique = {};
444
+ for (var i = 0, length = data[key].length; i < length; i++) {
445
+ if (data[key][i] !== undefined && unique[data[key][i] + ""] === undefined) {
446
+ t += "&" + URI.buildQueryParameter(key, data[key][i]);
447
+ if (duplicates !== true) {
448
+ unique[data[key][i] + ""] = true;
449
+ }
450
+ }
451
+ }
452
+ } else if (data[key] !== undefined) {
453
+ t += '&' + URI.buildQueryParameter(key, data[key]);
454
+ }
455
+ }
456
+ }
457
+
458
+ return t.substring(1);
459
+ };
460
+ URI.buildQueryParameter = function(name, value) {
461
+ // http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded
462
+ // don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization
463
+ return URI.encodeQuery(name) + (value !== null ? "=" + URI.encodeQuery(value) : "");
464
+ };
465
+
466
+ URI.addQuery = function(data, name, value) {
467
+ if (typeof name === "object") {
468
+ for (var key in name) {
469
+ if (Object.prototype.hasOwnProperty.call(name, key)) {
470
+ URI.addQuery(data, key, name[key]);
471
+ }
472
+ }
473
+ } else if (typeof name === "string") {
474
+ if (data[name] === undefined) {
475
+ data[name] = value;
476
+ return;
477
+ } else if (typeof data[name] === "string") {
478
+ data[name] = [data[name]];
479
+ }
480
+
481
+ if (!isArray(value)) {
482
+ value = [value];
483
+ }
484
+
485
+ data[name] = data[name].concat(value);
486
+ } else {
487
+ throw new TypeError("URI.addQuery() accepts an object, string as the name parameter");
488
+ }
489
+ };
490
+ URI.removeQuery = function(data, name, value) {
491
+ if (isArray(name)) {
492
+ for (var i = 0, length = name.length; i < length; i++) {
493
+ data[name[i]] = undefined;
494
+ }
495
+ } else if (typeof name === "object") {
496
+ for (var key in name) {
497
+ if (Object.prototype.hasOwnProperty.call(name, key)) {
498
+ URI.removeQuery(data, key, name[key]);
499
+ }
500
+ }
501
+ } else if (typeof name === "string") {
502
+ if (value !== undefined) {
503
+ if (data[name] === value) {
504
+ data[name] = undefined;
505
+ } else if (isArray(data[name])) {
506
+ data[name] = filterArrayValues(data[name], value);
507
+ }
508
+ } else {
509
+ data[name] = undefined;
510
+ }
511
+ } else {
512
+ throw new TypeError("URI.addQuery() accepts an object, string as the first parameter");
513
+ }
514
+ };
515
+
516
+ URI.commonPath = function(one, two) {
517
+ var length = Math.min(one.length, two.length),
518
+ pos;
519
+
520
+ // find first non-matching character
521
+ for (pos = 0; pos < length; pos++) {
522
+ if (one[pos] !== two[pos]) {
523
+ pos--;
524
+ break;
525
+ }
526
+ }
527
+
528
+ if (pos < 1) {
529
+ return one[0] === two[0] && one[0] === '/' ? '/' : '';
530
+ }
531
+
532
+ // revert to last /
533
+ if (one[pos] !== '/') {
534
+ pos = one.substring(0, pos).lastIndexOf('/');
535
+ }
536
+
537
+ return one.substring(0, pos + 1);
538
+ };
539
+
540
+ URI.withinString = function(string, callback) {
541
+ // expression used is "gruber revised" (@gruber v2) determined to be the best solution in
542
+ // a regex sprint we did a couple of ages ago at
543
+ // * http://mathiasbynens.be/demo/url-regex
544
+ // * http://rodneyrehm.de/t/url-regex.html
545
+
546
+ return string.replace(URI.find_uri_expression, callback);
547
+ };
548
+
549
+ URI.ensureValidHostname = function(v) {
550
+ // Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986)
551
+ // they are not part of DNS and therefore ignored by URI.js
552
+
553
+ if (v.match(URI.invalid_hostname_characters)) {
554
+ // test punycode
555
+ if (!punycode) {
556
+ throw new TypeError("Hostname '" + v + "' contains characters other than [A-Z0-9.-] and Punycode.js is not available");
557
+ }
558
+
559
+ if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) {
560
+ throw new TypeError("Hostname '" + v + "' contains characters other than [A-Z0-9.-]");
561
+ }
562
+ }
563
+ };
564
+
565
+ p.build = function(deferBuild) {
566
+ if (deferBuild === true) {
567
+ this._deferred_build = true;
568
+ } else if (deferBuild === undefined || this._deferred_build) {
569
+ this._string = URI.build(this._parts);
570
+ this._deferred_build = false;
571
+ }
572
+
573
+ return this;
574
+ };
575
+
576
+ p.clone = function() {
577
+ return new URI(this);
578
+ };
579
+
580
+ p.toString = function() {
581
+ return this.build(false)._string;
582
+ };
583
+ p.valueOf = function() {
584
+ return this.toString();
585
+ };
586
+
587
+ // generate simple accessors
588
+ _parts = {protocol: 'protocol', username: 'username', password: 'password', hostname: 'hostname', port: 'port'};
589
+ generateAccessor = function(_part){
590
+ return function(v, build) {
591
+ if (v === undefined) {
592
+ return this._parts[_part] || "";
593
+ } else {
594
+ this._parts[_part] = v;
595
+ this.build(!build);
596
+ return this;
597
+ }
598
+ };
599
+ };
600
+
601
+ for (_part in _parts) {
602
+ p[_part] = generateAccessor(_parts[_part]);
603
+ }
604
+
605
+ // generate accessors with optionally prefixed input
606
+ _parts = {query: '?', fragment: '#'};
607
+ generateAccessor = function(_part, _key){
608
+ return function(v, build) {
609
+ if (v === undefined) {
610
+ return this._parts[_part] || "";
611
+ } else {
612
+ if (v !== null) {
613
+ v = v + "";
614
+ if (v[0] === _key) {
615
+ v = v.substring(1);
616
+ }
617
+ }
618
+
619
+ this._parts[_part] = v;
620
+ this.build(!build);
621
+ return this;
622
+ }
623
+ };
624
+ };
625
+
626
+ for (_part in _parts) {
627
+ p[_part] = generateAccessor(_part, _parts[_part]);
628
+ }
629
+
630
+ // generate accessors with prefixed output
631
+ _parts = {search: ['?', 'query'], hash: ['#', 'fragment']};
632
+ generateAccessor = function(_part, _key){
633
+ return function(v, build) {
634
+ var t = this[_part](v, build);
635
+ return typeof t === "string" && t.length ? (_key + t) : t;
636
+ };
637
+ };
638
+
639
+ for (_part in _parts) {
640
+ p[_part] = generateAccessor(_parts[_part][1], _parts[_part][0]);
641
+ }
642
+
643
+ p.pathname = function(v, build) {
644
+ if (v === undefined || v === true) {
645
+ var res = this._parts.path || (this._parts.urn ? '' : '/');
646
+ return v ? URI.decodePath(res) : res;
647
+ } else {
648
+ this._parts.path = v ? URI.recodePath(v) : "/";
649
+ this.build(!build);
650
+ return this;
651
+ }
652
+ };
653
+ p.path = p.pathname;
654
+ p.href = function(href, build) {
655
+ if (href === undefined) {
656
+ 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
+ }
683
+ }
684
+ } else {
685
+ throw new TypeError("invalid input");
686
+ }
687
+
688
+ this.build(!build);
689
+ return this;
690
+ }
691
+ };
692
+
693
+ // identification accessors
694
+ 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;
703
+
704
+ if (this._parts.hostname) {
705
+ relative = false;
706
+ ip4 = URI.ip4_expression.test(this._parts.hostname);
707
+ ip6 = URI.ip6_expression.test(this._parts.hostname);
708
+ ip = ip4 || ip6;
709
+ name = !ip;
710
+ sld = name && SLD && SLD.has(this._parts.hostname);
711
+ idn = name && URI.idn_expression.test(this._parts.hostname);
712
+ punycode = name && URI.punycode_expression.test(this._parts.hostname);
713
+ }
714
+
715
+ switch (what.toLowerCase()) {
716
+ case 'relative':
717
+ return relative;
718
+
719
+ case 'absolute':
720
+ return !relative;
721
+
722
+ // hostname identification
723
+ case 'domain':
724
+ case 'name':
725
+ return name;
726
+
727
+ case 'sld':
728
+ return sld;
729
+
730
+ case 'ip':
731
+ return ip;
732
+
733
+ case 'ip4':
734
+ case 'ipv4':
735
+ case 'inet4':
736
+ return ip4;
737
+
738
+ case 'ip6':
739
+ case 'ipv6':
740
+ case 'inet6':
741
+ return ip6;
742
+
743
+ case 'idn':
744
+ return idn;
745
+
746
+ case 'url':
747
+ return !this._parts.urn;
748
+
749
+ case 'urn':
750
+ return !!this._parts.urn;
751
+
752
+ case 'punycode':
753
+ return punycode;
754
+ }
755
+
756
+ return null;
757
+ };
758
+
759
+ // component specific input validation
760
+ var _protocol = p.protocol,
761
+ _port = p.port,
762
+ _hostname = p.hostname;
763
+
764
+ p.protocol = function(v, build) {
765
+ if (v !== undefined) {
766
+ if (v) {
767
+ // accept trailing ://
768
+ v = v.replace(/:(\/\/)?$/, '');
769
+
770
+ if (v.match(/[^a-zA-z0-9\.+-]/)) {
771
+ throw new TypeError("Protocol '" + v + "' contains characters other than [A-Z0-9.+-]");
772
+ }
773
+ }
774
+ }
775
+ return _protocol.call(this, v, build);
776
+ };
777
+ p.scheme = p.protocol;
778
+ p.port = function(v, build) {
779
+ if (this._parts.urn) {
780
+ return v === undefined ? '' : this;
781
+ }
782
+
783
+ if (v !== undefined) {
784
+ if (v === 0) {
785
+ v = null;
786
+ }
787
+
788
+ if (v) {
789
+ v += "";
790
+ if (v[0] === ":") {
791
+ v = v.substring(1);
792
+ }
793
+
794
+ if (v.match(/[^0-9]/)) {
795
+ throw new TypeError("Port '" + v + "' contains characters other than [0-9]");
796
+ }
797
+ }
798
+ }
799
+ return _port.call(this, v, build);
800
+ };
801
+ p.hostname = function(v, build) {
802
+ if (this._parts.urn) {
803
+ return v === undefined ? '' : this;
804
+ }
805
+
806
+ if (v !== undefined) {
807
+ var x = {};
808
+ URI.parseHost(v, x);
809
+ v = x.hostname;
810
+ }
811
+ return _hostname.call(this, v, build);
812
+ };
813
+
814
+ // combination accessors
815
+ p.host = function(v, build) {
816
+ if (this._parts.urn) {
817
+ return v === undefined ? '' : this;
818
+ }
819
+
820
+ if (v === undefined) {
821
+ return this._parts.hostname ? URI.buildHost(this._parts) : "";
822
+ } else {
823
+ URI.parseHost(v, this._parts);
824
+ this.build(!build);
825
+ return this;
826
+ }
827
+ };
828
+ p.authority = function(v, build) {
829
+ if (this._parts.urn) {
830
+ return v === undefined ? '' : this;
831
+ }
832
+
833
+ if (v === undefined) {
834
+ return this._parts.hostname ? URI.buildAuthority(this._parts) : "";
835
+ } else {
836
+ URI.parseAuthority(v, this._parts);
837
+ this.build(!build);
838
+ return this;
839
+ }
840
+ };
841
+ p.userinfo = function(v, build) {
842
+ if (this._parts.urn) {
843
+ return v === undefined ? '' : this;
844
+ }
845
+
846
+ if (v === undefined) {
847
+ if (!this._parts.username) {
848
+ return "";
849
+ }
850
+
851
+ var t = URI.buildUserinfo(this._parts);
852
+ return t.substring(0, t.length -1);
853
+ } else {
854
+ if (v[v.length-1] !== '@') {
855
+ v += '@';
856
+ }
857
+
858
+ URI.parseUserinfo(v, this._parts);
859
+ this.build(!build);
860
+ return this;
861
+ }
862
+ };
863
+
864
+ // fraction accessors
865
+ p.subdomain = function(v, build) {
866
+ if (this._parts.urn) {
867
+ return v === undefined ? '' : this;
868
+ }
869
+
870
+ // convenience, return "www" from "www.example.org"
871
+ if (v === undefined) {
872
+ if (!this._parts.hostname || this.is('IP')) {
873
+ return "";
874
+ }
875
+
876
+ // grab domain and add another segment
877
+ var end = this._parts.hostname.length - this.domain().length - 1;
878
+ return this._parts.hostname.substring(0, end) || "";
879
+ } 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));
883
+
884
+ if (v && v[v.length - 1] !== '.') {
885
+ v += ".";
886
+ }
887
+
888
+ if (v) {
889
+ URI.ensureValidHostname(v);
890
+ }
891
+
892
+ this._parts.hostname = this._parts.hostname.replace(replace, v);
893
+ this.build(!build);
894
+ return this;
895
+ }
896
+ };
897
+ p.domain = function(v, build) {
898
+ if (this._parts.urn) {
899
+ return v === undefined ? '' : this;
900
+ }
901
+
902
+ if (typeof v === 'boolean') {
903
+ build = v;
904
+ v = undefined;
905
+ }
906
+
907
+ // convenience, return "example.org" from "www.example.org"
908
+ if (v === undefined) {
909
+ if (!this._parts.hostname || this.is('IP')) {
910
+ return "";
911
+ }
912
+
913
+ // if hostname consists of 1 or 2 segments, it must be the domain
914
+ var t = this._parts.hostname.match(/\./g);
915
+ if (t && t.length < 2) {
916
+ return this._parts.hostname;
917
+ }
918
+
919
+ // grab tld and add another segment
920
+ var end = this._parts.hostname.length - this.tld(build).length - 1;
921
+ end = this._parts.hostname.lastIndexOf('.', end -1) + 1;
922
+ return this._parts.hostname.substring(end) || "";
923
+ } else {
924
+ if (!v) {
925
+ throw new TypeError("cannot set domain empty");
926
+ }
927
+
928
+ URI.ensureValidHostname(v);
929
+
930
+ if (!this._parts.hostname || this.is('IP')) {
931
+ this._parts.hostname = v;
932
+ } else {
933
+ var replace = new RegExp(escapeRegEx(this.domain()) + "$");
934
+ this._parts.hostname = this._parts.hostname.replace(replace, v);
935
+ }
936
+
937
+ this.build(!build);
938
+ return this;
939
+ }
940
+ };
941
+ p.tld = function(v, build) {
942
+ if (this._parts.urn) {
943
+ return v === undefined ? '' : this;
944
+ }
945
+
946
+ if (typeof v === 'boolean') {
947
+ build = v;
948
+ v = undefined;
949
+ }
950
+
951
+ // return "org" from "www.example.org"
952
+ if (v === undefined) {
953
+ if (!this._parts.hostname || this.is('IP')) {
954
+ return "";
955
+ }
956
+
957
+ var pos = this._parts.hostname.lastIndexOf('.'),
958
+ tld = this._parts.hostname.substring(pos + 1);
959
+
960
+ if (build !== true && SLD && SLD.list[tld.toLowerCase()]) {
961
+ return SLD.get(this._parts.hostname) || tld;
962
+ }
963
+
964
+ return tld;
965
+ } else {
966
+ var replace;
967
+ if (!v) {
968
+ throw new TypeError("cannot set TLD empty");
969
+ } else if (v.match(/[^a-zA-Z0-9-]/)) {
970
+ if (SLD && SLD.is(v)) {
971
+ replace = new RegExp(escapeRegEx(this.tld()) + "$");
972
+ this._parts.hostname = this._parts.hostname.replace(replace, v);
973
+ } else {
974
+ throw new TypeError("TLD '" + v + "' contains characters other than [A-Z0-9]");
975
+ }
976
+ } else if (!this._parts.hostname || this.is('IP')) {
977
+ throw new ReferenceError("cannot set TLD on non-domain host");
978
+ } else {
979
+ replace = new RegExp(escapeRegEx(this.tld()) + "$");
980
+ this._parts.hostname = this._parts.hostname.replace(replace, v);
981
+ }
982
+
983
+ this.build(!build);
984
+ return this;
985
+ }
986
+ };
987
+ p.directory = function(v, build) {
988
+ if (this._parts.urn) {
989
+ return v === undefined ? '' : this;
990
+ }
991
+
992
+ if (v === undefined || v === true) {
993
+ if (!this._parts.path && !this._parts.hostname) {
994
+ return '';
995
+ }
996
+
997
+ if (this._parts.path === '/') {
998
+ return '/';
999
+ }
1000
+
1001
+ var end = this._parts.path.length - this.filename().length - 1,
1002
+ res = this._parts.path.substring(0, end) || (this._parts.hostname ? "/" : "");
1003
+
1004
+ return v ? URI.decodePath(res) : res;
1005
+
1006
+ } 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));
1010
+
1011
+ // fully qualifier directories begin with a slash
1012
+ if (!this.is('relative')) {
1013
+ if (!v) {
1014
+ v = '/';
1015
+ }
1016
+
1017
+ if (v[0] !== '/') {
1018
+ v = "/" + v;
1019
+ }
1020
+ }
1021
+
1022
+ // directories always end with a slash
1023
+ if (v && v[v.length - 1] !== '/') {
1024
+ v += '/';
1025
+ }
1026
+
1027
+ v = URI.recodePath(v);
1028
+ this._parts.path = this._parts.path.replace(replace, v);
1029
+ this.build(!build);
1030
+ return this;
1031
+ }
1032
+ };
1033
+ p.filename = function(v, build) {
1034
+ if (this._parts.urn) {
1035
+ return v === undefined ? '' : this;
1036
+ }
1037
+
1038
+ if (v === undefined || v === true) {
1039
+ if (!this._parts.path || this._parts.path === '/') {
1040
+ return "";
1041
+ }
1042
+
1043
+ var pos = this._parts.path.lastIndexOf('/'),
1044
+ res = this._parts.path.substring(pos+1);
1045
+
1046
+ return v ? URI.decodePathSegment(res) : res;
1047
+ } else {
1048
+ var mutatedDirectory = false;
1049
+ if (v[0] === '/') {
1050
+ v = v.substring(1);
1051
+ }
1052
+
1053
+ if (v.match(/\.?\//)) {
1054
+ mutatedDirectory = true;
1055
+ }
1056
+
1057
+ var replace = new RegExp(escapeRegEx(this.filename()) + "$");
1058
+ v = URI.recodePath(v);
1059
+ this._parts.path = this._parts.path.replace(replace, v);
1060
+
1061
+ if (mutatedDirectory) {
1062
+ this.normalizePath(build);
1063
+ } else {
1064
+ this.build(!build);
1065
+ }
1066
+
1067
+ return this;
1068
+ }
1069
+ };
1070
+ p.suffix = function(v, build) {
1071
+ if (this._parts.urn) {
1072
+ return v === undefined ? '' : this;
1073
+ }
1074
+
1075
+ if (v === undefined || v === true) {
1076
+ if (!this._parts.path || this._parts.path === '/') {
1077
+ return "";
1078
+ }
1079
+
1080
+ var filename = this.filename(),
1081
+ pos = filename.lastIndexOf('.'),
1082
+ s, res;
1083
+
1084
+ if (pos === -1) {
1085
+ return "";
1086
+ }
1087
+
1088
+ // suffix may only contain alnum characters (yup, I made this up.)
1089
+ s = filename.substring(pos+1);
1090
+ res = (/^[a-z0-9%]+$/i).test(s) ? s : "";
1091
+ return v ? URI.decodePathSegment(res) : res;
1092
+ } else {
1093
+ if (v[0] === '.') {
1094
+ v = v.substring(1);
1095
+ }
1096
+
1097
+ var suffix = this.suffix(),
1098
+ replace;
1099
+
1100
+ if (!suffix) {
1101
+ if (!v) {
1102
+ return this;
1103
+ }
1104
+
1105
+ this._parts.path += '.' + URI.recodePath(v);
1106
+ } else if (!v) {
1107
+ replace = new RegExp(escapeRegEx("." + suffix) + "$");
1108
+ } else {
1109
+ replace = new RegExp(escapeRegEx(suffix) + "$");
1110
+ }
1111
+
1112
+ if (replace) {
1113
+ v = URI.recodePath(v);
1114
+ this._parts.path = this._parts.path.replace(replace, v);
1115
+ }
1116
+
1117
+ this.build(!build);
1118
+ return this;
1119
+ }
1120
+ };
1121
+ 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);
1126
+
1127
+ if (typeof segment !== 'number') {
1128
+ build = v;
1129
+ v = segment;
1130
+ segment = undefined;
1131
+ }
1132
+
1133
+ if (segment !== undefined && typeof segment !== 'number') {
1134
+ throw new Error("Bad segment '" + segment + "', must be 0-based integer");
1135
+ }
1136
+
1137
+ if (absolute) {
1138
+ segments.shift();
1139
+ }
1140
+
1141
+ if (segment < 0) {
1142
+ // allow negative indexes to address from the end
1143
+ segment = Math.max(segments.length + segment, 0);
1144
+ }
1145
+
1146
+ if (v === undefined) {
1147
+ return segment === undefined
1148
+ ? segments
1149
+ : segments[segment];
1150
+ } else if (segment === null || segments[segment] === undefined) {
1151
+ if (isArray(v)) {
1152
+ segments = v;
1153
+ } else if (v || (typeof v === "string" && v.length)) {
1154
+ if (segments[segments.length -1] === "") {
1155
+ // empty trailing elements have to be overwritten
1156
+ // to prefent results such as /foo//bar
1157
+ segments[segments.length -1] = v;
1158
+ } else {
1159
+ segments.push(v);
1160
+ }
1161
+ }
1162
+ } else {
1163
+ if (v || (typeof v === "string" && v.length)) {
1164
+ segments[segment] = v;
1165
+ } else {
1166
+ segments.splice(segment, 1);
1167
+ }
1168
+ }
1169
+
1170
+ if (absolute) {
1171
+ segments.unshift("");
1172
+ }
1173
+
1174
+ return this.path(segments.join(separator), build);
1175
+ };
1176
+
1177
+ // mutating query string
1178
+ var q = p.query;
1179
+ p.query = function(v, build) {
1180
+ if (v === true) {
1181
+ return URI.parseQuery(this._parts.query);
1182
+ } else if (v !== undefined && typeof v !== "string") {
1183
+ this._parts.query = URI.buildQuery(v);
1184
+ this.build(!build);
1185
+ return this;
1186
+ } else {
1187
+ return q.call(this, v, build);
1188
+ }
1189
+ };
1190
+ p.addQuery = function(name, value, build) {
1191
+ var data = URI.parseQuery(this._parts.query);
1192
+ URI.addQuery(data, name, value);
1193
+ this._parts.query = URI.buildQuery(data);
1194
+ if (typeof name !== "string") {
1195
+ build = value;
1196
+ }
1197
+
1198
+ this.build(!build);
1199
+ return this;
1200
+ };
1201
+ p.removeQuery = function(name, value, build) {
1202
+ var data = URI.parseQuery(this._parts.query);
1203
+ URI.removeQuery(data, name, value);
1204
+ this._parts.query = URI.buildQuery(data);
1205
+ if (typeof name !== "string") {
1206
+ build = value;
1207
+ }
1208
+
1209
+ this.build(!build);
1210
+ return this;
1211
+ };
1212
+ p.addSearch = p.addQuery;
1213
+ p.removeSearch = p.removeQuery;
1214
+
1215
+ // sanitizing URLs
1216
+ p.normalize = function() {
1217
+ if (this._parts.urn) {
1218
+ return this
1219
+ .normalizeProtocol(false)
1220
+ .normalizeQuery(false)
1221
+ .normalizeFragment(false)
1222
+ .build();
1223
+ }
1224
+
1225
+ return this
1226
+ .normalizeProtocol(false)
1227
+ .normalizeHostname(false)
1228
+ .normalizePort(false)
1229
+ .normalizePath(false)
1230
+ .normalizeQuery(false)
1231
+ .normalizeFragment(false)
1232
+ .build();
1233
+ };
1234
+ p.normalizeProtocol = function(build) {
1235
+ if (typeof this._parts.protocol === "string") {
1236
+ this._parts.protocol = this._parts.protocol.toLowerCase();
1237
+ this.build(!build);
1238
+ }
1239
+
1240
+ return this;
1241
+ };
1242
+ p.normalizeHostname = function(build) {
1243
+ if (this._parts.hostname) {
1244
+ if (this.is('IDN') && punycode) {
1245
+ this._parts.hostname = punycode.toASCII(this._parts.hostname);
1246
+ } else if (this.is('IPv6') && IPv6) {
1247
+ this._parts.hostname = IPv6.best(this._parts.hostname);
1248
+ }
1249
+
1250
+ this._parts.hostname = this._parts.hostname.toLowerCase();
1251
+ this.build(!build);
1252
+ }
1253
+
1254
+ return this;
1255
+ };
1256
+ p.normalizePort = function(build) {
1257
+ // remove port of it's the protocol's default
1258
+ if (typeof this._parts.protocol === "string" && this._parts.port === URI.defaultPorts[this._parts.protocol]) {
1259
+ this._parts.port = null;
1260
+ this.build(!build);
1261
+ }
1262
+
1263
+ return this;
1264
+ };
1265
+ p.normalizePath = function(build) {
1266
+ if (this._parts.urn) {
1267
+ return this;
1268
+ }
1269
+
1270
+ if (!this._parts.path || this._parts.path === '/') {
1271
+ return this;
1272
+ }
1273
+
1274
+ var _was_relative,
1275
+ _was_relative_prefix,
1276
+ _path = this._parts.path,
1277
+ _parent, _pos;
1278
+
1279
+ // handle relative paths
1280
+ if (_path[0] !== '/') {
1281
+ if (_path[0] === '.') {
1282
+ _was_relative_prefix = _path.substring(0, _path.indexOf('/'));
1283
+ }
1284
+ _was_relative = true;
1285
+ _path = '/' + _path;
1286
+ }
1287
+ // resolve simples
1288
+ _path = _path.replace(/(\/(\.\/)+)|\/{2,}/g, '/');
1289
+ // resolve parents
1290
+ while (true) {
1291
+ _parent = _path.indexOf('/../');
1292
+ if (_parent === -1) {
1293
+ // no more ../ to resolve
1294
+ break;
1295
+ } else if (_parent === 0) {
1296
+ // top level cannot be relative...
1297
+ _path = _path.substring(3);
1298
+ break;
1299
+ }
1300
+
1301
+ _pos = _path.substring(0, _parent).lastIndexOf('/');
1302
+ if (_pos === -1) {
1303
+ _pos = _parent;
1304
+ }
1305
+ _path = _path.substring(0, _pos) + _path.substring(_parent + 3);
1306
+ }
1307
+ // revert to relative
1308
+ 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
+ }
1314
+ }
1315
+
1316
+ _path = URI.recodePath(_path);
1317
+ this._parts.path = _path;
1318
+ this.build(!build);
1319
+ return this;
1320
+ };
1321
+ p.normalizePathname = p.normalizePath;
1322
+ p.normalizeQuery = function(build) {
1323
+ if (typeof this._parts.query === "string") {
1324
+ if (!this._parts.query.length) {
1325
+ this._parts.query = null;
1326
+ } else {
1327
+ this.query(URI.parseQuery(this._parts.query));
1328
+ }
1329
+
1330
+ this.build(!build);
1331
+ }
1332
+
1333
+ return this;
1334
+ };
1335
+ p.normalizeFragment = function(build) {
1336
+ if (!this._parts.fragment) {
1337
+ this._parts.fragment = null;
1338
+ this.build(!build);
1339
+ }
1340
+
1341
+ return this;
1342
+ };
1343
+ p.normalizeSearch = p.normalizeQuery;
1344
+ p.normalizeHash = p.normalizeFragment;
1345
+
1346
+ p.iso8859 = function() {
1347
+ // expect unicode input, iso8859 output
1348
+ var e = URI.encode,
1349
+ d = URI.decode;
1350
+
1351
+ URI.encode = escape;
1352
+ URI.decode = decodeURIComponent;
1353
+ this.normalize();
1354
+ URI.encode = e;
1355
+ URI.decode = d;
1356
+ return this;
1357
+ };
1358
+
1359
+ p.unicode = function() {
1360
+ // expect iso8859 input, unicode output
1361
+ var e = URI.encode,
1362
+ d = URI.decode;
1363
+
1364
+ URI.encode = strictEncodeURIComponent;
1365
+ URI.decode = unescape;
1366
+ this.normalize();
1367
+ URI.encode = e;
1368
+ URI.decode = d;
1369
+ return this;
1370
+ };
1371
+
1372
+ p.readable = function() {
1373
+ var uri = this.clone();
1374
+ // removing username, password, because they shouldn't be displayed according to RFC 3986
1375
+ uri.username("").password("").normalize();
1376
+ var t = '';
1377
+ if (uri._parts.protocol) {
1378
+ t += uri._parts.protocol + '://';
1379
+ }
1380
+
1381
+ if (uri._parts.hostname) {
1382
+ if (uri.is('punycode') && punycode) {
1383
+ t += punycode.toUnicode(uri._parts.hostname);
1384
+ if (uri._parts.port) {
1385
+ t += ":" + uri._parts.port;
1386
+ }
1387
+ } else {
1388
+ t += uri.host();
1389
+ }
1390
+ }
1391
+
1392
+ if (uri._parts.hostname && uri._parts.path && uri._parts.path[0] !== '/') {
1393
+ t += '/';
1394
+ }
1395
+
1396
+ t += uri.path(true);
1397
+ if (uri._parts.query) {
1398
+ var q = '';
1399
+ for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) {
1400
+ var kv = (qp[i] || "").split('=');
1401
+ q += '&' + URI.decodeQuery(kv[0])
1402
+ .replace(/&/g, '%26');
1403
+
1404
+ if (kv[1] !== undefined) {
1405
+ q += "=" + URI.decodeQuery(kv[1])
1406
+ .replace(/&/g, '%26');
1407
+ }
1408
+ }
1409
+ t += '?' + q.substring(1);
1410
+ }
1411
+
1412
+ t += uri.hash();
1413
+ return t;
1414
+ };
1415
+
1416
+ // resolving relative and absolute URLs
1417
+ p.absoluteTo = function(base) {
1418
+ var resolved = this.clone(),
1419
+ properties = ['protocol', 'username', 'password', 'hostname', 'port'],
1420
+ basedir, i, p;
1421
+
1422
+ if (this._parts.urn) {
1423
+ throw new Error('URNs do not have any generally defined hierachical components');
1424
+ }
1425
+
1426
+ if (this._parts.hostname) {
1427
+ return resolved;
1428
+ }
1429
+
1430
+ if (!(base instanceof URI)) {
1431
+ base = new URI(base);
1432
+ }
1433
+
1434
+ for (i = 0, p; p = properties[i]; i++) {
1435
+ resolved._parts[p] = base._parts[p];
1436
+ }
1437
+
1438
+ properties = ['query', 'path'];
1439
+ for (i = 0, p; p = properties[i]; i++) {
1440
+ if (!resolved._parts[p] && base._parts[p]) {
1441
+ resolved._parts[p] = base._parts[p];
1442
+ }
1443
+ }
1444
+
1445
+ if (resolved.path()[0] !== '/') {
1446
+ basedir = base.directory();
1447
+ resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path;
1448
+ resolved.normalizePath();
1449
+ }
1450
+
1451
+ resolved.build();
1452
+ return resolved;
1453
+ };
1454
+ p.relativeTo = function(base) {
1455
+ var relative = this.clone(),
1456
+ properties = ['protocol', 'username', 'password', 'hostname', 'port'],
1457
+ common,
1458
+ _base;
1459
+
1460
+ if (this._parts.urn) {
1461
+ throw new Error('URNs do not have any generally defined hierachical components');
1462
+ }
1463
+
1464
+ if (!(base instanceof URI)) {
1465
+ base = new URI(base);
1466
+ }
1467
+
1468
+ if (this.path()[0] !== '/' || base.path()[0] !== '/') {
1469
+ throw new Error('Cannot calculate common path from non-relative URLs');
1470
+ }
1471
+
1472
+ common = URI.commonPath(relative.path(), base.path());
1473
+ _base = base.directory();
1474
+
1475
+ for (var i = 0, p; p = properties[i]; i++) {
1476
+ relative._parts[p] = null;
1477
+ }
1478
+
1479
+ if (!common || common === '/') {
1480
+ return relative;
1481
+ }
1482
+
1483
+ 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 += '../';
1492
+ }
1493
+
1494
+ relative._parts.path = relative._parts.path.replace(_common, parents);
1495
+ }
1496
+
1497
+ relative.build();
1498
+ return relative;
1499
+ };
1500
+
1501
+ // comparing URIs
1502
+ 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;
1511
+
1512
+ one.normalize();
1513
+ two.normalize();
1514
+
1515
+ // exact match
1516
+ if (one.toString() === two.toString()) {
1517
+ return true;
1518
+ }
1519
+
1520
+ // extract query string
1521
+ one_query = one.query();
1522
+ two_query = two.query();
1523
+ one.query("");
1524
+ two.query("");
1525
+
1526
+ // definitely not equal if not even non-query parts match
1527
+ if (one.toString() !== two.toString()) {
1528
+ return false;
1529
+ }
1530
+
1531
+ // query parameters have the same length, even if they're permutated
1532
+ if (one_query.length !== two_query.length) {
1533
+ return false;
1534
+ }
1535
+
1536
+ one_map = URI.parseQuery(one_query);
1537
+ two_map = URI.parseQuery(two_query);
1538
+
1539
+ for (key in one_map) {
1540
+ if (Object.prototype.hasOwnProperty.call(one_map, key)) {
1541
+ if (!isArray(one_map[key])) {
1542
+ if (one_map[key] !== two_map[key]) {
1543
+ return false;
1544
+ }
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
+ }
1563
+ }
1564
+
1565
+ checked[key] = true;
1566
+ }
1567
+ }
1568
+
1569
+ for (key in two_map) {
1570
+ if (Object.prototype.hasOwnProperty.call(two_map, key)) {
1571
+ if (!checked[key]) {
1572
+ // two contains a parameter not present in one
1573
+ return false;
1574
+ }
1575
+ }
1576
+ }
1577
+
1578
+ return true;
1579
+ };
1580
+
1581
+ (typeof module !== 'undefined' && module.exports
1582
+ ? module.exports = URI
1583
+ : window.URI = URI
1584
+ );
1585
+
1586
+ })();