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

340 lines
8.5 KiB
JavaScript
Raw Normal View History

/* 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;
}
let 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;
};
let 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_NORMAL,
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 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__("scheme", function() uri.scheme);
this.__defineGetter__("userPass", function() userPass);
this.__defineGetter__("host", function() host);
this.__defineGetter__("hostname", function() host);
this.__defineGetter__("port", function() port);
this.__defineGetter__("path", function() uri.path);
this.__defineGetter__("pathname", function() pathname);
this.__defineGetter__("hash", function() hash);
this.__defineGetter__("href", function() uri.spec);
this.__defineGetter__("origin", function() uri.prePath);
this.__defineGetter__("protocol", function() uri.scheme + ":");
this.__defineGetter__("search", function() 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;
let 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;
};
let 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;