var uri = new URI(); // same as new URI(location.href) // string var uri = new URI("http://example.org"); // URI object for cloning var uri = new URI(new URI("http://example.org")); // URI parts object var uri = new URI({ protocol: "http", host: "example.org" }); // without new keyword var uri = URI("http://example.org"); // resolving right in the constructor var uri = URI("../foobar.html", "http://example.org/hello/world.html"); // which is exactly the same as URI("../foobar.html").absoluteTo("http://example.org/hello/world.html"); // but specified in URL constructor
The following parts can be specified in an object:
var uri = new URI({ protocol: "http", // no trailing : username: "user", password: "pass", hostname: "example.org", port: "80", // string, please // "path", not "pathname", sorry path: "/foo/bar.html", // "query", not "search", sorry query: "foo=bar&bar=baz", // no leading ? // "fragment", not "hash", sorry fragment: "frag" // no leading # });
using only components of URIs:
// Look ma! I'm only working the pathname var uri = new URI("some/directory/file.html"); // Look ma! I'm only working the query string var uri = new URI("?foo=bar"); // Look ma! I'm only working the fragment / hash var uri = new URI("#call-me-hash"); // and any combination of the above…
Get a copy of the current URI instance
var uri = new URI("http://example.org"), uri2 = uri.clone(); uri2.tld("com"); uri == "http://example.org"; uri2 == "http://example.com";
.scheme() is an alias of .protocol()
var uri = new URI("http://example.org/foo/hello.html"); // get protocol uri.protocol(); // returns string "http" // set protocol uri.protocol("ftp"); // returns the URI instance for chaining // relative scheme uri.protocol(""); uri.toString() === "//example.org/foo/hello.html";
NOTE: Throws a TypeError
on illegal input, that is anything but [a-z0-9.+-]
and [empty string]
and null
var uri = new URI("http://user:[email protected]/foo/hello.html"); // get username uri.username(); // returns string "user" // set username uri.username("user"); // returns the URI instance for chaining
var uri = new URI("http://user:[email protected]/foo/hello.html"); // get password uri.password(); // returns string "pass" // set password uri.password("user"); // returns the URI instance for chaining
var uri = new URI("http://example.org/foo/hello.html"); // get protocol uri.hostname(); // returns string // set protocol uri.hostname("example.org"); // returns the URI instance for chaining
NOTE: .hostname() returns the actual hostname, whereas .host() returns the hostname including the port
var uri = new URI("http://example.org:8080/foo/hello.html"); // get port uri.port(); // returns string "8080" // set port uri.port("80"); // returns the URI instance for chaining
NOTE: although the port may be considered an integer, within URI it is a string.
NOTE: Throws a TypeError
on illegal input
var uri = new URI("http://example.org:80/foo/hello.html"); // get host uri.host(); // returns string "example.org:80" // set host uri.host("example.org:80"); // returns the URI instance for chaining
NOTE: .hostname() returns the actual hostname, whereas .host() returns the hostname including the port
Userinfo is comprised of username and password
var uri = new URI("http://user:[email protected]:88/foo/hello.html"); // get userinfo uri.userinfo(); // returns string "user:pass" // set userinfo uri.userinfo("user:pass"); // returns the URI instance for chaining
Authority is comprised of username, password, hostname and port
var uri = new URI("http://user:[email protected]:88/foo/hello.html"); // get authority uri.authority(); // returns string "user:[email protected]:88" // set authority uri.authority("user:[email protected]:80"); // returns the URI instance for chaining
NOTE: .authority() will reset any of username, password and port if they're not specified.
.domain() is a convenience method that returns example.org
from the hostname www.example.org
.
var uri = new URI("http://example.org/foo/hello.html"); // get domain uri.domain(); // returns string "example.org" // set domain uri.domain("otherdomain.com"); // returns the URI instance for chaining // Second Level Domain (SLD) Support (as of URI.js 1.5.0) uri = new URI("http://example.co.uk/foo/hello.html"); uri.domain(); // return string "example.co.uk" uri.domain(true); // return string "co.uk"
NOTE: .domain() will throw an error if you pass it an empty string.
NOTE: Throws a TypeError
on illegal input
.subdomain() is a convenience method that returns www
from the hostname www.example.org
.
var uri = new URI("http://www.example.org/foo/hello.html"); // get subdomain uri.subdomain(); // returns string "www" // set subdomain uri.subdomain("other.subdomain"); // returns the URI instance for chaining
NOTE: Throws a TypeError
on illegal input
.tld() is a convenience method that returns org
from the hostname www.example.org
.
var uri = new URI("http://example.org/foo/hello.html"); // get tld uri.tld(); // returns string "org" // set tld uri.tld("com"); // returns the URI instance for chaining // Second Level Domain (SLD) Support (as of URI.js 1.5.0) uri = new URI("http://example.co.uk/foo/hello.html"); uri.tld(); // return string "co.uk" uri.tld(true); // return string "uk"
NOTE: Throws an Error
if you pass it an empty string or use it on an IP-host.
.path() is an alias of .pathname()
var uri = new URI("http://example.org/foo/hello.html"); // get pathname uri.pathname(); // returns string "/foo/hello.html" // set pathname uri.pathname("/foo/hello.html"); // returns the URI instance for chaining // will encode for you uri.pathname("/hello world/"); uri.pathname() === "/hello%20world/"; // will decode for you uri.pathname(true) === "/hello world/";
.directory() is an convenience method for mutating the directory part of a path
var uri = new URI("http://example.org/foo/hello.html"); // get directory uri.directory(); // returns string "/foo" (no trailing slash) // set directory uri.directory("/bar"); // returns the URI instance for chaining // uri == "http://example.org/bar/hello.html" // will encode for you uri.directory("/hello world/"); uri.directory() === "/hello%20world"; // will decode for you uri.directory(true) === "/hello world"; uri.href("http://example.com/foo").directory() // -&t; "/" uri.href("/foo").directory() // -&t; "/" uri.href("foo").directory() // -&t; ""
.filename() is an convenience method for mutating the filename part of a path
var uri = new URI("http://example.org/foo/hello.html"); // get filename uri.filename(); // returns string "hello.html" (no leading slash) // set filename uri.filename("world.xml"); // returns the URI instance for chaining // uri == "http://example.org/bar/world.xml" // will encode for you uri.filename("hello world.html"); uri.filename() === "hello%20world.html"; // will decode for you uri.filename(true) === "hello world.html";
NOTE: If you pass ../file.html
, the directory will be changed accordingly
.suffix() is an convenience method for mutating the filename part of a path
var uri = new URI("http://example.org/foo/hello.html"); // get suffix uri.suffix(); // returns string "html" (no leading dot) // set suffix uri.suffix("xml"); // returns the URI instance for chaining // uri == "http://example.org/bar/world.xml" // will encode for you uri.suffix("würgh"); uri.suffix() === "w%C3%BCrgh"; // will decode for you uri.suffix(true) === "würgh";
.segment() allows convenient access to directory levels / URN segments within the path
var uri = new URI("http://example.org/foo/hello.html"); // get segments uri.segment(); // returns array ["foo", "hello.html"] // set segments uri.segment(["foo", "bar", "foobar.html"]); // -> http://example.org/foo/bar/foobar.html // get specific level uri.segment(0); // returns "foo" uri.segment(1); // returns "bar" uri.segment(-1); // returns "foobar.html" // set specific level uri.segment(0, "bar"); // -> http://example.org/bar/bar/foobar.html // remove specific level uri.segment(0, ""); // -> http://example.org/bar/foobar.html // append level uri.segment("appendthis"); // -> http://example.org/bar/foobar.html/appendthis
var uri = new URI("http://example.org/foo/hello.html?foo=bar&bar=baz"); // get search uri.search(); // returns string "?foo=bar&bar=baz" (leading ?) // get query uri.query(); // returns string "foo=bar&bar=baz" (no leading ?) // .query() and .search() behave the same for the following: // set search uri.search("?foo=bar&bar=baz"); // returns the URI instance for chaining uri.search("foo=bar&bar=baz"); // returns the URI instance for chaining // uri == "http://example.org/bar/world.html?foo=bar&bar=baz" uri.search(""); // returns the URI instance for chaining // uri == "http://example.org/bar/world.html" // get data map: uri.search(true); // returns { foo: "bar", hello : ["world", "mars"] } // set data map: uri.search({ foo: "bar", hello : ["world", "mars"] }); // uri == "http://example.org/bar/world.html?foo=bar&hello=world&hello=mars" // CAUTION: beware of arrays, the following are not quite the same // If you're dealing with PHP, you probably want the latter… uri.search("?foo=bar&bar=baz"); uri.search("?foo=bar[]&bar[]=baz");
Note that names and values passed in an object are encoded automatically. The object, resulting from parsing the query string, contains decoded values
Hint: If you're using jQuery, have a look at their .serialize() function.
var uri = new URI("http://example.org/foo/hello.html#world"); // get hash uri.hash(); // returns string "#world" (leading #) // get fragment uri.fragment(); // returns string "world" (no leading #) // .hash() and .fragment() behave the same for the following: // set hash uri.hash("#mars"); // returns the URI instance for chaining uri.hash("mars"); // returns the URI instance for chaining // uri == "http://example.org/bar/world.xml#mars"
.is() tells what a URL is. It responds with a boolean and can be asked the following questions:
relative
true
if URL doesn't have a hostnameabsolute
true
if URL has a hostnameurn
true
if URI is a URNurl
true
if URI is a URLdomain
, name
true
if hostname is not an IPsld
true
if hostname is a second level domain (i.e. "example.co.uk")idn
true
if hostname contains non-alphanumeric characters and is not an IPpunycode
true
if hostname contains xn--
ip
true
if hostname is IPv4 or IPv6ip4
, ipv4
, inet4
true
if hostname is IPv4ip6
, ipv6
, inet6
true
if hostname is IPv6var uri = new URI("http://example.org/"); uri.is("relative") === false; uri.is("absolute") === true; uri.is("urn") === false; uri.is("url") === true; uri.is("name") === true; uri.is("sld") === false; uri.is("punycode") === false; uri.is("IDN") === false; // case doesn't matter uri.is("idn") === false; // case doesn't matter uri.is("ip") === false; var uri = new URI("http://123.123.123.123/"); uri.is("relative") === false; uri.is("absolute") === true; uri.is("urn") === false; uri.is("url") === true; uri.is("name") === false; uri.is("sld") === false; uri.is("IP") === true; uri.is("IPv4") === true; uri.is("IPv6") === false; var uri = new URI("http://fe80:0000:0000:0000:0204:61ff:fe9d:f156/"); uri.is("IP") === true; uri.is("IPv4") === false; uri.is("IPv6") === true; var uri = new URI("/hello/world.html"); uri.is("relative") === true; uri.is("absolute") === false; uri.is("urn") === false; uri.is("url") === true; uri.is("name") === false; uri.is("IP") === false; var uri = new URI("http://example.co.uk/"); uri.is("name") === true; uri.is("sld") === true; var uri = new URI("mailto:[email protected]"); uri.is("relative") === false; uri.is("absolute") === false; uri.is("urn") === true; uri.is("url") === false; uri.is("name") === false; uri.is("sld") === false; uri.is("punycode") === false; uri.is("idn") === false; uri.is("ip") === false;
.addQuery() is an alias of .addSearch()
var uri = new URI("?hello=world"); uri.addSearch("hello", "mars"); // returns the URI instance for chaining // uri == "?hello=world&hello=mars" uri.addSearch({ foo: "bar", goodbye : ["world", "mars"] }); // uri == "?hello=world&hello=mars&foo=bar&goodbye=world&goodbye=mars" // CAUTION: beware of arrays, the following are not quite the same // If you're dealing with PHP, you probably want the latter… uri.addSearch("foo", ["bar", "baz"]); uri.addSearch("foo[]", ["bar", "baz"]);
Note that names and values passed in are encoded automatically.
.removeQuery() is an alias of .removeSearch()
var uri = new URI("?hello=world&hello=mars&foo=bar"); // remove an attribute uri.removeSearch("hello"); // returns the URI instance for chaining // uri == "?foo=bar" // remove an attribute with value filter uri.search("?hello=world&hello=mars&foo=bar"); uri.removeSearch("hello", "world"); // returns the URI instance for chaining // uri == "?hello=mars&foo=bar" // remove multiple values uri.search("?hello=world&hello=mars&foo=bar&mine=true"); uri.removeSearch(["hello", "foo"]); // uri == "?mine=true" // remove multiple values with value filter uri.search("?hello=world&hello=mars&foo=bar&mine=true&a=1&a=2&a=3"); uri.removeSearch({hello: "world", foo: undefined, a: ["1", "3"]}); // uri == "?hello=mars&mine=true&a=2"
There are virtually no limits to what you might do with fragments (hash). Every system has their own bag of tricks. As a result URI.js cannot offer any of the following tools right out of the box. The most common abuse of fragments are storing URLs or query string like data.
The file src/URI.fragmentQuery.js is a "plugin" that allows you to store data in hashes in the same manner the .query() functions provide.
var uri = new URI("#?hello=world"); uri.addFragment("hello", "mars"); // returns the URI instance for chaining // uri == "#?hello=world&hello=mars"
The file src/URI.fragmentURI.js is a "plugin" that allows you to store URLs in hashes.
var uri = URI("http://example.org/#!/foo/bar/baz.html"), furi = uri.fragment(true); // manipulating the fragment URI furi.pathname() === "/foo/bar/baz.html"; furi.pathname("/hello.html"); // has direct effect on the actual URI uri.toString() === "http://example.org/#!/hello.html"
executes normalizeProtocol(), normalizeHostname(), normalizePort(), normalizePath(), normalizeSearch(), normalizeHash()
var uri = new URI("hTTp://www.example.org/"); // normalize protocol uri.normalizeProtocol(); // returns the URI instance for chaining // uri == "http://www.example.org/"
For IDN conversion punycode.js must be available (bundled in URI.js). For IPv6-best-notation conversion IPv6.js must be available (bundled in URI.js). Also lower-cases hostnames.
var uri = new URI("http://www.exämple.org/"); // normalize IDN host uri.normalizeHostname(); // returns the URI instance for chaining // uri == "http://www.xn--exmple-cua.org/" // normalize IPv6 host uri.hostname("fe80:0000:0000:0000:0204:61ff:fe9d:f156"); uri.normalizeHostname(); // returns the URI instance for chaining // uri == "http://fe80::204:61ff:fe9d:f156/" // normalize hostname to lower case uri.hostname("wWw.eXample.Org"); uri.normalizeHostname(); // returns the URI instance for chaining // uri == "http://www.example.org/"
There is no .normalizeHost(), as .host() is a property comprised of .hostname() and .port()
Removes the port, if it's the default for the given protocol (http: 80, https: 443, ftp: 21).
The list of default ports can be modified at URI.defaultPorts
var uri = new URI("http://example.org:80/foo.html"); // normalize port uri.normalizePort(); // returns the URI instance for chaining // uri == "http://example.org/foo.html"
.normalizePath() is an alias of .normalizePathname(), they resolve relative hierarchies
var uri = new URI("/hello/foo/woo/.././../world.html"); // normalize path uri.normalizePathname(); // returns the URI instance for chaining // uri == "/hello/world.html"
Turns ?&foo=bar&&foo=bar&foo=baz&
into ?foo=bar&foo=baz
and removes ? if there is no query string.
var uri = new URI("?&foo=bar&&foo=bar&foo=baz&"); // normalize search uri.normalizeSearch(); // returns the URI instance for chaining // uri == "?foo=bar&foo=baz"
removes # if there is no hash
var uri = new URI("http://example.org/foo/hello.html#"); // normalize hash uri.normalizeHash(); // returns the URI instance for chaining // uri == "http://example.org/bar/world.xml"
.iso8859() converts unicode-encoded escape sequences to ISO8859-encoded escape sequences. It does this by calling .normalize() internally.
var uri = new URI("/%C3%A4.html"); uri.iso8859(); // returns the URI instance for chaining // uri == "/%E4.html"
NOTE: You can make URI work with ISO8859 encoding by default by calling URI.iso8859().
.unicode() converts ISO8859-encoded escape sequences to unicode-encoded escape sequences. It does this by calling .normalize() internally.
var uri = new URI("/%E4.html"); uri.unicode(); // returns the URI instance for chaining // uri == "/%C3%A4.html"
Formats URLs to be human readable (much like your browser does nowadays).
var uri = new URI("http://foo:[email protected]/" + "hello%20world/ä.html?foo%5B%5D=b+är#fragment"); u.readable() === "http://www.exämple.org/" + "hello world/ä.html?foo[]=b är#fragment";
.relativeTo() compares two paths an makes one relative to the other
var uri = new URI("/relative/path"); // make path relative var relUri = uri.relativeTo("/relative/sub/foo/sub/file"); // returns a new URI instance // relUri == "../../../path" // also works for relative directories URI("../world.html").absoluteTo("foo/bar/baz.html"); // -> "foo/world.html" // absolute URLs are passed through unchanged URI("http://example.org/world.html").absoluteTo("http://google.com/baz"); // -> "http://example.org/world.html" // absolute URLs relative to absolute URLs URI("http://example.org/world.html").clone().authority("").absoluteTo("http://google.com/baz"); // -> "http://google.com/world.html"
.relativeTo() and .absoluteTo() reverse each other.
.absoluteTo() makes a relative path absolute based on another path
var uri = new URI("../../../path"); // make path absolute var relUri = uri.absoluteTo("/relative/sub/foo/sub/file"); // returns a new URI instance // relUri == "/relative/path"
.relativeTo() and .absoluteTo() reverse each other.
.equals() determines if the given URLs are the same - disregarding default ports, capitalization, dot-pathnames, query-parameter order, etc.
var a = "http://example.org/foo/bar.html" + "?foo=bar&hello=world&hello=mars#fragment", b; // normalizing URI before comparison: b = "http://exAMPle.org:80/foo/../foo/bar.html" + "?foo=bar&hello=world&hello=mars#fragment"; a !== b; URI(a).equals(b) === true; // comparing query string parameters: b = "http://example.org/foo/bar.html" + "?hello=mars&foo=bar&hello=world&#fragment"; a !== b; URI(a).equals(b) === true; // shorthand for comparing to window.location.href: URI(a).equals();
parses a string into its URI components. returns an object containing the found components
var result = URI.parse("http://example.org/foo.html"); result === { protocol: "http", username: null, password: null, hostname: "example.org", port: null, path: "/foo.html", query: null, fragment: null };
parses a string's beginning into its URI components username, password, hostname, port.
Found components are appended to the parts
parameter.
Remaining string is returned
var parts = {}, result = URI.parseAuthority("user:[email protected]:8080/foo.html", parts); result === "/foo.html"; parts === { username: "user", password: "pass", hostname: "example.org", port: "8080" };
parses a string's beginning into its URI components username, password.
Found components are appended to the parts
parameter.
Remaining string is returned
var parts = {}, result = URI.parseUserinfo("user:[email protected]:8080/foo.html", parts); result === "example.org:8080/foo.html"; parts === { username: "user", password: "pass" };
parses a string's beginning into its URI components hostname, port.
Found components are appended to the parts
parameter.
Remaining string is returned
var parts = {}, result = URI.parseAuthority("example.org:8080/foo.html", parts); result === "/foo.html"; parts === { hostname: "example.org", port: "8080" };
Parses the passed query string into an object. Returns object {propertyName: propertyValue}
var result = URI.parseQuery("?foo=bar&hello=world&hello=mars&bam=&yup"); result === { foo: "bar", hello: ["world", "mars"], bam: "", yup: null };
serializes the URI components passed in parts
into a URI string
var parts = { protocol: "http", username: null, password: null, hostname: "example.org", port: null, path: "/foo.html", query: null, fragment: null }; URI.build(parts) === "http://example.org/foo.html";
serializes the URI components username, password, hostname, port passed in parts
into a URI string
var parts = { username: "user", password: "pass", hostname: "example.org", port: "8080" }; URI.buildAuthority(parts) === "user:[email protected]:8080";
serializes the URI components username, password passed in parts
into a URI string
var parts = { username: "user", password: "pass" }; URI.buildUserinfo(parts) === "user:pass@";
serializes the URI components hostname, port passed in parts
into a URI string
var parts = { hostname: "example.org", port: "8080" }; URI.buildHost(parts) === "example.org:8080";
serializes the query string parameters
var data = { foo: "bar", hello: ["world", "mars", "mars"], bam: "", yup: null, removed: undefined } // Note: duplicate hello=mars is dropped (default behavior!) URI.buildQuery(data) === "foo=bar&hello=world&hello=mars&bam=&yup"; // Note: duplicate hello=mars is preserved URI.buildQuery(data, true) === "foo=bar&hello=world&hello=mars&hello=mars&bam=&yup";
To preserve duplicate values, use URI.buildQuery() directly:
var uri = new URI("http://example.org/foo.html?bar=baz"), data = uri.query(true); data.some = "new data"; uri.query(URI.buildQuery(data, true)); // you can also use the static URI.addQuery() and URI.removeQuery() URI.addQuery(data, "hello", "world"); uri.query(URI.buildQuery(data, true));
adds data to a map
var data = {}; URI.addQuery(data, "hello", "mars"); data === {hello: "mars"}; URI.addQuery(data, "hello", "world"); data === {hello: ["mars", "world"]}; URI.addQuery(data, {foo: "bar", goodbye : ["world", "mars"]}); data === {hello: ["mars", "world"], foo: "bar", goodbye : ["world", "mars"]};
removes data from a map
var data === {hello: ["mars", "world"], foo: "bar", goodbye : ["world", "mars"]}; URI.removeQuery(data, "hello"); data === {foo: "bar", goodbye : ["world", "mars"]}; // remove an attribute with value filter data = {hello: ["world", "mars"], foo: "bar"}; URI.removeQuery(data, "hello", "world"); data === {hello: ["mars"], foo: "bar"} // yes, still an array // remove multiple values data = {hello: ["world", "mars"], foo: "bar", mine: "true"} URI.removeQuery(["hello", "foo"]); data === {mine: "true"}; // remove multiple values with value filter data = {hello: ["world", "mars"], foo: "bar", mine: "true", a: ["1", "2", "3"]} URI.removeQuery({hello: "world", foo: undefined, a: ["1", "3"]}); data === {hello: ["mars"], mine: "true", a: ["2"]}
URI.commonPath() determines the common base directory of two paths.
URI.commonPath("/foo/bar/baz.html", "/foo/bar/world.html"); // returns "/foo/bar/" URI.commonPath("/foo/bar/baz.html", "/foo/bazz/world.html"); // returns "/foo/" URI.commonPath("/foo/bar/baz.html", "/other/world.html"); // returns "/" URI.commonPath("/foo", "bar"); // returns ""
URI.withinString() identifies URIs within text, e.g. to translate them to <a>-Tags. (Obviously you'd want to put the urls inside the href-Attribute and escape them properly…)
var source = "Hello www.example.com,\n" + "http://google.com is a search engine, like http://www.bing.com\n" + "http://exämple.org/foo.html?baz=la#bumm is an IDN URL,\n" + "http://123.123.123.123/foo.html is IPv4 and " + "http://fe80:0000:0000:0000:0204:61ff:fe9d:f156/foobar.html is IPv6.\n" + "links can also be in parens (http://example.org) " + "or quotes »http://example.org«."; var result = URI.withinString(source, function(url) { // callback needs to return a string // feel free to URI(url).normalize().toString() or something return "<a>" + url + "</a>"; }); /* result is: Hello <a>www.example.com</a>, <a>http://google.com</a> is a search engine, like <a>http://www.bing.com</a> <a>http://exämple.org/foo.html?baz=la#bumm</a> is an IDN URL, <a>http://123.123.123.123/foo.html</a> is IPv4 and <a>http://fe80:0000:0000:0000:0204:61ff:fe9d:f156/foobar.html</a> is IPv6. links can also be in parens (<a>http://example.org</a>) or quotes »<a>http://example.org</a>«. */ // a proper replacement could look like the following: var escapeHtml = function(string) { return string .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """); } result = URI.withinString(source, function(url) { var uri = new URI(url); uri.normalize(); return "<a href="" + escapeHtml(uri) + "">" + escapeHtml(url.readable()) + "</a>"; });
URI.iso8859() tells URI.js to use the older escape/unescape methods, for backwards compatibility with non-unicode platforms.
URI.iso8859(); var uri = new URI("http://example.org/foo/æ.html"); // http://example.org/foo/%E6.html
URI.unicode() restores the default unicode-encoded URLs.
URI.unicode(); var uri = new URI("http://example.org/foo/æ.html"); // http://example.org/foo/%C3%A6.html
URI.expand() is a convenience wrapper for URITemplate
.
While URITemplate#expand
returns a string, URI.expand()
returns an URI
instance.
URI.expand("/foo/{var}/{iable}", { "var": "bar", "iable": "hello world.html" }); // returns URI("/foo/bar/hello%20world.html")