better error messages
[guerillascript.git] / main / modules / sbapi / sandbox.js
blobe3b424acb76e16f4b453ba7b4dc4060cf7e01935
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
5  *
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:
12  *
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.
16  *
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.
24  *
25  * The above copyright notice and this permission notice shall be included in all
26  * copies or substantial portions of the Software.
27  */
28 ////////////////////////////////////////////////////////////////////////////////
29 let {GuerillaXmlHttpReqester} = require("sbapi/xmlhttprequest");
30 let {ScriptStorage} = require("sbapi/scriptstorage");
31 let {openTab} = require("sbapi/opentab");
34 ////////////////////////////////////////////////////////////////////////////////
35 function fileReadBinary (fl) {
36   let istream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
37   istream.init(fl, -1, -1, false);
38   let bstream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
39   bstream.setInputStream(istream);
40   let bytes = bstream.readBytes(bstream.available());
41   bstream.close();
42   return bytes;
46 ////////////////////////////////////////////////////////////////////////////////
47 let ioSvc = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
49 function uriFromUrl (url, base) {
50   let baseUri = null;
51   if (typeof(base) === "string") {
52     baseUri = uriFromUrl(base);
53   } else if (base) {
54     baseUri = base;
55   }
56   try {
57     return ioSvc.newURI(url, null, baseUri);
58   } catch (e) {
59     return null;
60   }
64 ////////////////////////////////////////////////////////////////////////////////
65 // string aString: A string of data to be hashed.
66 // string aAlg: optional; the hash algorithm to be used; possible values are: MD2, MD5, SHA1, SHA256, SHA384, and SHA512; defaults to SHA1
67 // string aCharset: optional; the charset used by the passed string; defaults to UTF-8
68 function cryptoHashStr (aString, aAlg, aCharset) {
69   const PR_UINT32_MAX = 0xffffffff; // this tells updateFromStream to read the entire string
71   let str = ""+aString;
72   let alg = (""+(aAlg||"SHA1")).trim().toUpperCase();
73   let charset = (""+(aCharset||"UTF-8")).trim();
75   let chashObj = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
77   try {
78     chashObj.initWithString(alg);
79   } catch (e) {
80     logError("invalid hash algorithm: '"+aAlg+"'");
81     throw new Error("invalid hash algorithm: '"+aAlg+"'");
82   }
84   let uniconvObj = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
85   try {
86     uniconvObj.charset = charset;
87   } catch (e) {
88     logError("invalid charset: '"+aCharset+"'");
89     throw new Error("invalid charset: '"+aCharset+"'");
90   }
92   if (str) chashObj.updateFromStream(uniconvObj.convertToInputStream(str), PR_UINT32_MAX);
93   let hash = chashObj.finish(false); // hash as raw octets
94   return [("0"+hash.charCodeAt(i).toString(16)).slice(-2) for (i in hash)].join("");
98 ////////////////////////////////////////////////////////////////////////////////
99 function safeHTMLParser (domWin, htmlstr, baseUrl) {
100   //conlog("domWin: "+domWin);
101   //conlog("htmlstr: "+htmlstr);
102   //conlog("baseUrl: "+baseUrl);
103   let doc = domWin.document.implementation.createDocument("", "", domWin.document.implementation.createDocumentType("html", "", ""));
104   doc.appendChild(doc.createElement("html"));
105   doc.documentElement.appendChild(doc.createElement("body"));
107   let baseUri = null, frag;
108   if (typeof(baseUrl) !== "undefined") baseUri = uriFromUrl(baseUrl);
110   let pu = Cc["@mozilla.org/parserutils;1"].createInstance(Ci.nsIParserUtils);
111   if (!pu) { logError("FUCKED!"); return null; }
113   frag = pu.parseFragment(htmlstr, 0, false, baseUri, doc.body);
114   doc.adoptNode(frag);
115   doc.body.appendChild(frag);
116   return doc;
120 ////////////////////////////////////////////////////////////////////////////////
121 function genUUID () {
122   let uuidgen = Cc["@mozilla.org/uuid-generator;1"].createInstance(Ci.nsIUUIDGenerator);
123   if (!uuidgen) throw new Error("no UUID generator available!");
124   return uuidgen.generateUUID().toString();
128 ////////////////////////////////////////////////////////////////////////////////
129 let storageObjects = {};
132 function getStorageObject (nfo) {
133   if (nfo.name in storageObjects) {
134     return storageObjects[nfo.name];
135   } else {
136     let res = new ScriptStorage(nfo);
137     storageObjects[nfo.name] = res;
138     return res;
139   }
143 exports.closeStorageObjects = function () {
144   for (let k in storageObjects) {
145     if (typeof(k) != "string") continue;
146     let dbo = storageObjects[k];
147     //if (typeof(dbo) != "object") continue;
148     if (dbo.opened) {
149       debuglog("freeing storage object for '", k, "'");
150       dbo.close();
151     }
152   }
153   storageObjects = {};
157 ////////////////////////////////////////////////////////////////////////////////
158 exports.createSandbox = function (domWin, nfo, url) {
159   let sandbox;
160   let scres = nfo.resources;
162   if (nfo.unwrapped) {
163     // create "unwrapped" sandbox
164     sandbox = Cu.Sandbox(domWin, {
165       sandboxName: "unwrapped",
166       sameZoneAs: domWin, // help GC a little
167       sandboxPrototype: domWin/*.wrappedJSObject*/,
168       wantXrays: false,
169     });
170     // alias unsafeWindow for compatibility
171     Cu.evalInSandbox("const unsafeWindow = window;", sandbox);
172   } else {
173     // create "real" sandbox
174     sandbox = Cu.Sandbox(domWin, {
175       sandboxName: nfo.name,
176       sameZoneAs: domWin, // help GC a little
177       sandboxPrototype: domWin/*.wrappedJSObject*/,
178       wantXrays: true,
179     });
181     // Note that because waivers aren't propagated between origins, we need the
182     // unsafeWindow getter to live in the sandbox.  See http://bugzil.la/1043958
183     let unsafeWindowGetter = new sandbox.Function("return (window.wrappedJSObject||window);");
184     Object.defineProperty(sandbox, "unsafeWindow", {get: unsafeWindowGetter});
186     sandbox.GM_generateUUID = tieto(null, genUUID);
187     sandbox.GM_cryptoHash = tieto(null, cryptoHashStr);
189     //Object.defineProperty(sandbox, "GM_safeHTMLParser", {get: function () tieto(null, safeHTMLParser, domWin)});
190     sandbox.GM_safeHTMLParser = tieto(null, safeHTMLParser, domWin);
192     let scriptStorage = getStorageObject(nfo);
193     sandbox.GM_getValue = tieto(scriptStorage, "getValue");
194     sandbox.GM_setValue = tieto(scriptStorage, "setValue");
195     sandbox.GM_deleteValue = tieto(scriptStorage, "deleteValue");
196     sandbox.GM_listValues = tieto(scriptStorage, "listValues");
198     sandbox.GM_xmlhttpRequest = tieto(new GuerillaXmlHttpReqester(domWin, url, sandbox), "contentStartRequest");
200     sandbox.GM_addStyle = tieto(null, function (doc, cssstr) {
201       var head = doc.getElementsByTagName("head")[0];
202       if (head) {
203         var style = doc.createElement("style");
204         style.textContent = cssstr;
205         style.type = "text/css";
206         head.appendChild(style);
207         return style;
208       }
209       return null;
210     }, domWin.document);
212     sandbox.GM_openInTab = tieto(null, openTab, domWin);
214     sandbox.GM_getResourceText = tieto(null, function (name) {
215       if (typeof(name) === "undefined") throw new Error("GM_getResourceText(): no name given!");
216       name = ""+name;
217       let rsrc = scres[name];
218       if (!rsrc) throw new Error("GM_getResourceText(): no resource found: '"+name+"'");
219       return fileReadText(rsrc.file);
220     });
222     sandbox.GM_getResourceURL = tieto(null, function (name) {
223       //logError("GM_getResourceURL(): stub!");
224       //throw new Error("GM_getResourceURL() not implemented");
225       if (typeof(name) === "undefined") throw new Error("GM_getResourceText(): no name given!");
226       name = ""+name;
227       let rsrc = scres[name];
228       if (!rsrc) throw new Error("GM_getResourceText(): no resource found: '"+name+"'");
229       let rawdata = fileReadBinary(rsrc.file);
230       return "data:"+rsrc.contenttype+";base64,"+encodeURIComponent(btoa(rawdata));
231     });
233     // stubs
234     sandbox.GM_registerMenuCommand = tieto(null, function () { logError("GM_registerMenuCommand(): stub!"); });
235     sandbox.GM_setClipboard = tieto(null, function () { logError("GM_setClipboard(): stub!"); });
236   }
238   // provide log functions for both wrapped and unwrapped scripts
239   if (!nfo.unwrapped || nfo.wantlog) {
240     sandbox.conlog = tieto(null, conlog);
241     sandbox.logError = tieto(null, logError);
242     if (!nfo.unwrapped) sandbox.GM_log = tieto(null, conlog);
243   }
245   Object.defineProperty(sandbox, "GM_info", {
246     get: tieto(null, function () { logError("GM_info(): stub!"); return {}; }),
247   });
249   // nuke sandbox when this window unloaded
250   domWin.addEventListener("unload", function () {
251     if (sandbox) {
252       debuglog("**** NUKING SANDBOX *** [", nfo.name, "] : [", url, "]");
253       Cu.nukeSandbox(sandbox);
254       sandbox = null;
255     }
256   }, true);
259   return sandbox;
263 exports.runInSandbox = function (sandbox, nfo) {
264   // eval the code, with anonymous wrappers when/if appropriate
265   function evalLazyWrap (code, fileName) {
266     try {
267       Cu.evalInSandbox(code, sandbox, "ECMAv5", fileName, 1);
268     } catch (e) {
269       if ("return not in function" == e.message) {
270         // we never anon wrap unless forced to by a "return not in a function" error
271         logError("please, do not use `return` in top-level code in "+fileName+":"+e.lineNumber);
272         Cu.evalInSandbox("(function(){ "+code+"\n})()", sandbox, "ECMAv5", fileName, 1);
273       } else {
274         // otherwise raise
275         throw e;
276       }
277     }
278   }
280   // eval the code, with a try/catch to report errors cleanly
281   function evalNoThrow (code, fileName) {
282     try {
283       evalLazyWrap(code, fileName);
284     } catch (e) {
285       logException("UJS", e);
286       return false;
287     }
288     return true;
289   }
291   try {
292     // eval libraries
293     for (let fl of nfo.libs) {
294       if (!fl.exists() || fl.isDirectory() || !fl.isReadable()) return;
295       let text = fileReadText(fl);
296       //conlog("*** lib: ", fl.path);
297       if (!evalNoThrow(text, "libs/"+fl.leafName)) return;
298     }
299     // eval includes
300     for (let fl of nfo.incs) {
301       if (!fl.exists() || fl.isDirectory() || !fl.isReadable()) return;
302       let text = fileReadText(fl);
303       //conlog("*** inc: ", fl.path);
304       if (!evalNoThrow(text, nfo.name+"/"+fl.leafName)) return;
305     }
306     // eval main script
307     {
308       if (!nfo.file.exists() || nfo.file.isDirectory() || !nfo.file.isReadable()) return;
309       let text = fileReadText(nfo.file);
310       //conlog("*** main: ", nfo.file.path);
311       evalNoThrow(text, nfo.name+".js");
312     }
313   } catch (e) {
314     logException("XUJS", e);
315   }