lint url.js
authorisaacs <i@izs.me>
Thu, 2 Dec 2010 19:34:53 +0000 (11:34 -0800)
committerRyan Dahl <ry@tinyclouds.org>
Thu, 2 Dec 2010 19:46:32 +0000 (11:46 -0800)
lib/url.js

index 2dcb18e..a0f274e 100644 (file)
@@ -3,26 +3,35 @@ exports.resolve = urlResolve;
 exports.resolveObject = urlResolveObject;
 exports.format = urlFormat;
 
-// define these here so at least they only have to be compiled once on the first module load.
+// define these here so at least they only have to be
+// compiled once on the first module load.
 var protocolPattern = /^([a-z0-9]+:)/,
-  portPattern = /:[0-9]+$/,
-  nonHostChars = ["/", "?", ";", "#"],
-  hostlessProtocol = {
-    "file":true,
-    "file:":true
-  },
-  slashedProtocol = {
-    "http":true, "https":true, "ftp":true, "gopher":true, "file":true,
-    "http:":true, "https:":true, "ftp:":true, "gopher:":true, "file:":true
-  },
-  path = require("path"), // internal module, guaranteed to be loaded already.
-  querystring = require('querystring');
-
-function urlParse (url, parseQueryString, slashesDenoteHost) {
-  if (url && typeof(url) === "object" && url.href) return url;
-
-  var out = { href : url },
-    rest = url;
+    portPattern = /:[0-9]+$/,
+    nonHostChars = ['/', '?', ';', '#'],
+    hostlessProtocol = {
+      'file': true,
+      'file:': true
+    },
+    slashedProtocol = {
+      'http': true,
+      'https': true,
+      'ftp': true,
+      'gopher': true,
+      'file': true,
+      'http:': true,
+      'https:': true,
+      'ftp:': true,
+      'gopher:': true,
+      'file:': true
+    },
+    path = require('path'), // internal module, guaranteed to be loaded already.
+    querystring = require('querystring');
+
+function urlParse(url, parseQueryString, slashesDenoteHost) {
+  if (url && typeof(url) === 'object' && url.href) return url;
+
+  var out = { href: url },
+      rest = url;
 
   var proto = protocolPattern.exec(rest);
   if (proto) {
@@ -36,27 +45,29 @@ function urlParse (url, parseQueryString, slashesDenoteHost) {
   // resolution will treat //foo/bar as host=foo,path=bar because that's
   // how the browser resolves relative URLs.
   if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {
-    var slashes = rest.substr(0, 2) === "//";
+    var slashes = rest.substr(0, 2) === '//';
     if (slashes && !(proto && hostlessProtocol[proto])) {
       rest = rest.substr(2);
       out.slashes = true;
     }
   }
-  if (!hostlessProtocol[proto] && (slashes || (proto && !slashedProtocol[proto]))) {
+  if (!hostlessProtocol[proto] &&
+      (slashes || (proto && !slashedProtocol[proto]))) {
     // there's a hostname.
     // the first instance of /, ?, ;, or # ends the host.
     // don't enforce full RFC correctness, just be unstupid about it.
     var firstNonHost = -1;
-    for (var i = 0, l = nonHostChars.length; i < l; i ++) {
+    for (var i = 0, l = nonHostChars.length; i < l; i++) {
       var index = rest.indexOf(nonHostChars[i]);
-      if (index !== -1 && (firstNonHost < 0 || index < firstNonHost)) firstNonHost = index;
+      if (index !== -1 &&
+          (firstNonHost < 0 || index < firstNonHost)) firstNonHost = index;
     }
     if (firstNonHost !== -1) {
       out.host = rest.substr(0, firstNonHost);
       rest = rest.substr(firstNonHost);
     } else {
       out.host = rest;
-      rest = "";
+      rest = '';
     }
 
     // pull out the auth and port.
@@ -66,22 +77,23 @@ function urlParse (url, parseQueryString, slashesDenoteHost) {
       var key = keys[i];
       out[key] = p[key];
     }
-    // we've indicated that there is a hostname, so even if it's empty, it has to be present.
-    out.hostname = out.hostname || "";
+    // we've indicated that there is a hostname,
+    // so even if it's empty, it has to be present.
+    out.hostname = out.hostname || '';
   }
 
   // now rest is set to the post-host stuff.
   // chop off from the tail first.
-  var hash = rest.indexOf("#");
+  var hash = rest.indexOf('#');
   if (hash !== -1) {
     // got a fragment string.
     out.hash = rest.substr(hash);
     rest = rest.slice(0, hash);
   }
-  var qm = rest.indexOf("?");
+  var qm = rest.indexOf('?');
   if (qm !== -1) {
     out.search = rest.substr(qm);
-    out.query = rest.substr(qm+1);
+    out.query = rest.substr(qm + 1);
     if (parseQueryString) {
       out.query = querystring.parse(out.query);
     }
@@ -90,52 +102,57 @@ function urlParse (url, parseQueryString, slashesDenoteHost) {
   if (rest) out.pathname = rest;
 
   return out;
-};
+}
 
 // format a parsed object into a url string
-function urlFormat (obj) {
-  // ensure it's an object, and not a string url. If it's an obj, this is a no-op.
-  // this way, you can call url_format() on strings to clean up potentially wonky urls.
-  if (typeof(obj) === "string") obj = urlParse(obj);
-
-  var protocol = obj.protocol || "",
-    host = (obj.host !== undefined) ? obj.host
-      : obj.hostname !== undefined ? (
-        (obj.auth ? obj.auth + "@" : "")
-        + obj.hostname
-        + (obj.port ? ":" + obj.port : "")
-      )
-      : false,
-    pathname = obj.pathname || "",
-    search = obj.search || (
-      obj.query && ( "?" + (
-        typeof(obj.query) === "object"
-        ? querystring.stringify(obj.query)
-        : String(obj.query)
-      ))
-    ) || "",
-    hash = obj.hash || "";
-
-  if (protocol && protocol.substr(-1) !== ":") protocol += ":";
+function urlFormat(obj) {
+  // ensure it's an object, and not a string url.
+  // If it's an obj, this is a no-op.
+  // this way, you can call url_format() on strings
+  // to clean up potentially wonky urls.
+  if (typeof(obj) === 'string') obj = urlParse(obj);
+
+  var protocol = obj.protocol || '',
+      host = (obj.host !== undefined) ? obj.host :
+          obj.hostname !== undefined ? (
+              (obj.auth ? obj.auth + '@' : '') +
+              obj.hostname +
+              (obj.port ? ':' + obj.port : '')
+          ) :
+          false,
+      pathname = obj.pathname || '',
+      search = obj.search || (
+          obj.query && ('?' + (
+              typeof(obj.query) === 'object' ?
+              querystring.stringify(obj.query) :
+              String(obj.query)
+          ))
+      ) || '',
+      hash = obj.hash || '';
+
+  if (protocol && protocol.substr(-1) !== ':') protocol += ':';
 
   // only the slashedProtocols get the //.  Not mailto:, xmpp:, etc.
   // unless they had them to begin with.
-  if (obj.slashes || (!protocol || slashedProtocol[protocol]) && host !== false) {
-    host = "//" + (host || "");
-    if (pathname && pathname.charAt(0) !== "/") pathname = "/" + pathname;
-  } else if (!host) host = "";
+  if (obj.slashes ||
+      (!protocol || slashedProtocol[protocol]) && host !== false) {
+    host = '//' + (host || '');
+    if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname;
+  } else if (!host) {
+    host = '';
+  }
 
-  if (hash && hash.charAt(0) !== "#") hash = "#" + hash;
-  if (search && search.charAt(0) !== "?") search = "?" + search;
+  if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
+  if (search && search.charAt(0) !== '?') search = '?' + search;
 
   return protocol + host + pathname + search + hash;
-};
+}
 
-function urlResolve (source, relative) {
+function urlResolve(source, relative) {
   return urlFormat(urlResolveObject(source, relative));
-};
+}
 
-function urlResolveObject (source, relative) {
+function urlResolveObject(source, relative) {
   if (!source) return relative;
 
   source = urlParse(urlFormat(source), false, true);
@@ -144,7 +161,7 @@ function urlResolveObject (source, relative) {
   // hash is always overridden, no matter what.
   source.hash = relative.hash;
 
-  if (relative.href === "") return source;
+  if (relative.href === '') return source;
 
   // hrefs like //foo/bar always cut to the protocol.
   if (relative.slashes && !relative.protocol) {
@@ -153,55 +170,62 @@ function urlResolveObject (source, relative) {
   }
 
   if (relative.protocol && relative.protocol !== source.protocol) {
-    // if it's a known url protocol, then changing the protocol does weird things
-    // first, if it's not file:, then we MUST have a host, and if there was a path
+    // if it's a known url protocol, then changing
+    // the protocol does weird things
+    // first, if it's not file:, then we MUST have a host,
+    // and if there was a path
     // to begin with, then we MUST have a path.
-    // if it is file:, then the host is dropped, because that's known to be hostless.
+    // if it is file:, then the host is dropped,
+    // because that's known to be hostless.
     // anything else is assumed to be absolute.
 
     if (!slashedProtocol[relative.protocol]) return relative;
 
     source.protocol = relative.protocol;
     if (!relative.host && !hostlessProtocol[relative.protocol]) {
-      var relPath = (relative.pathname || "").split("/");
+      var relPath = (relative.pathname || '').split('/');
       while (relPath.length && !(relative.host = relPath.shift()));
-      if (!relative.host) relative.host = "";
-      if (relPath[0] !== "") relPath.unshift("");
-      if (relPath.length < 2) relPath.unshift("");
-      relative.pathname = relPath.join("/");
+      if (!relative.host) relative.host = '';
+      if (relPath[0] !== '') relPath.unshift('');
+      if (relPath.length < 2) relPath.unshift('');
+      relative.pathname = relPath.join('/');
     }
     source.pathname = relative.pathname;
     source.search = relative.search;
     source.query = relative.query;
-    source.host = relative.host || "";
+    source.host = relative.host || '';
     delete source.auth;
     delete source.hostname;
     source.port = relative.port;
     return source;
   }
 
-  var isSourceAbs = (source.pathname && source.pathname.charAt(0) === "/"),
-    isRelAbs = (
-      relative.host !== undefined
-      || relative.pathname && relative.pathname.charAt(0) === "/"
-    ),
-    mustEndAbs = (isRelAbs || isSourceAbs || (source.host && relative.pathname)),
-    removeAllDots = mustEndAbs,
-    srcPath = source.pathname && source.pathname.split("/") || [],
-    relPath = relative.pathname && relative.pathname.split("/") || [],
-    psychotic = source.protocol && !slashedProtocol[source.protocol] && source.host !== undefined;
-
-  // if the url is a non-slashed url, then relative links like ../.. should be able
+  var isSourceAbs = (source.pathname && source.pathname.charAt(0) === '/'),
+      isRelAbs = (
+          relative.host !== undefined ||
+          relative.pathname && relative.pathname.charAt(0) === '/'
+      ),
+      mustEndAbs = (isRelAbs || isSourceAbs ||
+                    (source.host && relative.pathname)),
+      removeAllDots = mustEndAbs,
+      srcPath = source.pathname && source.pathname.split('/') || [],
+      relPath = relative.pathname && relative.pathname.split('/') || [],
+      psychotic = source.protocol &&
+          !slashedProtocol[source.protocol] &&
+          source.host !== undefined;
+
+  // if the url is a non-slashed url, then relative
+  // links like ../.. should be able
   // to crawl up to the hostname, as well.  This is strange.
   // source.protocol has already been set by now.
   // Later on, put the first path part into the host field.
-  if ( psychotic ) {
+  if (psychotic) {
 
     delete source.hostname;
     delete source.auth;
     delete source.port;
     if (source.host) {
-      if (srcPath[0] === "") srcPath[0] = source.host;
+      if (srcPath[0] === '') srcPath[0] = source.host;
       else srcPath.unshift(source.host);
     }
     delete source.host;
@@ -211,17 +235,18 @@ function urlResolveObject (source, relative) {
       delete relative.auth;
       delete relative.port;
       if (relative.host) {
-        if (relPath[0] === "") relPath[0] = relative.host;
+        if (relPath[0] === '') relPath[0] = relative.host;
         else relPath.unshift(relative.host);
       }
       delete relative.host;
     }
-    mustEndAbs = mustEndAbs && (relPath[0] === "" || srcPath[0] === "");
+    mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === '');
   }
 
   if (isRelAbs) {
     // it's absolute.
-    source.host = (relative.host || relative.host === "") ? relative.host : source.host;
+    source.host = (relative.host || relative.host === '') ?
+                      relative.host : source.host;
     source.search = relative.search;
     source.query = relative.query;
     srcPath = relPath;
@@ -234,9 +259,9 @@ function urlResolveObject (source, relative) {
     srcPath = srcPath.concat(relPath);
     source.search = relative.search;
     source.query = relative.query;
-  } else if ("search" in relative) {
+  } else if ('search' in relative) {
     // just pull out the search.
-    // like href="?foo".
+    // like href='?foo'.
     // Put this after the other two cases because it simplifies the booleans
     if (psychotic) {
       source.host = srcPath.shift();
@@ -254,58 +279,64 @@ function urlResolveObject (source, relative) {
 
   // resolve dots.
   // if a url ENDs in . or .., then it must get a trailing slash.
-  // however, if it ends in anything else non-slashy, then it must NOT get a trailing slash.
+  // however, if it ends in anything else non-slashy,
+  // then it must NOT get a trailing slash.
   var last = srcPath.slice(-1)[0];
   var hasTrailingSlash = (
-    (source.host || relative.host) && (last === "." || last === "..")
-    || last === ""
-  );
+      (source.host || relative.host) && (last === '.' || last === '..') ||
+      last === '');
 
-  // Figure out if this has to end up as an absolute url, or should continue to be relative.
+  // Figure out if this has to end up as an absolute url,
+  // or should continue to be relative.
   srcPath = path.normalizeArray(srcPath, true);
-  if (srcPath.length === 1 && srcPath[0] === ".") srcPath = [];
+  if (srcPath.length === 1 && srcPath[0] === '.') srcPath = [];
   if (mustEndAbs || removeAllDots) {
     // all dots must go.
     var dirs = [];
-    srcPath.forEach(function (dir, i) {
-      if (dir === "..") dirs.pop();
-      else if (dir !== ".") dirs.push(dir);
+    srcPath.forEach(function(dir, i) {
+      if (dir === '..') {
+        dirs.pop();
+      } else if (dir !== '.') {
+        dirs.push(dir);
+      }
     });
 
-    if (mustEndAbs && dirs[0] !== "" && (!dirs[0] || dirs[0].charAt(0) !== "/")) {
-      dirs.unshift("");
+    if (mustEndAbs && dirs[0] !== '' &&
+        (!dirs[0] || dirs[0].charAt(0) !== '/')) {
+      dirs.unshift('');
     }
     srcPath = dirs;
   }
-  if (hasTrailingSlash && (srcPath.join("/").substr(-1) !== "/")) {
-    srcPath.push("");
+  if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) {
+    srcPath.push('');
   }
 
-  var isAbsolute = srcPath[0] === "" || (srcPath[0] && srcPath[0].charAt(0) === "/");
+  var isAbsolute = srcPath[0] === '' ||
+      (srcPath[0] && srcPath[0].charAt(0) === '/');
 
   // put the host back
-  if ( psychotic ) {
-    source.host = isAbsolute ? "" : srcPath.shift();
+  if (psychotic) {
+    source.host = isAbsolute ? '' : srcPath.shift();
   }
 
   mustEndAbs = mustEndAbs || (source.host && srcPath.length);
 
   if (mustEndAbs && !isAbsolute) {
-    srcPath.unshift("");
+    srcPath.unshift('');
   }
 
-  source.pathname = srcPath.join("/");
+  source.pathname = srcPath.join('/');
 
 
   return source;
-};
+}
 
-function parseHost (host) {
+function parseHost(host) {
   var out = {};
-  var at = host.indexOf("@");
+  var at = host.indexOf('@');
   if (at !== -1) {
     out.auth = host.substr(0, at);
-    host = host.substr(at+1); // drop the @
+    host = host.substr(at + 1); // drop the @
   }
   var port = portPattern.exec(host);
   if (port) {