/* 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;