diff options
Diffstat (limited to 'toolkit/jetpack/sdk/url.js')
-rw-r--r-- | toolkit/jetpack/sdk/url.js | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/url.js b/toolkit/jetpack/sdk/url.js new file mode 100644 index 000000000..ae16ac4a8 --- /dev/null +++ b/toolkit/jetpack/sdk/url.js @@ -0,0 +1,349 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +module.metadata = { + "stability": "experimental" +}; + +const { Cc, Ci, Cr, Cu } = require("chrome"); + +const { Class } = require("./core/heritage"); +const base64 = require("./base64"); +var tlds = Cc["@mozilla.org/network/effective-tld-service;1"] + .getService(Ci.nsIEffectiveTLDService); + +var ios = Cc['@mozilla.org/network/io-service;1'] + .getService(Ci.nsIIOService); + +var resProt = ios.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + +var URLParser = Cc["@mozilla.org/network/url-parser;1?auth=no"] + .getService(Ci.nsIURLParser); + +const { Services } = Cu.import("resource://gre/modules/Services.jsm"); + +function newURI(uriStr, base) { + try { + let baseURI = base ? ios.newURI(base, null, null) : null; + return ios.newURI(uriStr, null, baseURI); + } + catch (e) { + if (e.result == Cr.NS_ERROR_MALFORMED_URI) { + throw new Error("malformed URI: " + uriStr); + } + if (e.result == Cr.NS_ERROR_FAILURE || + e.result == Cr.NS_ERROR_ILLEGAL_VALUE) { + throw new Error("invalid URI: " + uriStr); + } + } +} + +function resolveResourceURI(uri) { + var resolved; + try { + resolved = resProt.resolveURI(uri); + } + catch (e) { + if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) { + throw new Error("resource does not exist: " + uri.spec); + } + } + return resolved; +} + +var fromFilename = exports.fromFilename = function fromFilename(path) { + var file = Cc['@mozilla.org/file/local;1'] + .createInstance(Ci.nsILocalFile); + file.initWithPath(path); + return ios.newFileURI(file).spec; +}; + +var toFilename = exports.toFilename = function toFilename(url) { + var uri = newURI(url); + if (uri.scheme == "resource") + uri = newURI(resolveResourceURI(uri)); + if (uri.scheme == "chrome") { + var channel = ios.newChannelFromURI2(uri, + null, // aLoadingNode + Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); + try { + channel = channel.QueryInterface(Ci.nsIFileChannel); + return channel.file.path; + } + catch (e) { + if (e.result == Cr.NS_NOINTERFACE) { + throw new Error("chrome url isn't on filesystem: " + url); + } + } + } + if (uri.scheme == "file") { + var file = uri.QueryInterface(Ci.nsIFileURL).file; + return file.path; + } + throw new Error("cannot map to filename: " + url); +}; + +function URL(url, base) { + if (!(this instanceof URL)) { + return new URL(url, base); + } + + var uri = newURI(url, base); + + var userPass = null; + try { + userPass = uri.userPass ? uri.userPass : null; + } + catch (e) { + if (e.result != Cr.NS_ERROR_FAILURE) { + throw e; + } + } + + var host = null; + try { + host = uri.host; + } + catch (e) { + if (e.result != Cr.NS_ERROR_FAILURE) { + throw e; + } + } + + var port = null; + try { + port = uri.port == -1 ? null : uri.port; + } + catch (e) { + if (e.result != Cr.NS_ERROR_FAILURE) { + throw e; + } + } + + let fileName = "/"; + try { + fileName = uri.QueryInterface(Ci.nsIURL).fileName; + } catch (e) { + if (e.result != Cr.NS_NOINTERFACE) { + throw e; + } + } + + let uriData = [uri.path, uri.path.length, {}, {}, {}, {}, {}, {}]; + URLParser.parsePath.apply(URLParser, uriData); + let [{ value: filepathPos }, { value: filepathLen }, + { value: queryPos }, { value: queryLen }, + { value: refPos }, { value: refLen }] = uriData.slice(2); + + let hash = uri.ref ? "#" + uri.ref : ""; + let pathname = uri.path.substr(filepathPos, filepathLen); + let search = uri.path.substr(queryPos, queryLen); + search = search ? "?" + search : ""; + + this.__defineGetter__("fileName", () => fileName); + this.__defineGetter__("scheme", () => uri.scheme); + this.__defineGetter__("userPass", () => userPass); + this.__defineGetter__("host", () => host); + this.__defineGetter__("hostname", () => host); + this.__defineGetter__("port", () => port); + this.__defineGetter__("path", () => uri.path); + this.__defineGetter__("pathname", () => pathname); + this.__defineGetter__("hash", () => hash); + this.__defineGetter__("href", () => uri.spec); + this.__defineGetter__("origin", () => uri.prePath); + this.__defineGetter__("protocol", () => uri.scheme + ":"); + this.__defineGetter__("search", () => search); + + Object.defineProperties(this, { + toString: { + value() { + return new String(uri.spec).toString(); + }, + enumerable: false + }, + valueOf: { + value() { + return new String(uri.spec).valueOf(); + }, + enumerable: false + }, + toSource: { + value() { + return new String(uri.spec).toSource(); + }, + enumerable: false + }, + // makes more sense to flatten to string, easier to travel across JSON + toJSON: { + value() { + return new String(uri.spec).toString(); + }, + enumerable: false + } + }); + + return this; +}; + +URL.prototype = Object.create(String.prototype); +exports.URL = URL; + +/** + * Parse and serialize a Data URL. + * + * See: http://tools.ietf.org/html/rfc2397 + * + * Note: Could be extended in the future to decode / encode automatically binary + * data. + */ +const DataURL = Class({ + + get base64 () { + return "base64" in this.parameters; + }, + + set base64 (value) { + if (value) + this.parameters["base64"] = ""; + else + delete this.parameters["base64"]; + }, + /** + * Initialize the Data URL object. If a uri is given, it will be parsed. + * + * @param {String} [uri] The uri to parse + * + * @throws {URIError} if the Data URL is malformed + */ + initialize: function(uri) { + // Due to bug 751834 it is not possible document and define these + // properties in the prototype. + + /** + * An hashmap that contains the parameters of the Data URL. By default is + * empty, that accordingly to RFC is equivalent to {"charset" : "US-ASCII"} + */ + this.parameters = {}; + + /** + * The MIME type of the data. By default is empty, that accordingly to RFC + * is equivalent to "text/plain" + */ + this.mimeType = ""; + + /** + * The string that represent the data in the Data URL + */ + this.data = ""; + + if (typeof uri === "undefined") + return; + + uri = String(uri); + + let matches = uri.match(/^data:([^,]*),(.*)$/i); + + if (!matches) + throw new URIError("Malformed Data URL: " + uri); + + let mediaType = matches[1].trim(); + + this.data = decodeURIComponent(matches[2].trim()); + + if (!mediaType) + return; + + let parametersList = mediaType.split(";"); + + this.mimeType = parametersList.shift().trim(); + + for (let parameter, i = 0; parameter = parametersList[i++];) { + let pairs = parameter.split("="); + let name = pairs[0].trim(); + let value = pairs.length > 1 ? decodeURIComponent(pairs[1].trim()) : ""; + + this.parameters[name] = value; + } + + if (this.base64) + this.data = base64.decode(this.data); + + }, + + /** + * Returns the object as a valid Data URL string + * + * @returns {String} The Data URL + */ + toString : function() { + let parametersList = []; + + for (let name in this.parameters) { + let encodedParameter = encodeURIComponent(name); + let value = this.parameters[name]; + + if (value) + encodedParameter += "=" + encodeURIComponent(value); + + parametersList.push(encodedParameter); + } + + // If there is at least a parameter, add an empty string in order + // to start with a `;` on join call. + if (parametersList.length > 0) + parametersList.unshift(""); + + let data = this.base64 ? base64.encode(this.data) : this.data; + + return "data:" + + this.mimeType + + parametersList.join(";") + "," + + encodeURIComponent(data); + } +}); + +exports.DataURL = DataURL; + +var getTLD = exports.getTLD = function getTLD (url) { + let uri = newURI(url.toString()); + let tld = null; + try { + tld = tlds.getPublicSuffix(uri); + } + catch (e) { + if (e.result != Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS && + e.result != Cr.NS_ERROR_HOST_IS_IP_ADDRESS) { + throw e; + } + } + return tld; +}; + +var isValidURI = exports.isValidURI = function (uri) { + try { + newURI(uri); + } + catch(e) { + return false; + } + return true; +} + +function isLocalURL(url) { + if (String.indexOf(url, './') === 0) + return true; + + try { + return ['resource', 'data', 'chrome'].indexOf(URL(url).scheme) > -1; + } + catch(e) {} + + return false; +} +exports.isLocalURL = isLocalURL; |