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