diff --git a/CHANGELOG.md b/CHANGELOG.md index 08f60e2e..9b5355b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,67 @@ The release notes tracked in this document are also made available on the [releases page](https://github.com/medialize/URI.js/releases) +### 1.19.5 (December 30th 2020) ### + +* dropping jquery.URI.js from minified bundle accidentally added since v1.19.3 - [Issue #404](https://github.com/medialize/URI.js/issues/404) + +### 1.19.4 (December 23rd 2020) ### + +* **SECURITY** fixing [`URI.parseAuthority()`](http://medialize.github.io/URI.js/docs.html#static-parseAuthority) to rewrite `\` to `/` as Node and Browsers do - followed up to by [alesandroortiz](https://github.com/alesandroortiz) in [PR #403](https://github.com/medialize/URI.js/issues/403), relates to [Issue #233](https://github.com/medialize/URI.js/pull/233) + +### 1.19.3 (December 20th 2020) ### + +* **SECURITY** fixing [`URI.parseAuthority()`](http://medialize.github.io/URI.js/docs.html#static-parseAuthority) to rewrite `\` to `/` as Node and Browsers do - disclosed privately by [alesandroortiz](https://github.com/alesandroortiz), relates to [Issue #233](https://github.com/medialize/URI.js/pull/233) + +### 1.19.2 (October 20th 2019) ### + +* fixing [`URI.build()`](http://medialize.github.io/URI.js/docs.html#static-build) to properly handle relative paths when a scheme is given - [Issue #387](https://github.com/medialize/URI.js/issues/387) +* fixing [`URI.buildQuery()`](http://medialize.github.io/URI.js/docs.html#static-buildQuery) to properly handle empty param name - [Issue #243](https://github.com/medialize/URI.js/issues/243), [PR #383](https://github.com/medialize/URI.js/issues/383) +* support Composer [PR #386](https://github.com/medialize/URI.js/issues/386) + +### 1.19.1 (February 10th 2018) ### + +* fixing [`.href()`](http://medialize.github.io/URI.js/docs.html#href) to parse `query` property - [Issue #366](https://github.com/medialize/URI.js/issues/366), [PR #367](https://github.com/medialize/URI.js/issues/367) + +### 1.19.0 (October 1st 2017) ### + +* adding `.setFragment()` to [query fragment plugin](http://medialize.github.io/URI.js/docs.html#fragment-abuse-query) - [Issue #338](https://github.com/medialize/URI.js/issues/338), [PR #356](https://github.com/medialize/URI.js/issues/356) +* adding setting [`URI.preventInvalidHostname`](http://medialize.github.io/URI.js/docs.html#setting-preventInvalidHostname) to control if an error should be thrown on invalid input - [Issue #352](https://github.com/medialize/URI.js/issues/352), [Issue #354](https://github.com/medialize/URI.js/issues/354), [Issue #355](https://github.com/medialize/URI.js/issues/355) - effectively making the changes of version 1.18.11 opt-in rather than default. + +### 1.18.12 (August 9th 2017) ### + +* making [`URI.parse()`](http://medialize.github.io/URI.js/docs.html#static-parse) allow `_` in hostname - [Issue #347](https://github.com/medialize/URI.js/issues/347), [PR #348](https://github.com/medialize/URI.js/issues/348) +* fixing [`URI.parse()`](http://medialize.github.io/URI.js/docs.html#static-parse) to not use `Number.isNumber()` for IE compatibility - [Issue #350](https://github.com/medialize/URI.js/issues/350), [PR #351](https://github.com/medialize/URI.js/issues/351) + +### 1.18.11 (August 8th 2017) ### + +* making [`URI.parse()`](http://medialize.github.io/URI.js/docs.html#static-parse) throw on invalid port and hostname - [Issue #344](https://github.com/medialize/URI.js/issues/344), [PR #345](https://github.com/medialize/URI.js/issues/345) + +### 1.18.10 (March 30th 2017) ### + +* adding support for [CentralNic](https://en.wikipedia.org/wiki/CentralNic#Second-level_domains) Second Level Domains - [Issue #333](https://github.com/medialize/URI.js/issues/333) + +### 1.18.9 (March 6th 2017) ### + +* adding option `strict` to [`URITemplate()`](http://medialize.github.io/URI.js/uri-template.html) in order to throw an exception in case a placeholder could not be replaced - [PR #330](https://github.com/medialize/URI.js/issues/330) + +### 1.18.8 (February 27th 2017) ### + +* fixing [`.absoluteTo()`](http://medialize.github.io/URI.js/docs.html#absoluteto) to not resolve URIs containing a scheme - [Issue #328](https://github.com/medialize/URI.js/issues/328) + +### 1.18.7 (February 13th 2017) ### + +* fixing [`URI.withinString()`](http://medialize.github.io/URI.js/docs.html#static-withinString) to ignore `www.` and `http://.` - [Issue #327](https://github.com/medialize/URI.js/issues/327) + +### 1.18.6 (February 10th 2017) ### + +* fixing [`URITemplate()`](http://medialize.github.io/URI.js/uri-template.html) to allow `'` (single quotes) in literals - [PR #326](https://github.com/medialize/URI.js/pull/326) + +### 1.18.5 (January 30th 2017) ### + +* prevent `new URI(null)` from blowing up - [PR #321](https://github.com/medialize/URI.js/issues/321) +* fixing [`URI.withinString()`](http://medialize.github.io/URI.js/docs.html#static-withinString) to properly handle fully contained parentheses - [Issue #325](https://github.com/medialize/URI.js/issues/325) + ### 1.18.4 (December 4th 2016) ### * fixing [`URI.withinString()`](http://medialize.github.io/URI.js/docs.html#static-withinString) to capture balanced parentheses - [Issue #247](https://github.com/medialize/URI.js/issues/247) diff --git a/README.md b/README.md index 06dbbffc..ecee39be 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ --- +> **IMPORTANT:** You **may not need URI.js** anymore! Modern browsers provide the [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) and [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) interfaces. + +--- + > **NOTE:** The npm package name changed to `urijs` --- @@ -114,7 +118,7 @@ URI("/foo/bar/baz.html") ### RequireJS ### -Clone the URI.js repository or use a package manager to get URI.js into your project. +Clone the URI.js repository or use a package manager to get URI.js into your project. ```javascript require.config({ diff --git a/bower.json b/bower.json index 6fbcff2e..f7d7a13b 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "urijs", - "version": "1.18.4", + "version": "1.19.5", "main": "src/URI.js", "ignore": [ ".*", diff --git a/build.js b/build.js index 0808d985..f16736ad 100644 --- a/build.js +++ b/build.js @@ -1,17 +1,17 @@ (function($, undefined){ window.URL = window.webkitURL || window.URL; window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder; - + function build(files) { var $out = $('#output'), $progress = $('#prog'), sources = [], connections = [], source; - + $out.parent().hide(); $progress.show().prop('value', 1).text('Loading Files'); - + for (var i = 0, length = files.length; i < length; i++) { sources.push(""); (function(i, file){ @@ -20,7 +20,7 @@ function build(files) { }, "text")); })(i, files[i]); } - + $.when.apply($, connections).done(function() { $progress.prop('value', 2).text('Compiling Scripts'); $.post('https://closure-compiler.appspot.com/compile', { @@ -29,13 +29,13 @@ function build(files) { output_format: "text", output_info: "compiled_code" }, function(data) { - var code = "/*! URI.js v1.18.4 http://medialize.github.io/URI.js/ */\n/* build contains: " + files.join(', ') + " */\n" + data; + var code = "/*! URI.js v1.19.5 http://medialize.github.io/URI.js/ */\n/* build contains: " + files.join(', ') + " */\n" + data; $progress.hide(); $out.val(code).parent().show(); $out.prev().find('a').remove(); $out.prev().prepend(download(code)); - }).error(function() { - alert("Your browser is incapable of cross-domain communication.\nPlease see instructions for manual build below."); + }).error(function() { + alert("Your browser is incapable of cross-domain communication.\nPlease see instructions for manual build below."); }); }); }; @@ -48,7 +48,7 @@ function download(code) { a.href = window.URL.createObjectURL(blob); a.textContent = 'Download'; a.dataset.downloadurl = ['text/javascript', a.download, a.href].join(':'); - + return a; }; @@ -70,7 +70,7 @@ $(function(){ var val = $(this).val(); val.length && files.push(val); }); - + build(files); }); }); diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..71a29072 --- /dev/null +++ b/composer.json @@ -0,0 +1,6 @@ +{ + "name": "medialize/uri.js", + "description": "URLs in JavaScript", + "type": "library", + "license": "MIT" +} diff --git a/docs.html b/docs.html index 31d58ed5..2feb9e43 100644 --- a/docs.html +++ b/docs.html @@ -5,7 +5,7 @@ URI.js - API Documentation - + @@ -30,7 +30,7 @@ Fork me on GitHub

URI.js

- + - + @@ -166,25 +166,25 @@

URI.js

Static Helper Functions - +

URI.js API

- +

URI Constructor

var uri = new URI(); // same as new URI(location.href)
 // string
@@ -204,10 +204,10 @@ 

URI Constructor

// resolving right in the constructor var uri = URI("../foobar.html", "http://example.org/hello/world.html"); -// which is exactly the same as +// 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 :
@@ -268,7 +268,7 @@ 

cloning URIs

uri2.tld("com"); uri == "http://example.org/"; uri2 == "http://example.com/";
- +

href()

get and set the entire URI

var uri = URI("http://example.com");
@@ -276,15 +276,15 @@ 

href()

uri.href("ftp://google.org"); uri.toString() === "ftp://google.org/"
- +

toString(), valueOf()

serialize the URI to string. valueOf() is an alias to toString(), as string is the base primitive.

var uri = URI("http://example.com");
 var s = uri.toString();
 typeof s === "string";
 s === "http://example.com/";
- - + +

protocol(), scheme()

.scheme() is an alias of .protocol()

var uri = new URI("http://example.org/foo/hello.html");
@@ -304,14 +304,14 @@ 

username()

uri.username(); // returns string "user" // set username uri.username("user"); // returns the URI instance for chaining
- +

password()

var uri = new URI("http://user:pass@example.org/foo/hello.html");
 // get password
 uri.password(); // returns string "pass"
 // set password
 uri.password("user"); // returns the URI instance for chaining
- +

hostname()

var uri = new URI("http://example.org/foo/hello.html");
 // get hostname
@@ -319,7 +319,7 @@ 

hostname()

// set hostname uri.hostname("example.org"); // returns the URI instance for chaining

.hostname() returns the actual hostname, whereas .host() returns the hostname including the port

- +

port()

var uri = new URI("http://example.org:8080/foo/hello.html");
 // get port
@@ -328,7 +328,7 @@ 

port()

uri.port("80"); // returns the URI instance for chaining

although the port may be considered an integer, within URI it is a string.

Throws a TypeError on illegal input

- +

host()

var uri = new URI("http://example.org:80/foo/hello.html");
 // get host
@@ -345,7 +345,7 @@ 

userinfo()

uri.userinfo(); // returns string "user:pass" // set userinfo uri.userinfo("user:pass"); // returns the URI instance for chaining
- +

authority()

Authority is comprised of username, password, hostname and port

var uri = new URI("http://user:pass@example.org:88/foo/hello.html");
@@ -384,7 +384,7 @@ 

domain()

uri.domain(true); // return string "co.uk"

.domain() will throw an error if you pass it an empty string.

Throws a TypeError on illegal input

- +

subdomain()

.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");
@@ -393,7 +393,7 @@ 

subdomain()

// set subdomain uri.subdomain("other.subdomain"); // returns the URI instance for chaining

Throws a TypeError on illegal input

- +

tld()

.tld() is a convenience method that returns org from the hostname www.example.org.

var uri = new URI("http://example.org/foo/hello.html");
@@ -427,7 +427,7 @@ 

pathname(), path()

URI("/").path() === "/"; URI("http://example.org").path() === "/";
- +

directory()

.directory() is an convenience method for mutating the directory part of a path

var uri = new URI("http://example.org/foo/hello.html");
@@ -449,7 +449,7 @@ 

directory()

// -&t; "/" uri.href("foo").directory() // -&t; ""
- +

filename()

.filename() is an convenience method for mutating the filename part of a path

var uri = new URI("http://example.org/foo/hello.html");
@@ -465,7 +465,7 @@ 

filename()

// will decode for you uri.filename(true) === "hello world.html";

If you pass ../file.html, the directory will be changed accordingly

- +

suffix()

.suffix() is an convenience method for mutating the filename part of a path

var uri = new URI("http://example.org/foo/hello.html");
@@ -480,7 +480,7 @@ 

suffix()

uri.suffix() === "w%C3%BCrgh"; // will decode for you uri.suffix(true) === "würgh";
- +

segment()

.segment() allows convenient access to directory levels / URN segments within the path. See .segmentCoded() for an interface that transparently encodes and decodes path segments.

var uri = new URI("http://example.org/foo/hello.html");
@@ -520,8 +520,8 @@ 

segmentCoded()

// append level uri.segmentCoded("append this"); // -> http://example.org/bar/foobar.html/append%20this
- - + +
var uri = new URI("http://example.org/foo/hello.html?foo=bar&bar=baz");
 // get search
@@ -563,7 +563,7 @@ 
 // 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. +

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.

@@ -584,7 +584,7 @@

hash(), fragment()

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" - +

resource()

Resource is comprised of path, query and fragment

var uri = new URI("http://example.org/foo/hello.html?query=string#hash");
@@ -659,10 +659,10 @@ 

is()

uri.is("punycode") === false; uri.is("idn") === false; uri.is("ip") === false;
- - + +

Working with the query string

- +

setSearch(), setQuery()

.setQuery() is an alias of .setSearch()

var uri = new URI("?hello=world");
@@ -680,7 +680,7 @@ 

setSearch(), setQuery()

uri.setSearch("foo", ["bar", "baz"]); uri.setSearch("foo[]", ["bar", "baz"]);

Note that names and values passed in are encoded automatically.

- +

addSearch(), addQuery()

.addQuery() is an alias of .addSearch()

var uri = new URI("?hello=world");
@@ -698,7 +698,7 @@ 

addSearch(), addQuery()

uri.addSearch("foo", ["bar", "baz"]); uri.addSearch("foo[]", ["bar", "baz"]);

Note that names and values passed in are encoded automatically.

- +

removeSearch(), removeQuery()

.removeQuery() is an alias of .removeSearch()

var uri = new URI("?hello=world&hello=mars&foo=bar");
@@ -714,7 +714,7 @@ 

removeSearch(), removeQuery()

// remove multiple values uri.search("?hello=world&hello=mars&foo=bar&mine=true"); uri.removeSearch(["hello", "foo"]); -// uri == "?mine=true" +// 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"); @@ -775,27 +775,27 @@

hasSearch(), hasQuery()

Working with the Fragment (Hash)

- There are virtually no limits to what you might do with fragments (hash). - Every system has their own bag of tricks. + 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.

- +

- Usually a prefix is used to identify data with special meaning. This prefix can be pretty much what you want. - For URIs it's usually ! and for query-like data it often is ?. + Usually a prefix is used to identify data with special meaning. This prefix can be pretty much what you want. + For URIs it's usually ! and for query-like data it often is ?. But they don't have to, which is why you can define a global default: URI.fragmentPrefix = "$";

Query String Fragments

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"
 
 // to change the fragment prefix on an instance level:
-uri.fragmentPrefix("!"); 
+uri.fragmentPrefix("!");
 
 // to change the fragment prefix on a global level:
 URI.fragmentPrefix = "!";
@@ -803,7 +803,7 @@

Query String Fragments

URL Fragments

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");
 var furi = uri.fragment(true);
 
@@ -815,22 +815,22 @@ 

URL Fragments

uri.toString() === "http://example.org/#!/hello.html" // to change the fragment prefix on an instance level: -uri.fragmentPrefix("?"); +uri.fragmentPrefix("?"); // to change the fragment prefix on a global level: URI.fragmentPrefix = "?";
- +

Normalizing URLs

- +

normalize()

executes normalizeProtocol(), normalizeHostname(), normalizePort(), normalizePath(), normalizeSearch(), normalizeHash()

- +

normalizeProtocol()

var uri = new URI("hTTp://www.example.org/");
 // normalize protocol
 uri.normalizeProtocol(); // returns the URI instance for chaining
 // uri == "http://www.example.org/"
- +

normalizeHostname()

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.

@@ -878,7 +878,7 @@

normalizeHash(), normalizeFragment()

// normalize hash uri.normalizeHash(); // returns the URI instance for chaining // uri == "http://example.org/bar/world.xml"
- +

Charsets / Encodings

@@ -894,10 +894,10 @@

unicode()

var uri = new URI("/%E4.html");
 uri.unicode(); // returns the URI instance for chaining
 // uri == "/%C3%A4.html"
- - + +

Formatting URLs

- +

readable()

Formats URLs to be human readable (much like your browser does nowadays).

var uri = new URI("http://foo:bar@www.xn--exmple-cua.org/"
@@ -955,10 +955,10 @@ 

absoluteTo()

u.absoluteTo('https://'); // -> "https://example.com/path"

.relativeTo() and .absoluteTo() reverse each other.

- +

Comparing URLs

- +

equals()

.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"
@@ -982,52 +982,52 @@ 

equals()

// shorthand for comparing to window.location.href: URI(a).equals();
- +

Parsing URLs

- +

URI.parse(string url)

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, 
+  protocol: "http",
+  username: null,
+  password: null,
   hostname: "example.org",
   port: null,
   path: "/foo.html",
   query: null,
   fragment: null
 };
- +

URI.parseAuthority(string url, object parts)

-

parses a string's beginning into its URI components username, password, hostname, port. +

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 = {};
 var result = URI.parseAuthority("user:pass@example.org:8080/foo.html", parts);
 result === "/foo.html";
 parts === {
-  username: "user", 
-  password: "pass", 
+  username: "user",
+  password: "pass",
   hostname: "example.org",
   port: "8080"
 };
- +

URI.parseUserinfo(string url, object parts)

-

parses a string's beginning into its URI components username, password. +

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 = {};
 var result = URI.parseUserinfo("user:pass@example.org:8080/foo.html", parts);
 result === "example.org:8080/foo.html";
 parts === {
-  username: "user", 
+  username: "user",
   password: "pass"
 };
- +

URI.parseHost(string url, object parts)

-

parses a string's beginning into its URI components hostname, port. +

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 = {};
@@ -1037,7 +1037,18 @@ 

URI.parseHost(string url, object par hostname: "example.org", port: "8080" };

- +

As of v1.19.0 you can activate host name validation (disabled by default):

+
URI.preventInvalidHostname = true;
+var parts = {};
+var result = URI.parseHost("/foo.html", parts);
+// throws TypeError
+
+URI.preventInvalidHostname = false;
+var uri = new URI('http://example.org/')
+  .preventInvalidHostname(true)
+  .hostname('')
+// throws TypeError
+

URI.parseQuery(string querystring)

Parses the passed query string into an object. Returns object {propertyName: propertyValue}

var result = URI.parseQuery("?foo=bar&hello=world&hello=mars&bam=&yup");
@@ -1047,15 +1058,15 @@ 

URI.parseQuery(string querystring)

bam: "", yup: null };
- +

Serializing URLs

- +

URI.build(object parts)

serializes the URI components passed in parts into a URI string

var parts = {
-  protocol: "http", 
-  username: null, 
-  password: null, 
+  protocol: "http",
+  username: null,
+  password: null,
   hostname: "example.org",
   port: null,
   path: "/foo.html",
@@ -1063,12 +1074,12 @@ 

URI.build(object parts)

fragment: null }; URI.build(parts) === "http://example.org/foo.html";
- +

URI.buildAuthority(object parts)

serializes the URI components username, password, hostname, port passed in parts into a URI string

var parts = {
-  username: "user", 
-  password: "pass", 
+  username: "user",
+  password: "pass",
   hostname: "example.org",
   port: "8080"
 };
@@ -1077,11 +1088,11 @@ 

URI.buildAuthority(object parts)

URI.buildUserinfo(object parts)

serializes the URI components username, password passed in parts into a URI string

var parts = {
-  username: "user", 
+  username: "user",
   password: "pass"
 };
 URI.buildUserinfo(parts) === "user:pass@";
- +

URI.buildHost(object parts)

serializes the URI components hostname, port passed in parts into a URI string

var parts = {
@@ -1089,7 +1100,7 @@ 

URI.buildHost(object parts)

port: "8080" }; URI.buildHost(parts) === "example.org:8080";
- +

URI.buildQuery(object data, [boolean duplicateQueryParameters], [boolean escapeQuerySpace])

serializes the query string parameters

var data = {
@@ -1115,7 +1126,7 @@ 

URI.buildQuery(object data, [booleanURI.addQuery() and URI.removeQuery() URI.addQuery(data, "hello", "world"); uri.query(URI.buildQuery(data, true));

- +

As of v1.8.0 you can configure query parameter de/duplication:

// make all new URI instances allow duplicates:
 URI.duplicateQueryParameters = true; // default is false
@@ -1151,29 +1162,29 @@ 

URI.buildQuery(object data, [boolean - +

Encoding and Decoding URLs

- +

URI.encode()

Encode an URI component with strict compliance to RFC3986

URI.encode("hä lo#w*rl:d!") === "h%C3%A4%20lo%23w%2Arl%3Ad%21";
 // vs.
 encodeURIComponent("hä lo#w*rl:d!") === "h%C3%A4%20lo%23w*rl%3Ad!";
 // not how * and ! were not encoded
- +

URI.decode()

Decode an URI component

URI.decode("h%C3%A4%20lo%23w%2Arl%3Ad%21") === "hä lo#w*rl:d!";
 // note:
 URI.decode === decodeURIComponent;
- +

URI.encodeReserved()

Encode an URI component whilst preserving reserved characters

URI.encodeReserved("ä:/?#[]@!$&'()*+,;=") === "%C3%A4:/?#[]@!$&'()*+,;=";
 // vs.
-URI.encode("ä:/?#[]@!$&'()*+,;=") === 
+URI.encode("ä:/?#[]@!$&'()*+,;=") ===
   "%C3%A4%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D";
- +

URI.encodeQuery()

Encode a query string component. Works like encode(), except it handles %20 as + (space) if URI.escapeQuerySpace = true;.

URI.escapeQuerySpace = true; // default
@@ -1184,7 +1195,7 @@ 

URI.encodeQuery()

// vs. URI.encode(" ") === "%20";
- +

URI.decodeQuery()

Decode a query string component. Works like decode(), except it handles + as %20 (space) if URI.escapeQuerySpace = true;.

URI.escapeQuerySpace = true; // default
@@ -1195,8 +1206,8 @@ 

URI.decodeQuery()

// vs. URI.decode("+") === "+";
- - + +

Static Helper Functions

URI.noConflict()

@@ -1210,7 +1221,7 @@

URI.noConflict()

// restore all objects and return them as a map {URI: ..., IPv6: ..., ....} URI.noConflict(true);
- +

URI.addQuery()

adds data to a map

var data = {};
@@ -1223,7 +1234,7 @@ 

URI.addQuery()

URI.addQuery(data, {foo: "bar", goodbye : ["world", "mars"]}); data === {hello: ["mars", "world"], foo: "bar", goodbye : ["world", "mars"]};
- +

URI.removeQuery()

removes data from a map

var data === {hello: ["mars", "world"], foo: "bar", goodbye : ["world", "mars"]};
@@ -1260,8 +1271,8 @@ 

URI.commonPath()

URI.commonPath("/foo", "bar"); // returns ""
-

URI.joinPath()

-

URI.joinPath() composes a path from directory tokens.

+

URI.joinPaths()

+

URI.joinPaths() composes a path from directory tokens.

URI.joinPaths('/a/b', '/c', 'd', '/e');
 // returns URI("/a/b/c/d/e")
 
@@ -1303,7 +1314,7 @@ 

URI.withinString()

*/ // a proper replacement could look like the following: -var escapeHtml = function(string) { +var escapeHtml = function(string) { return string .replace(/&/g, "&amp;") .replace(/</g, "&lt;") @@ -1313,7 +1324,7 @@

URI.withinString()

var result = URI.withinString(source, function(url) { var uri = new URI(url); uri.normalize(); - return "<a href="" + escapeHtml(uri) + "">" + return "<a href="" + escapeHtml(uri) + "">" + escapeHtml(uri.readable()) + "</a>"; });
@@ -1368,23 +1379,23 @@

URI.withinString()

/* result is: That <code>example.com/</code> is just a domain */
- +

URI.iso8859()

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()

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()

-

URI.expand() is a convenience wrapper for URITemplate. +

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",
@@ -1393,7 +1404,7 @@ 

URI.expand()

// returns URI("/foo/bar/hello%20world.html")
- +
diff --git a/package.json b/package.json index c94e3969..dd71db7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "urijs", - "version": "1.18.4", + "version": "1.19.5", "title": "URI.js - Mutating URLs", "author": { "name": "Rodney Rehm", diff --git a/src/IPv6.js b/src/IPv6.js index 915f76fa..26f84d1a 100644 --- a/src/IPv6.js +++ b/src/IPv6.js @@ -2,7 +2,7 @@ * URI.js - Mutating URLs * IPv6 Support * - * Version: 1.18.4 + * Version: 1.19.5 * * Author: Rodney Rehm * Web: http://medialize.github.io/URI.js/ @@ -174,7 +174,7 @@ if (root.IPv6 === this) { root.IPv6 = _IPv6; } - + return this; } diff --git a/src/SecondLevelDomains.js b/src/SecondLevelDomains.js index c42d1aa6..125d1c5a 100644 --- a/src/SecondLevelDomains.js +++ b/src/SecondLevelDomains.js @@ -2,7 +2,7 @@ * URI.js - Mutating URLs * Second Level Domain (SLD) Support * - * Version: 1.18.4 + * Version: 1.19.5 * * Author: Rodney Rehm * Web: http://medialize.github.io/URI.js/ @@ -173,7 +173,12 @@ 'ye':' co com gov ltd me net org plc ', 'yu':' ac co edu gov org ', 'za':' ac agric alt bourse city co cybernet db edu gov grondar iaccess imt inca landesign law mil net ngo nis nom olivetti org pix school tm web ', - 'zm':' ac co com edu gov net org sch ' + 'zm':' ac co com edu gov net org sch ', + // https://en.wikipedia.org/wiki/CentralNic#Second-level_domains + 'com': 'ar br cn de eu gb gr hu jpn kr no qc ru sa se uk us uy za ', + 'net': 'gb jp se uk ', + 'org': 'ae', + 'de': 'com ' }, // gorhill 2013-10-25: Using indexOf() instead Regexp(). Significant boost // in both performance and memory footprint. No initialization required. diff --git a/src/URI.fragmentQuery.js b/src/URI.fragmentQuery.js index 4a73441d..1b8391c8 100644 --- a/src/URI.fragmentQuery.js +++ b/src/URI.fragmentQuery.js @@ -19,6 +19,10 @@ // uri.toString() === "http://example.org/#?bar=foo&name=value"; // uri.removeFragment("name"); // uri.toString() === "http://example.org/#?bar=foo"; +// uri.setFragment("name", "value1"); +// uri.toString() === "http://example.org/#?bar=foo&name=value1"; +// uri.setFragment("name", "value2"); +// uri.toString() === "http://example.org/#?bar=foo&name=value2"; (function (root, factory) { 'use strict'; @@ -57,12 +61,12 @@ p.fragment = function(v, build) { var prefix = this._parts.fragmentPrefix; var fragment = this._parts.fragment || ''; - + if (v === true) { if (fragment.substring(0, prefix.length) !== prefix) { return {}; } - + return URI.parseQuery(fragment.substring(prefix.length)); } else if (v !== undefined && typeof v !== 'string') { this._parts.fragment = prefix + URI.buildQuery(v); @@ -96,9 +100,22 @@ this.build(!build); return this; }; + p.setFragment = function(name, value, build) { + var prefix = this._parts.fragmentPrefix; + var data = URI.parseQuery((this._parts.fragment || '').substring(prefix.length)); + URI.setQuery(data, name, value); + this._parts.fragment = prefix + URI.buildQuery(data); + if (typeof name !== 'string') { + build = value; + } + + this.build(!build); + return this; + }; p.addHash = p.addFragment; p.removeHash = p.removeFragment; + p.setHash = p.setFragment; // extending existing object rather than defining something new return URI; -})); \ No newline at end of file +})); diff --git a/src/URI.js b/src/URI.js index 30dcc876..757507d5 100644 --- a/src/URI.js +++ b/src/URI.js @@ -1,7 +1,7 @@ /*! * URI.js - Mutating URLs * - * Version: 1.18.4 + * Version: 1.19.5 * * Author: Rodney Rehm * Web: http://medialize.github.io/URI.js/ @@ -61,6 +61,12 @@ } } + if (url === null) { + if (_urlSupplied) { + throw new TypeError('null is not a valid argument for URI'); + } + } + this.href(url); // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor @@ -71,7 +77,11 @@ return this; } - URI.version = '1.18.4'; + function isInteger(value) { + return /^[0-9]+$/.test(value); + } + + URI.version = '1.19.5'; var p = URI.prototype; var hasOwn = Object.prototype.hasOwnProperty; @@ -191,17 +201,22 @@ query: null, fragment: null, // state + preventInvalidHostname: URI.preventInvalidHostname, duplicateQueryParameters: URI.duplicateQueryParameters, escapeQuerySpace: URI.escapeQuerySpace }; }; + // state: throw on invalid hostname + // see https://github.com/medialize/URI.js/pull/345 + // and https://github.com/medialize/URI.js/issues/354 + URI.preventInvalidHostname = false; // state: allow duplicate query parameters (a=1&a=1) URI.duplicateQueryParameters = false; // state: replaces + with %20 (space in query strings) URI.escapeQuerySpace = true; // static properties URI.protocol_expression = /^[a-z][a-z0-9.+-]*$/i; - URI.idn_expression = /[^a-z0-9\.-]/i; + URI.idn_expression = /[^a-z0-9\._-]/i; URI.punycode_expression = /(xn--)/i; // well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care? URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; @@ -234,10 +249,16 @@ ws: '80', wss: '443' }; + // list of protocols which always require a hostname + URI.hostProtocols = [ + 'http', + 'https' + ]; + // allowed hostname characters according to RFC 3986 // ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded - // I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . - - URI.invalid_hostname_characters = /[^a-zA-Z0-9\.-]/; + // I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . - _ + URI.invalid_hostname_characters = /[^a-zA-Z0-9\.\-:_]/; // map DOM Elements to their URI attribute URI.domAttributes = { 'a': 'href', @@ -469,7 +490,9 @@ URI.parse = function(string, parts) { var pos; if (!parts) { - parts = {}; + parts = { + preventInvalidHostname: URI.preventInvalidHostname + }; } // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment] @@ -522,6 +545,10 @@ return parts; }; URI.parseHost = function(string, parts) { + if (!string) { + string = ''; + } + // Copy chrome, IE, opera backslash-handling behavior. // Back slashes before the query string get converted to forward slashes // See: https://github.com/joyent/node/blob/386fd24f49b0e9d1a8a076592a404168faeecc34/lib/url.js#L115-L124 @@ -569,6 +596,14 @@ string = '/' + string; } + if (parts.preventInvalidHostname) { + URI.ensureValidHostname(parts.hostname, parts.protocol); + } + + if (parts.port) { + URI.ensureValidPort(parts.port); + } + return string.substring(pos) || '/'; }; URI.parseAuthority = function(string, parts) { @@ -577,17 +612,22 @@ }; URI.parseUserinfo = function(string, parts) { // extract username:password + var _string = string + var firstBackSlash = string.indexOf('\\'); + if (firstBackSlash !== -1) { + string = string.replace(/\\/g, '/') + } var firstSlash = string.indexOf('/'); var pos = string.lastIndexOf('@', firstSlash > -1 ? firstSlash : string.length - 1); var t; - // authority@ must come before /path + // authority@ must come before /path or \path if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) { t = string.substring(0, pos).split(':'); parts.username = t[0] ? URI.decode(t[0]) : null; t.shift(); parts.password = t[0] ? URI.decode(t.join(':')) : null; - string = string.substring(pos + 1); + string = _string.substring(pos + 1); } else { parts.username = null; parts.password = null; @@ -634,6 +674,7 @@ URI.build = function(parts) { var t = ''; + var requireAbsolutePath = false if (parts.protocol) { t += parts.protocol + ':'; @@ -641,12 +682,13 @@ if (!parts.urn && (t || parts.hostname)) { t += '//'; + requireAbsolutePath = true } t += (URI.buildAuthority(parts) || ''); if (typeof parts.path === 'string') { - if (parts.path.charAt(0) !== '/' && typeof parts.hostname === 'string') { + if (parts.path.charAt(0) !== '/' && requireAbsolutePath) { t += '/'; } @@ -709,7 +751,7 @@ var t = ''; var unique, key, i, length; for (key in data) { - if (hasOwn.call(data, key) && key) { + if (hasOwn.call(data, key)) { if (isArray(data[key])) { unique = {}; for (i = 0, length = data[key].length; i < length; i++) { @@ -758,6 +800,21 @@ throw new TypeError('URI.addQuery() accepts an object, string as the name parameter'); } }; + + URI.setQuery = function(data, name, value) { + if (typeof name === 'object') { + for (var key in name) { + if (hasOwn.call(name, key)) { + URI.setQuery(data, key, name[key]); + } + } + } else if (typeof name === 'string') { + data[name] = value === undefined ? null : value; + } else { + throw new TypeError('URI.setQuery() accepts an object, string as the name parameter'); + } + }; + URI.removeQuery = function(data, name, value) { var i, length, key; @@ -978,11 +1035,17 @@ } if (parensEnd > -1) { - slice = slice.slice(0, parensEnd) + slice.slice(parensEnd + 1).replace(_trim, ''); + slice = slice.slice(0, parensEnd) + slice.slice(parensEnd).replace(_trim, ''); } else { slice = slice.replace(_trim, ''); } + if (slice.length <= match[0].length) { + // the extract only contains the starting marker of a URI, + // e.g. "www" or "http://" + continue; + } + if (options.ignore && options.ignore.test(slice)) { continue; } @@ -1003,22 +1066,44 @@ return string; }; - URI.ensureValidHostname = function(v) { + URI.ensureValidHostname = function(v, protocol) { // Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986) // they are not part of DNS and therefore ignored by URI.js - if (v.match(URI.invalid_hostname_characters)) { + var hasHostname = !!v; // not null and not an empty string + var hasProtocol = !!protocol; + var rejectEmptyHostname = false; + + if (hasProtocol) { + rejectEmptyHostname = arrayContains(URI.hostProtocols, protocol); + } + + if (rejectEmptyHostname && !hasHostname) { + throw new TypeError('Hostname cannot be empty, if protocol is ' + protocol); + } else if (v && v.match(URI.invalid_hostname_characters)) { // test punycode if (!punycode) { - throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-] and Punycode.js is not available'); + throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_] and Punycode.js is not available'); } - if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) { - throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); + throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_]'); } } }; + URI.ensureValidPort = function (v) { + if (!v) { + return; + } + + var port = Number(v); + if (isInteger(port) && (port > 0) && (port < 65536)) { + return; + } + + throw new TypeError('Port "' + v + '" is not a valid port'); + }; + // noConflict URI.noConflict = function(removeAll) { if (removeAll) { @@ -1163,10 +1248,14 @@ } else if (_URI || _object) { var src = _URI ? href._parts : href; for (key in src) { + if (key === 'query') { continue; } if (hasOwn.call(this._parts, key)) { this._parts[key] = src[key]; } } + if (src.query) { + this.query(src.query, false); + } } else { throw new TypeError('invalid input'); } @@ -1247,16 +1336,15 @@ var _hostname = p.hostname; p.protocol = function(v, build) { - if (v !== undefined) { - if (v) { - // accept trailing :// - v = v.replace(/:(\/\/)?$/, ''); + if (v) { + // accept trailing :// + v = v.replace(/:(\/\/)?$/, ''); - if (!v.match(URI.protocol_expression)) { - throw new TypeError('Protocol "' + v + '" contains characters other than [A-Z0-9.+-] or doesn\'t start with [A-Z]'); - } + if (!v.match(URI.protocol_expression)) { + throw new TypeError('Protocol "' + v + '" contains characters other than [A-Z0-9.+-] or doesn\'t start with [A-Z]'); } } + return _protocol.call(this, v, build); }; p.scheme = p.protocol; @@ -1276,9 +1364,7 @@ v = v.substring(1); } - if (v.match(/[^0-9]/)) { - throw new TypeError('Port "' + v + '" contains characters other than [0-9]'); - } + URI.ensureValidPort(v); } } return _port.call(this, v, build); @@ -1289,14 +1375,18 @@ } if (v !== undefined) { - var x = {}; + var x = { preventInvalidHostname: this._parts.preventInvalidHostname }; var res = URI.parseHost(v, x); if (res !== '/') { throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); } v = x.hostname; + if (this._parts.preventInvalidHostname) { + URI.ensureValidHostname(v, this._parts.protocol); + } } + return _hostname.call(this, v, build); }; @@ -1414,8 +1504,12 @@ v += '.'; } + if (v.indexOf(':') !== -1) { + throw new TypeError('Domains cannot contain colons'); + } + if (v) { - URI.ensureValidHostname(v); + URI.ensureValidHostname(v, this._parts.protocol); } this._parts.hostname = this._parts.hostname.replace(replace, v); @@ -1454,7 +1548,11 @@ throw new TypeError('cannot set domain empty'); } - URI.ensureValidHostname(v); + if (v.indexOf(':') !== -1) { + throw new TypeError('Domains cannot contain colons'); + } + + URI.ensureValidHostname(v, this._parts.protocol); if (!this._parts.hostname || this.is('IP')) { this._parts.hostname = v; @@ -1565,7 +1663,7 @@ return v === undefined ? '' : this; } - if (v === undefined || v === true) { + if (typeof v !== 'string') { if (!this._parts.path || this._parts.path === '/') { return ''; } @@ -2065,7 +2163,10 @@ base = new URI(base); } - if (!resolved._parts.protocol) { + if (resolved._parts.protocol) { + // Directly returns even if this._parts.hostname is empty. + return resolved; + } else { resolved._parts.protocol = base._parts.protocol; } @@ -2225,6 +2326,11 @@ }; // state + p.preventInvalidHostname = function(v) { + this._parts.preventInvalidHostname = !!v; + return this; + }; + p.duplicateQueryParameters = function(v) { this._parts.duplicateQueryParameters = !!v; return this; diff --git a/src/URI.min.js b/src/URI.min.js index dfdbc3be..452fa68f 100644 --- a/src/URI.min.js +++ b/src/URI.min.js @@ -1,14 +1,14 @@ -/*! URI.js v1.18.4 http://medialize.github.io/URI.js/ */ +/*! URI.js v1.19.5 http://medialize.github.io/URI.js/ */ /* build contains: IPv6.js, punycode.js, SecondLevelDomains.js, URI.js, URITemplate.js */ -(function(f,l){"object"===typeof module&&module.exports?module.exports=l():"function"===typeof define&&define.amd?define(l):f.IPv6=l(f)})(this,function(f){var l=f&&f.IPv6;return{best:function(g){g=g.toLowerCase().split(":");var k=g.length,b=8;""===g[0]&&""===g[1]&&""===g[2]?(g.shift(),g.shift()):""===g[0]&&""===g[1]?g.shift():""===g[k-1]&&""===g[k-2]&&g.pop();k=g.length;-1!==g[k-1].indexOf(".")&&(b=7);var m;for(m=0;mf;f++)if("0"===k[0]&&1f&&(k=h,f=l)):"0"===g[m]&&(q=!0,h=m,l=1);l>f&&(k=h,f=l);1=f&&h>>10&1023|55296),b=56320|b&1023);return e+=r(b)}).join("")}function z(b,e){return b+22+75*(26>b)-((0!=e)<<5)}function v(b,e,h){var g=0;b=h?p(b/700):b>>1;for(b+=p(b/e);455d&&(d=0);for(n=0;n=h&&l("invalid-input");w=b.charCodeAt(d++); -w=10>w-48?w-22:26>w-65?w-65:26>w-97?w-97:36;(36<=w||w>p((2147483647-f)/g))&&l("overflow");f+=w*g;k=t<=c?1:t>=c+26?26:t-c;if(wp(2147483647/w)&&l("overflow");g*=w}g=e.length+1;c=v(f-n,g,0==n);p(f/g)>2147483647-a&&l("overflow");a+=p(f/g);f%=g;e.splice(f++,0,a)}return m(e)}function q(e){var h,g,f,k,a,c,d,n,t,w=[],q,m,A;e=b(e);q=e.length;h=128;g=0;a=72;for(c=0;ct&&w.push(r(t));for((f=k=w.length)&&w.push("-");f=h&&tp((2147483647-g)/m)&&l("overflow");g+=(d-h)*m;h=d;for(c=0;c=a+26?26:d-a;if(n= 0x80 (not a basic code point)","invalid-input":"Invalid input"},p=Math.floor,r=String.fromCharCode,B;x={version:"1.3.2",ucs2:{decode:b,encode:m},decode:h,encode:q,toASCII:function(b){return k(b,function(b){return y.test(b)?"xn--"+q(b):b})},toUnicode:function(b){return k(b,function(b){return E.test(b)?h(b.slice(4).toLowerCase()):b})}};if("function"== -typeof define&&"object"==typeof define.amd&&define.amd)define("punycode",function(){return x});else if(A&&C)if(module.exports==A)C.exports=x;else for(B in x)x.hasOwnProperty(B)&&(A[B]=x[B]);else f.punycode=x})(this); -(function(f,l){"object"===typeof module&&module.exports?module.exports=l():"function"===typeof define&&define.amd?define(l):f.SecondLevelDomains=l(f)})(this,function(f){var l=f&&f.SecondLevelDomains,g={list:{ac:" com gov mil net org ",ae:" ac co gov mil name net org pro sch ",af:" com edu gov net org ",al:" com edu gov mil net org ",ao:" co ed gv it og pb ",ar:" com edu gob gov int mil net org tur ",at:" ac co gv or ",au:" asn com csiro edu gov id net org ",ba:" co com edu gov mil net org rs unbi unmo unsa untz unze ", +(function(r,x){"object"===typeof module&&module.exports?module.exports=x():"function"===typeof define&&define.amd?define(x):r.IPv6=x(r)})(this,function(r){var x=r&&r.IPv6;return{best:function(k){k=k.toLowerCase().split(":");var m=k.length,d=8;""===k[0]&&""===k[1]&&""===k[2]?(k.shift(),k.shift()):""===k[0]&&""===k[1]?k.shift():""===k[m-1]&&""===k[m-2]&&k.pop();m=k.length;-1!==k[m-1].indexOf(".")&&(d=7);var q;for(q=0;qE;E++)if("0"===m[0]&&1E&&(m=h,E=A)):"0"===k[q]&&(p=!0,h=q,A=1);A>E&&(m=h,E=A);1=J&&C>>10&1023|55296),t=56320|t&1023);return C+=g(t)}).join("")}function E(l,t,C){var y=0;l=C?v(l/700):l>>1;for(l+=v(l/t);455c&&(c=0);for(a=0;a=C&&x("invalid-input");var f=l.charCodeAt(c++);f=10>f-48?f-22:26>f-65?f-65:26>f-97?f-97:36; +(36<=f||f>v((2147483647-y)/e))&&x("overflow");y+=f*e;var n=b<=M?1:b>=M+26?26:b-M;if(fv(2147483647/f)&&x("overflow");e*=f}e=t.length+1;M=E(y-a,e,0==a);v(y/e)>2147483647-J&&x("overflow");J+=v(y/e);y%=e;t.splice(y++,0,J)}return q(t)}function h(l){var t,C,y,J=[];l=d(l);var M=l.length;var a=128;var b=0;var c=72;for(y=0;ye&&J.push(g(e))}for((t=C=J.length)&&J.push("-");t=a&&ev((2147483647-b)/n)&& +x("overflow");b+=(f-a)*n;a=f;for(y=0;y=c+26?26:f-c;if(ze)-0));z=v(I/z)}J.push(g(z+22+75*(26>z)-0));c=E(b,n,t==C);b=0;++t}++b;++a}return J.join("")}var p="object"==typeof exports&&exports&&!exports.nodeType&&exports,D="object"==typeof module&&module&&!module.nodeType&&module,u="object"==typeof global&&global;if(u.global===u||u.window===u|| +u.self===u)r=u;var K=/^xn--/,F=/[^\x20-\x7E]/,w=/[\x2E\u3002\uFF0E\uFF61]/g,H={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},v=Math.floor,g=String.fromCharCode,B;var G={version:"1.3.2",ucs2:{decode:d,encode:q},decode:A,encode:h,toASCII:function(l){return m(l,function(t){return F.test(t)?"xn--"+h(t):t})},toUnicode:function(l){return m(l,function(t){return K.test(t)?A(t.slice(4).toLowerCase()): +t})}};if("function"==typeof define&&"object"==typeof define.amd&&define.amd)define("punycode",function(){return G});else if(p&&D)if(module.exports==p)D.exports=G;else for(B in G)G.hasOwnProperty(B)&&(p[B]=G[B]);else r.punycode=G})(this); +(function(r,x){"object"===typeof module&&module.exports?module.exports=x():"function"===typeof define&&define.amd?define(x):r.SecondLevelDomains=x(r)})(this,function(r){var x=r&&r.SecondLevelDomains,k={list:{ac:" com gov mil net org ",ae:" ac co gov mil name net org pro sch ",af:" com edu gov net org ",al:" com edu gov mil net org ",ao:" co ed gv it og pb ",ar:" com edu gob gov int mil net org tur ",at:" ac co gv or ",au:" asn com csiro edu gov id net org ",ba:" co com edu gov mil net org rs unbi unmo unsa untz unze ", bb:" biz co com edu gov info net org store tv ",bh:" biz cc com edu gov info net org ",bn:" com edu gov net org ",bo:" com edu gob gov int mil net org tv ",br:" adm adv agr am arq art ato b bio blog bmd cim cng cnt com coop ecn edu eng esp etc eti far flog fm fnd fot fst g12 ggf gov imb ind inf jor jus lel mat med mil mus net nom not ntr odo org ppg pro psc psi qsl rec slg srv tmp trd tur tv vet vlog wiki zlg ",bs:" com edu gov net org ",bz:" du et om ov rg ",ca:" ab bc mb nb nf nl ns nt nu on pe qc sk yk ", ck:" biz co edu gen gov info net org ",cn:" ac ah bj com cq edu fj gd gov gs gx gz ha hb he hi hl hn jl js jx ln mil net nm nx org qh sc sd sh sn sx tj tw xj xz yn zj ",co:" com edu gov mil net nom org ",cr:" ac c co ed fi go or sa ",cy:" ac biz com ekloges gov ltd name net org parliament press pro tm ","do":" art com edu gob gov mil net org sld web ",dz:" art asso com edu gov net org pol ",ec:" com edu fin gov info med mil net org pro ",eg:" com edu eun gov mil name net org sci ",er:" com edu gov ind mil net org rochest w ", es:" com edu gob nom org ",et:" biz com edu gov info name net org ",fj:" ac biz com info mil name net org pro ",fk:" ac co gov net nom org ",fr:" asso com f gouv nom prd presse tm ",gg:" co net org ",gh:" com edu gov mil org ",gn:" ac com gov net org ",gr:" com edu gov mil net org ",gt:" com edu gob ind mil net org ",gu:" com edu gov net org ",hk:" com edu gov idv net org ",hu:" 2000 agrar bolt casino city co erotica erotika film forum games hotel info ingatlan jogasz konyvelo lakas media news org priv reklam sex shop sport suli szex tm tozsde utazas video ", @@ -20,71 +20,74 @@ ps:" com edu gov net org plo sec ",pw:" belau co ed go ne or ",ro:" arts com fir tw:" club com ebiz edu game gov idv mil net org ",mu:" ac co com gov net or org ",mz:" ac co edu gov org ",na:" co com ",nz:" ac co cri geek gen govt health iwi maori mil net org parliament school ",pa:" abo ac com edu gob ing med net nom org sld ",pt:" com edu gov int net nome org publ ",py:" com edu gov mil net org ",qa:" com edu gov mil net org ",re:" asso com nom ",ru:" ac adygeya altai amur arkhangelsk astrakhan bashkiria belgorod bir bryansk buryatia cbg chel chelyabinsk chita chukotka chuvashia com dagestan e-burg edu gov grozny int irkutsk ivanovo izhevsk jar joshkar-ola kalmykia kaluga kamchatka karelia kazan kchr kemerovo khabarovsk khakassia khv kirov koenig komi kostroma kranoyarsk kuban kurgan kursk lipetsk magadan mari mari-el marine mil mordovia mosreg msk murmansk nalchik net nnov nov novosibirsk nsk omsk orenburg org oryol penza perm pp pskov ptz rnd ryazan sakhalin samara saratov simbirsk smolensk spb stavropol stv surgut tambov tatarstan tom tomsk tsaritsyn tsk tula tuva tver tyumen udm udmurtia ulan-ude vladikavkaz vladimir vladivostok volgograd vologda voronezh vrn vyatka yakutia yamal yekaterinburg yuzhno-sakhalinsk ", rw:" ac co com edu gouv gov int mil net ",sa:" com edu gov med net org pub sch ",sd:" com edu gov info med net org tv ",se:" a ac b bd c d e f g h i k l m n o org p parti pp press r s t tm u w x y z ",sg:" com edu gov idn net org per ",sn:" art com edu gouv org perso univ ",sy:" com edu gov mil net news org ",th:" ac co go in mi net or ",tj:" ac biz co com edu go gov info int mil name net nic org test web ",tn:" agrinet com defense edunet ens fin gov ind info intl mincom nat net org perso rnrt rns rnu tourism ", tz:" ac co go ne or ",ua:" biz cherkassy chernigov chernovtsy ck cn co com crimea cv dn dnepropetrovsk donetsk dp edu gov if in ivano-frankivsk kh kharkov kherson khmelnitskiy kiev kirovograd km kr ks kv lg lugansk lutsk lviv me mk net nikolaev od odessa org pl poltava pp rovno rv sebastopol sumy te ternopil uzhgorod vinnica vn zaporizhzhe zhitomir zp zt ",ug:" ac co go ne or org sc ",uk:" ac bl british-library co cym gov govt icnet jet lea ltd me mil mod national-library-scotland nel net nhs nic nls org orgn parliament plc police sch scot soc ", -us:" dni fed isa kids nsn ",uy:" com edu gub mil net org ",ve:" co com edu gob info mil net org web ",vi:" co com k12 net org ",vn:" ac biz com edu gov health info int name net org pro ",ye:" co com gov ltd me net org plc ",yu:" ac co edu gov org ",za:" ac agric alt bourse city co cybernet db edu gov grondar iaccess imt inca landesign law mil net ngo nis nom olivetti org pix school tm web ",zm:" ac co com edu gov net org sch "},has:function(f){var b=f.lastIndexOf(".");if(0>=b||b>=f.length-1)return!1; -var k=f.lastIndexOf(".",b-1);if(0>=k||k>=b-1)return!1;var l=g.list[f.slice(b+1)];return l?0<=l.indexOf(" "+f.slice(k+1,b)+" "):!1},is:function(f){var b=f.lastIndexOf(".");if(0>=b||b>=f.length-1||0<=f.lastIndexOf(".",b-1))return!1;var k=g.list[f.slice(b+1)];return k?0<=k.indexOf(" "+f.slice(0,b)+" "):!1},get:function(f){var b=f.lastIndexOf(".");if(0>=b||b>=f.length-1)return null;var k=f.lastIndexOf(".",b-1);if(0>=k||k>=b-1)return null;var l=g.list[f.slice(b+1)];return!l||0>l.indexOf(" "+f.slice(k+ -1,b)+" ")?null:f.slice(k+1)},noConflict:function(){f.SecondLevelDomains===this&&(f.SecondLevelDomains=l);return this}};return g}); -(function(f,l){"object"===typeof module&&module.exports?module.exports=l(require("./punycode"),require("./IPv6"),require("./SecondLevelDomains")):"function"===typeof define&&define.amd?define(["./punycode","./IPv6","./SecondLevelDomains"],l):f.URI=l(f.punycode,f.IPv6,f.SecondLevelDomains,f)})(this,function(f,l,g,k){function b(a,c){var d=1<=arguments.length,n=2<=arguments.length;if(!(this instanceof b))return d?n?new b(a,c):new b(a):new b;if(void 0===a){if(d)throw new TypeError("undefined is not a valid argument for URI"); -a="undefined"!==typeof location?location.href+"":""}this.href(a);return void 0!==c?this.absoluteTo(c):this}function m(a){return a.replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")}function z(a){return void 0===a?"Undefined":String(Object.prototype.toString.call(a)).slice(8,-1)}function v(a){return"Array"===z(a)}function h(a,c){var d={},b,t;if("RegExp"===z(c))d=null;else if(v(c))for(b=0,t=c.length;b]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u2018\u2019]))/ig;b.findUri={start:/\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi,end:/[\s\r\n]|$/,trim:/[`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u201e\u2018\u2019]+$/,parens:/(\([^\)]*\)|\[[^\]]*\]|\{[^}]*\}|<[^>]*>)/g};b.defaultPorts={http:"80",https:"443",ftp:"21", -gopher:"70",ws:"80",wss:"443"};b.invalid_hostname_characters=/[^a-zA-Z0-9\.-]/;b.domAttributes={a:"href",blockquote:"cite",link:"href",base:"href",script:"src",form:"action",img:"src",area:"href",iframe:"src",embed:"src",source:"src",track:"src",input:"src",audio:"src",video:"src"};b.getDomAttribute=function(a){if(a&&a.nodeName){var c=a.nodeName.toLowerCase();if("input"!==c||"image"===a.type)return b.domAttributes[c]}};b.encode=x;b.decode=decodeURIComponent;b.iso8859=function(){b.encode=escape;b.decode= -unescape};b.unicode=function(){b.encode=x;b.decode=decodeURIComponent};b.characters={pathname:{encode:{expression:/%(24|26|2B|2C|3B|3D|3A|40)/ig,map:{"%24":"$","%26":"&","%2B":"+","%2C":",","%3B":";","%3D":"=","%3A":":","%40":"@"}},decode:{expression:/[\/\?#]/g,map:{"/":"%2F","?":"%3F","#":"%23"}}},reserved:{encode:{expression:/%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,map:{"%3A":":","%2F":"/","%3F":"?","%23":"#","%5B":"[","%5D":"]","%40":"@","%21":"!","%24":"$","%26":"&","%27":"'", -"%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",","%3B":";","%3D":"="}}},urnpath:{encode:{expression:/%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig,map:{"%21":"!","%24":"$","%27":"'","%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",","%3B":";","%3D":"=","%40":"@"}},decode:{expression:/[\/\?#:]/g,map:{"/":"%2F","?":"%3F","#":"%23",":":"%3A"}}}};b.encodeQuery=function(a,c){var d=b.encode(a+"");void 0===c&&(c=b.escapeQuerySpace);return c?d.replace(/%20/g,"+"):d};b.decodeQuery=function(a,c){a+="";void 0===c&& -(c=b.escapeQuerySpace);try{return b.decode(c?a.replace(/\+/g,"%20"):a)}catch(d){return a}};var r={encode:"encode",decode:"decode"},B,F=function(a,c){return function(d){try{return b[c](d+"").replace(b.characters[a][c].expression,function(d){return b.characters[a][c].map[d]})}catch(n){return d}}};for(B in r)b[B+"PathSegment"]=F("pathname",r[B]),b[B+"UrnPathSegment"]=F("urnpath",r[B]);r=function(a,c,d){return function(n){var t;t=d?function(a){return b[c](b[d](a))}:b[c];n=(n+"").split(a);for(var e=0, -f=n.length;eb)return a.charAt(0)===c.charAt(0)&&"/"===a.charAt(0)?"/":"";if("/"!==a.charAt(b)||"/"!==c.charAt(b))b=a.substring(0,b).lastIndexOf("/");return a.substring(0,b+1)};b.withinString=function(a,c,d){d||(d={}); -var e=d.start||b.findUri.start,f=d.end||b.findUri.end,h=d.trim||b.findUri.trim,g=d.parens||b.findUri.parens,q=/[a-z0-9-]=["']?$/i;for(e.lastIndex=0;;){var k=e.exec(a);if(!k)break;k=k.index;if(d.ignoreHtml){var l=a.slice(Math.max(k-3,0),k);if(l&&q.test(l))continue}for(var p=k+a.slice(k).search(f),l=a.slice(k,p),p=-1;;){var m=g.exec(l);if(!m)break;p=Math.max(p,m.index+m[0].length)}l=-1d.length)return this._parts.hostname;d=this._parts.hostname.length-this.tld(c).length-1;d=this._parts.hostname.lastIndexOf(".",d-1)+1;return this._parts.hostname.substring(d)||""}if(!a)throw new TypeError("cannot set domain empty");b.ensureValidHostname(a); -!this._parts.hostname||this.is("IP")?this._parts.hostname=a:(d=new RegExp(m(this.domain())+"$"),this._parts.hostname=this._parts.hostname.replace(d,a));this.build(!c);return this};e.tld=function(a,c){if(this._parts.urn)return void 0===a?"":this;"boolean"===typeof a&&(c=a,a=void 0);if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var d=this._parts.hostname.lastIndexOf("."),d=this._parts.hostname.substring(d+1);return!0!==c&&g&&g.list[d.toLowerCase()]?g.get(this._parts.hostname)||d:d}if(a)if(a.match(/[^a-zA-Z0-9-]/))if(g&& -g.is(a))d=new RegExp(m(this.tld())+"$"),this._parts.hostname=this._parts.hostname.replace(d,a);else throw new TypeError('TLD "'+a+'" contains characters other than [A-Z0-9]');else{if(!this._parts.hostname||this.is("IP"))throw new ReferenceError("cannot set TLD on non-domain host");d=new RegExp(m(this.tld())+"$");this._parts.hostname=this._parts.hostname.replace(d,a)}else throw new TypeError("cannot set TLD empty");this.build(!c);return this};e.directory=function(a,c){if(this._parts.urn)return void 0=== -a?"":this;if(void 0===a||!0===a){if(!this._parts.path&&!this._parts.hostname)return"";if("/"===this._parts.path)return"/";var d=this._parts.path.length-this.filename().length-1,d=this._parts.path.substring(0,d)||(this._parts.hostname?"/":"");return a?b.decodePath(d):d}d=this._parts.path.length-this.filename().length;d=this._parts.path.substring(0,d);d=new RegExp("^"+m(d));this.is("relative")||(a||(a="/"),"/"!==a.charAt(0)&&(a="/"+a));a&&"/"!==a.charAt(a.length-1)&&(a+="/");a=b.recodePath(a);this._parts.path= -this._parts.path.replace(d,a);this.build(!c);return this};e.filename=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a||!0===a){if(!this._parts.path||"/"===this._parts.path)return"";var d=this._parts.path.lastIndexOf("/"),d=this._parts.path.substring(d+1);return a?b.decodePathSegment(d):d}d=!1;"/"===a.charAt(0)&&(a=a.substring(1));a.match(/\.?\//)&&(d=!0);var e=new RegExp(m(this.filename())+"$");a=b.recodePath(a);this._parts.path=this._parts.path.replace(e,a);d?this.normalizePath(c): -this.build(!c);return this};e.suffix=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a||!0===a){if(!this._parts.path||"/"===this._parts.path)return"";var d=this.filename(),e=d.lastIndexOf(".");if(-1===e)return"";d=d.substring(e+1);d=/^[a-z0-9%]+$/i.test(d)?d:"";return a?b.decodePathSegment(d):d}"."===a.charAt(0)&&(a=a.substring(1));if(d=this.suffix())e=a?new RegExp(m(d)+"$"):new RegExp(m("."+d)+"$");else{if(!a)return this;this._parts.path+="."+b.recodePath(a)}e&&(a=b.recodePath(a), -this._parts.path=this._parts.path.replace(e,a));this.build(!c);return this};e.segment=function(a,c,d){var b=this._parts.urn?":":"/",e=this.path(),f="/"===e.substring(0,1),e=e.split(b);void 0!==a&&"number"!==typeof a&&(d=c,c=a,a=void 0);if(void 0!==a&&"number"!==typeof a)throw Error('Bad segment "'+a+'", must be 0-based integer');f&&e.shift();0>a&&(a=Math.max(e.length+a,0));if(void 0===c)return void 0===a?e:e[a];if(null===a||void 0===e[a])if(v(c)){e=[];a=0;for(var h=c.length;a=d||d>=m.length-1)return!1;var q=m.lastIndexOf(".",d-1);if(0>=q||q>=d-1)return!1;var E=k.list[m.slice(d+1)];return E?0<=E.indexOf(" "+m.slice(q+1,d)+" "):!1},is:function(m){var d=m.lastIndexOf(".");if(0>=d||d>=m.length-1||0<=m.lastIndexOf(".",d-1))return!1;var q=k.list[m.slice(d+1)];return q?0<=q.indexOf(" "+m.slice(0,d)+" "):!1},get:function(m){var d=m.lastIndexOf(".");if(0>=d||d>=m.length-1)return null;var q=m.lastIndexOf(".",d-1); +if(0>=q||q>=d-1)return null;var E=k.list[m.slice(d+1)];return!E||0>E.indexOf(" "+m.slice(q+1,d)+" ")?null:m.slice(q+1)},noConflict:function(){r.SecondLevelDomains===this&&(r.SecondLevelDomains=x);return this}};return k}); +(function(r,x){"object"===typeof module&&module.exports?module.exports=x(require("./punycode"),require("./IPv6"),require("./SecondLevelDomains")):"function"===typeof define&&define.amd?define(["./punycode","./IPv6","./SecondLevelDomains"],x):r.URI=x(r.punycode,r.IPv6,r.SecondLevelDomains,r)})(this,function(r,x,k,m){function d(a,b){var c=1<=arguments.length,e=2<=arguments.length;if(!(this instanceof d))return c?e?new d(a,b):new d(a):new d;if(void 0===a){if(c)throw new TypeError("undefined is not a valid argument for URI"); +a="undefined"!==typeof location?location.href+"":""}if(null===a&&c)throw new TypeError("null is not a valid argument for URI");this.href(a);return void 0!==b?this.absoluteTo(b):this}function q(a){return a.replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")}function E(a){return void 0===a?"Undefined":String(Object.prototype.toString.call(a)).slice(8,-1)}function A(a){return"Array"===E(a)}function h(a,b){var c={},e;if("RegExp"===E(b))c=null;else if(A(b)){var f=0;for(e=b.length;f]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u2018\u2019]))/ig;d.findUri={start:/\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi,end:/[\s\r\n]|$/,trim:/[`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u201e\u2018\u2019]+$/,parens:/(\([^\)]*\)|\[[^\]]*\]|\{[^}]*\}|<[^>]*>)/g};d.defaultPorts={http:"80",https:"443",ftp:"21", +gopher:"70",ws:"80",wss:"443"};d.hostProtocols=["http","https"];d.invalid_hostname_characters=/[^a-zA-Z0-9\.\-:_]/;d.domAttributes={a:"href",blockquote:"cite",link:"href",base:"href",script:"src",form:"action",img:"src",area:"href",iframe:"src",embed:"src",source:"src",track:"src",input:"src",audio:"src",video:"src"};d.getDomAttribute=function(a){if(a&&a.nodeName){var b=a.nodeName.toLowerCase();if("input"!==b||"image"===a.type)return d.domAttributes[b]}};d.encode=F;d.decode=decodeURIComponent;d.iso8859= +function(){d.encode=escape;d.decode=unescape};d.unicode=function(){d.encode=F;d.decode=decodeURIComponent};d.characters={pathname:{encode:{expression:/%(24|26|2B|2C|3B|3D|3A|40)/ig,map:{"%24":"$","%26":"&","%2B":"+","%2C":",","%3B":";","%3D":"=","%3A":":","%40":"@"}},decode:{expression:/[\/\?#]/g,map:{"/":"%2F","?":"%3F","#":"%23"}}},reserved:{encode:{expression:/%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,map:{"%3A":":","%2F":"/","%3F":"?","%23":"#","%5B":"[","%5D":"]","%40":"@", +"%21":"!","%24":"$","%26":"&","%27":"'","%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",","%3B":";","%3D":"="}}},urnpath:{encode:{expression:/%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig,map:{"%21":"!","%24":"$","%27":"'","%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",","%3B":";","%3D":"=","%40":"@"}},decode:{expression:/[\/\?#:]/g,map:{"/":"%2F","?":"%3F","#":"%23",":":"%3A"}}}};d.encodeQuery=function(a,b){var c=d.encode(a+"");void 0===b&&(b=d.escapeQuerySpace);return b?c.replace(/%20/g,"+"):c};d.decodeQuery= +function(a,b){a+="";void 0===b&&(b=d.escapeQuerySpace);try{return d.decode(b?a.replace(/\+/g,"%20"):a)}catch(c){return a}};var G={encode:"encode",decode:"decode"},l,t=function(a,b){return function(c){try{return d[b](c+"").replace(d.characters[a][b].expression,function(e){return d.characters[a][b].map[e]})}catch(e){return c}}};for(l in G)d[l+"PathSegment"]=t("pathname",G[l]),d[l+"UrnPathSegment"]=t("urnpath",G[l]);G=function(a,b,c){return function(e){var f=c?function(I){return d[b](d[c](I))}:d[b]; +e=(e+"").split(a);for(var n=0,z=e.length;ne)return a.charAt(0)===b.charAt(0)&&"/"===a.charAt(0)?"/":"";if("/"!==a.charAt(e)||"/"!==b.charAt(e))e=a.substring(0,e).lastIndexOf("/");return a.substring(0,e+1)};d.withinString=function(a,b,c){c||(c={});var e=c.start||d.findUri.start,f=c.end||d.findUri.end,n=c.trim||d.findUri.trim,z= +c.parens||d.findUri.parens,I=/[a-z0-9-]=["']?$/i;for(e.lastIndex=0;;){var L=e.exec(a);if(!L)break;var P=L.index;if(c.ignoreHtml){var N=a.slice(Math.max(P-3,0),P);if(N&&I.test(N))continue}var O=P+a.slice(P).search(f);N=a.slice(P,O);for(O=-1;;){var Q=z.exec(N);if(!Q)break;O=Math.max(O,Q.index+Q[0].length)}N=-1b))throw new TypeError('Port "'+a+'" is not a valid port');}};d.noConflict=function(a){if(a)return a={URI:this.noConflict()},m.URITemplate&&"function"===typeof m.URITemplate.noConflict&&(a.URITemplate=m.URITemplate.noConflict()),m.IPv6&&"function"===typeof m.IPv6.noConflict&&(a.IPv6=m.IPv6.noConflict()),m.SecondLevelDomains&&"function"===typeof m.SecondLevelDomains.noConflict&&(a.SecondLevelDomains=m.SecondLevelDomains.noConflict()), +a;m.URI===this&&(m.URI=v);return this};g.build=function(a){if(!0===a)this._deferred_build=!0;else if(void 0===a||this._deferred_build)this._string=d.build(this._parts),this._deferred_build=!1;return this};g.clone=function(){return new d(this)};g.valueOf=g.toString=function(){return this.build(!1)._string};g.protocol=w("protocol");g.username=w("username");g.password=w("password");g.hostname=w("hostname");g.port=w("port");g.query=H("query","?");g.fragment=H("fragment","#");g.search=function(a,b){var c= +this.query(a,b);return"string"===typeof c&&c.length?"?"+c:c};g.hash=function(a,b){var c=this.fragment(a,b);return"string"===typeof c&&c.length?"#"+c:c};g.pathname=function(a,b){if(void 0===a||!0===a){var c=this._parts.path||(this._parts.hostname?"/":"");return a?(this._parts.urn?d.decodeUrnPath:d.decodePath)(c):c}this._parts.path=this._parts.urn?a?d.recodeUrnPath(a):"":a?d.recodePath(a):"/";this.build(!b);return this};g.path=g.pathname;g.href=function(a,b){var c;if(void 0===a)return this.toString(); +this._string="";this._parts=d._parts();var e=a instanceof d,f="object"===typeof a&&(a.hostname||a.path||a.pathname);a.nodeName&&(f=d.getDomAttribute(a),a=a[f]||"",f=!1);!e&&f&&void 0!==a.pathname&&(a=a.toString());if("string"===typeof a||a instanceof String)this._parts=d.parse(String(a),this._parts);else if(e||f){e=e?a._parts:a;for(c in e)"query"!==c&&B.call(this._parts,c)&&(this._parts[c]=e[c]);e.query&&this.query(e.query,!1)}else throw new TypeError("invalid input");this.build(!b);return this}; +g.is=function(a){var b=!1,c=!1,e=!1,f=!1,n=!1,z=!1,I=!1,L=!this._parts.urn;this._parts.hostname&&(L=!1,c=d.ip4_expression.test(this._parts.hostname),e=d.ip6_expression.test(this._parts.hostname),b=c||e,n=(f=!b)&&k&&k.has(this._parts.hostname),z=f&&d.idn_expression.test(this._parts.hostname),I=f&&d.punycode_expression.test(this._parts.hostname));switch(a.toLowerCase()){case "relative":return L;case "absolute":return!L;case "domain":case "name":return f;case "sld":return n;case "ip":return b;case "ip4":case "ipv4":case "inet4":return c; +case "ip6":case "ipv6":case "inet6":return e;case "idn":return z;case "url":return!this._parts.urn;case "urn":return!!this._parts.urn;case "punycode":return I}return null};var C=g.protocol,y=g.port,J=g.hostname;g.protocol=function(a,b){if(a&&(a=a.replace(/:(\/\/)?$/,""),!a.match(d.protocol_expression)))throw new TypeError('Protocol "'+a+"\" contains characters other than [A-Z0-9.+-] or doesn't start with [A-Z]");return C.call(this,a,b)};g.scheme=g.protocol;g.port=function(a,b){if(this._parts.urn)return void 0=== +a?"":this;void 0!==a&&(0===a&&(a=null),a&&(a+="",":"===a.charAt(0)&&(a=a.substring(1)),d.ensureValidPort(a)));return y.call(this,a,b)};g.hostname=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0!==a){var c={preventInvalidHostname:this._parts.preventInvalidHostname};if("/"!==d.parseHost(a,c))throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-]');a=c.hostname;this._parts.preventInvalidHostname&&d.ensureValidHostname(a,this._parts.protocol)}return J.call(this, +a,b)};g.origin=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0===a){var c=this.protocol();return this.authority()?(c?c+"://":"")+this.authority():""}c=d(a);this.protocol(c.protocol()).authority(c.authority()).build(!b);return this};g.host=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0===a)return this._parts.hostname?d.buildHost(this._parts):"";if("/"!==d.parseHost(a,this._parts))throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-]'); +this.build(!b);return this};g.authority=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0===a)return this._parts.hostname?d.buildAuthority(this._parts):"";if("/"!==d.parseAuthority(a,this._parts))throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-]');this.build(!b);return this};g.userinfo=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0===a){var c=d.buildUserinfo(this._parts);return c?c.substring(0,c.length-1):c}"@"!==a[a.length-1]&& +(a+="@");d.parseUserinfo(a,this._parts);this.build(!b);return this};g.resource=function(a,b){if(void 0===a)return this.path()+this.search()+this.hash();var c=d.parse(a);this._parts.path=c.path;this._parts.query=c.query;this._parts.fragment=c.fragment;this.build(!b);return this};g.subdomain=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var c=this._parts.hostname.length-this.domain().length-1;return this._parts.hostname.substring(0, +c)||""}c=this._parts.hostname.length-this.domain().length;c=this._parts.hostname.substring(0,c);c=new RegExp("^"+q(c));a&&"."!==a.charAt(a.length-1)&&(a+=".");if(-1!==a.indexOf(":"))throw new TypeError("Domains cannot contain colons");a&&d.ensureValidHostname(a,this._parts.protocol);this._parts.hostname=this._parts.hostname.replace(c,a);this.build(!b);return this};g.domain=function(a,b){if(this._parts.urn)return void 0===a?"":this;"boolean"===typeof a&&(b=a,a=void 0);if(void 0===a){if(!this._parts.hostname|| +this.is("IP"))return"";var c=this._parts.hostname.match(/\./g);if(c&&2>c.length)return this._parts.hostname;c=this._parts.hostname.length-this.tld(b).length-1;c=this._parts.hostname.lastIndexOf(".",c-1)+1;return this._parts.hostname.substring(c)||""}if(!a)throw new TypeError("cannot set domain empty");if(-1!==a.indexOf(":"))throw new TypeError("Domains cannot contain colons");d.ensureValidHostname(a,this._parts.protocol);!this._parts.hostname||this.is("IP")?this._parts.hostname=a:(c=new RegExp(q(this.domain())+ +"$"),this._parts.hostname=this._parts.hostname.replace(c,a));this.build(!b);return this};g.tld=function(a,b){if(this._parts.urn)return void 0===a?"":this;"boolean"===typeof a&&(b=a,a=void 0);if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var c=this._parts.hostname.lastIndexOf(".");c=this._parts.hostname.substring(c+1);return!0!==b&&k&&k.list[c.toLowerCase()]?k.get(this._parts.hostname)||c:c}if(a)if(a.match(/[^a-zA-Z0-9-]/))if(k&&k.is(a))c=new RegExp(q(this.tld())+"$"),this._parts.hostname= +this._parts.hostname.replace(c,a);else throw new TypeError('TLD "'+a+'" contains characters other than [A-Z0-9]');else{if(!this._parts.hostname||this.is("IP"))throw new ReferenceError("cannot set TLD on non-domain host");c=new RegExp(q(this.tld())+"$");this._parts.hostname=this._parts.hostname.replace(c,a)}else throw new TypeError("cannot set TLD empty");this.build(!b);return this};g.directory=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0===a||!0===a){if(!this._parts.path&& +!this._parts.hostname)return"";if("/"===this._parts.path)return"/";var c=this._parts.path.length-this.filename().length-1;c=this._parts.path.substring(0,c)||(this._parts.hostname?"/":"");return a?d.decodePath(c):c}c=this._parts.path.length-this.filename().length;c=this._parts.path.substring(0,c);c=new RegExp("^"+q(c));this.is("relative")||(a||(a="/"),"/"!==a.charAt(0)&&(a="/"+a));a&&"/"!==a.charAt(a.length-1)&&(a+="/");a=d.recodePath(a);this._parts.path=this._parts.path.replace(c,a);this.build(!b); +return this};g.filename=function(a,b){if(this._parts.urn)return void 0===a?"":this;if("string"!==typeof a){if(!this._parts.path||"/"===this._parts.path)return"";var c=this._parts.path.lastIndexOf("/");c=this._parts.path.substring(c+1);return a?d.decodePathSegment(c):c}c=!1;"/"===a.charAt(0)&&(a=a.substring(1));a.match(/\.?\//)&&(c=!0);var e=new RegExp(q(this.filename())+"$");a=d.recodePath(a);this._parts.path=this._parts.path.replace(e,a);c?this.normalizePath(b):this.build(!b);return this};g.suffix= +function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0===a||!0===a){if(!this._parts.path||"/"===this._parts.path)return"";var c=this.filename(),e=c.lastIndexOf(".");if(-1===e)return"";c=c.substring(e+1);c=/^[a-z0-9%]+$/i.test(c)?c:"";return a?d.decodePathSegment(c):c}"."===a.charAt(0)&&(a=a.substring(1));if(c=this.suffix())e=a?new RegExp(q(c)+"$"):new RegExp(q("."+c)+"$");else{if(!a)return this;this._parts.path+="."+d.recodePath(a)}e&&(a=d.recodePath(a),this._parts.path=this._parts.path.replace(e, +a));this.build(!b);return this};g.segment=function(a,b,c){var e=this._parts.urn?":":"/",f=this.path(),n="/"===f.substring(0,1);f=f.split(e);void 0!==a&&"number"!==typeof a&&(c=b,b=a,a=void 0);if(void 0!==a&&"number"!==typeof a)throw Error('Bad segment "'+a+'", must be 0-based integer');n&&f.shift();0>a&&(a=Math.max(f.length+a,0));if(void 0===b)return void 0===a?f:f[a];if(null===a||void 0===f[a])if(A(b)){f=[];a=0;for(var z=b.length;a{}'"`^| \\]/;g.expand=function(b,f){var h=v[b.operator],k=h.named?"Named":"Unnamed",l=b.variables,q=[],m,y,u;for(u=0;y=l[u];u++)if(m=f.get(y.name),m.val.length){if(1{}"`^| \\]/;k.expand=function(h,p,D){var u=A[h.operator],K=u.named?"Named":"Unnamed";h=h.variables;var F=[],w,H;for(H=0;w=h[H];H++){var v=p.get(w.name);if(0===v.type&&D&&D.strict)throw Error('Missing expansion value for variable "'+ +w.name+'"');if(v.val.length){if(1{}'"`^| \\]/; + URITemplate.LITERAL_PATTERN = /[<>{}"`^| \\]/; // expand parsed expression (expression, not template!) - URITemplate.expand = function(expression, data) { + URITemplate.expand = function(expression, data, opts) { // container for defined options for the given operator var options = operators[expression.operator]; // expansion type (include keys or not) @@ -154,6 +154,9 @@ for (i = 0; (variable = variables[i]); i++) { // fetch simplified data source d = data.get(variable.name); + if (d.type === 0 && opts && opts.strict) { + throw new Error('Missing expansion value for variable "' + variable.name + '"'); + } if (!d.val.length) { if (d.type) { // empty variables (empty string) @@ -320,7 +323,7 @@ }; // expand template through given data map - p.expand = function(data) { + p.expand = function(data, opts) { var result = ''; if (!this.parts || !this.parts.length) { @@ -340,7 +343,7 @@ // literal string ? this.parts[i] // expression - : URITemplate.expand(this.parts[i], data); + : URITemplate.expand(this.parts[i], data, opts); /*jshint laxbreak: false */ } diff --git a/src/jquery.URI.js b/src/jquery.URI.js index 0dd4a397..258e5a82 100644 --- a/src/jquery.URI.js +++ b/src/jquery.URI.js @@ -2,7 +2,7 @@ * URI.js - Mutating URLs * jQuery Plugin * - * Version: 1.18.4 + * Version: 1.19.5 * * Author: Rodney Rehm * Web: http://medialize.github.io/URI.js/jquery-uri-plugin.html @@ -31,7 +31,7 @@ var comparable = {}; var compare = { - // equals + // equals '=': function(value, target) { return value === target; }, @@ -51,7 +51,7 @@ // add trailing slash so /dir/ will match the deep-end as well value += '/'; } - + return !!(value + '').match(new RegExp(escapeRegEx(target), 'i')); }, 'equals:': function(uri, target) { @@ -74,7 +74,7 @@ // compensate ambiguous that is not an image return undefined; } - + // NOTE: as we use a static mapping from element to attribute, // the HTML5 attribute issue should not come up again // https://github.com/medialize/URI.js/issues/69 @@ -120,17 +120,17 @@ var $this = this.first(); var elem = $this.get(0); var property = getUriProperty(elem); - + if (!property) { throw new Error('Element "' + elem.nodeName + '" does not have either property: href, src, action, cite'); } - + if (uri !== undefined) { var old = $this.data('uri'); if (old) { return old.href(uri); } - + if (!(uri instanceof URI)) { uri = URI(uri || ''); } @@ -142,7 +142,7 @@ uri = URI($this.attr(property) || ''); } } - + uri._dom_element = elem; uri._dom_attribute = property; uri.normalize(); @@ -164,7 +164,7 @@ this._string = URI.build(this._parts); this._deferred_build = false; } - + return this; }; @@ -173,12 +173,12 @@ var pseudoArgs = /^([a-zA-Z]+)\s*([\^\$*]?=|:)\s*(['"]?)(.+)\3|^\s*([a-zA-Z0-9]+)\s*$/; function uriPseudo (elem, text) { var match, property, uri; - + // skip anything without src|href|action and bad :uri() syntax if (!getUriProperty(elem) || !text) { return false; } - + match = text.match(pseudoArgs); if (!match || (!match[5] && match[2] !== ':' && !compare[match[2]])) { @@ -188,7 +188,7 @@ } uri = $(elem).uri(); - + if (match[5]) { return uri.is(match[5]); } else if (match[2] === ':') { @@ -197,7 +197,7 @@ // filers seem to fail silently return false; } - + return compare[property](uri, match[4]); } else { property = match[1].toLowerCase(); @@ -205,7 +205,7 @@ // filers seem to fail silently return false; } - + return compare[match[2]](uri[property](), match[4], property); } diff --git a/src/jquery.URI.min.js b/src/jquery.URI.min.js index 3d0a54a7..fdedfac3 100644 --- a/src/jquery.URI.min.js +++ b/src/jquery.URI.min.js @@ -1,7 +1,7 @@ -/*! URI.js v1.18.4 http://medialize.github.io/URI.js/ */ +/*! URI.js v1.19.5 http://medialize.github.io/URI.js/ */ /* build contains: jquery.URI.js */ -(function(d,e){"object"===typeof module&&module.exports?module.exports=e(require("jquery"),require("./URI")):"function"===typeof define&&define.amd?define(["jquery","./URI"],e):e(d.jQuery,d.URI)})(this,function(d,e){function h(a){return a.replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")}function k(a){var b=a.nodeName.toLowerCase();if("input"!==b||"image"===a.type)return e.domAttributes[b]}function p(a){return{get:function(b){return d(b).uri()[a]()},set:function(b,c){d(b).uri()[a](c);return c}}}function l(a, -b){var c,e,f;if(!k(a)||!b)return!1;c=b.match(q);if(!c||!c[5]&&":"!==c[2]&&!g[c[2]])return!1;f=d(a).uri();if(c[5])return f.is(c[5]);if(":"===c[2])return e=c[1].toLowerCase()+":",g[e]?g[e](f,c[4]):!1;e=c[1].toLowerCase();return m[e]?g[c[2]](f[e](),c[4],e):!1}var m={},g={"=":function(a,b){return a===b},"^=":function(a,b){return!!(a+"").match(new RegExp("^"+h(b),"i"))},"$=":function(a,b){return!!(a+"").match(new RegExp(h(b)+"$","i"))},"*=":function(a,b,c){"directory"===c&&(a+="/");return!!(a+"").match(new RegExp(h(b), -"i"))},"equals:":function(a,b){return a.equals(b)},"is:":function(a,b){return a.is(b)}};d.each("origin authority directory domain filename fragment hash host hostname href password path pathname port protocol query resource scheme search subdomain suffix tld username".split(" "),function(a,b){m[b]=!0;d.attrHooks["uri:"+b]=p(b)});var r=function(a,b){return d(a).uri().href(b).toString()};d.each(["src","href","action","uri","cite"],function(a,b){d.attrHooks[b]={set:r}});d.attrHooks.uri.get=function(a){return d(a).uri()}; -d.fn.uri=function(a){var b=this.first(),c=b.get(0),d=k(c);if(!d)throw Error('Element "'+c.nodeName+'" does not have either property: href, src, action, cite');if(void 0!==a){var f=b.data("uri");if(f)return f.href(a);a instanceof e||(a=e(a||""))}else{if(a=b.data("uri"))return a;a=e(b.attr(d)||"")}a._dom_element=c;a._dom_attribute=d;a.normalize();b.data("uri",a);return a};e.prototype.build=function(a){if(this._dom_element)this._string=e.build(this._parts),this._deferred_build=!1,this._dom_element.setAttribute(this._dom_attribute, -this._string),this._dom_element[this._dom_attribute]=this._string;else if(!0===a)this._deferred_build=!0;else if(void 0===a||this._deferred_build)this._string=e.build(this._parts),this._deferred_build=!1;return this};var n,q=/^([a-zA-Z]+)\s*([\^\$*]?=|:)\s*(['"]?)(.+)\3|^\s*([a-zA-Z0-9]+)\s*$/;n=d.expr.createPseudo?d.expr.createPseudo(function(a){return function(b){return l(b,a)}}):function(a,b,c){return l(a,c[3])};d.expr[":"].uri=n;return d}); +(function(d,e){"object"===typeof module&&module.exports?module.exports=e(require("jquery"),require("./URI")):"function"===typeof define&&define.amd?define(["jquery","./URI"],e):e(d.jQuery,d.URI)})(this,function(d,e){function k(a){return a.replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")}function l(a){var b=a.nodeName.toLowerCase();if("input"!==b||"image"===a.type)return e.domAttributes[b]}function p(a){return{get:function(b){return d(b).uri()[a]()},set:function(b,c){d(b).uri()[a](c);return c}}}function m(a, +b){if(!l(a)||!b)return!1;var c=b.match(q);if(!c||!c[5]&&":"!==c[2]&&!h[c[2]])return!1;var g=d(a).uri();if(c[5])return g.is(c[5]);if(":"===c[2]){var f=c[1].toLowerCase()+":";return h[f]?h[f](g,c[4]):!1}f=c[1].toLowerCase();return n[f]?h[c[2]](g[f](),c[4],f):!1}var n={},h={"=":function(a,b){return a===b},"^=":function(a,b){return!!(a+"").match(new RegExp("^"+k(b),"i"))},"$=":function(a,b){return!!(a+"").match(new RegExp(k(b)+"$","i"))},"*=":function(a,b,c){"directory"===c&&(a+="/");return!!(a+"").match(new RegExp(k(b), +"i"))},"equals:":function(a,b){return a.equals(b)},"is:":function(a,b){return a.is(b)}};d.each("origin authority directory domain filename fragment hash host hostname href password path pathname port protocol query resource scheme search subdomain suffix tld username".split(" "),function(a,b){n[b]=!0;d.attrHooks["uri:"+b]=p(b)});var r=function(a,b){return d(a).uri().href(b).toString()};d.each(["src","href","action","uri","cite"],function(a,b){d.attrHooks[b]={set:r}});d.attrHooks.uri.get=function(a){return d(a).uri()}; +d.fn.uri=function(a){var b=this.first(),c=b.get(0),g=l(c);if(!g)throw Error('Element "'+c.nodeName+'" does not have either property: href, src, action, cite');if(void 0!==a){var f=b.data("uri");if(f)return f.href(a);a instanceof e||(a=e(a||""))}else{if(a=b.data("uri"))return a;a=e(b.attr(g)||"")}a._dom_element=c;a._dom_attribute=g;a.normalize();b.data("uri",a);return a};e.prototype.build=function(a){if(this._dom_element)this._string=e.build(this._parts),this._deferred_build=!1,this._dom_element.setAttribute(this._dom_attribute, +this._string),this._dom_element[this._dom_attribute]=this._string;else if(!0===a)this._deferred_build=!0;else if(void 0===a||this._deferred_build)this._string=e.build(this._parts),this._deferred_build=!1;return this};var q=/^([a-zA-Z]+)\s*([\^\$*]?=|:)\s*(['"]?)(.+)\3|^\s*([a-zA-Z0-9]+)\s*$/;var t=d.expr.createPseudo?d.expr.createPseudo(function(a){return function(b){return m(b,a)}}):function(a,b,c){return m(a,c[3])};d.expr[":"].uri=t;return d}); diff --git a/test/test.js b/test/test.js index 03ecc5a0..672b3f93 100644 --- a/test/test.js +++ b/test/test.js @@ -19,6 +19,11 @@ URI(undefined); }, TypeError, 'Failing undefined input'); }); + test('URI(null)', function() { + raises(function() { + URI(null); + }, TypeError, 'Failing undefined input'); + }); test('new URI(string)', function() { var u = new URI('http://example.org/'); ok(u instanceof URI, 'instanceof URI'); @@ -29,6 +34,49 @@ ok(u instanceof URI, 'instanceof URI'); ok(u._parts.hostname !== undefined, 'host undefined'); }); + + test('new URI(object)', function() { + var u = new URI({ + protocol: 'http', + hostname: 'example.org', + query: { + foo: 'bar', + bar: 'foo', + }, + }); + ok(u instanceof URI, 'instanceof URI'); + ok(typeof u.query() === 'string', 'query is string'); + equal(u.query(), 'foo=bar&bar=foo', 'query has right value'); + equal(u.search(), '?foo=bar&bar=foo', 'search has right value'); + deepEqual(u.query(true), { foo: 'bar', bar: 'foo' }, 'query(true) value'); + deepEqual(u.search(true), { foo: 'bar', bar: 'foo' }, 'search(true) value'); + }); + test('new URI(object)', function() { + var u = new URI({ + protocol: 'http', + hostname: 'example.org', + query: 'foo=bar&bar=foo', + }); + ok(u instanceof URI, 'instanceof URI'); + ok(typeof u.query() === 'string', 'query is string'); + equal(u.query(), 'foo=bar&bar=foo', 'query has right value'); + equal(u.search(), '?foo=bar&bar=foo', 'search has right value'); + deepEqual(u.query(true), { foo: 'bar', bar: 'foo' }, 'query(true) value'); + deepEqual(u.search(true), { foo: 'bar', bar: 'foo' }, 'search(true) value'); + }); + test('new URI(object)', function() { + var u = new URI({ + protocol: 'http', + hostname: 'example.org', + query: '?foo=bar&bar=foo', + }); + ok(u instanceof URI, 'instanceof URI'); + ok(typeof u.query() === 'string', 'query is string'); + equal(u.query(), 'foo=bar&bar=foo', 'query has right value'); + equal(u.search(), '?foo=bar&bar=foo', 'search has right value'); + deepEqual(u.query(true), { foo: 'bar', bar: 'foo' }, 'query(true) value'); + deepEqual(u.search(true), { foo: 'bar', bar: 'foo' }, 'search(true) value'); + }); test('new URI(Location)', function () { var u = new URI(location); equal(u.href(), String(location.href), 'location object'); @@ -119,6 +167,32 @@ ok(u instanceof URI, 'instanceof URI'); ok(u._parts.hostname !== undefined, 'host undefined'); }); + test('function URI(string) with invalid port "port" throws', function () { + raises(function () { + new URI('http://example.org:port'); + }, TypeError, "throws TypeError"); + }); + test('function URI(string) with invalid port "0" throws', function () { + raises(function () { + new URI('http://example.org:0'); + }, TypeError, "throws TypeError"); + }); + test('function URI(string) with invalid port "65536" throws', function () { + raises(function () { + new URI('http://example.org:65536'); + }, TypeError, "throws TypeError"); + }); + test('function URI(string) with protocol and without hostname should throw', function () { + new URI('http://'); + + URI.preventInvalidHostname = true; + raises(function () { + new URI('http://'); + }, TypeError, "throws TypeError"); + + URI.preventInvalidHostname = false; + new URI('http://'); + }); test('new URI(string, string)', function() { // see http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor var u = new URI('../foobar.html', 'http://example.org/hello/world.html'); @@ -160,6 +234,16 @@ })(t); } + module('serializing'); + test('scheme and relative path', function() { + var u = new URI('') + .protocol('food') + .path('test/file.csv') + .toString() + + equal(u.toString(), 'food:///test/file.csv', 'relative-path with scheme but no authority'); + }); + module('mutating basics'); test('protocol', function() { var u = new URI('http://example.org/foo.html'); @@ -218,13 +302,28 @@ equal(u.hostname(), 'abc.foobar.lala', 'hostname changed'); equal(u+'', 'http://abc.foobar.lala/foo.html', 'hostname changed url'); - u.hostname(''); - equal(u.hostname(), '', 'hostname removed'); - equal(u+'', 'http:///foo.html', 'hostname removed url'); + u.hostname('some_where.exa_mple.org'); + equal(u.hostname(), 'some_where.exa_mple.org', 'hostname changed'); + equal(u+'', 'http://some_where.exa_mple.org/foo.html', 'hostname changed url'); raises(function() { u.hostname('foo\\bar.com'); }, TypeError, 'Failing backslash detection in hostname'); + + // instance does not fall back to global setting + URI.preventInvalidHostname = true; + u.hostname(''); + u.hostname(null); + URI.preventInvalidHostname = false; + + u.preventInvalidHostname(true); + raises(function() { + u.hostname(''); + }, TypeError, "Trying to set an empty hostname with http(s) protocol throws a TypeError"); + + raises(function() { + u.hostname(null); + }, TypeError, "Trying to set hostname to null with http(s) protocol throws a TypeError"); }); test('port', function() { var u = new URI('http://example.org/foo.html'); @@ -367,6 +466,11 @@ equal(u.port(), '', 'host removed port'); equal(u+'', 'http://some-domain.com/foo.html', 'host modified url'); + u.host('some_where.exa_mple.org:44'); + equal(u.hostname(), 'some_where.exa_mple.org', 'host modified hostname #2'); + equal(u.port(), '44', 'port restored'); + equal(u+'', 'http://some_where.exa_mple.org:44/foo.html', 'host modified url #2'); + raises(function() { u.host('foo\\bar.com'); }, TypeError, 'Failing backslash detection in host'); @@ -496,6 +600,9 @@ equal(u.hostname(), 'foo.example.org', 'changed subdomain foo.'); equal(u+'', 'http://foo.example.org/foo.html', 'changed url foo.'); + u.subdomain('foo_bar'); + equal(u.hostname(), 'foo_bar.example.org', 'changed subdomain foo_bar'); + equal(u+'', 'http://foo_bar.example.org/foo.html', 'changed url foo_bar'); }); test('domain', function() { var u = new URI('http://www.example.org/foo.html'); @@ -526,6 +633,13 @@ u.subdomain('foo'); equal(u.href(), 'http://foo.test/', 'subdomain set on (dot-less)'); + + u.subdomain('bar'); + equal(u.href(), 'http://bar.foo.test/', 'subdomain set on foo.test'); + + u.domain('exam_ple.org'); + equal(u.domain(), 'exam_ple.org', 'domain after changed domain exam_ple.org'); + equal(u+'', 'http://bar.exam_ple.org/', 'url after changed domain exam_ple.org'); }); test('tld', function() { var u = new URI('http://www.example.org/foo.html'); @@ -555,29 +669,20 @@ }); test('sld', function() { - // Lets just test them all.. - // Calling URI.is(), URI.domain(), URI.subdomain() allows us to indirectly - // test SLD.has(), SLD.is() and SLD.get() - var u = new URI('http://www.example.org/foo.html'); - equal(u.is('sld'), false, 'is not sld'); - var list = SecondLevelDomains.list; - var tlds = Object.keys(list); - var iTld = tlds.length; - var tld, slds, sld, iSld; - while ( iTld-- ) { - tld = tlds[iTld]; - slds = list[tld].trim().split(/\s+/); - iSld = slds.length; - while ( iSld-- ) { - sld = slds[iSld].trim() + '.' + tld; - u.hostname('www.example.' + sld); - equal(u.is('sld'), true, 'is sld'); - equal(u.domain(), 'example.' + sld, 'domain is example.' + sld); - equal(u.subdomain(), 'www', 'subdomain is www'); - u.hostname('www.example.' + tld); - equal(u.is('sld'), false, 'is not sld'); - } - } + var u = new URI('http://www.example.ch/foo.html') + equal(u.is('sld'), false, 'is() www.example.ch'); + equal(u.domain(), 'example.ch', 'domain() www.example.ch'); + equal(u.subdomain(), 'www', 'subdomain() www.example.ch'); + + u = new URI('http://www.example.com/foo.html') + equal(u.is('sld'), false, 'is() www.example.com'); + equal(u.domain(), 'example.com', 'domain() www.example.com'); + equal(u.subdomain(), 'www', 'subdomain() www.example.com'); + + u = new URI('http://www.example.eu.com/foo.html') + equal(u.is('sld'), true, 'is() www.example.eu.com'); + equal(u.domain(), 'example.eu.com', 'domain() www.example.eu.com'); + equal(u.subdomain(), 'www', 'subdomain() www.example.eu.com'); }); test('directory', function() { var u = new URI('http://www.example.org/some/directory/foo.html'); @@ -1226,6 +1331,9 @@ u.query('?&foo=bar&foo=bar').normalizeQuery(); equal(u.query(), 'foo=bar', 'duplicate key=value resolution'); + + u.query('?=bar').normalizeQuery(); + equal(u.query(), '=bar', 'query without key'); }); test('normalizeFragment', function() { var u = new URI('http://example.org/foobar.html#'); @@ -1235,6 +1343,9 @@ test('readable', function() { var u = new URI('http://foo:bar@www.xn--exmple-cua.org/hello%20world/ä.html?foo%5B%5D=b+är#fragment'); equal(u.readable(), 'http://www.exämple.org/hello world/ä.html?foo[]=b är#fragment', 'readable URL'); + + var u = new URI('http://example.org/?=5640'); + equal(u.readable(), 'http://example.org/?=5640', 'readable URL: query without key'); }); module('resolving URLs'); @@ -1337,6 +1448,11 @@ url: 'http://github.com//the_relative_url', base: 'http://example.com/foo/bar', result: 'http://github.com//the_relative_url' + }, { + name: 'absolute passthru - file:/// - urljoin (#328)', + url: 'file:///C:/skyclan/snipkit', + base: 'http://example.com/foo/bar', + result: 'file:///C:/skyclan/snipkit' }, { name: 'file paths - urljoin', url: 'anotherFile', @@ -1580,15 +1696,17 @@ + '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«, ' - + 'yet https://example.com/with_(balanced_parentheses) contains the closing parens, but ' - + 'https://example.com/with_unbalanced_parentheses) does not.'; + + 'yet https://example.com/with_(balanced_parentheses) and https://example.com/with_(balanced_parentheses).txt contain the closing parens, but ' + + 'https://example.com/with_unbalanced_parentheses) does not.\n' + + 'Note that www. is not a URL and neither is http://.'; var expected = '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«, ' - + 'yet https://example.com/with_(balanced_parentheses) contains the closing parens, but ' - + 'https://example.com/with_unbalanced_parentheses) does not.'; + + 'yet https://example.com/with_(balanced_parentheses) and https://example.com/with_(balanced_parentheses).txt contain the closing parens, but ' + + 'https://example.com/with_unbalanced_parentheses) does not.\n' + + 'Note that www. is not a URL and neither is http://.'; /*jshint laxbreak: false */ var result = URI.withinString(source, function(url) { return '' + url + ''; @@ -1655,6 +1773,36 @@ deepEqual(links, expected, 'urls extracted'); equal(result, source, 'source not modified'); }); + test('ensureValidPort', function() { + + function testPort(value) { + var result = true; + try { + URI.ensureValidPort(value); + } catch(e) { + result = false; + } + + return result; + } + + equal(testPort(8000), true); + equal(testPort('8080'), true); + + equal(testPort(0), true); + equal(testPort(1), true); + + equal(testPort(65535), true); + equal(testPort(65536), false); + + equal(testPort(-8080), false); + equal(testPort('-8080'), false); + + equal(testPort('aaa8080'), false); + equal(testPort('8080a'), false); + + equal(testPort(8080.2), false); + }); test('noConflict', function() { var actual_lib = URI; // actual library; after loading, before noConflict() var unconflicted = URI.noConflict(); @@ -1717,6 +1865,44 @@ result = URI.joinPaths('a', '', '', 'b', '', '').toString(); equal(result, 'a/b/', 'trailing empty segment'); }); + test('setQuery', function () { + var o = {foo: 'bar'}; + + URI.setQuery(o, 'foo', 'bam'); + deepEqual(o, {foo: 'bam'}, 'set name, value'); + + URI.setQuery(o, 'array', ['one', 'two']); + deepEqual(o, {foo: 'bam', array: ['one', 'two']}, 'set name, array'); + + URI.setQuery(o, 'foo', 'qux'); + deepEqual(o, {foo: 'qux', array: ['one', 'two']}, 'override name, value'); + + o = {foo: 'bar'}; + URI.setQuery(o, {baz: 'qux'}); + deepEqual(o, {foo: 'bar', baz: 'qux'}, 'set {name: value}'); + + URI.setQuery(o, {bar: ['1', '2']}); + deepEqual(o, {foo: 'bar', bar: ['1', '2'], baz: 'qux'}, 'set {name: array}'); + + URI.setQuery(o, {foo: 'qux'}); + deepEqual(o, {foo: 'qux', bar: ['1', '2'], baz: 'qux'}, 'override {name: value}'); + + o = {foo: 'bar'}; + URI.setQuery(o, {bam: null, baz: ''}); + deepEqual(o, {foo: 'bar', bam: null, baz: ''}, 'set {name: null}'); + + o = {foo: 'bar'}; + URI.setQuery(o, 'empty'); + deepEqual(o, {foo: 'bar', empty: null}, 'set undefined'); + + o = {foo: 'bar'}; + URI.setQuery(o, 'empty', ''); + deepEqual(o, {foo: 'bar', empty: ''}, 'set empty string'); + + o = {}; + URI.setQuery(o, 'some value', 'must be encoded because of = and ? and #'); + deepEqual(o, {'some value': 'must be encoded because of = and ? and #'}, 'encoding'); + }); module('comparing URLs'); test('equals', function() { @@ -1838,4 +2024,29 @@ equal(URI.encodeReserved('ä:/?#[]@!$&\'()*+,;='), '%C3%A4:/?#[]@!$&\'()*+,;='); }); + module('SecondLevelDomains'); + test('SecondLevelDomains.get()', function() { + equal(SecondLevelDomains.get('www.example.ch'), null, 'www.example.ch') + equal(SecondLevelDomains.get('www.example.com'), null, 'www.example.com') + equal(SecondLevelDomains.get('www.example.eu.com'), 'eu.com', 'www.example.eu.com') + equal(SecondLevelDomains.get('www.example.co.uk'), 'co.uk', 'www.example.co.uk') + }); + test('SecondLevelDomains.has()', function() { + equal(SecondLevelDomains.has('www.example.ch'), false, 'www.example.ch') + equal(SecondLevelDomains.has('www.example.com'), false, 'www.example.com') + equal(SecondLevelDomains.has('www.example.eu.com'), true, 'www.example.eu.com') + equal(SecondLevelDomains.has('www.example.co.uk'), true, 'www.example.co.uk') + }); + test('SecondLevelDomains.is()', function() { + equal(SecondLevelDomains.is('ch'), false, 'ch') + equal(SecondLevelDomains.is('example.ch'), false, 'example.ch') + + equal(SecondLevelDomains.is('com'), false, 'com') + equal(SecondLevelDomains.is('eu.com'), true, 'eu.com') + equal(SecondLevelDomains.is('example.com'), false, 'example.com') + + equal(SecondLevelDomains.is('uk'), false, 'uk') + equal(SecondLevelDomains.is('co.uk'), true, 'co.uk') + }); + })(); diff --git a/test/test_fragmentQuery.js b/test/test_fragmentQuery.js index a98406bf..7c4d5a4b 100644 --- a/test/test_fragmentQuery.js +++ b/test/test_fragmentQuery.js @@ -5,15 +5,15 @@ module('URI.fragmentQuery'); test('storing query-data in fragment', function() { var u = URI('http://example.org'); - + deepEqual(u.fragment(true), {}, 'empty map for missing fragment'); u = URI('http://example.org/#'); deepEqual(u.fragment(true), {}, 'empty map for empty fragment'); - + u = URI('http://example.org/#?hello=world'); deepEqual(u.fragment(true), {hello: 'world'}, 'reading data object'); - + u.fragment({bar: 'foo'}); deepEqual(u.fragment(true), {bar: 'foo'}, 'setting data object'); equal(u.toString(), 'http://example.org/#?bar=foo', 'setting data object serialized'); @@ -21,39 +21,47 @@ u.addFragment('name', 'value'); deepEqual(u.fragment(true), {bar: 'foo', name: 'value'}, 'adding value'); equal(u.toString(), 'http://example.org/#?bar=foo&name=value', 'adding value serialized'); - + u.removeFragment('bar'); deepEqual(u.fragment(true), {name: 'value'}, 'removing value bar'); equal(u.toString(), 'http://example.org/#?name=value', 'removing value bar serialized'); - + u.removeFragment('name'); deepEqual(u.fragment(true), {}, 'removing value name'); equal(u.toString(), 'http://example.org/#?', 'removing value name serialized'); + + u.setFragment('name', 'value1'); + deepEqual(u.fragment(true), {name: 'value1'}, 'setting name to value1'); + equal(u.toString(), 'http://example.org/#?name=value1', 'setting name to value1 serialized'); + + u.setFragment('name', 'value2'); + deepEqual(u.fragment(true), {name: 'value2'}, 'setting name to value2'); + equal(u.toString(), 'http://example.org/#?name=value2', 'setting name to value2 serialized'); }); test('fragmentPrefix', function() { var u; - + URI.fragmentPrefix = '!'; u = URI('http://example.org'); equal(u._parts.fragmentPrefix, '!', 'init using global property'); - + u.fragment('#?hello=world'); equal(u.fragment(), '?hello=world', 'unparsed ?'); deepEqual(u.fragment(true), {}, 'parsing ? prefix'); - + u.fragment('#!hello=world'); equal(u.fragment(), '!hello=world', 'unparsed !'); deepEqual(u.fragment(true), {hello: 'world'}, 'parsing ! prefix'); - + u.fragmentPrefix('§'); equal(u.fragment(), '!hello=world', 'unparsed §'); deepEqual(u.fragment(true), {}, 'parsing § prefix'); - + u.fragment('#§hello=world'); equal(u.fragment(), '§hello=world', 'unparsed §'); deepEqual(u.fragment(true), {hello: 'world'}, 'parsing § prefix'); - + URI.fragmentPrefix = '?'; }); -})(); \ No newline at end of file +})(); diff --git a/test/test_jim.js b/test/test_jim.js index 2be6def1..cdb27624 100644 --- a/test/test_jim.js +++ b/test/test_jim.js @@ -98,6 +98,18 @@ u.filename('../name.html?query'); equal(u.filename(), 'name.html%3Fquery', 'filename() has set invalid filename'); equal(u.directory(), '/dir1', 'filename() has not altered directory properly'); + + u.filename(null); + equal(u.filename(), 'name.html%3Fquery', 'filename() has set invalid filename'); + equal(u.directory(), '/dir1', 'filename() has not altered directory properly'); + + u.filename(false); + equal(u.filename(), 'name.html%3Fquery', 'filename() has set invalid filename'); + equal(u.directory(), '/dir1', 'filename() has not altered directory properly'); + + u.filename(0); + equal(u.filename(), 'name.html%3Fquery', 'filename() has set invalid filename'); + equal(u.directory(), '/dir1', 'filename() has not altered directory properly'); }); test('addQuery', function() { var u = new URI('http://example.com/dir1/dir2/?query1=value1&query2=value2#hash'); @@ -134,7 +146,7 @@ domain = "com"; for (j=0; j<3; j++) { //start new subdomain - domain = "." + domain; + domain = "." + domain; for (i=0; i<70; i++) { domain = "a" + domain; } @@ -143,4 +155,4 @@ equals(u.hostname() == domain, true, "set domain() with 70-character subdomain not valid domainname"); }); */ -})(); \ No newline at end of file +})(); diff --git a/test/test_template.js b/test/test_template.js index 16db0179..b2b5f037 100644 --- a/test/test_template.js +++ b/test/test_template.js @@ -408,4 +408,10 @@ }, Error, 'Failing invalid literal'); }); + test('Strict mode', function () { + raises(function() { + new URITemplate("/{foo}/bar").expand({ foobar: 123 }, { strict: true }); + }, Error, 'Missing expansion value for variable in strict mode.'); + }); + })(); diff --git a/test/urls.js b/test/urls.js index b38337ec..14255c16 100644 --- a/test/urls.js +++ b/test/urls.js @@ -629,6 +629,54 @@ var urls = [{ idn: false, punycode: false } + }, { + name: 'missing authority', + url: 'food:///test/file.csv', + parts: { + protocol: 'food', + username: null, + password: null, + hostname: null, + port: null, + path: '/test/file.csv', + query: null, + fragment: null + }, + accessors: { + protocol: 'food', + username: '', + password: '', + port: '', + path: '/test/file.csv', + query: '', + fragment: '', + resource: '/test/file.csv', + authority: '', + origin: '', + userinfo: '', + subdomain: '', + domain: '', + tld: '', + directory: '/test', + filename: 'file.csv', + suffix: 'csv', + hash: '', + search: '', + host: '', + hostname: '' + }, + is: { + urn: false, + url: true, + relative: true, + name: false, + sld: false, + ip: false, + ip4: false, + ip6: false, + idn: false, + punycode: false + } }, { name: 'IPv4', url: 'http://user:pass@123.123.123.123:123/some/directory/file.html?query=string#fragment', @@ -1112,6 +1160,55 @@ var urls = [{ idn: false, punycode: false } + }, { + // https://github.com/medialize/URI.js/issues/347 + name: 'Underscore in domain', + url: 'http://user:pass@some_where.exa_mple.org:123/some/directory/file.html?query=string#fragment', + parts: { + protocol: 'http', + username: 'user', + password: 'pass', + hostname: 'some_where.exa_mple.org', + port: '123', + path: '/some/directory/file.html', + query: 'query=string', + fragment: 'fragment' + }, + accessors: { + protocol: 'http', + username: 'user', + password: 'pass', + port: '123', + path: '/some/directory/file.html', + query: 'query=string', + fragment: 'fragment', + resource: '/some/directory/file.html?query=string#fragment', + authority: 'user:pass@some_where.exa_mple.org:123', + origin: 'http://user:pass@some_where.exa_mple.org:123', + userinfo: 'user:pass', + subdomain: 'some_where', + domain: 'exa_mple.org', + tld: 'org', + directory: '/some/directory', + filename: 'file.html', + suffix: 'html', + hash: '#fragment', + search: '?query=string', + host: 'some_where.exa_mple.org:123', + hostname: 'some_where.exa_mple.org' + }, + is: { + urn: false, + url: true, + relative: false, + name: true, + sld: false, + ip: false, + ip4: false, + ip6: false, + idn: false, + punycode: false + } }, { name: 'IDN (punycode)', url: 'http://user:pass@xn--exmple-cua.org:123/some/directory/file.html?query=string#fragment', @@ -1887,6 +1984,104 @@ var urls = [{ idn: false, punycode: false } + }, { + name: 'backslashes authority', + url: 'https://attacker.com\\@example.com/some/directory/file.html?query=string#fragment', + _url: 'https://attacker.com/@example.com/some/directory/file.html?query=string#fragment', + parts: { + protocol: 'https', + username: null, + password: null, + hostname: 'attacker.com', + port: null, + path: '/@example.com/some/directory/file.html', + query: 'query=string', + fragment: 'fragment' + }, + accessors: { + protocol: 'https', + username: '', + password: '', + port: '', + path: '/@example.com/some/directory/file.html', + query: 'query=string', + fragment: 'fragment', + resource: '/@example.com/some/directory/file.html?query=string#fragment', + authority: 'attacker.com', + origin: 'https://attacker.com', + userinfo: '', + subdomain: '', + domain: 'attacker.com', + tld: 'com', + directory: '/@example.com/some/directory', + filename: 'file.html', + suffix: 'html', + hash: '#fragment', + search: '?query=string', + host: 'attacker.com', + hostname: 'attacker.com' + }, + is: { + urn: false, + url: true, + relative: false, + name: true, + sld: false, + ip: false, + ip4: false, + ip6: false, + idn: false, + punycode: false + } + }, { + name: 'backslashes authority, no ending slash', + url: 'https://attacker.com\\@example.com', + _url: 'https://attacker.com/@example.com', + parts: { + protocol: 'https', + username: null, + password: null, + hostname: 'attacker.com', + port: null, + path: '/@example.com', + query: null, + fragment: null + }, + accessors: { + protocol: 'https', + username: '', + password: '', + port: '', + path: '/@example.com', + query: '', + fragment: '', + resource: '/@example.com', + authority: 'attacker.com', + origin: 'https://attacker.com', + userinfo: '', + subdomain: '', + domain: 'attacker.com', + tld: 'com', + directory: '/', + filename: '@example.com', + suffix: 'com', + hash: '', + search: '', + host: 'attacker.com', + hostname: 'attacker.com' + }, + is: { + urn: false, + url: true, + relative: false, + name: true, + sld: false, + ip: false, + ip4: false, + ip6: false, + idn: false, + punycode: false + } } ]; diff --git a/uri-template.html b/uri-template.html index d49065c9..c9407629 100644 --- a/uri-template.html +++ b/uri-template.html @@ -107,6 +107,11 @@

Using URI Templates

template.expand({file : function(key) { return "hello world.html"; }}); + +// Using strict mode +var template = new URITemplate("http://example.org/{file}"); +var result = template.expand({filename: "hello world.html"}, { strict: true }); +// Uncaught Error: Missing expansion value for variable "file"