url.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  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. var punycode = require('punycode');
  22. exports.parse = urlParse;
  23. exports.resolve = urlResolve;
  24. exports.resolveObject = urlResolveObject;
  25. exports.format = urlFormat;
  26. exports.Url = Url;
  27. function Url() {
  28. this.protocol = null;
  29. this.slashes = null;
  30. this.auth = null;
  31. this.host = null;
  32. this.port = null;
  33. this.hostname = null;
  34. this.hash = null;
  35. this.search = null;
  36. this.query = null;
  37. this.pathname = null;
  38. this.path = null;
  39. this.href = null;
  40. }
  41. // Reference: RFC 3986, RFC 1808, RFC 2396
  42. // define these here so at least they only have to be
  43. // compiled once on the first module load.
  44. var protocolPattern = /^([a-z0-9.+-]+:)/i,
  45. portPattern = /:[0-9]*$/,
  46. // RFC 2396: characters reserved for delimiting URLs.
  47. // We actually just auto-escape these.
  48. delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'],
  49. // RFC 2396: characters not allowed for various reasons.
  50. unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims),
  51. // Allowed by RFCs, but cause of XSS attacks. Always escape these.
  52. autoEscape = ['\''].concat(unwise),
  53. // Characters that are never ever allowed in a hostname.
  54. // Note that any invalid chars are also handled, but these
  55. // are the ones that are *expected* to be seen, so we fast-path
  56. // them.
  57. nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape),
  58. hostEndingChars = ['/', '?', '#'],
  59. hostnameMaxLen = 255,
  60. hostnamePartPattern = /^[a-z0-9A-Z_-]{0,63}$/,
  61. hostnamePartStart = /^([a-z0-9A-Z_-]{0,63})(.*)$/,
  62. // protocols that can allow "unsafe" and "unwise" chars.
  63. unsafeProtocol = {
  64. 'javascript': true,
  65. 'javascript:': true
  66. },
  67. // protocols that never have a hostname.
  68. hostlessProtocol = {
  69. 'javascript': true,
  70. 'javascript:': true
  71. },
  72. // protocols that always contain a // bit.
  73. slashedProtocol = {
  74. 'http': true,
  75. 'https': true,
  76. 'ftp': true,
  77. 'gopher': true,
  78. 'file': true,
  79. 'http:': true,
  80. 'https:': true,
  81. 'ftp:': true,
  82. 'gopher:': true,
  83. 'file:': true
  84. },
  85. querystring = require('querystring');
  86. function urlParse(url, parseQueryString, slashesDenoteHost) {
  87. if (url && isObject(url) && url instanceof Url) return url;
  88. var u = new Url;
  89. u.parse(url, parseQueryString, slashesDenoteHost);
  90. return u;
  91. }
  92. Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
  93. if (!isString(url)) {
  94. throw new TypeError("Parameter 'url' must be a string, not " + typeof url);
  95. }
  96. var rest = url;
  97. // trim before proceeding.
  98. // This is to support parse stuff like " http://foo.com \n"
  99. rest = rest.trim();
  100. var proto = protocolPattern.exec(rest);
  101. if (proto) {
  102. proto = proto[0];
  103. var lowerProto = proto.toLowerCase();
  104. this.protocol = lowerProto;
  105. rest = rest.substr(proto.length);
  106. }
  107. // figure out if it's got a host
  108. // user@server is *always* interpreted as a hostname, and url
  109. // resolution will treat //foo/bar as host=foo,path=bar because that's
  110. // how the browser resolves relative URLs.
  111. if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {
  112. var slashes = rest.substr(0, 2) === '//';
  113. if (slashes && !(proto && hostlessProtocol[proto])) {
  114. rest = rest.substr(2);
  115. this.slashes = true;
  116. }
  117. }
  118. if (!hostlessProtocol[proto] &&
  119. (slashes || (proto && !slashedProtocol[proto]))) {
  120. // there's a hostname.
  121. // the first instance of /, ?, ;, or # ends the host.
  122. //
  123. // If there is an @ in the hostname, then non-host chars *are* allowed
  124. // to the left of the last @ sign, unless some host-ending character
  125. // comes *before* the @-sign.
  126. // URLs are obnoxious.
  127. //
  128. // ex:
  129. // http://a@b@c/ => user:a@b host:c
  130. // http://a@b?@c => user:a host:c path:/?@c
  131. // v0.12 TODO(isaacs): This is not quite how Chrome does things.
  132. // Review our test case against browsers more comprehensively.
  133. // find the first instance of any hostEndingChars
  134. var hostEnd = -1;
  135. for (var i = 0; i < hostEndingChars.length; i++) {
  136. var hec = rest.indexOf(hostEndingChars[i]);
  137. if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
  138. hostEnd = hec;
  139. }
  140. // at this point, either we have an explicit point where the
  141. // auth portion cannot go past, or the last @ char is the decider.
  142. var auth, atSign;
  143. if (hostEnd === -1) {
  144. // atSign can be anywhere.
  145. atSign = rest.lastIndexOf('@');
  146. } else {
  147. // atSign must be in auth portion.
  148. // http://a@b/c@d => host:b auth:a path:/c@d
  149. atSign = rest.lastIndexOf('@', hostEnd);
  150. }
  151. // Now we have a portion which is definitely the auth.
  152. // Pull that off.
  153. if (atSign !== -1) {
  154. auth = rest.slice(0, atSign);
  155. rest = rest.slice(atSign + 1);
  156. this.auth = decodeURIComponent(auth);
  157. }
  158. // the host is the remaining to the left of the first non-host char
  159. hostEnd = -1;
  160. for (var i = 0; i < nonHostChars.length; i++) {
  161. var hec = rest.indexOf(nonHostChars[i]);
  162. if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
  163. hostEnd = hec;
  164. }
  165. // if we still have not hit it, then the entire thing is a host.
  166. if (hostEnd === -1)
  167. hostEnd = rest.length;
  168. this.host = rest.slice(0, hostEnd);
  169. rest = rest.slice(hostEnd);
  170. // pull out port.
  171. this.parseHost();
  172. // we've indicated that there is a hostname,
  173. // so even if it's empty, it has to be present.
  174. this.hostname = this.hostname || '';
  175. // if hostname begins with [ and ends with ]
  176. // assume that it's an IPv6 address.
  177. var ipv6Hostname = this.hostname[0] === '[' &&
  178. this.hostname[this.hostname.length - 1] === ']';
  179. // validate a little.
  180. if (!ipv6Hostname) {
  181. var hostparts = this.hostname.split(/\./);
  182. for (var i = 0, l = hostparts.length; i < l; i++) {
  183. var part = hostparts[i];
  184. if (!part) continue;
  185. if (!part.match(hostnamePartPattern)) {
  186. var newpart = '';
  187. for (var j = 0, k = part.length; j < k; j++) {
  188. if (part.charCodeAt(j) > 127) {
  189. // we replace non-ASCII char with a temporary placeholder
  190. // we need this to make sure size of hostname is not
  191. // broken by replacing non-ASCII by nothing
  192. newpart += 'x';
  193. } else {
  194. newpart += part[j];
  195. }
  196. }
  197. // we test again with ASCII char only
  198. if (!newpart.match(hostnamePartPattern)) {
  199. var validParts = hostparts.slice(0, i);
  200. var notHost = hostparts.slice(i + 1);
  201. var bit = part.match(hostnamePartStart);
  202. if (bit) {
  203. validParts.push(bit[1]);
  204. notHost.unshift(bit[2]);
  205. }
  206. if (notHost.length) {
  207. rest = '/' + notHost.join('.') + rest;
  208. }
  209. this.hostname = validParts.join('.');
  210. break;
  211. }
  212. }
  213. }
  214. }
  215. if (this.hostname.length > hostnameMaxLen) {
  216. this.hostname = '';
  217. } else {
  218. // hostnames are always lower case.
  219. this.hostname = this.hostname.toLowerCase();
  220. }
  221. if (!ipv6Hostname) {
  222. // IDNA Support: Returns a puny coded representation of "domain".
  223. // It only converts the part of the domain name that
  224. // has non ASCII characters. I.e. it dosent matter if
  225. // you call it with a domain that already is in ASCII.
  226. var domainArray = this.hostname.split('.');
  227. var newOut = [];
  228. for (var i = 0; i < domainArray.length; ++i) {
  229. var s = domainArray[i];
  230. newOut.push(s.match(/[^A-Za-z0-9_-]/) ?
  231. 'xn--' + punycode.encode(s) : s);
  232. }
  233. this.hostname = newOut.join('.');
  234. }
  235. var p = this.port ? ':' + this.port : '';
  236. var h = this.hostname || '';
  237. this.host = h + p;
  238. this.href += this.host;
  239. // strip [ and ] from the hostname
  240. // the host field still retains them, though
  241. if (ipv6Hostname) {
  242. this.hostname = this.hostname.substr(1, this.hostname.length - 2);
  243. if (rest[0] !== '/') {
  244. rest = '/' + rest;
  245. }
  246. }
  247. }
  248. // now rest is set to the post-host stuff.
  249. // chop off any delim chars.
  250. if (!unsafeProtocol[lowerProto]) {
  251. // First, make 100% sure that any "autoEscape" chars get
  252. // escaped, even if encodeURIComponent doesn't think they
  253. // need to be.
  254. for (var i = 0, l = autoEscape.length; i < l; i++) {
  255. var ae = autoEscape[i];
  256. var esc = encodeURIComponent(ae);
  257. if (esc === ae) {
  258. esc = escape(ae);
  259. }
  260. rest = rest.split(ae).join(esc);
  261. }
  262. }
  263. // chop off from the tail first.
  264. var hash = rest.indexOf('#');
  265. if (hash !== -1) {
  266. // got a fragment string.
  267. this.hash = rest.substr(hash);
  268. rest = rest.slice(0, hash);
  269. }
  270. var qm = rest.indexOf('?');
  271. if (qm !== -1) {
  272. this.search = rest.substr(qm);
  273. this.query = rest.substr(qm + 1);
  274. if (parseQueryString) {
  275. this.query = querystring.parse(this.query);
  276. }
  277. rest = rest.slice(0, qm);
  278. } else if (parseQueryString) {
  279. // no query string, but parseQueryString still requested
  280. this.search = '';
  281. this.query = {};
  282. }
  283. if (rest) this.pathname = rest;
  284. if (slashedProtocol[lowerProto] &&
  285. this.hostname && !this.pathname) {
  286. this.pathname = '/';
  287. }
  288. //to support http.request
  289. if (this.pathname || this.search) {
  290. var p = this.pathname || '';
  291. var s = this.search || '';
  292. this.path = p + s;
  293. }
  294. // finally, reconstruct the href based on what has been validated.
  295. this.href = this.format();
  296. return this;
  297. };
  298. // format a parsed object into a url string
  299. function urlFormat(obj) {
  300. // ensure it's an object, and not a string url.
  301. // If it's an obj, this is a no-op.
  302. // this way, you can call url_format() on strings
  303. // to clean up potentially wonky urls.
  304. if (isString(obj)) obj = urlParse(obj);
  305. if (!(obj instanceof Url)) return Url.prototype.format.call(obj);
  306. return obj.format();
  307. }
  308. Url.prototype.format = function() {
  309. var auth = this.auth || '';
  310. if (auth) {
  311. auth = encodeURIComponent(auth);
  312. auth = auth.replace(/%3A/i, ':');
  313. auth += '@';
  314. }
  315. var protocol = this.protocol || '',
  316. pathname = this.pathname || '',
  317. hash = this.hash || '',
  318. host = false,
  319. query = '';
  320. if (this.host) {
  321. host = auth + this.host;
  322. } else if (this.hostname) {
  323. host = auth + (this.hostname.indexOf(':') === -1 ?
  324. this.hostname :
  325. '[' + this.hostname + ']');
  326. if (this.port) {
  327. host += ':' + this.port;
  328. }
  329. }
  330. if (this.query &&
  331. isObject(this.query) &&
  332. Object.keys(this.query).length) {
  333. query = querystring.stringify(this.query);
  334. }
  335. var search = this.search || (query && ('?' + query)) || '';
  336. if (protocol && protocol.substr(-1) !== ':') protocol += ':';
  337. // only the slashedProtocols get the //. Not mailto:, xmpp:, etc.
  338. // unless they had them to begin with.
  339. if (this.slashes ||
  340. (!protocol || slashedProtocol[protocol]) && host !== false) {
  341. host = '//' + (host || '');
  342. if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname;
  343. } else if (!host) {
  344. host = '';
  345. }
  346. if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
  347. if (search && search.charAt(0) !== '?') search = '?' + search;
  348. pathname = pathname.replace(/[?#]/g, function(match) {
  349. return encodeURIComponent(match);
  350. });
  351. search = search.replace('#', '%23');
  352. return protocol + host + pathname + search + hash;
  353. };
  354. function urlResolve(source, relative) {
  355. return urlParse(source, false, true).resolve(relative);
  356. }
  357. Url.prototype.resolve = function(relative) {
  358. return this.resolveObject(urlParse(relative, false, true)).format();
  359. };
  360. function urlResolveObject(source, relative) {
  361. if (!source) return relative;
  362. return urlParse(source, false, true).resolveObject(relative);
  363. }
  364. Url.prototype.resolveObject = function(relative) {
  365. if (isString(relative)) {
  366. var rel = new Url();
  367. rel.parse(relative, false, true);
  368. relative = rel;
  369. }
  370. var result = new Url();
  371. Object.keys(this).forEach(function(k) {
  372. result[k] = this[k];
  373. }, this);
  374. // hash is always overridden, no matter what.
  375. // even href="" will remove it.
  376. result.hash = relative.hash;
  377. // if the relative url is empty, then there's nothing left to do here.
  378. if (relative.href === '') {
  379. result.href = result.format();
  380. return result;
  381. }
  382. // hrefs like //foo/bar always cut to the protocol.
  383. if (relative.slashes && !relative.protocol) {
  384. // take everything except the protocol from relative
  385. Object.keys(relative).forEach(function(k) {
  386. if (k !== 'protocol')
  387. result[k] = relative[k];
  388. });
  389. //urlParse appends trailing / to urls like http://www.example.com
  390. if (slashedProtocol[result.protocol] &&
  391. result.hostname && !result.pathname) {
  392. result.path = result.pathname = '/';
  393. }
  394. result.href = result.format();
  395. return result;
  396. }
  397. if (relative.protocol && relative.protocol !== result.protocol) {
  398. // if it's a known url protocol, then changing
  399. // the protocol does weird things
  400. // first, if it's not file:, then we MUST have a host,
  401. // and if there was a path
  402. // to begin with, then we MUST have a path.
  403. // if it is file:, then the host is dropped,
  404. // because that's known to be hostless.
  405. // anything else is assumed to be absolute.
  406. if (!slashedProtocol[relative.protocol]) {
  407. Object.keys(relative).forEach(function(k) {
  408. result[k] = relative[k];
  409. });
  410. result.href = result.format();
  411. return result;
  412. }
  413. result.protocol = relative.protocol;
  414. if (!relative.host && !hostlessProtocol[relative.protocol]) {
  415. var relPath = (relative.pathname || '').split('/');
  416. while (relPath.length && !(relative.host = relPath.shift()));
  417. if (!relative.host) relative.host = '';
  418. if (!relative.hostname) relative.hostname = '';
  419. if (relPath[0] !== '') relPath.unshift('');
  420. if (relPath.length < 2) relPath.unshift('');
  421. result.pathname = relPath.join('/');
  422. } else {
  423. result.pathname = relative.pathname;
  424. }
  425. result.search = relative.search;
  426. result.query = relative.query;
  427. result.host = relative.host || '';
  428. result.auth = relative.auth;
  429. result.hostname = relative.hostname || relative.host;
  430. result.port = relative.port;
  431. // to support http.request
  432. if (result.pathname || result.search) {
  433. var p = result.pathname || '';
  434. var s = result.search || '';
  435. result.path = p + s;
  436. }
  437. result.slashes = result.slashes || relative.slashes;
  438. result.href = result.format();
  439. return result;
  440. }
  441. var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'),
  442. isRelAbs = (
  443. relative.host ||
  444. relative.pathname && relative.pathname.charAt(0) === '/'
  445. ),
  446. mustEndAbs = (isRelAbs || isSourceAbs ||
  447. (result.host && relative.pathname)),
  448. removeAllDots = mustEndAbs,
  449. srcPath = result.pathname && result.pathname.split('/') || [],
  450. relPath = relative.pathname && relative.pathname.split('/') || [],
  451. psychotic = result.protocol && !slashedProtocol[result.protocol];
  452. // if the url is a non-slashed url, then relative
  453. // links like ../.. should be able
  454. // to crawl up to the hostname, as well. This is strange.
  455. // result.protocol has already been set by now.
  456. // Later on, put the first path part into the host field.
  457. if (psychotic) {
  458. result.hostname = '';
  459. result.port = null;
  460. if (result.host) {
  461. if (srcPath[0] === '') srcPath[0] = result.host;
  462. else srcPath.unshift(result.host);
  463. }
  464. result.host = '';
  465. if (relative.protocol) {
  466. relative.hostname = null;
  467. relative.port = null;
  468. if (relative.host) {
  469. if (relPath[0] === '') relPath[0] = relative.host;
  470. else relPath.unshift(relative.host);
  471. }
  472. relative.host = null;
  473. }
  474. mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === '');
  475. }
  476. if (isRelAbs) {
  477. // it's absolute.
  478. result.host = (relative.host || relative.host === '') ?
  479. relative.host : result.host;
  480. result.hostname = (relative.hostname || relative.hostname === '') ?
  481. relative.hostname : result.hostname;
  482. result.search = relative.search;
  483. result.query = relative.query;
  484. srcPath = relPath;
  485. // fall through to the dot-handling below.
  486. } else if (relPath.length) {
  487. // it's relative
  488. // throw away the existing file, and take the new path instead.
  489. if (!srcPath) srcPath = [];
  490. srcPath.pop();
  491. srcPath = srcPath.concat(relPath);
  492. result.search = relative.search;
  493. result.query = relative.query;
  494. } else if (!isNullOrUndefined(relative.search)) {
  495. // just pull out the search.
  496. // like href='?foo'.
  497. // Put this after the other two cases because it simplifies the booleans
  498. if (psychotic) {
  499. result.hostname = result.host = srcPath.shift();
  500. //occationaly the auth can get stuck only in host
  501. //this especialy happens in cases like
  502. //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
  503. var authInHost = result.host && result.host.indexOf('@') > 0 ?
  504. result.host.split('@') : false;
  505. if (authInHost) {
  506. result.auth = authInHost.shift();
  507. result.host = result.hostname = authInHost.shift();
  508. }
  509. }
  510. result.search = relative.search;
  511. result.query = relative.query;
  512. //to support http.request
  513. if (!isNull(result.pathname) || !isNull(result.search)) {
  514. result.path = (result.pathname ? result.pathname : '') +
  515. (result.search ? result.search : '');
  516. }
  517. result.href = result.format();
  518. return result;
  519. }
  520. if (!srcPath.length) {
  521. // no path at all. easy.
  522. // we've already handled the other stuff above.
  523. result.pathname = null;
  524. //to support http.request
  525. if (result.search) {
  526. result.path = '/' + result.search;
  527. } else {
  528. result.path = null;
  529. }
  530. result.href = result.format();
  531. return result;
  532. }
  533. // if a url ENDs in . or .., then it must get a trailing slash.
  534. // however, if it ends in anything else non-slashy,
  535. // then it must NOT get a trailing slash.
  536. var last = srcPath.slice(-1)[0];
  537. var hasTrailingSlash = (
  538. (result.host || relative.host) && (last === '.' || last === '..') ||
  539. last === '');
  540. // strip single dots, resolve double dots to parent dir
  541. // if the path tries to go above the root, `up` ends up > 0
  542. var up = 0;
  543. for (var i = srcPath.length; i >= 0; i--) {
  544. last = srcPath[i];
  545. if (last == '.') {
  546. srcPath.splice(i, 1);
  547. } else if (last === '..') {
  548. srcPath.splice(i, 1);
  549. up++;
  550. } else if (up) {
  551. srcPath.splice(i, 1);
  552. up--;
  553. }
  554. }
  555. // if the path is allowed to go above the root, restore leading ..s
  556. if (!mustEndAbs && !removeAllDots) {
  557. for (; up--; up) {
  558. srcPath.unshift('..');
  559. }
  560. }
  561. if (mustEndAbs && srcPath[0] !== '' &&
  562. (!srcPath[0] || srcPath[0].charAt(0) !== '/')) {
  563. srcPath.unshift('');
  564. }
  565. if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) {
  566. srcPath.push('');
  567. }
  568. var isAbsolute = srcPath[0] === '' ||
  569. (srcPath[0] && srcPath[0].charAt(0) === '/');
  570. // put the host back
  571. if (psychotic) {
  572. result.hostname = result.host = isAbsolute ? '' :
  573. srcPath.length ? srcPath.shift() : '';
  574. //occationaly the auth can get stuck only in host
  575. //this especialy happens in cases like
  576. //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
  577. var authInHost = result.host && result.host.indexOf('@') > 0 ?
  578. result.host.split('@') : false;
  579. if (authInHost) {
  580. result.auth = authInHost.shift();
  581. result.host = result.hostname = authInHost.shift();
  582. }
  583. }
  584. mustEndAbs = mustEndAbs || (result.host && srcPath.length);
  585. if (mustEndAbs && !isAbsolute) {
  586. srcPath.unshift('');
  587. }
  588. if (!srcPath.length) {
  589. result.pathname = null;
  590. result.path = null;
  591. } else {
  592. result.pathname = srcPath.join('/');
  593. }
  594. //to support request.http
  595. if (!isNull(result.pathname) || !isNull(result.search)) {
  596. result.path = (result.pathname ? result.pathname : '') +
  597. (result.search ? result.search : '');
  598. }
  599. result.auth = relative.auth || result.auth;
  600. result.slashes = result.slashes || relative.slashes;
  601. result.href = result.format();
  602. return result;
  603. };
  604. Url.prototype.parseHost = function() {
  605. var host = this.host;
  606. var port = portPattern.exec(host);
  607. if (port) {
  608. port = port[0];
  609. if (port !== ':') {
  610. this.port = port.substr(1);
  611. }
  612. host = host.substr(0, host.length - port.length);
  613. }
  614. if (host) this.hostname = host;
  615. };
  616. function isString(arg) {
  617. return typeof arg === "string";
  618. }
  619. function isObject(arg) {
  620. return typeof arg === 'object' && arg !== null;
  621. }
  622. function isNull(arg) {
  623. return arg === null;
  624. }
  625. function isNullOrUndefined(arg) {
  626. return arg == null;
  627. }