added "guerilla package" commands
[guerillascript.git] / modules / pkg / downpkg.js
blob700a3b669bec9265cb631308d413960fee5d02bb
1 /* coded by Ketmar // Invisible Vector (psyc://ketmar.no-ip.org/~Ketmar)
2  * Understanding is not required. Only obedience.
3  *
4  * This program is free software. It comes without any warranty, to
5  * the extent permitted by applicable law. You can redistribute it
6  * and/or modify it under the terms of the Do What The Fuck You Want
7  * To Public License, Version 2, as published by Sam Hocevar. See
8  * http://www.wtfpl.net/txt/copying/ for more details.
9  */
10 ////////////////////////////////////////////////////////////////////////////////
11 let {parseMeta} = require("utils/metaparser");
14 ////////////////////////////////////////////////////////////////////////////////
15 function genFileObj (fname) {
16   if (typeof(fname) !== "string") throw new Error("non-string file name in `genFileObj()`");
17   if (fname.length == 0) throw new Error("empty file name in `genFileObj()`");
18   if (fname[0] == '/') {
19     let fl = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
20     fl.initWithPath(fname);
21     return fl;
22   } else {
23     let fl = getUserPkgTempDir();
24     for (let p of fname.split("/")) if (p && p != ".") fl.append(p);
25     return fl;
26   }
30 ////////////////////////////////////////////////////////////////////////////////
31 // package downloader
32 // this downloads all package files and creates package object:
33 //   string downurl
34 //   string name
35 //   string version
36 //   string diskname
37 //   string sha512
38 //   variant[] meta
39 //   ResInfo[] resources
40 // ResInfo:
41 //   string url
42 //   string diskname
43 //   string resname
44 //   string sha512
47 // state:
48 // -1: cancelled
49 //  0: not started
50 //  1: running
51 //  2: aborted with error
52 //  3: complete
53 function PkgDownloader (aurl) {
54   this.mainURL = aurl;
55   this.state = 0;
56   this.reslist = []; // resources to download: {name, url}; if `name` is null, this is js file
57   this.curdown = null;
58   //this.curres = null;
59   //this.pkg = {};
60   this.onError = function () {};
61   this.onComplete = function () {};
62   this.onCancel = function () {};
63   // return `false` from `onMainReceived` to abort downloading and call `onCancel`
64   this.onMainReceived = function () { return true; };
65   return this;
69 PkgDownloader.prototype.clearTempDir = function () {
70   let fl = getUserPkgTempDir();
71   try { fl.remove(true); } catch (e) {}
72   try { if (!fl.exists()) fl.create(fl.DIRECTORY_TYPE, 0700); } catch (e) {}
76 PkgDownloader.prototype.__defineGetter__("started", function () { return (this.state > 0); }); // was started, not necessarily running
77 PkgDownloader.prototype.__defineGetter__("running", function () { return (this.state == 1); });
78 PkgDownloader.prototype.__defineGetter__("cancelled", function () { return (this.state < 0); });
79 PkgDownloader.prototype.__defineGetter__("aborted", function () { return (this.state == 2); });
80 PkgDownloader.prototype.__defineGetter__("complete", function () { return (this.state == 3); });
83 PkgDownloader.prototype.genFileName = function (ext) {
84   let fl = getUserPkgTempDir();
85   if (typeof(ext) === "undefined" || !ext) {
86     fl.append("pkgmain.js");
87   } else {
88     if (ext[0] != ".") ext = "."+ext;
89     fl.append("pkgres"+ext);
90   }
91   fl.createUnique(fl.NORMAL_FILE_TYPE, 0600);
92   let res = fl.path;
93   //conlog("temp file: [", res, "]");
94   //fl.close();
95   return res;
99 // https://raw.githubusercontent.com/adsbypasser/adsbypasser/v5.31.0/css/align_center.css
100 PkgDownloader.prototype.getExtFromUrl = function (url) {
101   let ioSvc = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
103   function uriFromUrl (url, base) {
104     let baseUri = null;
105     if (typeof(base) === "string") {
106       baseUri = uriFromUrl(base);
107     } else if (base) {
108       baseUri = base;
109     }
110     try {
111       return ioSvc.newURI(url, null, baseUri);
112     } catch (e) {
113       return null;
114     }
115   }
117   try {
118     let uo = uriFromUrl(url);
119     let path = uo.path;
120     if (!path || path[path.length-1] == "/") return null;
121     let mt = path.match(/(\.[^.]+)$/);
122     if (mt) return mt[1];
123   } catch (e) {}
124   return null;
128 // callback:
129 //   nsIHttpChannel chan
130 //   bool success
131 //   string filename
132 //   string url
133 //   bool cancelled
134 PkgDownloader.prototype.downloadFile = function (destfname, aurl, callback) {
135   conlog("downloading: ", aurl);
136   let ioSrv = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService)
137   let done = false, cancelled = false;
138   let fl = genFileObj(destfname);
139   let downObserver = {
140     onDownloadComplete: function (dldr, request, ctxt, status, fl) {
141       done = true;
142       let cc = request.QueryInterface(Ci.nsIHttpChannel);
143       if (!cancelled && callback) {
144         try {
145           callback(cc, (status == 0 && !cancelled), destfname, aurl, cancelled);
146         } catch (e) {
147           logException("downloadFile error", e);
148         }
149       }
150     },
151   };
152   let dldr = Cc["@mozilla.org/network/downloader;1"].createInstance(Ci.nsIDownloader);
153   dldr.init(downObserver, fl);
154   let httpChan = ioSrv.newChannel(aurl, "", null).QueryInterface(Ci.nsIHttpChannel);
155   httpChan.setRequestHeader("User-Agent", "GuerillaScript/0.666", false);
156   httpChan.redirectionLimit = 16;
157   httpChan.asyncOpen(dldr, fl);
158   //conlog("downloading...");
159   return {
160     get done () done,
161     get filename () destfname,
162     get url () aurl,
163     cancel: function () {
164       if (!done && !cancelled) {
165         cancelled = true;
166         httpChan.cancel(Cr.NS_BINDING_ABORTED);
167       }
168     },
169   }
173 PkgDownloader.prototype.cancel = function () {
174   if (this.state != 1) throw new Error("can't cancel from invalid state");
175   if (this.state == 1 && this.curdown) {
176     this.curdown.cancel();
177     this.curdown = null;
178   }
179   this.state = -1;
180   if ("pkg" in this) delete this.pkg;
181   if ("curres" in this) delete this.pkg;
182   this.onCancel();
186 PkgDownloader.prototype.start = function () {
187   if (this.state != 0) throw new Error("can't start from invalid state");
188   this.state = 1;
189   let fname = this.genFileName();
190   //conlog("fname: [", fname, "]");
191   try {
192     this.curdown = this.downloadFile(fname, this.mainURL, hitch(this, "downCBMain"));
193   } catch (e) {
194     try { let fl = genFileObj(fname); fl.remove(false); } catch (e) {}
195     this.state = 2;
196     this.onError();
197   }
201 PkgDownloader.prototype.__abort = function (skipOnError) {
202   this.state = 2;
203   if ("pkg" in this) delete this.pkg;
204   if ("curres" in this) delete this.pkg;
205   if (!skipOnError) try { this.onError(); } catch (e) { logException("onError", e); }
209 PkgDownloader.prototype._abort = function (filename, cancelled) {
210   if (typeof(filename) === "string") {
211     try { let fl = genFileObj(fname); fl.remove(false); } catch (e) {}
212   }
213   this.__abort(cancelled);
217 PkgDownloader.prototype.downCBMain = function (chan, success, filename, url, cancelled) {
218   //conlog("downCBMain: [", filename, "] : ", success, " : ", chan.responseStatus, " : ", chan.requestSucceeded);
219   this.curdown = null;
220   try {
221     if (!success || !chan.requestSucceeded) { this._abort(filename, cancelled); return; } // failed to download main file
222     // main file downloaded
223     let ct = (chan.getResponseHeader("Content-Type")||"");
224     if (!(/^text\/javascript(?:;|$)/.test(ct)) && !(/^text\/plain(?:;|$)/.test(ct))) {
225       // invalid content type
226       this._abort(filename, cancelled);
227       return;
228     }
229     // parse metadata
230     let meta = parseMeta(fileReadText(genFileObj(filename)));
231     if (meta.length == 0) { this._abort(filename, cancelled); return; }
232     this.pkg = {};
233     this.pkg.downurl = this.mainURL;
234     this.pkg.meta = meta;
235     // get package name
236     let name = meta.getField("name");
237     if (name) {
238       this.pkg.name = name.value;
239     } else {
240       name = diskname.split("/");
241       this.pkg.name = name[name.length-1];
242     }
243     // get package version
244     let ver = meta.getField("version");
245     this.pkg.version = (ver ? ver.value : "");
246     // diskname and sum
247     this.pkg.diskname = filename;
248     this.pkg.sha512 = calcFileSha512(this.pkg.diskname);
249     // now build download list
250     this.pkg.resources = new Array();
251     let resList = new Array(); // resources to download: {name, url}
252     // js files
253     let rqf = {};
254     meta.forEachField("require", function (kv) {
255       if (!rqf[kv.value]) {
256         if (/^https?:/.test(kv.value)) resList.push({name:null, url:kv.value});
257         rqf[kv.value] = true;
258       }
259     });
260     // resources
261     rqf = {};
262     meta.forEachField("resource", function (kv) {
263       let rn = kv.value.name;
264       let ru = kv.value.url;
265       if (!rqf[rn] && (/^https?:/.test(ru))) rqf[rn] = ru;
266     });
267     for (let [rn, ru] in Iterator(rqf)) resList.push({name:rn, url:ru});
268     this.reslist = resList;
270     if (!this.onMainReceived()) {
271       // cancel downloading
272       this.onCancel();
273       return;
274     }
276     this.downloadNextResource();
277   } catch (e) {
278     logException("DOWN", e);
279     this.__abort(false);
280   }
284 PkgDownloader.prototype.downloadNextResource = function () {
285   this.curdown = null;
286   if (this.reslist.length == 0) {
287     conlog("package download complete");
288     this.state = 3;
289     try { this.onComplete(); } catch (e) { logException("DOWN", e); }
290     return;
291   }
292   // we have some resource to download, do it
293   try {
294     let res = this.reslist[0];
295     conlog("downloading resource '", res.name, "' from: ", res.url);
296     let ext = this.getExtFromUrl(res.url);
297     if (!ext) { this._abort(filename, cancelled); return; }
298     this.curres = res;
299     this.reslist = this.reslist.slice(1);
300     let fname = this.genFileName(ext);
301     this.curdown = this.downloadFile(fname, res.url, hitch(this, "downCBNextRes"));
302   } catch (e) {
303     logException("DOWN", e);
304     // just in case
305     this.__abort(false);
306   }
310 PkgDownloader.prototype.downCBNextRes = function (chan, success, filename, url, cancelled) {
311   this.curdown = null;
312   if (!success || !chan.requestSucceeded) { this._abort(filename, cancelled); return; } // failed to download resource file
313   try {
314     let resobj = {};
315     resobj.url = url;
316     resobj.diskname = filename;
317     resobj.resname = this.curres.name; // string or null
318     resobj.sha512 = calcFileSha512(resobj.diskname);
319     this.pkg.resources.push(resobj);
320     this.downloadNextResource();
321   } catch (e) {
322     logException("DOWN", e);
323     // just in case
324     this.__abort(false);
325   }
329 ////////////////////////////////////////////////////////////////////////////////
330 exports.PkgDownloader = PkgDownloader;