/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ * Forked from https://github.com/github/url-polyfill * Version 16c1aa Feb 9 2018. */ (function(scope) { "use strict"; // feature detect for URL constructor var hasWorkingUrl = false; if (!scope.forceJURL) { try { var u = new URL("b", "http://a"); u.pathname = "c%20d"; hasWorkingUrl = u.href === "http://a/c%20d"; } catch (e) {} } if (hasWorkingUrl) return; var relative = Object.create(null); relative["ftp"] = 21; relative["file"] = 0; relative["gopher"] = 70; relative["http"] = 80; relative["https"] = 443; relative["ws"] = 80; relative["wss"] = 443; var relativePathDotMapping = Object.create(null); relativePathDotMapping["%2e"] = "."; relativePathDotMapping[".%2e"] = ".."; relativePathDotMapping["%2e."] = ".."; relativePathDotMapping["%2e%2e"] = ".."; function isRelativeScheme(scheme) { return relative[scheme] !== undefined; } function invalid() { clear.call(this); this._isInvalid = true; } function IDNAToASCII(h) { if ("" == h) { invalid.call(this); } // XXX return h.toLowerCase(); } function percentEscape(c) { var unicode = c.charCodeAt(0); if ( unicode > 0x20 && unicode < 0x7f && // " # < > ? ` [0x22, 0x23, 0x3c, 0x3e, 0x3f, 0x60].indexOf(unicode) == -1 ) { return c; } return encodeURIComponent(c); } function percentEscapeQuery(c) { // XXX This actually needs to encode c using encoding and then // convert the bytes one-by-one. var unicode = c.charCodeAt(0); if ( unicode > 0x20 && unicode < 0x7f && // " # < > ` (do not escape '?') [0x22, 0x23, 0x3c, 0x3e, 0x60].indexOf(unicode) == -1 ) { return c; } return encodeURIComponent(c); } var EOF = undefined, ALPHA = /[a-zA-Z]/, ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/; function parse(input, stateOverride, base) { function err(message) { errors.push(message); } var state = stateOverride || "scheme start", cursor = 0, buffer = "", seenAt = false, seenBracket = false, errors = []; loop: while ( (input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid ) { var c = input[cursor]; switch (state) { case "scheme start": if (c && ALPHA.test(c)) { buffer += c.toLowerCase(); // ASCII-safe state = "scheme"; } else if (!stateOverride) { buffer = ""; state = "no scheme"; continue; } else { err("Invalid scheme."); break loop; } break; case "scheme": if (c && ALPHANUMERIC.test(c)) { buffer += c.toLowerCase(); // ASCII-safe } else if (":" == c) { this._scheme = buffer; buffer = ""; if (stateOverride) { break loop; } if (isRelativeScheme(this._scheme)) { this._isRelative = true; } if ("file" == this._scheme) { state = "relative"; } else if ( this._isRelative && base && base._scheme == this._scheme ) { state = "relative or authority"; } else if (this._isRelative) { state = "authority first slash"; } else { state = "scheme data"; } } else if (!stateOverride) { buffer = ""; cursor = 0; state = "no scheme"; continue; } else if (EOF == c) { break loop; } else { err("Code point not allowed in scheme: " + c); break loop; } break; case "scheme data": if ("?" == c) { query = "?"; state = "query"; } else if ("#" == c) { this._fragment = "#"; state = "fragment"; } else { // XXX error handling if (EOF != c && "\t" != c && "\n" != c && "\r" != c) { this._schemeData += percentEscape(c); } } break; case "no scheme": if (!base || !isRelativeScheme(base._scheme)) { err("Missing scheme."); invalid.call(this); } else { state = "relative"; continue; } break; case "relative or authority": if ("/" == c && "/" == input[cursor + 1]) { state = "authority ignore slashes"; } else { err("Expected /, got: " + c); state = "relative"; continue; } break; case "relative": this._isRelative = true; if ("file" != this._scheme) this._scheme = base._scheme; if (EOF == c) { this._host = base._host; this._port = base._port; this._path = base._path.slice(); this._query = base._query; this._username = base._username; this._password = base._password; break loop; } else if ("/" == c || "\\" == c) { if ("\\" == c) err("\\ is an invalid code point."); state = "relative slash"; } else if ("?" == c) { this._host = base._host; this._port = base._port; this._path = base._path.slice(); this._query = "?"; this._username = base._username; this._password = base._password; state = "query"; } else if ("#" == c) { this._host = base._host; this._port = base._port; this._path = base._path.slice(); this._query = base._query; this._fragment = "#"; this._username = base._username; this._password = base._password; state = "fragment"; } else { var nextC = input[cursor + 1]; var nextNextC = input[cursor + 2]; if ( "file" != this._scheme || !ALPHA.test(c) || (nextC != ":" && nextC != "|") || (EOF != nextNextC && "/" != nextNextC && "\\" != nextNextC && "?" != nextNextC && "#" != nextNextC) ) { this._host = base._host; this._port = base._port; this._username = base._username; this._password = base._password; this._path = base._path.slice(); this._path.pop(); } state = "relative path"; continue; } break; case "relative slash": if ("/" == c || "\\" == c) { if ("\\" == c) { err("\\ is an invalid code point."); } if ("file" == this._scheme) { state = "file host"; } else { state = "authority ignore slashes"; } } else { if ("file" != this._scheme) { this._host = base._host; this._port = base._port; this._username = base._username; this._password = base._password; } state = "relative path"; continue; } break; case "authority first slash": if ("/" == c) { state = "authority second slash"; } else { err("Expected '/', got: " + c); state = "authority ignore slashes"; continue; } break; case "authority second slash": state = "authority ignore slashes"; if ("/" != c) { err("Expected '/', got: " + c); continue; } break; case "authority ignore slashes": if ("/" != c && "\\" != c) { state = "authority"; continue; } else { err("Expected authority, got: " + c); } break; case "authority": if ("@" == c) { if (seenAt) { err("@ already seen."); buffer += "%40"; } seenAt = true; for (var i = 0; i < buffer.length; i++) { var cp = buffer[i]; if ("\t" == cp || "\n" == cp || "\r" == cp) { err("Invalid whitespace in authority."); continue; } // XXX check URL code points if (":" == cp && null === this._password) { this._password = ""; continue; } var tempC = percentEscape(cp); null !== this._password ? (this._password += tempC) : (this._username += tempC); } buffer = ""; } else if ( EOF == c || "/" == c || "\\" == c || "?" == c || "#" == c ) { cursor -= buffer.length; buffer = ""; state = "host"; continue; } else { buffer += c; } break; case "file host": if (EOF == c || "/" == c || "\\" == c || "?" == c || "#" == c) { if ( buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ":" || buffer[1] == "|") ) { state = "relative path"; } else if (buffer.length == 0) { state = "relative path start"; } else { this._host = IDNAToASCII.call(this, buffer); buffer = ""; state = "relative path start"; } continue; } else if ("\t" == c || "\n" == c || "\r" == c) { err("Invalid whitespace in file host."); } else { buffer += c; } break; case "host": case "hostname": if (":" == c && !seenBracket) { // XXX host parsing this._host = IDNAToASCII.call(this, buffer); buffer = ""; state = "port"; if ("hostname" == stateOverride) { break loop; } } else if ( EOF == c || "/" == c || "\\" == c || "?" == c || "#" == c ) { this._host = IDNAToASCII.call(this, buffer); buffer = ""; state = "relative path start"; if (stateOverride) { break loop; } continue; } else if ("\t" != c && "\n" != c && "\r" != c) { if ("[" == c) { seenBracket = true; } else if ("]" == c) { seenBracket = false; } buffer += c; } else { err("Invalid code point in host/hostname: " + c); } break; case "port": if (/[0-9]/.test(c)) { buffer += c; } else if ( EOF == c || "/" == c || "\\" == c || "?" == c || "#" == c || stateOverride ) { if ("" != buffer) { var temp = parseInt(buffer, 10); if (temp != relative[this._scheme]) { this._port = temp + ""; } buffer = ""; } if (stateOverride) { break loop; } state = "relative path start"; continue; } else if ("\t" == c || "\n" == c || "\r" == c) { err("Invalid code point in port: " + c); } else { invalid.call(this); } break; case "relative path start": if ("\\" == c) err("'\\' not allowed in path."); state = "relative path"; if ("/" != c && "\\" != c) { continue; } break; case "relative path": if ( EOF == c || "/" == c || "\\" == c || (!stateOverride && ("?" == c || "#" == c)) ) { if ("\\" == c) { err("\\ not allowed in relative path."); } var tmp; if ((tmp = relativePathDotMapping[buffer.toLowerCase()])) { buffer = tmp; } if (".." == buffer) { this._path.pop(); if ("/" != c && "\\" != c) { this._path.push(""); } } else if ("." == buffer && "/" != c && "\\" != c) { this._path.push(""); } else if ("." != buffer) { if ( "file" == this._scheme && this._path.length == 0 && buffer.length == 2 && ALPHA.test(buffer[0]) && buffer[1] == "|" ) { buffer = buffer[0] + ":"; } this._path.push(buffer); } buffer = ""; if ("?" == c) { this._query = "?"; state = "query"; } else if ("#" == c) { this._fragment = "#"; state = "fragment"; } } else if ("\t" != c && "\n" != c && "\r" != c) { buffer += percentEscape(c); } break; case "query": if (!stateOverride && "#" == c) { this._fragment = "#"; state = "fragment"; } else if (EOF != c && "\t" != c && "\n" != c && "\r" != c) { this._query += percentEscapeQuery(c); } break; case "fragment": if (EOF != c && "\t" != c && "\n" != c && "\r" != c) { this._fragment += c; } break; } cursor++; } } function clear() { this._scheme = ""; this._schemeData = ""; this._username = ""; this._password = null; this._host = ""; this._port = ""; this._path = []; this._query = ""; this._fragment = ""; this._isInvalid = false; this._isRelative = false; } // Does not process domain names or IP addresses. // Does not handle encoding for the query parameter. function jURL(url, base /* , encoding */) { if (base !== undefined && !(base instanceof jURL)) base = new jURL(String(base)); url = String(url); this._url = url; clear.call(this); var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, ""); // encoding = encoding || 'utf-8' parse.call(this, input, null, base); } jURL.prototype = { toString: function() { return this.href; }, get href() { if (this._isInvalid) return this._url; var authority = ""; if ("" != this._username || null != this._password) { authority = this._username + (null != this._password ? ":" + this._password : "") + "@"; } return ( this.protocol + (this._isRelative ? "//" + authority + this.host : "") + this.pathname + this._query + this._fragment ); }, set href(href) { clear.call(this); parse.call(this, href); }, get protocol() { return this._scheme + ":"; }, set protocol(protocol) { if (this._isInvalid) return; parse.call(this, protocol + ":", "scheme start"); }, get host() { return this._isInvalid ? "" : this._port ? this._host + ":" + this._port : this._host; }, set host(host) { if (this._isInvalid || !this._isRelative) return; parse.call(this, host, "host"); }, get hostname() { return this._host; }, set hostname(hostname) { if (this._isInvalid || !this._isRelative) return; parse.call(this, hostname, "hostname"); }, get port() { return this._port; }, set port(port) { if (this._isInvalid || !this._isRelative) return; parse.call(this, port, "port"); }, get pathname() { return this._isInvalid ? "" : this._isRelative ? "/" + this._path.join("/") : this._schemeData; }, set pathname(pathname) { if (this._isInvalid || !this._isRelative) return; this._path = []; parse.call(this, pathname, "relative path start"); }, get search() { return this._isInvalid || !this._query || "?" == this._query ? "" : this._query; }, set search(search) { if (this._isInvalid || !this._isRelative) return; this._query = "?"; if ("?" == search[0]) search = search.slice(1); parse.call(this, search, "query"); }, get hash() { return this._isInvalid || !this._fragment || "#" == this._fragment ? "" : this._fragment; }, set hash(hash) { if (this._isInvalid) return; this._fragment = "#"; if ("#" == hash[0]) hash = hash.slice(1); parse.call(this, hash, "fragment"); }, get origin() { var host; if (this._isInvalid || !this._scheme) { return ""; } // javascript: Gecko returns String(""), WebKit/Blink String("null") // Gecko throws error for "data://" // data: Gecko returns "", Blink returns "data://", WebKit returns "null" // Gecko returns String("") for file: mailto: // WebKit/Blink returns String("SCHEME://") for file: mailto: switch (this._scheme) { case "data": case "file": case "javascript": case "mailto": return "null"; } host = this.host; if (!host) { return ""; } return this._scheme + "://" + host; } }; // Copy over the static methods var OriginalURL = scope.URL; if (OriginalURL) { jURL.createObjectURL = function(blob) { // IE extension allows a second optional options argument. // http://msdn.microsoft.com/en-us/library/ie/hh772302(v=vs.85).aspx return OriginalURL.createObjectURL.apply(OriginalURL, arguments); }; jURL.revokeObjectURL = function(url) { OriginalURL.revokeObjectURL(url); }; } scope.URL = jURL; })(window);