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