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;