Skip to content

Commit

Permalink
Run "@grant none" scripts with a <script> tag in the document.
Browse files Browse the repository at this point in the history
No eval, no sandbox at all!

Refs greasemonkey#1427
  • Loading branch information
arantius committed Jun 25, 2012
1 parent a3cedf4 commit 4c268d8
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 71 deletions.
3 changes: 3 additions & 0 deletions chrome.manifest
Expand Up @@ -5,6 +5,9 @@ contract @greasemonkey.mozdev.org/greasemonkey-service;1 {77bf3650-1cd6-11da-8
category profile-after-change @greasemonkey.mozdev.org/greasemonkey-service;1 @greasemonkey.mozdev.org/greasemonkey-service;1
category content-policy @greasemonkey.mozdev.org/greasemonkey-service;1 @greasemonkey.mozdev.org/greasemonkey-service;1

component {20d898f3-2fb8-4b3a-b8c7-7ad6c2c48598} components/scriptProtocol.js
contract @mozilla.org/network/protocol;1?name=greasemonkey-script {20d898f3-2fb8-4b3a-b8c7-7ad6c2c48598}

content greasemonkey content/
skin greasemonkey classic/1.0 skin/

Expand Down
93 changes: 23 additions & 70 deletions components/greasemonkey.js
Expand Up @@ -3,7 +3,6 @@
var DESCRIPTION = "GM_GreasemonkeyService";
var CONTRACTID = "@greasemonkey.mozdev.org/greasemonkey-service;1";
var CLASSID = Components.ID("{77bf3650-1cd6-11da-8cd6-0800200c9a66}");
var GM_GUID = "{e4a8a97b-f2ed-450b-b12d-ee082ba24781}";

var Cc = Components.classes;
var Ci = Components.interfaces;
Expand Down Expand Up @@ -34,7 +33,6 @@ var gExtensionPath = (function() {

// Only a particular set of strings are allowed. See: http://goo.gl/ex2LJ
var gMaxJSVersion = "ECMAv5";
var gGreasemonkeyVersion = 'unknown';

var gMenuCommands = [];
var gStartupHasRun = false;
Expand Down Expand Up @@ -89,21 +87,6 @@ function GM_apiLeakCheck(apiName) {
function createSandbox(
aScript, aContentWin, aChromeWin, aFirebugConsole, aUrl
) {
if (GM_util.inArray(aScript.grants, 'none')) {
// If there is an explicit none grant, use a plain unwrapped sandbox
// with no other content.
var contentSandbox = new Components.utils.Sandbox(
aContentWin,
{
'sandboxName': aScript.id,
'sandboxPrototype': aContentWin,
'wantXrays': false,
});
Components.utils.evalInSandbox(
'const GM_info = ' + uneval(info(aScript)), contentSandbox);
return contentSandbox;
}

var sandbox = new Components.utils.Sandbox(
aContentWin,
{
Expand Down Expand Up @@ -167,11 +150,6 @@ function createSandbox(
'contentStartRequest');
}

if (GM_util.inArray(aScript.grants, 'GM_info')) {
Components.utils.evalInSandbox(
'const GM_info = ' + uneval(info(aScript)), sandbox);
}

return sandbox;
}

Expand Down Expand Up @@ -208,45 +186,12 @@ function getFirebugConsole(wrappedContentWin, chromeWin) {
}
}

function info(aScript) {
var matches = [];
for (var i = 0, m = null; m = aScript.matches[i]; i++) {
matches[matches.length] = m.pattern;
}
return {
'version': gGreasemonkeyVersion,
'scriptWillUpdate': aScript.isRemoteUpdateAllowed(),
'script': {
'description': aScript.description,
'excludes': aScript.excludes,
// 'icon': ???,
'includes': aScript.includes,
'matches': matches,
'name': aScript.name,
'namespace': aScript.namespace,
// 'requires': ???,
// 'resources': ???,
'run-at': aScript.runAt,
'unwrap': aScript.unwrap,
'version': aScript.version,
},
'scriptMetaStr': extractMeta(aScript.textContent),
}
}

function isTempScript(uri) {
if (uri.scheme != "file") return false;
var file = gFileProtocolHandler.getFileFromURLSpec(uri.spec);
return gTmpDir.contains(file, true);
}

function loadGreasemonkeyVersion() {
Components.utils.import("resource://gre/modules/AddonManager.jsm");
AddonManager.getAddonByID(GM_GUID, function(addon) {
gGreasemonkeyVersion = new String(addon.version);
});
}

function openInTab(safeContentWin, chromeWin, url, aLoadInBackground) {
if (!GM_apiLeakCheck("GM_openInTab")) {
return undefined;
Expand Down Expand Up @@ -346,7 +291,6 @@ function startup(aService) {

Cu.import(gmRunScriptFilename);
Cu.import("resource://greasemonkey/third-party/getChromeWinForContentWin.js");
Cu.import("resource://greasemonkey/parseScript.js");
Cu.import("resource://greasemonkey/prefmanager.js");
Cu.import("resource://greasemonkey/util.js"); // At top = fail in FF3.

Expand All @@ -358,8 +302,6 @@ function startup(aService) {
loader.loadSubScript("chrome://greasemonkey/content/xmlhttprequester.js");
loader.loadSubScript("chrome://greasemonkey/content/third-party/mpl-utils.js");

loadGreasemonkeyVersion();

var observerService = Components.classes['@mozilla.org/observer-service;1']
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(aService, 'document-element-inserted', false);
Expand Down Expand Up @@ -406,9 +348,9 @@ service.prototype.shouldLoad = function(ct, cl, org, ctx, mt, ext) {
return ret;
}

// Don't interrupt the "view-source:" scheme (which is
// triggered if the link in the error console is clicked).
if ("view-source" == cl.scheme) {
// Don't interrupt the "view-source:" scheme (which is triggered if the link
// in the error console is clicked), nor the "greasemonkey-script:" scheme.
if ("view-source" == cl.scheme || "greasemonkey-script" == cl.scheme) {
return ret;
}

Expand Down Expand Up @@ -517,15 +459,26 @@ service.prototype.injectScripts = function(
var firebugConsole = getFirebugConsole(wrappedContentWin, chromeWin);

for (var i = 0, script = null; script = scripts[i]; i++) {
var sandbox = createSandbox(
script, wrappedContentWin, chromeWin, firebugConsole, url);

var scriptSrc = GM_util.getScriptSource(script);
var shouldWrap = !script.unwrap && !GM_util.inArray(script.grants, 'none');
if (shouldWrap) scriptSrc = anonWrap(scriptSrc);
if (!runScriptInSandbox(scriptSrc, sandbox, script) && !shouldWrap) {
// Wrap anyway on early return.
runScriptInSandbox(anonWrap(scriptSrc), sandbox, script);
if (GM_util.inArray(script.grants, 'none')) {
// Create a script node.
var scriptNode = wrappedContentWin.document.createElement('script');
scriptNode.setAttribute('type', 'application/javascript;version=1.8');
scriptNode.setAttribute('src', [
'greasemonkey-script:', script.uuid, '/', script.name, '.user.js'
].join(''));
// Append it to the document to execute, remove it to clean up.
var insertPoint = wrappedContentWin.document.documentElement.firstChild;
insertPoint.appendChild(scriptNode);
insertPoint.removeChild(scriptNode);
} else {
var sandbox = createSandbox(
script, wrappedContentWin, chromeWin, firebugConsole, url);
var scriptSrc = GM_util.getScriptSource(script);
if (!script.unwrap) scriptSrc = anonWrap(scriptSrc);
if (!runScriptInSandbox(scriptSrc, sandbox, script) && script.unwrap) {
// Wrap anyway on early return.
runScriptInSandbox(anonWrap(scriptSrc), sandbox, script);
}
}
}
};
Expand Down
117 changes: 117 additions & 0 deletions components/scriptProtocol.js
@@ -0,0 +1,117 @@
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import('resource://greasemonkey/util.js');

const Cc = Components.classes;
const Ci = Components.interfaces;
const schemeName = 'greasemonkey-script';
const ioService = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService);


function ScriptChannel(aUri, aScript) {
// nsIRequest
this.loadFlags = 0;
this.loadGroup = null;
this.name = aUri.spec;
this.status = 200;

this.content = '';
if (aScript) {
this.content = GM_util.getScriptSource(aScript);
} else {
this.status = 404;
}

// nsIChannel
this.contentCharset = 'utf-8';
this.contentLength = this.content.length;
this.contentType = 'application/javascript';
this.notificationCallbacks = null;
this.originalURI = aUri;
this.owner = null;
this.securityInfo = null;
this.URI = aUri;

this.pipe = Cc['@mozilla.org/pipe;1'].createInstance(Ci.nsIPipe);
this.pipe.init(true, true, 0, 0, null);

var inputStreamChannel = Cc['@mozilla.org/network/input-stream-channel;1']
.createInstance(Ci.nsIInputStreamChannel);
inputStreamChannel.setURI(aUri);
inputStreamChannel.contentStream = this.pipe.inputStream;
this.channel = inputStreamChannel.QueryInterface(Ci.nsIChannel);
}

// nsIChannel
ScriptChannel.prototype.asyncOpen = function(aListener, aContext) {
this.channel.asyncOpen(aListener, aContext);
this.pipe.outputStream.write(this.content, this.content.length);
this.pipe.outputStream.close();
};


function ScriptProtocol() {}

// XPCOMUtils generation
ScriptProtocol.prototype.classDescription =
'Protocol handler for greasemonkey-script:';
ScriptProtocol.prototype.classID =
Components.ID('{20d898f3-2fb8-4b3a-b8c7-7ad6c2c48598}');
ScriptProtocol.prototype.contractID =
'@mozilla.org/network/protocol;1?name=' + schemeName;
ScriptProtocol.prototype.QueryInterface = XPCOMUtils.generateQI([
Components.interfaces.nsIProtocolHandler,
Components.interfaces.nsISupports,
]);

// nsIProtocolHandler
ScriptProtocol.prototype.scheme = schemeName;
ScriptProtocol.prototype.defaultPort = -1;
ScriptProtocol.prototype.protocolFlags = 0
| Ci.nsIProtocolHandler.URI_INHERITS_SECURITY_CONTEXT
| Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE
| Ci.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE
| Ci.nsIProtocolHandler.URI_NOAUTH
| Ci.nsIProtocolHandler.URI_NON_PERSISTABLE
| Ci.nsIProtocolHandler.URI_NORELATIVE
;

// nsIProtocolHandler
ScriptProtocol.prototype.allowPort = function(aPort, aScheme) {
return false;
};

// nsIProtocolHandler
ScriptProtocol.prototype.newURI = function(aSpec, aCharset, aBaseUri) {
var uri = Cc['@mozilla.org/network/simple-uri;1'].createInstance(Ci.nsIURI);
uri.spec = aSpec;
return uri;
};

// nsIProtocolHandler
ScriptProtocol.prototype.newChannel = function(aUri) {
var m = aUri.spec.match(/greasemonkey-script:([-0-9a-f]+)\/(.*)/);
var script = GM_util.getService().config.getMatchingScripts(function(script) {
return script.uuid == m[1];
})[0];
if (aUri.spec.match(/\.user\.js$/)) {
return new ScriptChannel(aUri, script);
} else if (script) {
for (var i = 0, resource = null; resource = script.resources[i]; i++) {
if (resource.name == m[2]) {
return ioService.newChannelFromURI(
GM_util.getUriFromFile(resource.file));
}
}
} else {
return new ScriptChannel(aUri, script);
}
};


const components = [ScriptProtocol];
if ("generateNSGetFactory" in XPCOMUtils) {
var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); // Gecko 2.0+
} else {
var NSGetModule = XPCOMUtils.generateNSGetModule(components); // Gecko 1.9.x
}
46 changes: 46 additions & 0 deletions modules/script.js
Expand Up @@ -2,6 +2,7 @@ var EXPORTED_SYMBOLS = ['Script'];

Components.utils.import('resource://gre/modules/AddonManager.jsm');
Components.utils.import('resource://greasemonkey/constants.js');
Components.utils.import("resource://greasemonkey/parseScript.js");
Components.utils.import('resource://greasemonkey/prefmanager.js');
Components.utils.import('resource://greasemonkey/scriptIcon.js');
Components.utils.import('resource://greasemonkey/scriptRequire.js');
Expand All @@ -15,6 +16,13 @@ var stringBundle = Components
.getService(Components.interfaces.nsIStringBundleService)
.createBundle("chrome://greasemonkey/locale/greasemonkey.properties");

var GM_GUID = "{e4a8a97b-f2ed-450b-b12d-ee082ba24781}";
var gGreasemonkeyVersion = 'unknown';
Components.utils.import("resource://gre/modules/AddonManager.jsm");
AddonManager.getAddonByID(GM_GUID, function(addon) {
gGreasemonkeyVersion = '' + addon.version;
});

function Script(configNode) {
this._observers = [];

Expand Down Expand Up @@ -47,6 +55,7 @@ function Script(configNode) {
this._updateVersion = null;
this._userExcludes = [];
this._userIncludes = [];
this._uuid = [];
this._version = null;

this.checkRemoteUpdates = true;
Expand Down Expand Up @@ -124,6 +133,9 @@ function Script_getDependencies() {
Script.prototype.__defineGetter__('description',
function Script_getDescription() { return this._description; });

Script.prototype.__defineGetter__('uuid',
function Script_getUuid() { return this._uuid; });

Script.prototype.__defineGetter__('version',
function Script_getVersion() { return this._version; });

Expand Down Expand Up @@ -298,6 +310,8 @@ Script.prototype._loadFromConfigNode = function(node) {
this._installTime = parseInt(node.getAttribute("installTime"), 10);
}

this._uuid = node.getAttribute("uuid");

for (var i = 0, childNode; childNode = node.childNodes[i]; i++) {
switch (childNode.nodeName) {
case "Exclude":
Expand Down Expand Up @@ -417,6 +431,7 @@ Script.prototype.toConfigNode = function(doc) {
scriptNode.setAttribute("namespace", this._namespace);
scriptNode.setAttribute("runAt", this._runAt);
scriptNode.setAttribute("updateAvailable", this.updateAvailable);
scriptNode.setAttribute("uuid", this._uuid);
scriptNode.setAttribute("version", this._version);

if (this._downloadURL) {
Expand Down Expand Up @@ -448,6 +463,32 @@ function Script_getPreviewURL() {
.newFileURI(this._tempFile).spec;
});

Script.prototype.info = function() {
var matches = [];
for (var i = 0, m = null; m = this.matches[i]; i++) {
matches[matches.length] = m.pattern;
}
return {
'version': gGreasemonkeyVersion,
'scriptWillUpdate': this.isRemoteUpdateAllowed(),
'script': {
'description': this.description,
'excludes': this.excludes,
// 'icon': ???,
'includes': this.includes,
'matches': matches,
'name': this.name,
'namespace': this.namespace,
// 'requires': ???,
// 'resources': ???,
'run-at': this.runAt,
'unwrap': this.unwrap,
'version': this.version,
},
'scriptMetaStr': extractMeta(this.textContent),
}
};

Script.prototype.isModified = function() {
if (!this.fileExists(this.file)) return false;
if (this._modifiedTime != this.file.lastModifiedTime) {
Expand Down Expand Up @@ -659,6 +700,11 @@ Script.prototype.checkConfig = function() {
this.grants = GM_util.sniffGrants(this);
this._changed('modified', 'grants');
}

if (!this._uuid || !this._uuid.length) {
this._uuid = GM_util.uuid();
this._changed('modified', 'uuid');
}
};

Script.prototype.checkForRemoteUpdate = function(aForced, aCallback) {
Expand Down

0 comments on commit 4c268d8

Please sign in to comment.