--- /dev/null
+/**
+ * uri.js
+ * A URI parser, compliant with assorted RFCs, providing parsing and resolution utilities.
+ **/
+
+exports.parse = uri_parse;
+exports.format = uri_format;
+exports.resolve = uri_resolve;
+exports.resolveObject = uri_resolveObject;
+
+
+/**** expressionKeys
+ members of a parsed URI object that you get
+ from evaluting the strict regular expression.
+*/
+var expressionKeys = [
+ "url",
+ "protocol",
+ "authorityRoot",
+ "authority",
+ "userInfo",
+ "user",
+ "password",
+ "domain",
+ "port",
+ "path",
+ "root",
+ "directory",
+ "file",
+ "query",
+ "anchor"
+ ],
+ strictExpression = new RegExp( /* url */
+ "^" +
+ "(?:" +
+ "([^:/?#]+):" + /* protocol */
+ ")?" +
+ "(?:" +
+ "(//)" + /* authorityRoot */
+ "(" + /* authority */
+ "(?:" +
+ "(" + /* userInfo */
+ "([^:@/]*)" + /* user */
+ ":?" +
+ "([^@/]*)" + /* password */
+ ")?" +
+ "@" +
+ ")?" +
+ "([^:/?#]*)" + /* domain */
+ "(?::(\\d*))?" + /* port */
+ ")" +
+ ")?" +
+ "(" + /* path */
+ "(/?)" + /* root */
+ "((?:[^?#/]*/)*)" +
+ "([^?#]*)" + /* file */
+ ")" +
+ "(?:\\?([^#]*))?" + /* query */
+ "(?:#(.*))?" /*anchor */
+ );
+
+/**** parse
+ a URI parser function that uses the `strictExpression`
+ and `expressionKeys` and returns an `Object`
+ mapping all `keys` to values.
+*/
+function uri_parse (url) {
+ var items = {},
+ parts = strictExpression.exec(url);
+
+ for (var i = 0; i < parts.length; i++) {
+ items[expressionKeys[i]] = parts[i] ? parts[i] : "";
+ }
+
+ items.root = (items.root || items.authorityRoot) ? '/' : '';
+
+ items.directories = items.directory.split("/");
+ if (items.directories[items.directories.length - 1] == "") {
+ items.directories.pop();
+ }
+
+ /* normalize */
+ var directories = [];
+ for (var i = 0; i < items.directories.length; i++) {
+ var directory = items.directories[i];
+ if (directory == '.') {
+ } else if (directory == '..') {
+ if (directories.length && directories[directories.length - 1] != '..')
+ directories.pop();
+ else
+ directories.push('..');
+ } else {
+ directories.push(directory);
+ }
+ }
+ items.directories = directories;
+
+ items.domains = items.domain.split(".");
+
+ return items;
+};
+
+
+/**** format
+ accepts a parsed URI object and returns
+ the corresponding string.
+*/
+function uri_format (object) {
+ if (typeof(object) == 'undefined')
+ 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(".") :
+ object.domain;
+ var userInfo = (
+ object.user ||
+ object.password
+ ) ? (
+ (object.user || "") +
+ (object.password ? ":" + object.password : "")
+ ) :
+ object.userInfo;
+ var authority = (
+ userInfo ||
+ domain ||
+ object.port
+ ) ? (
+ (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;
+ var authorityRoot =
+ object.authorityRoot
+ || authority ? "//" : "";
+
+ return object.url = ((
+ (object.protocol ? object.protocol + ":" : "") +
+ (authorityRoot) +
+ (authority) +
+ (object.root || (authority && path) ? "/" : "") +
+ (path ? path : "") +
+ (object.query ? "?" + object.query : "") +
+ (object.anchor ? "#" + object.anchor : "")
+ ) || 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)
+ 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;
+
+ if (
+ relative.protocol && relative.protocol != source.protocol ||
+ relative.authority && relative.authority != source.authority
+ ) {
+ source = relative;
+ } 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 (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;
+
+ return source;
+};
+
+
+/**** resolve
+ returns a URL resovled to a relative URL from a source URL.
+*/
+function uri_resolve (source, relative) {
+ return uri_format(uri_resolveObject(source, relative));
+};
+
--- /dev/null
+process.mixin(require("./common"));
+
+var uri = require("uri"),
+ sys = require("sys");
+
+// URIs to parse, and expected data
+// { url : parsed }
+var parseTests = {
+ "http://www.narwhaljs.org/blog/categories?id=news" : {
+ "url": "http://www.narwhaljs.org/blog/categories?id=news",
+ "protocol": "http",
+ "authorityRoot": "//",
+ "authority": "www.narwhaljs.org",
+ "userInfo": "",
+ "user": "",
+ "password": "",
+ "domain": "www.narwhaljs.org",
+ "port": "",
+ "path": "/blog/categories",
+ "root": "/",
+ "directory": "blog/",
+ "file": "categories",
+ "query": "id=news",
+ "anchor": "",
+ "directories": [
+ "blog"
+ ],
+ "domains": [
+ "www",
+ "narwhaljs",
+ "org"
+ ]
+ },
+ "http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=" : {
+ "url": "http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=",
+ "protocol": "http",
+ "authorityRoot": "//",
+ "authority": "mt0.google.com",
+ "userInfo": "",
+ "user": "",
+ "password": "",
+ "domain": "mt0.google.com",
+ "port": "",
+ "path": "/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=",
+ "root": "/",
+ "directory": "vt/",
+ "file": "lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=",
+ "query": "",
+ "anchor": "",
+ "directories": [
+ "vt"
+ ],
+ "domains": [
+ "mt0",
+ "google",
+ "com"
+ ]
+ },
+ "http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=" : {
+ "url": "http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=",
+ "protocol": "http",
+ "authorityRoot": "//",
+ "authority": "mt0.google.com",
+ "userInfo": "",
+ "user": "",
+ "password": "",
+ "domain": "mt0.google.com",
+ "port": "",
+ "path": "/vt/lyrs=m@114",
+ "root": "/",
+ "directory": "vt/",
+ "file": "lyrs=m@114",
+ "query": "??&hl=en&src=api&x=2&y=2&z=3&s=",
+ "anchor": "",
+ "directories": [
+ "vt"
+ ],
+ "domains": [
+ "mt0",
+ "google",
+ "com"
+ ]
+ },
+ "http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=" : {
+ "url": "http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=",
+ "protocol": "http",
+ "authorityRoot": "//",
+ "authority": "user:pass@mt0.google.com",
+ "userInfo": "user:pass",
+ "user": "user",
+ "password": "pass",
+ "domain": "mt0.google.com",
+ "port": "",
+ "path": "/vt/lyrs=m@114",
+ "root": "/",
+ "directory": "vt/",
+ "file": "lyrs=m@114",
+ "query": "??&hl=en&src=api&x=2&y=2&z=3&s=",
+ "anchor": "",
+ "directories": [
+ "vt"
+ ],
+ "domains": [
+ "mt0",
+ "google",
+ "com"
+ ]
+ },
+ "file:///etc/passwd" : {
+ "url": "file:///etc/passwd",
+ "protocol": "file",
+ "authorityRoot": "//",
+ "authority": "",
+ "userInfo": "",
+ "user": "",
+ "password": "",
+ "domain": "",
+ "port": "",
+ "path": "/etc/passwd",
+ "root": "/",
+ "directory": "etc/",
+ "file": "passwd",
+ "query": "",
+ "anchor": "",
+ "directories": [
+ "etc"
+ ],
+ "domains": [
+ ""
+ ]
+ },
+ "file:///etc/node/" : {
+ "url": "file:///etc/node/",
+ "protocol": "file",
+ "authorityRoot": "//",
+ "authority": "",
+ "userInfo": "",
+ "user": "",
+ "password": "",
+ "domain": "",
+ "port": "",
+ "path": "/etc/node/",
+ "root": "/",
+ "directory": "etc/node/",
+ "file": "",
+ "query": "",
+ "anchor": "",
+ "directories": [
+ "etc",
+ "node"
+ ],
+ "domains": [
+ ""
+ ]
+ }
+};
+for (var url in parseTests) {
+ var actual = uri.parse(url),
+ expected = parseTests[url];
+ for (var i in expected) {
+ // sys.debug(i);
+ // sys.debug("expect: " + JSON.stringify(expected[i]));
+ // sys.debug("actual: " + JSON.stringify(actual[i]));
+ var e = JSON.stringify(expected[i]),
+ a = JSON.stringify(actual[i]);
+ assert.equal(e, a, "parse(" + url + ")."+i+" == "+e+"\nactual: "+a);
+ }
+
+ var expected = url,
+ actual = uri.format(parseTests[url]);
+ assert.equal(expected, actual, "format("+url+") == "+url+"\nactual:"+actual);
+}
+
+[
+ // [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", "/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"]
+].forEach(function (relativeTest) {
+ var a = uri.resolve(relativeTest[0], relativeTest[1]),
+ e = relativeTest[2];
+ assert.equal(e, a,
+ "resolve("+[relativeTest[0], relativeTest[1]]+") == "+e+
+ "\n actual="+a);
+});
+