Update to fix failing test. Rewrite of the resolveObject function, and some tweaks...
authorisaacs <i@foohack.com>
Sun, 13 Dec 2009 00:29:12 +0000 (16:29 -0800)
committerRyan Dahl <ry@tinyclouds.org>
Fri, 18 Dec 2009 18:02:27 +0000 (19:02 +0100)
lib/uri.js
test/mjsunit/test-uri.js

index 0061460..45187e0 100644 (file)
@@ -126,6 +126,7 @@ function uri_format (object) {
     throw new Error("UrlError: URL undefined for urls#format");
   if (object instanceof String || typeof(object) === 'string')
     return object;
+  
   var domain =
     object.domains ?
     object.domains.join(".") :
@@ -138,7 +139,7 @@ function uri_format (object) {
       (object.password ? ":" + object.password : "") 
     ) :
     object.userInfo;
-  var authority = (
+  var authority = object.authority || ((
       userInfo ||
       domain ||
       object.port
@@ -146,20 +147,18 @@ function uri_format (object) {
       (userInfo ? userInfo + "@" : "") +
       (domain || "") + 
       (object.port ? ":" + object.port : "")
-    ) :
-    object.authority || "";
-    
+    ) : "");
+  
   var directory =
     object.directories ?
     object.directories.join("/") :
     object.directory;
   var path =
-    directory || object.file ?
-    (
-      (directory ? directory + "/" : "") +
-      (object.file || "")
-    ) :
-    object.path;
+    object.path ? object.path.substr(1) : (
+      (directory || object.file) ? (
+        (directory ? directory + "/" : "") +
+        (object.file || "")
+      ) : "");
   var authorityRoot = 
     object.authorityRoot
     || authority ? "//" : "";
@@ -175,74 +174,79 @@ function uri_format (object) {
   ) || object.url || "");
 };
 
-/**** resolveObject
-  returns an object representing a URL resolved from
-  a relative location and a source location.
-*/
 function uri_resolveObject (source, relative) {
-  if (!source) 
+  if (!source) return relative;
+  
+  // parse a string, or get new objects
+  source = uri_parse(uri_format(source));
+  relative = uri_parse(uri_format(relative));
+  
+  if (relative.url === "") return source;
+  
+  // links to xyz:... from abc:... are always absolute.
+  if (relative.protocol && source.protocol && relative.protocol !== source.protocol) {
     return relative;
+  }
+  
+  // if there's an authority root, but no protocol, then keep the current protocol
+  if (relative.authorityRoot && !relative.protocol) {
+    relative.protocol = source.protocol;
+  }
+  // if we have an authority root, then it's absolute
+  if (relative.authorityRoot) return relative;
 
-  source = uri_parse(source);
-  relative = uri_parse(relative);
-
-  if (relative.url == "")
-    return source;
-
-  delete source.url;
-  delete source.authority;
-  delete source.domain;
-  delete source.userInfo;
-  delete source.path;
-  delete source.directory;
+  
+  // at this point, we start doing the tricky stuff
+  // we know that relative doesn't have an authority, but might have root,
+  // path, file, query, etc.
+  // also, the directory segments might contain .. or .
+  // start mutating "source", and then return that.
 
-  if (
-    relative.protocol && relative.protocol != source.protocol ||
-    relative.authority && relative.authority != source.authority
-  ) {
-    source = relative;
+  // relative urls that start with / are absolute to the authority/protocol
+  if (relative.root) {
+    [
+      "path", "root", "directory", "directories", "file", "query", "anchor"
+    ].forEach(function (part) { source[part] = relative[part] });
   } else {
-    if (relative.root) {
-      source.directories = relative.directories;
-    } else {
-
-      var directories = relative.directories;
-      for (var i = 0; i < directories.length; i++) {
-        var directory = directories[i];
-        if (directory == ".") {
-        } else if (directory == "..") {
-          if (source.directories.length) {
-            source.directories.pop();
-          } else {
-            source.directories.push('..');
-          }
-        } else {
-          source.directories.push(directory);
-        }
-      }
-
-      if (relative.file == ".") {
-        relative.file = "";
-      } else if (relative.file == "..") {
-        source.directories.pop();
-        relative.file = "";
-      }
+    // if you have /a/b/c and you're going to x/y/z, then that's /a/b/x/y/z
+    // if you have /a/b/c/ and you're going ot x/y/z, then that's /a/b/c/x/y/z
+    // if you have /a/b/c and you're going to ?foo, then that's /a/b/c?foo
+    if (relative.file || relative.directory) {
+      source.file = relative.file;
+      source.query = relative.query;
+      source.anchor = relative.anchor;
     }
+    if (relative.query) source.query = relative.query;
+    if (relative.query || relative.anchor) source.anchor = relative.anchor;
+    
+    // just append the dirs. we'll sort out .. and . later
+    source.directories = source.directories.concat(relative.directories);
   }
-
-  if (relative.root)
-    source.root = relative.root;
-  if (relative.protcol)
-    source.protocol = relative.protocol;
-  if (!(!relative.path && relative.anchor))
-    source.file = relative.file;
-  source.query = relative.query;
-  source.anchor = relative.anchor;
-
+  
+  // back up "file" to the first non-dot
+  // one step for ., two for ..
+  var file = source.file;
+  while (file === ".." || file === ".") {
+    if (file === "..") source.directories.pop();
+    file = source.directories.pop();
+  }
+  source.file = file || "";
+  
+  // walk over the dirs, replacing a/b/c/.. with a/b/ and a/b/c/. with a/b/c
+  var dirs = [];
+  source.directories.forEach(function (dir, i) {
+    if (dir === "..") dirs.pop();
+    else if (dir !== "." && dir !== "") dirs.push(dir);
+  });
+  
+  // now construct path/etc.
+  source.directories = dirs;
+  source.directory = dirs.concat("").join("/");
+  source.path = source.root + source.directory + source.file;
+  source.url = uri_format(source);
   return source;
 };
 
-
 /**** resolve
   returns a URL resovled to a relative URL from a source URL.
 */
index 13ac656..6060fed 100644 (file)
@@ -168,6 +168,7 @@ for (var url in parseTests) {
   
   var expected = url,
     actual = uri.format(parseTests[url]);
+  
   assert.equal(expected, actual, "format("+url+") == "+url+"\nactual:"+actual);
 }
 
@@ -175,12 +176,13 @@ for (var url in parseTests) {
   // [from, path, expected]
   ["/foo/bar/baz", "quux", "/foo/bar/quux"],
   ["/foo/bar/baz", "quux/asdf", "/foo/bar/quux/asdf"],
-  ["/foo/bar/baz", "quux/baz", "/foo/bar/quux/baz"], //TODO: failing test
-  ["/foo/bar/baz", "../quux/baz", "/foo/quux/baz"], //TODO: failing test
+  ["/foo/bar/baz", "quux/baz", "/foo/bar/quux/baz"],
+  ["/foo/bar/baz", "../quux/baz", "/foo/quux/baz"],
   ["/foo/bar/baz", "/bar", "/bar"],
   ["/foo/bar/baz/", "quux", "/foo/bar/baz/quux"],
   ["/foo/bar/baz/", "quux/baz", "/foo/bar/baz/quux/baz"],
-  ["/foo/bar/baz", "../../../../../../../../quux/baz", "/quux/baz"]
+  ["/foo/bar/baz", "../../../../../../../../quux/baz", "/quux/baz"],
+  ["/foo/bar/baz", "../../../../../../../quux/baz", "/quux/baz"]
 ].forEach(function (relativeTest) {
   var a = uri.resolve(relativeTest[0], relativeTest[1]),
     e = relativeTest[2];