moved some sources and icon to separate dirs
[guerillascript.git] / modules / scriptcache.js
blob769e5ec8568eb398cb03c8a96ea1736f017b2616
1 /*
2 * Copyright 2015 Ketmar Dark <ketmar@ketmar.no-ip.org>
3 * Portions copyright 2004-2007 Aaron Boodman
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 {parseMeta} = require("utils/metaparser");
32 ////////////////////////////////////////////////////////////////////////////////
33 const nameExtRE = /^(.+)(\.[^.]+)$/;
36 ////////////////////////////////////////////////////////////////////////////////
37 /* nfo:
38 * nsIFile file: script file object
39 * string name: script name, without ".js" ("abcd")
40 * number lmtime: lastmod time
41 * RegExp[] incREs: regular expressions for "@include"; empty array means "*"
42 * RegExp[] excREs: regular expressions for "@exclude"
43 * bool unwrapped
44 * bool noframes
45 * bool wantlog: want `conlog()` and `GM_log()` APIs?
46 * string runat: run at "document-start", "document-end"
47 * nsIFile[] libs: list of required libraries
48 * nsIFile[] incs: list of required includes
49 * {string name} resources: resources dict, keyed by resource name, value is {nsIFile file, string contenttype}
50 * bool isPackage
51 * bool isUserJS
53 function dumpNfo (nfo) {
54 conlog(" ========================================");
55 conlog(" file: [", nfo.file.path, "]");
56 conlog(" name: [", nfo.name, "]");
57 conlog(" lmtime: [", nfo.lmtime, "]");
58 conlog(" isPackage: [", nfo.isPackage, "]");
59 conlog(" isUserJS: [", nfo.isUserJS, "]");
60 conlog(" unwrapped: [", nfo.unwrapped, "]");
61 conlog(" noframes: [", nfo.noframes, "]");
62 conlog(" wantlog: [", nfo.wantlog, "]");
63 conlog(" runat: [", nfo.runat, "]");
64 if (nfo.incREs.length) {
65 conlog(" include:"); for (let re of nfo.incREs) conlog(" ", re);
66 } else {
67 conlog(" include: *");
69 if (nfo.excREs.length) { conlog(" exclude:"); for (let re of nfo.excREs) conlog(" ", re); }
70 if (nfo.libs.length) { conlog(" libs:"); for (let fl of nfo.libs) conlog(" [", fl.path, "]"); }
71 if (nfo.incs.length) { conlog(" incs:"); for (let fl of nfo.incs) conlog(" [", fl.path, "]"); }
72 let wasOut = false;;
73 for (let [name, fl] in Iterator(nfo.resources)) {
74 if (!wasOut) conlog(" resources:");
75 wasOut = true;
76 conlog(" [", name, "]=[", fl.path, "]");
79 exports.dumpScriptNfo = dumpNfo;
82 ////////////////////////////////////////////////////////////////////////////////
83 let ujsdirLMT = false; // directory modify times; will become number
84 let userscripts = new Array(); // sorted by name
85 let pkgscripts = new Array(); // sorted by package name
88 ////////////////////////////////////////////////////////////////////////////////
89 // return `false` if this script should be excluded
90 function processScriptMeta (nfo, reqtformCB) {
91 function addIncLib (dir, fname, arr) {
92 //conlog("lib: [", dir.path, "] : [", fname, "]");
93 let ne = fname.match(nameExtRE);
94 if (!ne || ne[2] != ".js") return true; // invalid file name, skip it
95 let fl = buildRelFile(dir, fname);
96 if (!dir.contains(fl, true)) return false; // access restricted
97 if (!fl.exists()) return false;
98 if (!fl.isFile() || !fl.isReadable()) return false;
99 // check for duplicates
100 let found = false;
101 for (let ef of arr) if (ef.equals(fl)) { found = true; break; }
102 if (!found) arr.push(fl);
103 return true;
106 // return `false` if invalid or non-existing file accessed
107 function procJSIncLib (dir, value, arr) {
108 //conlog("libs: [", value, "]");
109 for (let fname of value.split(/\s+/)) if (fname && !addIncLib(dir, fname, arr)) return false;
110 return true;
113 try {
114 nfo.isUserJS = nfo.isPackage || (/\.user\.js$/.test(nfo.file.leafName)); // is ".user.js" script?
115 nfo.incREs = new Array();
116 nfo.excREs = new Array();
117 nfo.unwrapped = null;
118 nfo.noframes = (nfo.isUserJS ? false : true); // default
119 nfo.wantlog = null;
120 nfo.runat = "document-end"; // default
121 nfo.libs = new Array();
122 nfo.incs = new Array();
123 nfo.resources = {};
124 let text = fileReadText(nfo.file);
125 if (!text) return false;
126 let meta = parseMeta(text);
127 if (meta.length == 0) return false;
128 //conlog("got meta for ", nfo.file.path);
129 // parse meta fields
130 let incAll = false;
131 let wasGrantNone = false;
132 let wasGrantLog = false;
133 let wasGrantAnything = false;
134 for (let kv of meta) {
135 if (kv.name === "disabled") return false; // this script is disabled
136 if (kv.name === "exclude") {
137 if (!kv.value) continue;
138 if (kv.value === "*") return false; // excluded for all uris
139 let re = uri2re(kv.value);
140 if (re) nfo.excREs.push(re);
141 continue;
143 if (kv.name === "include") {
144 if (!incAll) {
145 if (!kv.value) continue;
146 if (kv.value === "*") {
147 incAll = true;
148 if (nfo.incREs.length != 0) nfo.incREs = new Array();
149 } else {
150 let re = uri2re(kv.value);
151 if (re) nfo.incREs.push(re);
154 continue;
156 if (kv.name === "run-at") {
157 switch (kv.value) {
158 case "document-start":
159 case "document-end":
160 nfo.runat = kv.value;
161 break;
163 continue;
165 if (kv.name === "unwrap" || kv.name === "unwrapped") {
166 nfo.unwrapped = true;
167 continue;
169 if (kv.name === "wrap" || kv.name === "wrapped") {
170 nfo.unwrapped = false;
171 continue;
173 if (kv.name === "noframe" || kv.name === "noframes") {
174 nfo.noframes = false;
175 continue;
177 if (kv.name === "frame" || kv.name === "frames") {
178 nfo.noframes = true;
179 continue;
181 if (kv.name === "library" || kv.name === "libraries") {
182 if (nfo.isPackage) continue;
183 if (!procJSIncLib(getUserLibDir(), kv.value, nfo.libs)) return false;
184 continue;
186 if (kv.name === "require" || kv.name === "requires") {
187 if (nfo.isPackage) continue;
188 let dir = nfo.file.parent; // remove script name
189 dir.append(nfo.name);
190 if (nfo.isPackage) {
191 // special processing for scripts from packages
192 if (kv.name !== "require" || !kv.value) continue;
193 if (typeof(reqtformCB) !== "function") continue;
194 let v = reqtformCB(nfo, kv.value);
195 if (typeof(v) === "undefined") continue;
196 if (v === false) return false; // alas
197 if (!v || typeof(v) !== "string") continue;
198 if (!addIncLib(dir, v, nfo.incs)) return false;
199 } else {
200 if (!procJSIncLib(dir, kv.value, nfo.incs)) return false;
202 continue;
204 if (kv.name === "grant") {
205 // "@grant none" means the same as "@unwrap"
206 switch (kv.value) {
207 case "none": wasGrantNone = true; break;
208 case "GM_log": wasGrantLog = true; break;
209 case "unsafeWindow": break;
210 default: if (kv.value) wasGrantAnything = true; break;
212 continue;
215 // met any includes?
216 if (!incAll && nfo.incREs.length == 0) return false; // no includes at all
217 // fix access rights
218 if (wasGrantLog) {
219 nfo.wantlog = true;
220 } else {
221 if (nfo.wantlog === null) nfo.wantlog = (!nfo.isPackage && !nfo.isUserJS);
223 // if we met "@grant none", this means "unwrap"
224 if (wasGrantNone) {
225 if (nfo.unwrapped === null) nfo.unwrapped = true;
226 } else if (wasGrantAnything) {
227 // other "@grant"s means "wrapped"
228 if (nfo.unwrapped === null) nfo.unwrapped = false;
229 } else {
230 // nothing was specified
231 if (nfo.unwrapped === null) nfo.unwrapped = false;
233 // public `conlog` function for unwrapped scripts
234 if (nfo.unwrapped) nfo.wantlog = true;
235 return true;
236 } catch (e) {
237 logException("processScriptMeta", e);
239 return false;
241 exports.processScriptMeta = processScriptMeta;
244 ////////////////////////////////////////////////////////////////////////////////
245 // bool forced: do force scan (i.e. ignore jsdirLMT value)
246 function scanUJSDirectory (forced) {
247 //if (guerillaOptions.debugCache) conlog("updating cache: '"+dir.path+"'");
248 let dir = getUserJSDir();
249 if (!dir.exists()) { userscripts = new Array(); return; }
250 if (!dir.isDirectory()) { userscripts = new Array(); return; }
251 let lmtime = dir.lastModifiedTime;
252 if (!forced && lmtime === ujsdirLMT) return; // do nothing, as nothing was changed
253 // rescan
254 ujsdirLMT = lmtime;
255 userscripts = new Array();
256 let en = dir.directoryEntries;
257 while (en.hasMoreElements()) {
258 let fl = en.getNext().QueryInterface(Ci.nsILocalFile);
259 if (!fl.exists()) continue;
260 if (!fl.isFile() || !fl.isReadable()) continue;
261 // check file name
262 let name = fl.leafName;
263 if (name[0] == "_") continue; // disabled
264 let ne = name.match(nameExtRE);
265 if (!ne || ne[2] != ".js") continue; // invalid extension
266 // create script nfo object
267 let nfo = {};
268 nfo.isPackage = false;
269 nfo.file = fl.clone();
270 nfo.name = ne[1]; // base name (w/o extension)
271 nfo.lmtime = fl.lastModifiedTime;
272 if (!processScriptMeta(nfo)) continue; // skip this script
273 //dumpNfo(nfo);
274 userscripts.push(nfo);
276 // sort scripts
277 userscripts.sort(function (a, b) {
278 let ap = a.file.leafName;
279 let bp = b.file.leafName;
280 if (ap < bp) return -1;
281 if (ap > bp) return 1;
282 return 0;
287 ////////////////////////////////////////////////////////////////////////////////
288 function scanPackages () {
289 pkgscripts = new Array(); // sorted by package name
290 for (let pi of pkgDB.getActivePackagesForCache()) {
291 let fl = newIFile(pi.path);
292 if (!fl.exists()) continue;
293 if (!fl.isFile() || !fl.isReadable()) continue;
294 let name = fl.leafName;
295 //if (name[0] == "_") continue; // disabled
296 let ne = name.match(nameExtRE);
297 if (!ne || ne[2] != ".js") continue; // invalid extension
298 // create script nfo object
299 let nfo = {};
300 nfo.isPackage = true;
301 nfo.file = fl.clone();
302 nfo.name = ne[1]; // base name (w/o extension)
303 nfo.lmtime = fl.lastModifiedTime;
304 if (!processScriptMeta(nfo)) continue; // skip this script
305 //conlog("path: ", pi.path);
306 // add includes
307 nfo.incs = new Array();
308 for (let ifn of pi.reqs) {
309 //conlog(" req: [", ifn, "]");
310 let ifl = newIFile(ifn);
311 if (!ifl.exists()) continue;
312 if (!ifl.isFile() || !ifl.isReadable()) continue;
313 let mt = ifl.leafName.match(nameExtRE);
314 if (!mt || mt[2] != ".js") continue; // invalid extension
315 nfo.incs.push(ifl);
317 // add resources
318 nfo.resources = {};
319 for (let [name, rsrc] in Iterator(pi.rsrc)) {
320 if (!name) continue; // just in case
321 //conlog(" res: [", name, "] is [", rsrc.path, "] : [", rsrc.contenttype, "]");
322 let rfl = newIFile(rsrc.path);
323 if (!rfl.exists()) continue;
324 if (!rfl.isFile() || !rfl.isReadable()) continue;
325 nfo.resources[name] = {file:rfl, contenttype:rsrc.contenttype};
327 //dumpNfo(nfo);
328 pkgscripts.push(nfo);
330 //conlog("=== pkgscripts ==="); for (let nfo of pkgscripts) dumpNfo(nfo);
334 ////////////////////////////////////////////////////////////////////////////////
335 exports.reset = function () {
336 if (guerillaOptions.debugCache) conlog("resetting script cache");
337 ujsdirLMT = false;
338 userscripts = new Array(); // sorted by name
339 pkgscripts = new Array(); // sorted by package name
343 ////////////////////////////////////////////////////////////////////////////////
344 // bool forced: do force scan (i.e. ignore jsdirLMT value)
345 function refreshCache (forced) {
346 if (ujsdirLMT === false) forced = true;
347 scanUJSDirectory(forced);
348 if (forced) scanPackages();
350 exports.refreshCache = refreshCache;
353 ////////////////////////////////////////////////////////////////////////////////
354 exports.dumpCache = function () {
355 conlog("=== userscripts ==="); for (let nfo of userscripts) dumpNfo(nfo);
356 conlog("=== pkgscripts ==="); for (let nfo of pkgscripts) dumpNfo(nfo);
360 ////////////////////////////////////////////////////////////////////////////////
361 exports.isGoodScheme = function (scstr) {
362 let cpos = scstr.indexOf(":");
363 if (cpos < 0) return false;
364 scstr = scstr.substr(0, cpos);
365 switch (scstr) {
366 case "http":
367 case "https":
368 //case "ftp": // no, really, why?
369 return true;
370 default:
371 return false;
376 ////////////////////////////////////////////////////////////////////////////////
377 function isGoodForUrl (nfo, url, inFrame, runat) {
378 if (!nfo) return false;
379 if (inFrame && nfo.noframes) return false;
380 if (runat !== nfo.runat) return false;
381 // check includes
382 if (nfo.incREs.length) {
383 let ok = false;
384 for (let re of nfo.incREs) if (re.test(url)) { ok = true; break; }
385 if (!ok) return false;
387 // url matches, check excludes
388 if (nfo.excREs.length) {
389 let ok = false;
390 for (let re of nfo.excREs) if (re.test(url)) { ok = true; break; }
391 if (ok) return false;
393 return true;
397 // string uri
398 // returns array of nfos or false
399 // url: string
400 // runat: string
401 exports.scriptsForUrl = function (url, inFrame, runat) {
402 let res = new Array();
403 inFrame = !!inFrame;
404 refreshCache();
405 // userscripts
406 for (let nfo of userscripts) {
407 if (!isGoodForUrl(nfo, url, inFrame, runat)) continue;
408 res.push(nfo);
410 // package scripts
411 for (let nfo of pkgscripts) {
412 if (!isGoodForUrl(nfo, url, inFrame, runat)) continue;
413 res.push(nfo);
415 return res;
419 ////////////////////////////////////////////////////////////////////////////////
420 // get script list; returns array of xnfo
421 // xnfo:
422 // string path
423 // string name
424 // bool islib;
425 (function (global) {
426 let list = new Array();
428 let jdLMT = false;
429 let ldLMT = false;
430 let lastModCount = 0;
432 function scanDir (dir, islib) {
433 if (!dir.exists()) return;
434 if (!dir.isDirectory()) return;
435 let en = dir.directoryEntries;
436 while (en.hasMoreElements()) {
437 let fl = en.getNext().QueryInterface(Ci.nsILocalFile);
438 if (!fl.exists()) continue;
439 if (!fl.isFile() || !fl.isReadable()) continue;
440 let ne = fl.leafName.match(nameExtRE);
441 if (!ne || ne[2] != ".js") continue;
442 let xnfo = {};
443 xnfo.path = fl.path;
444 xnfo.name = (islib ? "libs/" : "")+fl.leafName;
445 xnfo.islib = islib;
446 //conlog("getScriptsForEdit: '", xnfo.name, "' [", xnfo.path, "]");
447 list.push(xnfo);
451 global.getScriptsForEdit = function () {
452 let jdir = getUserJSDir();
453 let ldir = getUserLibDir();
455 if (jdLMT !== jdir.lastModifiedTime || ldLMT !== ldir.lastModifiedTime) {
456 ++lastModCount;
457 jdLMT = jdir.lastModifiedTime;
458 ldLMT = ldir.lastModifiedTime;
459 list = new Array();
460 scanDir(jdir, false);
461 scanDir(ldir, true);
464 return {lmod: lastModCount, list:list};
466 })(this);
467 exports.getScriptsForEdit = getScriptsForEdit;