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
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 ////////////////////////////////////////////////////////////////////////////////
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"
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
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
);
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
, "]"); }
73 for (let [name
, fl
] in Iterator(nfo
.resources
)) {
74 if (!wasOut
) conlog(" resources:");
76 conlog(" [", name
, "]=[", fl
.path
, "]");
81 ////////////////////////////////////////////////////////////////////////////////
82 let ujsdirLMT
= false; // directory modify times; will become number
83 let userscripts
= new Array(); // sorted by name
84 let pkgscripts
= new Array(); // sorted by package name
87 ////////////////////////////////////////////////////////////////////////////////
88 // return `false` if this script should be excluded
89 function processScriptMeta (nfo
, reqtformCB
) {
90 function addIncLib (dir
, fname
, arr
) {
91 //conlog("lib: [", dir.path, "] : [", fname, "]");
92 let ne
= fname
.match(nameExtRE
);
93 if (!ne
|| ne
[2] != ".js") return true; // invalid file name, skip it
94 let fl
= buildRelFile(dir
, fname
);
95 if (!dir
.contains(fl
, true)) return false; // access restricted
96 if (!fl
.exists()) return false;
97 if (!fl
.isFile() || !fl
.isReadable()) return false;
98 // check for duplicates
100 for (let ef
of arr
) if (ef
.equals(fl
)) { found
= true; break; }
101 if (!found
) arr
.push(fl
);
105 // return `false` if invalid or non-existing file accessed
106 function procJSIncLib (dir
, value
, arr
) {
107 //conlog("libs: [", value, "]");
108 for (let fname
of value
.split(/\s+/)) if (fname
&& !addIncLib(dir
, fname
, arr
)) return false;
113 nfo
.isUserJS
= nfo
.isPackage
|| (/\.user\.js$/.test(nfo
.file
.leafName
)); // is ".user.js" script?
114 nfo
.incREs
= new Array();
115 nfo
.excREs
= new Array();
116 nfo
.unwrapped
= null;
117 nfo
.noframes
= (nfo
.isUserJS
? false : true); // default
119 nfo
.runat
= "document-end"; // default
120 nfo
.libs
= new Array();
121 nfo
.incs
= new Array();
123 let text
= fileReadText(nfo
.file
);
124 if (!text
) return false;
125 let meta
= parseMeta(text
);
126 if (meta
.length
== 0) return false;
127 //conlog("got meta for ", nfo.file.path);
130 let wasGrantNone
= false;
131 let wasGrantLog
= false;
132 let wasGrantAnything
= false;
133 for (let kv
of meta
) {
134 if (kv
.name
=== "disabled") return false; // this script is disabled
135 if (kv
.name
=== "exclude") {
136 if (!kv
.value
) continue;
137 if (kv
.value
=== "*") return false; // excluded for all uris
138 let re
= uri2re(kv
.value
);
139 if (re
) nfo
.excREs
.push(re
);
142 if (kv
.name
=== "include") {
144 if (!kv
.value
) continue;
145 if (kv
.value
=== "*") {
147 if (nfo
.incREs
.length
!= 0) nfo
.incREs
= new Array();
149 let re
= uri2re(kv
.value
);
150 if (re
) nfo
.incREs
.push(re
);
155 if (kv
.name
=== "run-at") {
157 case "document-start":
159 nfo
.runat
= kv
.value
;
164 if (kv
.name
=== "unwrap" || kv
.name
=== "unwrapped") {
165 nfo
.unwrapped
= true;
168 if (kv
.name
=== "wrap" || kv
.name
=== "wrapped") {
169 nfo
.unwrapped
= false;
172 if (kv
.name
=== "noframe" || kv
.name
=== "noframes") {
173 nfo
.noframes
= false;
176 if (kv
.name
=== "frame" || kv
.name
=== "frames") {
180 if (kv
.name
=== "library" || kv
.name
=== "libraries") {
181 if (nfo
.isPackage
) continue;
182 if (!procJSIncLib(getUserLibDir(), kv
.value
, nfo
.libs
)) return false;
185 if (kv
.name
=== "require" || kv
.name
=== "requires") {
186 if (nfo
.isPackage
) continue;
187 let dir
= nfo
.file
.parent
; // remove script name
188 dir
.append(nfo
.name
);
190 // special processing for scripts from packages
191 if (kv
.name
!== "require" || !kv
.value
) continue;
192 if (typeof(reqtformCB
) !== "function") continue;
193 let v
= reqtformCB(nfo
, kv
.value
);
194 if (typeof(v
) === "undefined") continue;
195 if (v
=== false) return false; // alas
196 if (!v
|| typeof(v
) !== "string") continue;
197 if (!addIncLib(dir
, v
, nfo
.incs
)) return false;
199 if (!procJSIncLib(dir
, kv
.value
, nfo
.incs
)) return false;
203 if (kv
.name
=== "grant") {
204 // "@grant none" means the same as "@unwrap"
206 case "none": wasGrantNone
= true; break;
207 case "GM_log": wasGrantLog
= true; break;
208 case "unsafeWindow": break;
209 default: if (kv
.value
) wasGrantAnything
= true; break;
215 if (!incAll
&& nfo
.incREs
.length
== 0) return false; // no includes at all
220 if (nfo
.wantlog
=== null) nfo
.wantlog
= (!nfo
.isPackage
&& !nfo
.isUserJS
);
222 // if we met "@grant none", this means "unwrap"
224 if (nfo
.unwrapped
=== null) nfo
.unwrapped
= true;
225 } else if (wasGrantAnything
) {
226 // other "@grant"s means "wrapped"
227 if (nfo
.unwrapped
=== null) nfo
.unwrapped
= false;
229 // nothing was specified
230 if (nfo
.unwrapped
=== null) nfo
.unwrapped
= false;
232 // public `conlog` function for unwrapped scripts
233 if (nfo
.unwrapped
) nfo
.wantlog
= true;
236 logException("processScriptMeta", e
);
240 exports
.processScriptMeta
= processScriptMeta
;
243 ////////////////////////////////////////////////////////////////////////////////
244 // bool forced: do force scan (i.e. ignore jsdirLMT value)
245 function scanUJSDirectory (forced
) {
246 //if (guerillaOptions.debugCache) conlog("updating cache: '"+dir.path+"'");
247 let dir
= getUserJSDir();
248 if (!dir
.exists()) { userscripts
= new Array(); return; }
249 if (!dir
.isDirectory()) { userscripts
= new Array(); return; }
250 let lmtime
= dir
.lastModifiedTime
;
251 if (!forced
&& lmtime
=== ujsdirLMT
) return; // do nothing, as nothing was changed
254 userscripts
= new Array();
255 let en
= dir
.directoryEntries
;
256 while (en
.hasMoreElements()) {
257 let fl
= en
.getNext().QueryInterface(Ci
.nsILocalFile
);
258 if (!fl
.exists()) continue;
259 if (!fl
.isFile() || !fl
.isReadable()) continue;
261 let name
= fl
.leafName
;
262 if (name
[0] == "_") continue; // disabled
263 let ne
= name
.match(nameExtRE
);
264 if (!ne
|| ne
[2] != ".js") continue; // invalid extension
265 // create script nfo object
267 nfo
.isPackage
= false;
268 nfo
.file
= fl
.clone();
269 nfo
.name
= ne
[1]; // base name (w/o extension)
270 nfo
.lmtime
= fl
.lastModifiedTime
;
271 if (!processScriptMeta(nfo
)) continue; // skip this script
273 userscripts
.push(nfo
);
276 userscripts
.sort(function (a
, b
) {
277 let ap
= a
.file
.leafName
;
278 let bp
= b
.file
.leafName
;
279 if (ap
< bp
) return -1;
280 if (ap
> bp
) return 1;
286 ////////////////////////////////////////////////////////////////////////////////
287 function scanPackages () {
288 pkgscripts
= new Array(); // sorted by package name
289 for (let pi
of pkgDB
.getActivePackagesForCache()) {
290 let fl
= newIFile(pi
.path
);
291 if (!fl
.exists()) continue;
292 if (!fl
.isFile() || !fl
.isReadable()) continue;
293 let name
= fl
.leafName
;
294 //if (name[0] == "_") continue; // disabled
295 let ne
= name
.match(nameExtRE
);
296 if (!ne
|| ne
[2] != ".js") continue; // invalid extension
297 // create script nfo object
299 nfo
.isPackage
= true;
300 nfo
.file
= fl
.clone();
301 nfo
.name
= ne
[1]; // base name (w/o extension)
302 nfo
.lmtime
= fl
.lastModifiedTime
;
303 if (!processScriptMeta(nfo
)) continue; // skip this script
304 //conlog("path: ", pi.path);
306 nfo
.incs
= new Array();
307 for (let ifn
of pi
.reqs
) {
308 let ifl
= newIFile(ifn
);
309 if (!ifl
.exists()) continue;
310 if (!ifl
.isFile() || !ifl
.isReadable()) continue;
311 let mt
= name
.match(ifl
.leafName
);
312 if (!mt
|| mt
[2] != ".js") continue; // invalid extension
317 for (let [name
, path
] in Iterator(pi
.rsrc
)) {
318 if (!name
) continue; // just in case
319 let rfl
= newIFile(path
);
320 if (!rfl
.exists()) continue;
321 if (!rfl
.isFile() || !rfl
.isReadable()) continue;
322 nfo
.resources
[name
] = rfl
;
325 pkgscripts
.push(nfo
);
330 ////////////////////////////////////////////////////////////////////////////////
331 exports
.reset = function () {
332 if (guerillaOptions
.debugCache
) conlog("resetting script cache");
334 userscripts
= new Array(); // sorted by name
335 pkgscripts
= new Array(); // sorted by package name
339 ////////////////////////////////////////////////////////////////////////////////
340 // bool forced: do force scan (i.e. ignore jsdirLMT value)
341 function refreshCache (forced
) {
342 if (ujsdirLMT
=== false) forced
= true;
343 scanUJSDirectory(forced
);
344 if (forced
) scanPackages();
346 exports
.refreshCache
= refreshCache
;
349 ////////////////////////////////////////////////////////////////////////////////
350 exports
.dumpCache = function () {
351 conlog("=== userscripts ==="); for (let nfo
of userscripts
) dumpNfo(nfo
);
352 conlog("=== pkgscripts ==="); for (let nfo
of pkgscripts
) dumpNfo(nfo
);
356 ////////////////////////////////////////////////////////////////////////////////
357 exports
.isGoodScheme = function (scstr
) {
358 let cpos
= scstr
.indexOf(":");
359 if (cpos
< 0) return false;
360 scstr
= scstr
.substr(0, cpos
);
364 //case "ftp": // no, really, why?
372 ////////////////////////////////////////////////////////////////////////////////
373 function isGoodForUrl (nfo
, url
, inFrame
, runat
) {
374 if (!nfo
) return false;
375 if (inFrame
&& nfo
.noframes
) return false;
376 if (runat
!== nfo
.runat
) return false;
378 if (nfo
.incREs
.length
) {
380 for (let re
of nfo
.incREs
) if (re
.test(url
)) { ok
= true; break; }
381 if (!ok
) return false;
383 // url matches, check excludes
384 if (nfo
.excREs
.length
) {
386 for (let re
of nfo
.excREs
) if (re
.test(url
)) { ok
= true; break; }
387 if (ok
) return false;
394 // returns array of nfos or false
397 exports
.scriptsForUrl = function (url
, inFrame
, runat
) {
398 let res
= new Array();
402 for (let nfo
of userscripts
) {
403 if (!isGoodForUrl(nfo
, url
, inFrame
, runat
)) continue;
407 for (let nfo
of pkgscripts
) {
408 if (!isGoodForUrl(nfo
, url
, inFrame
, runat
)) continue;
415 ////////////////////////////////////////////////////////////////////////////////
416 // get script list; returns array of xnfo
422 let list
= new Array();
426 let lastModCount
= 0;
428 function scanDir (dir
, islib
) {
429 if (!dir
.exists()) return;
430 if (!dir
.isDirectory()) return;
431 let en
= dir
.directoryEntries
;
432 while (en
.hasMoreElements()) {
433 let fl
= en
.getNext().QueryInterface(Ci
.nsILocalFile
);
434 if (!fl
.exists()) continue;
435 if (!fl
.isFile() || !fl
.isReadable()) continue;
436 let ne
= fl
.leafName
.match(nameExtRE
);
437 if (!ne
|| ne
[2] != ".js") continue;
440 xnfo
.name
= (islib
? "libs/" : "")+fl
.leafName
;
442 //conlog("getScriptsForEdit: '", xnfo.name, "' [", xnfo.path, "]");
447 global
.getScriptsForEdit = function () {
448 let jdir
= getUserJSDir();
449 let ldir
= getUserLibDir();
451 if (jdLMT
!== jdir
.lastModifiedTime
|| ldLMT
!== ldir
.lastModifiedTime
) {
453 jdLMT
= jdir
.lastModifiedTime
;
454 ldLMT
= ldir
.lastModifiedTime
;
456 scanDir(jdir
, false);
460 return {lmod
: lastModCount
, list
:list
};
463 exports
.getScriptsForEdit
= getScriptsForEdit
;