addon-sdk/source/lib/sdk/url.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/addon-sdk/source/lib/sdk/url.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,319 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +"use strict";
     1.8 +
     1.9 +module.metadata = {
    1.10 +  "stability": "experimental"
    1.11 +};
    1.12 +
    1.13 +const { Cc, Ci, Cr } = require("chrome");
    1.14 +
    1.15 +const { Class } = require("./core/heritage");
    1.16 +const base64 = require("./base64");
    1.17 +var tlds = Cc["@mozilla.org/network/effective-tld-service;1"]
    1.18 +          .getService(Ci.nsIEffectiveTLDService);
    1.19 +
    1.20 +var ios = Cc['@mozilla.org/network/io-service;1']
    1.21 +          .getService(Ci.nsIIOService);
    1.22 +
    1.23 +var resProt = ios.getProtocolHandler("resource")
    1.24 +              .QueryInterface(Ci.nsIResProtocolHandler);
    1.25 +
    1.26 +var URLParser = Cc["@mozilla.org/network/url-parser;1?auth=no"]
    1.27 +                .getService(Ci.nsIURLParser);
    1.28 +
    1.29 +function newURI(uriStr, base) {
    1.30 +  try {
    1.31 +    let baseURI = base ? ios.newURI(base, null, null) : null;
    1.32 +    return ios.newURI(uriStr, null, baseURI);
    1.33 +  }
    1.34 +  catch (e) {
    1.35 +    if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
    1.36 +      throw new Error("malformed URI: " + uriStr);
    1.37 +    }
    1.38 +    if (e.result == Cr.NS_ERROR_FAILURE ||
    1.39 +        e.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
    1.40 +      throw new Error("invalid URI: " + uriStr);
    1.41 +    }
    1.42 +  }
    1.43 +}
    1.44 +
    1.45 +function resolveResourceURI(uri) {
    1.46 +  var resolved;
    1.47 +  try {
    1.48 +    resolved = resProt.resolveURI(uri);
    1.49 +  }
    1.50 +  catch (e) {
    1.51 +    if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
    1.52 +      throw new Error("resource does not exist: " + uri.spec);
    1.53 +    }
    1.54 +  }
    1.55 +  return resolved;
    1.56 +}
    1.57 +
    1.58 +let fromFilename = exports.fromFilename = function fromFilename(path) {
    1.59 +  var file = Cc['@mozilla.org/file/local;1']
    1.60 +             .createInstance(Ci.nsILocalFile);
    1.61 +  file.initWithPath(path);
    1.62 +  return ios.newFileURI(file).spec;
    1.63 +};
    1.64 +
    1.65 +let toFilename = exports.toFilename = function toFilename(url) {
    1.66 +  var uri = newURI(url);
    1.67 +  if (uri.scheme == "resource")
    1.68 +    uri = newURI(resolveResourceURI(uri));
    1.69 +  if (uri.scheme == "chrome") {
    1.70 +    var channel = ios.newChannelFromURI(uri);
    1.71 +    try {
    1.72 +      channel = channel.QueryInterface(Ci.nsIFileChannel);
    1.73 +      return channel.file.path;
    1.74 +    }
    1.75 +    catch (e) {
    1.76 +      if (e.result == Cr.NS_NOINTERFACE) {
    1.77 +        throw new Error("chrome url isn't on filesystem: " + url);
    1.78 +      }
    1.79 +    }
    1.80 +  }
    1.81 +  if (uri.scheme == "file") {
    1.82 +    var file = uri.QueryInterface(Ci.nsIFileURL).file;
    1.83 +    return file.path;
    1.84 +  }
    1.85 +  throw new Error("cannot map to filename: " + url);
    1.86 +};
    1.87 +
    1.88 +function URL(url, base) {
    1.89 +  if (!(this instanceof URL)) {
    1.90 +     return new URL(url, base);
    1.91 +  }
    1.92 +
    1.93 +  var uri = newURI(url, base);
    1.94 +
    1.95 +  var userPass = null;
    1.96 +  try {
    1.97 +    userPass = uri.userPass ? uri.userPass : null;
    1.98 +  }
    1.99 +  catch (e) {
   1.100 +    if (e.result != Cr.NS_ERROR_FAILURE) {
   1.101 +      throw e;
   1.102 +    }
   1.103 +  }
   1.104 +
   1.105 +  var host = null;
   1.106 +  try {
   1.107 +    host = uri.host;
   1.108 +  }
   1.109 +  catch (e) {
   1.110 +    if (e.result != Cr.NS_ERROR_FAILURE) {
   1.111 +      throw e;
   1.112 +    }
   1.113 +  }
   1.114 +
   1.115 +  var port = null;
   1.116 +  try {
   1.117 +    port = uri.port == -1 ? null : uri.port;
   1.118 +  }
   1.119 +  catch (e) {
   1.120 +    if (e.result != Cr.NS_ERROR_FAILURE) {
   1.121 +      throw e;
   1.122 +    }
   1.123 +  }
   1.124 +
   1.125 +  let uriData = [uri.path, uri.path.length, {}, {}, {}, {}, {}, {}];
   1.126 +  URLParser.parsePath.apply(URLParser, uriData);
   1.127 +  let [{ value: filepathPos }, { value: filepathLen },
   1.128 +    { value: queryPos }, { value: queryLen },
   1.129 +    { value: refPos }, { value: refLen }] = uriData.slice(2);
   1.130 +
   1.131 +  let hash = uri.ref ? "#" + uri.ref : "";
   1.132 +  let pathname = uri.path.substr(filepathPos, filepathLen);
   1.133 +  let search = uri.path.substr(queryPos, queryLen);
   1.134 +  search = search ? "?" + search : "";
   1.135 +
   1.136 +  this.__defineGetter__("scheme", function() uri.scheme);
   1.137 +  this.__defineGetter__("userPass", function() userPass);
   1.138 +  this.__defineGetter__("host", function() host);
   1.139 +  this.__defineGetter__("hostname", function() host);
   1.140 +  this.__defineGetter__("port", function() port);
   1.141 +  this.__defineGetter__("path", function() uri.path);
   1.142 +  this.__defineGetter__("pathname", function() pathname);
   1.143 +  this.__defineGetter__("hash", function() hash);
   1.144 +  this.__defineGetter__("href", function() uri.spec);
   1.145 +  this.__defineGetter__("origin", function() uri.prePath);
   1.146 +  this.__defineGetter__("protocol", function() uri.scheme + ":");
   1.147 +  this.__defineGetter__("search", function() search);
   1.148 +
   1.149 +  Object.defineProperties(this, {
   1.150 +    toString: {
   1.151 +      value: function URL_toString() new String(uri.spec).toString(),
   1.152 +      enumerable: false
   1.153 +    },
   1.154 +    valueOf: {
   1.155 +      value: function() new String(uri.spec).valueOf(),
   1.156 +      enumerable: false
   1.157 +    },
   1.158 +    toSource: {
   1.159 +      value: function() new String(uri.spec).toSource(),
   1.160 +      enumerable: false
   1.161 +    }
   1.162 +  });
   1.163 +
   1.164 +  return this;
   1.165 +};
   1.166 +
   1.167 +URL.prototype = Object.create(String.prototype);
   1.168 +exports.URL = URL;
   1.169 +
   1.170 +/**
   1.171 + * Parse and serialize a Data URL.
   1.172 + *
   1.173 + * See: http://tools.ietf.org/html/rfc2397
   1.174 + *
   1.175 + * Note: Could be extended in the future to decode / encode automatically binary
   1.176 + * data.
   1.177 + */
   1.178 +const DataURL = Class({
   1.179 +
   1.180 +  get base64 () {
   1.181 +    return "base64" in this.parameters;
   1.182 +  },
   1.183 +
   1.184 +  set base64 (value) {
   1.185 +    if (value)
   1.186 +      this.parameters["base64"] = "";
   1.187 +    else
   1.188 +      delete this.parameters["base64"];
   1.189 +  },
   1.190 +  /**
   1.191 +  * Initialize the Data URL object. If a uri is given, it will be parsed.
   1.192 +  *
   1.193 +  * @param {String} [uri] The uri to parse
   1.194 +  *
   1.195 +  * @throws {URIError} if the Data URL is malformed
   1.196 +   */
   1.197 +  initialize: function(uri) {
   1.198 +    // Due to bug 751834 it is not possible document and define these
   1.199 +    // properties in the prototype.
   1.200 +
   1.201 +    /**
   1.202 +     * An hashmap that contains the parameters of the Data URL. By default is
   1.203 +     * empty, that accordingly to RFC is equivalent to {"charset" : "US-ASCII"}
   1.204 +     */
   1.205 +    this.parameters = {};
   1.206 +
   1.207 +    /**
   1.208 +     * The MIME type of the data. By default is empty, that accordingly to RFC
   1.209 +     * is equivalent to "text/plain"
   1.210 +     */
   1.211 +    this.mimeType = "";
   1.212 +
   1.213 +    /**
   1.214 +     * The string that represent the data in the Data URL
   1.215 +     */
   1.216 +    this.data = "";
   1.217 +
   1.218 +    if (typeof uri === "undefined")
   1.219 +      return;
   1.220 +
   1.221 +    uri = String(uri);
   1.222 +
   1.223 +    let matches = uri.match(/^data:([^,]*),(.*)$/i);
   1.224 +
   1.225 +    if (!matches)
   1.226 +      throw new URIError("Malformed Data URL: " + uri);
   1.227 +
   1.228 +    let mediaType = matches[1].trim();
   1.229 +
   1.230 +    this.data = decodeURIComponent(matches[2].trim());
   1.231 +
   1.232 +    if (!mediaType)
   1.233 +      return;
   1.234 +
   1.235 +    let parametersList = mediaType.split(";");
   1.236 +
   1.237 +    this.mimeType = parametersList.shift().trim();
   1.238 +
   1.239 +    for (let parameter, i = 0; parameter = parametersList[i++];) {
   1.240 +      let pairs = parameter.split("=");
   1.241 +      let name = pairs[0].trim();
   1.242 +      let value = pairs.length > 1 ? decodeURIComponent(pairs[1].trim()) : "";
   1.243 +
   1.244 +      this.parameters[name] = value;
   1.245 +    }
   1.246 +
   1.247 +    if (this.base64)
   1.248 +      this.data = base64.decode(this.data);
   1.249 +
   1.250 +  },
   1.251 +
   1.252 +  /**
   1.253 +   * Returns the object as a valid Data URL string
   1.254 +   *
   1.255 +   * @returns {String} The Data URL
   1.256 +   */
   1.257 +  toString : function() {
   1.258 +    let parametersList = [];
   1.259 +
   1.260 +    for (let name in this.parameters) {
   1.261 +      let encodedParameter = encodeURIComponent(name);
   1.262 +      let value = this.parameters[name];
   1.263 +
   1.264 +      if (value)
   1.265 +        encodedParameter += "=" + encodeURIComponent(value);
   1.266 +
   1.267 +      parametersList.push(encodedParameter);
   1.268 +    }
   1.269 +
   1.270 +    // If there is at least a parameter, add an empty string in order
   1.271 +    // to start with a `;` on join call.
   1.272 +    if (parametersList.length > 0)
   1.273 +      parametersList.unshift("");
   1.274 +
   1.275 +    let data = this.base64 ? base64.encode(this.data) : this.data;
   1.276 +
   1.277 +    return "data:" +
   1.278 +      this.mimeType +
   1.279 +      parametersList.join(";") + "," +
   1.280 +      encodeURIComponent(data);
   1.281 +  }
   1.282 +});
   1.283 +
   1.284 +exports.DataURL = DataURL;
   1.285 +
   1.286 +let getTLD = exports.getTLD = function getTLD (url) {
   1.287 +  let uri = newURI(url.toString());
   1.288 +  let tld = null;
   1.289 +  try {
   1.290 +    tld = tlds.getPublicSuffix(uri);
   1.291 +  }
   1.292 +  catch (e) {
   1.293 +    if (e.result != Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS &&
   1.294 +        e.result != Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
   1.295 +      throw e;
   1.296 +    }
   1.297 +  }
   1.298 +  return tld;
   1.299 +};
   1.300 +
   1.301 +let isValidURI = exports.isValidURI = function (uri) {
   1.302 +  try {
   1.303 +    newURI(uri);
   1.304 +  }
   1.305 +  catch(e) {
   1.306 +    return false;
   1.307 +  }
   1.308 +  return true;
   1.309 +}
   1.310 +
   1.311 +function isLocalURL(url) {
   1.312 +  if (String.indexOf(url, './') === 0)
   1.313 +    return true;
   1.314 +
   1.315 +  try {
   1.316 +    return ['resource', 'data', 'chrome'].indexOf(URL(url).scheme) > -1;
   1.317 +  }
   1.318 +  catch(e) {}
   1.319 +
   1.320 +  return false;
   1.321 +}
   1.322 +exports.isLocalURL = isLocalURL;

mercurial