[SignalingServer] Optimize dependent modules
[platform/framework/web/wrtjs.git] / device_home / node_modules / express / node_modules / url / url.js
1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 'use strict';
23
24 var punycode = require('punycode');
25 var util = require('./util');
26
27 exports.parse = urlParse;
28 exports.resolve = urlResolve;
29 exports.resolveObject = urlResolveObject;
30 exports.format = urlFormat;
31
32 exports.Url = Url;
33
34 function Url() {
35   this.protocol = null;
36   this.slashes = null;
37   this.auth = null;
38   this.host = null;
39   this.port = null;
40   this.hostname = null;
41   this.hash = null;
42   this.search = null;
43   this.query = null;
44   this.pathname = null;
45   this.path = null;
46   this.href = null;
47 }
48
49 // Reference: RFC 3986, RFC 1808, RFC 2396
50
51 // define these here so at least they only have to be
52 // compiled once on the first module load.
53 var protocolPattern = /^([a-z0-9.+-]+:)/i,
54     portPattern = /:[0-9]*$/,
55
56     // Special case for a simple path URL
57     simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,
58
59     // RFC 2396: characters reserved for delimiting URLs.
60     // We actually just auto-escape these.
61     delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'],
62
63     // RFC 2396: characters not allowed for various reasons.
64     unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims),
65
66     // Allowed by RFCs, but cause of XSS attacks.  Always escape these.
67     autoEscape = ['\''].concat(unwise),
68     // Characters that are never ever allowed in a hostname.
69     // Note that any invalid chars are also handled, but these
70     // are the ones that are *expected* to be seen, so we fast-path
71     // them.
72     nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape),
73     hostEndingChars = ['/', '?', '#'],
74     hostnameMaxLen = 255,
75     hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/,
76     hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/,
77     // protocols that can allow "unsafe" and "unwise" chars.
78     unsafeProtocol = {
79       'javascript': true,
80       'javascript:': true
81     },
82     // protocols that never have a hostname.
83     hostlessProtocol = {
84       'javascript': true,
85       'javascript:': true
86     },
87     // protocols that always contain a // bit.
88     slashedProtocol = {
89       'http': true,
90       'https': true,
91       'ftp': true,
92       'gopher': true,
93       'file': true,
94       'http:': true,
95       'https:': true,
96       'ftp:': true,
97       'gopher:': true,
98       'file:': true
99     },
100     querystring = require('querystring');
101
102 function urlParse(url, parseQueryString, slashesDenoteHost) {
103   if (url && util.isObject(url) && url instanceof Url) return url;
104
105   var u = new Url;
106   u.parse(url, parseQueryString, slashesDenoteHost);
107   return u;
108 }
109
110 Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
111   if (!util.isString(url)) {
112     throw new TypeError("Parameter 'url' must be a string, not " + typeof url);
113   }
114
115   // Copy chrome, IE, opera backslash-handling behavior.
116   // Back slashes before the query string get converted to forward slashes
117   // See: https://code.google.com/p/chromium/issues/detail?id=25916
118   var queryIndex = url.indexOf('?'),
119       splitter =
120           (queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#',
121       uSplit = url.split(splitter),
122       slashRegex = /\\/g;
123   uSplit[0] = uSplit[0].replace(slashRegex, '/');
124   url = uSplit.join(splitter);
125
126   var rest = url;
127
128   // trim before proceeding.
129   // This is to support parse stuff like "  http://foo.com  \n"
130   rest = rest.trim();
131
132   if (!slashesDenoteHost && url.split('#').length === 1) {
133     // Try fast path regexp
134     var simplePath = simplePathPattern.exec(rest);
135     if (simplePath) {
136       this.path = rest;
137       this.href = rest;
138       this.pathname = simplePath[1];
139       if (simplePath[2]) {
140         this.search = simplePath[2];
141         if (parseQueryString) {
142           this.query = querystring.parse(this.search.substr(1));
143         } else {
144           this.query = this.search.substr(1);
145         }
146       } else if (parseQueryString) {
147         this.search = '';
148         this.query = {};
149       }
150       return this;
151     }
152   }
153
154   var proto = protocolPattern.exec(rest);
155   if (proto) {
156     proto = proto[0];
157     var lowerProto = proto.toLowerCase();
158     this.protocol = lowerProto;
159     rest = rest.substr(proto.length);
160   }
161
162   // figure out if it's got a host
163   // user@server is *always* interpreted as a hostname, and url
164   // resolution will treat //foo/bar as host=foo,path=bar because that's
165   // how the browser resolves relative URLs.
166   if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {
167     var slashes = rest.substr(0, 2) === '//';
168     if (slashes && !(proto && hostlessProtocol[proto])) {
169       rest = rest.substr(2);
170       this.slashes = true;
171     }
172   }
173
174   if (!hostlessProtocol[proto] &&
175       (slashes || (proto && !slashedProtocol[proto]))) {
176
177     // there's a hostname.
178     // the first instance of /, ?, ;, or # ends the host.
179     //
180     // If there is an @ in the hostname, then non-host chars *are* allowed
181     // to the left of the last @ sign, unless some host-ending character
182     // comes *before* the @-sign.
183     // URLs are obnoxious.
184     //
185     // ex:
186     // http://a@b@c/ => user:a@b host:c
187     // http://a@b?@c => user:a host:c path:/?@c
188
189     // v0.12 TODO(isaacs): This is not quite how Chrome does things.
190     // Review our test case against browsers more comprehensively.
191
192     // find the first instance of any hostEndingChars
193     var hostEnd = -1;
194     for (var i = 0; i < hostEndingChars.length; i++) {
195       var hec = rest.indexOf(hostEndingChars[i]);
196       if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
197         hostEnd = hec;
198     }
199
200     // at this point, either we have an explicit point where the
201     // auth portion cannot go past, or the last @ char is the decider.
202     var auth, atSign;
203     if (hostEnd === -1) {
204       // atSign can be anywhere.
205       atSign = rest.lastIndexOf('@');
206     } else {
207       // atSign must be in auth portion.
208       // http://a@b/c@d => host:b auth:a path:/c@d
209       atSign = rest.lastIndexOf('@', hostEnd);
210     }
211
212     // Now we have a portion which is definitely the auth.
213     // Pull that off.
214     if (atSign !== -1) {
215       auth = rest.slice(0, atSign);
216       rest = rest.slice(atSign + 1);
217       this.auth = decodeURIComponent(auth);
218     }
219
220     // the host is the remaining to the left of the first non-host char
221     hostEnd = -1;
222     for (var i = 0; i < nonHostChars.length; i++) {
223       var hec = rest.indexOf(nonHostChars[i]);
224       if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
225         hostEnd = hec;
226     }
227     // if we still have not hit it, then the entire thing is a host.
228     if (hostEnd === -1)
229       hostEnd = rest.length;
230
231     this.host = rest.slice(0, hostEnd);
232     rest = rest.slice(hostEnd);
233
234     // pull out port.
235     this.parseHost();
236
237     // we've indicated that there is a hostname,
238     // so even if it's empty, it has to be present.
239     this.hostname = this.hostname || '';
240
241     // if hostname begins with [ and ends with ]
242     // assume that it's an IPv6 address.
243     var ipv6Hostname = this.hostname[0] === '[' &&
244         this.hostname[this.hostname.length - 1] === ']';
245
246     // validate a little.
247     if (!ipv6Hostname) {
248       var hostparts = this.hostname.split(/\./);
249       for (var i = 0, l = hostparts.length; i < l; i++) {
250         var part = hostparts[i];
251         if (!part) continue;
252         if (!part.match(hostnamePartPattern)) {
253           var newpart = '';
254           for (var j = 0, k = part.length; j < k; j++) {
255             if (part.charCodeAt(j) > 127) {
256               // we replace non-ASCII char with a temporary placeholder
257               // we need this to make sure size of hostname is not
258               // broken by replacing non-ASCII by nothing
259               newpart += 'x';
260             } else {
261               newpart += part[j];
262             }
263           }
264           // we test again with ASCII char only
265           if (!newpart.match(hostnamePartPattern)) {
266             var validParts = hostparts.slice(0, i);
267             var notHost = hostparts.slice(i + 1);
268             var bit = part.match(hostnamePartStart);
269             if (bit) {
270               validParts.push(bit[1]);
271               notHost.unshift(bit[2]);
272             }
273             if (notHost.length) {
274               rest = '/' + notHost.join('.') + rest;
275             }
276             this.hostname = validParts.join('.');
277             break;
278           }
279         }
280       }
281     }
282
283     if (this.hostname.length > hostnameMaxLen) {
284       this.hostname = '';
285     } else {
286       // hostnames are always lower case.
287       this.hostname = this.hostname.toLowerCase();
288     }
289
290     if (!ipv6Hostname) {
291       // IDNA Support: Returns a punycoded representation of "domain".
292       // It only converts parts of the domain name that
293       // have non-ASCII characters, i.e. it doesn't matter if
294       // you call it with a domain that already is ASCII-only.
295       this.hostname = punycode.toASCII(this.hostname);
296     }
297
298     var p = this.port ? ':' + this.port : '';
299     var h = this.hostname || '';
300     this.host = h + p;
301     this.href += this.host;
302
303     // strip [ and ] from the hostname
304     // the host field still retains them, though
305     if (ipv6Hostname) {
306       this.hostname = this.hostname.substr(1, this.hostname.length - 2);
307       if (rest[0] !== '/') {
308         rest = '/' + rest;
309       }
310     }
311   }
312
313   // now rest is set to the post-host stuff.
314   // chop off any delim chars.
315   if (!unsafeProtocol[lowerProto]) {
316
317     // First, make 100% sure that any "autoEscape" chars get
318     // escaped, even if encodeURIComponent doesn't think they
319     // need to be.
320     for (var i = 0, l = autoEscape.length; i < l; i++) {
321       var ae = autoEscape[i];
322       if (rest.indexOf(ae) === -1)
323         continue;
324       var esc = encodeURIComponent(ae);
325       if (esc === ae) {
326         esc = escape(ae);
327       }
328       rest = rest.split(ae).join(esc);
329     }
330   }
331
332
333   // chop off from the tail first.
334   var hash = rest.indexOf('#');
335   if (hash !== -1) {
336     // got a fragment string.
337     this.hash = rest.substr(hash);
338     rest = rest.slice(0, hash);
339   }
340   var qm = rest.indexOf('?');
341   if (qm !== -1) {
342     this.search = rest.substr(qm);
343     this.query = rest.substr(qm + 1);
344     if (parseQueryString) {
345       this.query = querystring.parse(this.query);
346     }
347     rest = rest.slice(0, qm);
348   } else if (parseQueryString) {
349     // no query string, but parseQueryString still requested
350     this.search = '';
351     this.query = {};
352   }
353   if (rest) this.pathname = rest;
354   if (slashedProtocol[lowerProto] &&
355       this.hostname && !this.pathname) {
356     this.pathname = '/';
357   }
358
359   //to support http.request
360   if (this.pathname || this.search) {
361     var p = this.pathname || '';
362     var s = this.search || '';
363     this.path = p + s;
364   }
365
366   // finally, reconstruct the href based on what has been validated.
367   this.href = this.format();
368   return this;
369 };
370
371 // format a parsed object into a url string
372 function urlFormat(obj) {
373   // ensure it's an object, and not a string url.
374   // If it's an obj, this is a no-op.
375   // this way, you can call url_format() on strings
376   // to clean up potentially wonky urls.
377   if (util.isString(obj)) obj = urlParse(obj);
378   if (!(obj instanceof Url)) return Url.prototype.format.call(obj);
379   return obj.format();
380 }
381
382 Url.prototype.format = function() {
383   var auth = this.auth || '';
384   if (auth) {
385     auth = encodeURIComponent(auth);
386     auth = auth.replace(/%3A/i, ':');
387     auth += '@';
388   }
389
390   var protocol = this.protocol || '',
391       pathname = this.pathname || '',
392       hash = this.hash || '',
393       host = false,
394       query = '';
395
396   if (this.host) {
397     host = auth + this.host;
398   } else if (this.hostname) {
399     host = auth + (this.hostname.indexOf(':') === -1 ?
400         this.hostname :
401         '[' + this.hostname + ']');
402     if (this.port) {
403       host += ':' + this.port;
404     }
405   }
406
407   if (this.query &&
408       util.isObject(this.query) &&
409       Object.keys(this.query).length) {
410     query = querystring.stringify(this.query);
411   }
412
413   var search = this.search || (query && ('?' + query)) || '';
414
415   if (protocol && protocol.substr(-1) !== ':') protocol += ':';
416
417   // only the slashedProtocols get the //.  Not mailto:, xmpp:, etc.
418   // unless they had them to begin with.
419   if (this.slashes ||
420       (!protocol || slashedProtocol[protocol]) && host !== false) {
421     host = '//' + (host || '');
422     if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname;
423   } else if (!host) {
424     host = '';
425   }
426
427   if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
428   if (search && search.charAt(0) !== '?') search = '?' + search;
429
430   pathname = pathname.replace(/[?#]/g, function(match) {
431     return encodeURIComponent(match);
432   });
433   search = search.replace('#', '%23');
434
435   return protocol + host + pathname + search + hash;
436 };
437
438 function urlResolve(source, relative) {
439   return urlParse(source, false, true).resolve(relative);
440 }
441
442 Url.prototype.resolve = function(relative) {
443   return this.resolveObject(urlParse(relative, false, true)).format();
444 };
445
446 function urlResolveObject(source, relative) {
447   if (!source) return relative;
448   return urlParse(source, false, true).resolveObject(relative);
449 }
450
451 Url.prototype.resolveObject = function(relative) {
452   if (util.isString(relative)) {
453     var rel = new Url();
454     rel.parse(relative, false, true);
455     relative = rel;
456   }
457
458   var result = new Url();
459   var tkeys = Object.keys(this);
460   for (var tk = 0; tk < tkeys.length; tk++) {
461     var tkey = tkeys[tk];
462     result[tkey] = this[tkey];
463   }
464
465   // hash is always overridden, no matter what.
466   // even href="" will remove it.
467   result.hash = relative.hash;
468
469   // if the relative url is empty, then there's nothing left to do here.
470   if (relative.href === '') {
471     result.href = result.format();
472     return result;
473   }
474
475   // hrefs like //foo/bar always cut to the protocol.
476   if (relative.slashes && !relative.protocol) {
477     // take everything except the protocol from relative
478     var rkeys = Object.keys(relative);
479     for (var rk = 0; rk < rkeys.length; rk++) {
480       var rkey = rkeys[rk];
481       if (rkey !== 'protocol')
482         result[rkey] = relative[rkey];
483     }
484
485     //urlParse appends trailing / to urls like http://www.example.com
486     if (slashedProtocol[result.protocol] &&
487         result.hostname && !result.pathname) {
488       result.path = result.pathname = '/';
489     }
490
491     result.href = result.format();
492     return result;
493   }
494
495   if (relative.protocol && relative.protocol !== result.protocol) {
496     // if it's a known url protocol, then changing
497     // the protocol does weird things
498     // first, if it's not file:, then we MUST have a host,
499     // and if there was a path
500     // to begin with, then we MUST have a path.
501     // if it is file:, then the host is dropped,
502     // because that's known to be hostless.
503     // anything else is assumed to be absolute.
504     if (!slashedProtocol[relative.protocol]) {
505       var keys = Object.keys(relative);
506       for (var v = 0; v < keys.length; v++) {
507         var k = keys[v];
508         result[k] = relative[k];
509       }
510       result.href = result.format();
511       return result;
512     }
513
514     result.protocol = relative.protocol;
515     if (!relative.host && !hostlessProtocol[relative.protocol]) {
516       var relPath = (relative.pathname || '').split('/');
517       while (relPath.length && !(relative.host = relPath.shift()));
518       if (!relative.host) relative.host = '';
519       if (!relative.hostname) relative.hostname = '';
520       if (relPath[0] !== '') relPath.unshift('');
521       if (relPath.length < 2) relPath.unshift('');
522       result.pathname = relPath.join('/');
523     } else {
524       result.pathname = relative.pathname;
525     }
526     result.search = relative.search;
527     result.query = relative.query;
528     result.host = relative.host || '';
529     result.auth = relative.auth;
530     result.hostname = relative.hostname || relative.host;
531     result.port = relative.port;
532     // to support http.request
533     if (result.pathname || result.search) {
534       var p = result.pathname || '';
535       var s = result.search || '';
536       result.path = p + s;
537     }
538     result.slashes = result.slashes || relative.slashes;
539     result.href = result.format();
540     return result;
541   }
542
543   var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'),
544       isRelAbs = (
545           relative.host ||
546           relative.pathname && relative.pathname.charAt(0) === '/'
547       ),
548       mustEndAbs = (isRelAbs || isSourceAbs ||
549                     (result.host && relative.pathname)),
550       removeAllDots = mustEndAbs,
551       srcPath = result.pathname && result.pathname.split('/') || [],
552       relPath = relative.pathname && relative.pathname.split('/') || [],
553       psychotic = result.protocol && !slashedProtocol[result.protocol];
554
555   // if the url is a non-slashed url, then relative
556   // links like ../.. should be able
557   // to crawl up to the hostname, as well.  This is strange.
558   // result.protocol has already been set by now.
559   // Later on, put the first path part into the host field.
560   if (psychotic) {
561     result.hostname = '';
562     result.port = null;
563     if (result.host) {
564       if (srcPath[0] === '') srcPath[0] = result.host;
565       else srcPath.unshift(result.host);
566     }
567     result.host = '';
568     if (relative.protocol) {
569       relative.hostname = null;
570       relative.port = null;
571       if (relative.host) {
572         if (relPath[0] === '') relPath[0] = relative.host;
573         else relPath.unshift(relative.host);
574       }
575       relative.host = null;
576     }
577     mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === '');
578   }
579
580   if (isRelAbs) {
581     // it's absolute.
582     result.host = (relative.host || relative.host === '') ?
583                   relative.host : result.host;
584     result.hostname = (relative.hostname || relative.hostname === '') ?
585                       relative.hostname : result.hostname;
586     result.search = relative.search;
587     result.query = relative.query;
588     srcPath = relPath;
589     // fall through to the dot-handling below.
590   } else if (relPath.length) {
591     // it's relative
592     // throw away the existing file, and take the new path instead.
593     if (!srcPath) srcPath = [];
594     srcPath.pop();
595     srcPath = srcPath.concat(relPath);
596     result.search = relative.search;
597     result.query = relative.query;
598   } else if (!util.isNullOrUndefined(relative.search)) {
599     // just pull out the search.
600     // like href='?foo'.
601     // Put this after the other two cases because it simplifies the booleans
602     if (psychotic) {
603       result.hostname = result.host = srcPath.shift();
604       //occationaly the auth can get stuck only in host
605       //this especially happens in cases like
606       //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
607       var authInHost = result.host && result.host.indexOf('@') > 0 ?
608                        result.host.split('@') : false;
609       if (authInHost) {
610         result.auth = authInHost.shift();
611         result.host = result.hostname = authInHost.shift();
612       }
613     }
614     result.search = relative.search;
615     result.query = relative.query;
616     //to support http.request
617     if (!util.isNull(result.pathname) || !util.isNull(result.search)) {
618       result.path = (result.pathname ? result.pathname : '') +
619                     (result.search ? result.search : '');
620     }
621     result.href = result.format();
622     return result;
623   }
624
625   if (!srcPath.length) {
626     // no path at all.  easy.
627     // we've already handled the other stuff above.
628     result.pathname = null;
629     //to support http.request
630     if (result.search) {
631       result.path = '/' + result.search;
632     } else {
633       result.path = null;
634     }
635     result.href = result.format();
636     return result;
637   }
638
639   // if a url ENDs in . or .., then it must get a trailing slash.
640   // however, if it ends in anything else non-slashy,
641   // then it must NOT get a trailing slash.
642   var last = srcPath.slice(-1)[0];
643   var hasTrailingSlash = (
644       (result.host || relative.host || srcPath.length > 1) &&
645       (last === '.' || last === '..') || last === '');
646
647   // strip single dots, resolve double dots to parent dir
648   // if the path tries to go above the root, `up` ends up > 0
649   var up = 0;
650   for (var i = srcPath.length; i >= 0; i--) {
651     last = srcPath[i];
652     if (last === '.') {
653       srcPath.splice(i, 1);
654     } else if (last === '..') {
655       srcPath.splice(i, 1);
656       up++;
657     } else if (up) {
658       srcPath.splice(i, 1);
659       up--;
660     }
661   }
662
663   // if the path is allowed to go above the root, restore leading ..s
664   if (!mustEndAbs && !removeAllDots) {
665     for (; up--; up) {
666       srcPath.unshift('..');
667     }
668   }
669
670   if (mustEndAbs && srcPath[0] !== '' &&
671       (!srcPath[0] || srcPath[0].charAt(0) !== '/')) {
672     srcPath.unshift('');
673   }
674
675   if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) {
676     srcPath.push('');
677   }
678
679   var isAbsolute = srcPath[0] === '' ||
680       (srcPath[0] && srcPath[0].charAt(0) === '/');
681
682   // put the host back
683   if (psychotic) {
684     result.hostname = result.host = isAbsolute ? '' :
685                                     srcPath.length ? srcPath.shift() : '';
686     //occationaly the auth can get stuck only in host
687     //this especially happens in cases like
688     //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
689     var authInHost = result.host && result.host.indexOf('@') > 0 ?
690                      result.host.split('@') : false;
691     if (authInHost) {
692       result.auth = authInHost.shift();
693       result.host = result.hostname = authInHost.shift();
694     }
695   }
696
697   mustEndAbs = mustEndAbs || (result.host && srcPath.length);
698
699   if (mustEndAbs && !isAbsolute) {
700     srcPath.unshift('');
701   }
702
703   if (!srcPath.length) {
704     result.pathname = null;
705     result.path = null;
706   } else {
707     result.pathname = srcPath.join('/');
708   }
709
710   //to support request.http
711   if (!util.isNull(result.pathname) || !util.isNull(result.search)) {
712     result.path = (result.pathname ? result.pathname : '') +
713                   (result.search ? result.search : '');
714   }
715   result.auth = relative.auth || result.auth;
716   result.slashes = result.slashes || relative.slashes;
717   result.href = result.format();
718   return result;
719 };
720
721 Url.prototype.parseHost = function() {
722   var host = this.host;
723   var port = portPattern.exec(host);
724   if (port) {
725     port = port[0];
726     if (port !== ':') {
727       this.port = port.substr(1);
728     }
729     host = host.substr(0, host.length - port.length);
730   }
731   if (host) this.hostname = host;
732 };