more pm28 fixes
[guerillascript.git] / main / modules / sbapi / sandbox.js
blob900ea347f857e23c4bc5c39c5d04e53e7e523aa8
1 /*
2 * Portions copyright 2004-2007 Aaron Boodman
3 * Copyright 2015 Ketmar Dark <ketmar@ketmar.no-ip.org>
4 * Contributors: See contributors list in install.rdf and CREDITS
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * Note that this license applies only to the Greasemonkey extension source
14 * files, not to the user scripts which it runs. User scripts are licensed
15 * separately by their authors.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
25 * The above copyright notice and this permission notice shall be included in all
26 * copies or substantial portions of the Software.
28 ////////////////////////////////////////////////////////////////////////////////
29 let {GuerillaXmlHttpReqester} = require("sbapi/xmlhttprequest");
30 let {ScriptStorage} = require("sbapi/scriptstorage");
31 let {openTab} = require("sbapi/opentab");
32 let {buildRelFile} = require("scriptcache");
35 ////////////////////////////////////////////////////////////////////////////////
36 function fileReadBinary (fl) {
37 let istream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
38 istream.init(fl, -1, -1, false);
39 let bstream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
40 bstream.setInputStream(istream);
41 let bytes = bstream.readBytes(bstream.available());
42 bstream.close();
43 return bytes;
47 ////////////////////////////////////////////////////////////////////////////////
48 let ioSvc = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
50 function uriFromUrl (url, base) {
51 let baseUri = null;
52 if (typeof(base) === "string") {
53 baseUri = uriFromUrl(base);
54 } else if (base) {
55 baseUri = base;
57 try {
58 return ioSvc.newURI(url, null, baseUri);
59 } catch (e) {
60 return null;
65 let newFileURI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService).newFileURI;
68 ////////////////////////////////////////////////////////////////////////////////
69 // string aString: A string of data to be hashed.
70 // string aAlg: optional; the hash algorithm to be used; possible values are: MD2, MD5, SHA1, SHA256, SHA384, and SHA512; defaults to SHA1
71 // string aCharset: optional; the charset used by the passed string; defaults to UTF-8
72 function cryptoHashStr (aString, aAlg, aCharset) {
73 const PR_UINT32_MAX = 0xffffffff; // this tells updateFromStream to read the entire string
75 let str = ""+aString;
76 let alg = (""+(aAlg||"SHA1")).trim().toUpperCase();
77 let charset = (""+(aCharset||"UTF-8")).trim();
79 let chashObj = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
81 try {
82 chashObj.initWithString(alg);
83 } catch (e) {
84 logError("invalid hash algorithm: '"+aAlg+"'");
85 throw new Error("invalid hash algorithm: '"+aAlg+"'");
88 let uniconvObj = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
89 try {
90 uniconvObj.charset = charset;
91 } catch (e) {
92 logError("invalid charset: '"+aCharset+"'");
93 throw new Error("invalid charset: '"+aCharset+"'");
96 if (str) chashObj.updateFromStream(uniconvObj.convertToInputStream(str), PR_UINT32_MAX);
97 let hash = chashObj.finish(false); // hash as raw octets
98 //return [("0"+hash.charCodeAt(i).toString(16)).slice(-2) for (i in hash)].join("");
99 var res = "";
100 for (var i = 0; i < hash.length; ++i) res += ("0"+hash.charCodeAt(i).toString(16)).slice(-2);
101 return res;
105 ////////////////////////////////////////////////////////////////////////////////
106 function safeHTMLParser (domWin, htmlstr, baseUrl) {
107 //conlog("domWin: "+domWin);
108 //conlog("htmlstr: "+htmlstr);
109 //conlog("baseUrl: "+baseUrl);
110 let doc = domWin.document.implementation.createDocument("", "", domWin.document.implementation.createDocumentType("html", "", ""));
111 doc.appendChild(doc.createElement("html"));
112 doc.documentElement.appendChild(doc.createElement("body"));
114 let baseUri = null, frag;
115 if (typeof(baseUrl) !== "undefined") baseUri = uriFromUrl(baseUrl);
117 let pu = Cc["@mozilla.org/parserutils;1"].createInstance(Ci.nsIParserUtils);
118 if (!pu) { logError("FUCKED!"); return null; }
120 frag = pu.parseFragment(htmlstr, 0, false, baseUri, doc.body);
121 doc.adoptNode(frag);
122 doc.body.appendChild(frag);
123 return doc;
127 ////////////////////////////////////////////////////////////////////////////////
128 function genUUID () {
129 let uuidgen = Cc["@mozilla.org/uuid-generator;1"].createInstance(Ci.nsIUUIDGenerator);
130 if (!uuidgen) throw new Error("no UUID generator available!");
131 return uuidgen.generateUUID().toString();
135 ////////////////////////////////////////////////////////////////////////////////
136 let storageObjects = {};
139 function getStorageObject (nfo) {
140 if (nfo.name in storageObjects) {
141 return storageObjects[nfo.name];
142 } else {
143 let res = new ScriptStorage(nfo);
144 storageObjects[nfo.name] = res;
145 return res;
150 exports.closeStorageObjects = function () {
151 for (let k in storageObjects) {
152 if (typeof(k) != "string") continue;
153 let dbo = storageObjects[k];
154 //if (typeof(dbo) != "object") continue;
155 if (dbo.opened) {
156 debuglog("freeing storage object for '", k, "'");
157 dbo.close();
160 storageObjects = {};
164 ////////////////////////////////////////////////////////////////////////////////
165 exports.createSandbox = function (domWin, nfo, url) {
166 let sandbox;
167 let scres = nfo.resources;
169 if (nfo.unwrapped) {
170 // create "unwrapped" sandbox
171 sandbox = Cu.Sandbox(domWin, {
172 sandboxName: "unwrapped",
173 sameZoneAs: domWin, // help GC a little
174 sandboxPrototype: domWin/*.wrappedJSObject*/,
175 wantXrays: false,
177 // alias unsafeWindow for compatibility
178 Cu.evalInSandbox("const unsafeWindow = window;", sandbox);
179 //sandbox.isWrapped = !nfo.unwrapped;
180 } else {
181 // create "real" sandbox
182 sandbox = Cu.Sandbox(domWin, {
183 sandboxName: nfo.name,
184 sameZoneAs: domWin, // help GC a little
185 sandboxPrototype: domWin/*.wrappedJSObject*/,
186 wantXrays: true,
189 //sandbox.isWrapped = !nfo.unwrapped;
190 // no need for the "bugfix" from GM, as Pale Moon doesn't need it
191 //!sandbox.unsafeWindow = domWin.wrappedJSObject;
192 // alas, Tycho inherited this bug from the new Ff, so reapply it
194 var uwgok = false;
195 try {
196 var unsafeWindowGetter = new sandbox.Function("return window.wrappedJSObject||window;");
197 Object.defineProperty(sandbox, "unsafeWindow", {get: unsafeWindowGetter});
198 uwgok = true;
199 } catch (e) {
203 if (!uwgok) {
204 try {
205 //sandbox.unsafeWindow = domWin.wrappedJSObject;
206 var unsafeWinObj = domWin.wrappedJSObject;
207 var domwinobj = domWin;
208 var unsafeWindowGetter = function () {
209 logError("unsafeWindowGetter: "+domwinobj);
210 return domwinobj;
212 Object.defineProperty(sandbox, "unsafeWindow", {get: unsafeWindowGetter});
213 logError("usfw: "+domWin.wrappedJSObject);
214 uwgok = true;
215 } catch (e) {
216 logError("XERR: "+e.message);
220 try {
221 Cu.evalInSandbox("const unsafeWindow = window.wrappedJSObject||window;", sandbox, "ECMAv5", "__sandbox_creator__", 1);
222 } catch (e) {
223 logError("XERR: "+e.message);
226 // functions for interaction with unsafeWindow; see: http://goo.gl/C8Au16
227 sandbox.createObjectIn = Cu.createObjectIn;
228 sandbox.cloneInto = Cu.cloneInto;
229 sandbox.exportFunction = Cu.exportFunction;
231 sandbox.GM_generateUUID = tieto(null, genUUID);
232 sandbox.GM_cryptoHash = tieto(null, cryptoHashStr);
234 //Object.defineProperty(sandbox, "GM_safeHTMLParser", {get: function () tieto(null, safeHTMLParser, domWin)});
235 sandbox.GM_safeHTMLParser = tieto(null, safeHTMLParser, domWin);
237 let scriptStorage = getStorageObject(nfo);
238 sandbox.GM_getValue = tieto(scriptStorage, "getValue");
239 sandbox.GM_setValue = tieto(scriptStorage, "setValue");
240 sandbox.GM_deleteValue = tieto(scriptStorage, "deleteValue");
241 //sandbox.GM_listValues = tieto(scriptStorage, "listValues");
242 sandbox.GM_listValues = tieto(null, function (stg, sbox) {
243 //scriptStorage, "listValues");
244 let res = scriptStorage.listValues();
245 if (res) {
246 res = Cu.cloneInto(res, sbox);
248 return res;
249 }, scriptStorage, sandbox);
251 sandbox.GM_xmlhttpRequest = tieto(new GuerillaXmlHttpReqester(domWin, url, sandbox), "contentStartRequest");
253 sandbox.GM_addStyle = tieto(null, function (doc, cssstr) {
254 var head = doc.getElementsByTagName("head")[0];
255 if (head) {
256 var style = doc.createElement("style");
257 style.textContent = cssstr;
258 style.type = "text/css";
259 head.appendChild(style);
260 return style;
262 return null;
263 }, domWin.document);
265 sandbox.GM_openInTab = tieto(null, openTab, domWin);
267 sandbox.GM_getResourceText = tieto(null, function (name) {
268 if (typeof(name) === "undefined") throw new Error("GM_getResourceText(): no name given!");
269 name = ""+name;
270 let rsrc = scres[name];
271 if (!rsrc) throw new Error("GM_getResourceText(): no resource found: '"+name+"'");
272 return fileReadText(rsrc.file);
275 sandbox.GM_getResourceURL = tieto(null, function (name) {
276 //logError("GM_getResourceURL(): stub!");
277 //throw new Error("GM_getResourceURL() not implemented");
278 if (typeof(name) === "undefined") throw new Error("GM_getResourceText(): no name given!");
279 name = ""+name;
280 let rsrc = scres[name];
281 if (!rsrc) throw new Error("GM_getResourceText(): no resource found: '"+name+"'");
282 let rawdata = fileReadBinary(rsrc.file);
283 return "data:"+rsrc.contenttype+";base64,"+encodeURIComponent(btoa(rawdata));
286 // stubs
287 sandbox.GM_registerMenuCommand = tieto(null, function () { logError("GM_registerMenuCommand(): stub!"); });
288 sandbox.GM_setClipboard = tieto(null, function () { logError("GM_setClipboard(): stub!"); });
291 // provide log functions for both wrapped and unwrapped scripts
292 if (!nfo.unwrapped || nfo.wantlog) {
293 sandbox.conlog = tieto(null, conlog);
294 sandbox.logError = tieto(null, logError);
295 if (!nfo.unwrapped) sandbox.GM_log = tieto(null, conlog);
298 Object.defineProperty(sandbox, "GM_info", {
299 get: tieto(null, function () { logError("GM_info(): stub!"); return {}; }),
302 // nuke sandbox when this window unloaded
303 domWin.addEventListener("unload", function () {
304 if (sandbox) {
305 debuglog("**** NUKING SANDBOX *** [", nfo.name, "] : [", url, "]");
306 Cu.nukeSandbox(sandbox);
307 sandbox = null;
309 }, true);
312 return sandbox;
316 ////////////////////////////////////////////////////////////////////////////////
317 exports.runInSandbox = function (sandbox, nfo) {
318 // eval the code, with anonymous wrappers when/if appropriate
319 function evalLazyWrap (code, fileName, dowrap) {
320 try {
321 Cu.evalInSandbox(code, sandbox, "ECMAv5", fileName, 1);
322 } catch (e) {
323 if (dowrap && ("return not in function" == e.message)) {
324 // we never anon wrap unless forced to by a "return not in a function" error
325 logError("please, do not use `return` in top-level code in "+fileName+":"+e.lineNumber);
326 Cu.evalInSandbox("(function(){ "+code+"\n})()", sandbox, "ECMAv5", fileName, 1);
327 } else {
328 // otherwise raise
329 throw e;
334 // eval the code, with a try/catch to report errors cleanly
335 function evalNoThrow (code, fileName, dowrap) {
336 dowrap = !!dowrap;
337 try {
338 evalLazyWrap(code, fileName);
339 } catch (e) {
340 logException("UJS", e);
341 return false;
343 return true;
346 let libObj = {
347 _dir: null,
348 flist: {},
349 get dir () {
350 if (!this._dir) {
351 var dirr = getUserLibDir();
352 this._dir = dirr;
353 return dirr;
355 return this._dir;
359 let incObj = {
360 _dir: null,
361 flist: {},
362 get dir () {
363 if (!this._dir) {
364 var dirr = getUserJSDir();
365 dirr.append(nfo.name);
366 this._dir = dirr;
367 return dirr;
369 return this._dir;
373 let includer = function (fname) {
374 //debuglog("includer: ", JSON.stringify(fname));
375 if (typeof(fname) === "object") {
376 if (!("length" in fname)) throw new Error("string expected");
377 if (fname.length == 0) return;
378 } else {
379 if (typeof(fname) !== "string") throw new Error("string expected");
380 fname = [fname];
382 for (let jsfn of fname) {
383 if (jsfn === undefined || jsfn === null) continue;
384 if (typeof(jsfn) !== "string") throw new Error("string expected");
385 if (jsfn.length == 0) continue;
386 if (!(/\.js$/.test(jsfn))) jsfn += ".js";
387 let fl = buildRelFile(this.dir, jsfn);
388 //debuglog("loading ", fl.path);
389 if (!fl || !fl.exists() || fl.isDirectory() || !fl.isReadable()) {
390 throw new Error("can't import file: "+jsfn);
392 let path = fl.path;
393 let rq = this.flist[path];
394 if (rq !== undefined) {
395 if (rq === false) return; // nothing to do
396 throw new Error("circular import in file: "+jsfn);
398 this.flist[path] = true; // in progress
399 let text = fileReadText(fl);
400 evalLazyWrap(text, newFileURI(fl).spec, false);
401 this.flist[path] = false; // done
405 try {
406 sandbox.requirelib = tieto(libObj, includer);
407 sandbox.requireinc = tieto(incObj, includer);
409 // eval libraries
410 for (let fl of nfo.libs) {
411 if (!fl.exists() || fl.isDirectory() || !fl.isReadable()) return;
412 if (fl.path in reqLibs) continue; // already required
413 libObj.flist[fl.path] = true;
414 let text = fileReadText(fl);
415 let url = newFileURI(fl).spec;
416 if (!evalNoThrow(text, url)) return;
417 libObj.flist[fl.path] = false;
420 // eval includes
421 for (let fl of nfo.incs) {
422 if (!fl.exists() || fl.isDirectory() || !fl.isReadable()) return;
423 if (fl.path in reqFiles) continue; // already required
424 incObj.flist[fl.path] = true;
425 let text = fileReadText(fl);
426 let url = newFileURI(fl).spec;
427 if (!evalNoThrow(text, url)) return;
428 incObj.flist[fl.path] = false;
431 // eval main script
433 if (!nfo.file.exists() || nfo.file.isDirectory() || !nfo.file.isReadable()) return;
434 let text = fileReadText(nfo.file);
435 let url = newFileURI(nfo.file).spec;
436 evalNoThrow(text, url, true);
438 } catch (e) {
439 logException("XUJS", e);