carcass modifications; moved some functions from utils to modules
[guerillascript.git] / main / modules / pkg / downpkg.js
blob6b759b395df6993c204484509fb0eb59c86386c8
1 /* coded by Ketmar // Invisible Vector (psyc://ketmar.no-ip.org/~Ketmar)
2 * Understanding is not required. Only obedience.
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 require("utils/utils");
12 let {parseMeta} = require("utils/metaparser");
15 ////////////////////////////////////////////////////////////////////////////////
16 function genFileObj (fname) {
17 if (typeof(fname) !== "string") throw new Error("non-string file name in `genFileObj()`");
18 if (fname.length == 0) throw new Error("empty file name in `genFileObj()`");
19 if (fname[0] == '/') {
20 let fl = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
21 fl.initWithPath(fname);
22 return fl;
23 } else {
24 let fl = getUserPkgTempDir();
25 for (let p of fname.split("/")) if (p && p != ".") fl.append(p);
26 return fl;
31 ////////////////////////////////////////////////////////////////////////////////
32 function calcFileSha512 (fname) {
33 let fl = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
34 fl.initWithPath(fname);
35 let istream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
36 // open for reading
37 istream.init(fl, 0x01, 0400, istream.CLOSE_ON_EOF); // 0x01: PR_RDONLY
38 let chan = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
39 chan.init(chan.SHA512);
40 chan.updateFromStream(istream, 0xffffffff);
41 // pass false here to get binary data back
42 let hash = chan.finish(false);
43 return [("0"+hash.charCodeAt(i).toString(16)).slice(-2) for (i in hash)].join("");
47 ////////////////////////////////////////////////////////////////////////////////
48 // package downloader
49 // this downloads all package files and creates package object:
50 // string downurl
51 // string name
52 // string version
53 // string diskname
54 // string sha512
55 // variant[] meta
56 // ResInfo[] resources
57 // ResInfo:
58 // string url
59 // string diskname
60 // string resname
61 // string sha512
64 // state:
65 // -1: cancelled
66 // 0: not started
67 // 1: running
68 // 2: aborted with error
69 // 3: complete
70 function PkgDownloader (aurl) {
71 this.mainURL = aurl;
72 this.state = 0;
73 this.reslist = []; // resources to download: {name, url}; if `name` is null, this is js file
74 this.curdown = null;
75 //this.curres = null;
76 //this.pkg = {};
77 this.onError = function () {};
78 this.onComplete = function () {};
79 this.onCancel = function () {};
80 // return `false` from `onMainReceived` to abort downloading and call `onCancel`
81 this.onMainReceived = function () { return true; };
82 return this;
86 PkgDownloader.prototype.clearTempDir = function () {
87 let fl = getUserPkgTempDir();
88 try { fl.remove(true); } catch (e) {}
89 try { if (!fl.exists()) fl.create(fl.DIRECTORY_TYPE, 0700); } catch (e) {}
93 PkgDownloader.prototype.__defineGetter__("started", function () { return (this.state > 0); }); // was started, not necessarily running
94 PkgDownloader.prototype.__defineGetter__("running", function () { return (this.state == 1); });
95 PkgDownloader.prototype.__defineGetter__("cancelled", function () { return (this.state < 0); });
96 PkgDownloader.prototype.__defineGetter__("aborted", function () { return (this.state == 2); });
97 PkgDownloader.prototype.__defineGetter__("complete", function () { return (this.state == 3); });
100 PkgDownloader.prototype.genFileName = function (ext) {
101 let fl = getUserPkgTempDir();
102 if (typeof(ext) === "undefined" || !ext) {
103 fl.append("pkgmain.js");
104 } else {
105 if (ext[0] != ".") ext = "."+ext;
106 fl.append("pkgres"+ext);
108 fl.createUnique(fl.NORMAL_FILE_TYPE, 0600);
109 let res = fl.path;
110 //conlog("temp file: [", res, "]");
111 //fl.close();
112 return res;
116 // https://raw.githubusercontent.com/adsbypasser/adsbypasser/v5.31.0/css/align_center.css
117 PkgDownloader.prototype.getExtFromUrl = function (url) {
118 let ioSvc = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
120 function uriFromUrl (url, base) {
121 let baseUri = null;
122 if (typeof(base) === "string") {
123 baseUri = uriFromUrl(base);
124 } else if (base) {
125 baseUri = base;
127 try {
128 return ioSvc.newURI(url, null, baseUri);
129 } catch (e) {
130 return null;
134 try {
135 let uo = uriFromUrl(url);
136 let path = uo.path;
137 if (!path || path[path.length-1] == "/") return null;
138 let mt = path.match(/(\.[^.]+)$/);
139 if (mt) return mt[1];
140 } catch (e) {}
141 return null;
145 // callback:
146 // nsIHttpChannel chan
147 // bool success
148 // string filename
149 // string url
150 // bool cancelled
151 PkgDownloader.prototype.downloadFile = function (destfname, aurl, callback) {
152 conlog("downloading: ", aurl);
153 let ioSrv = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService)
154 let done = false, cancelled = false;
155 let fl = genFileObj(destfname);
156 let downObserver = {
157 onDownloadComplete: function (dldr, request, ctxt, status, fl) {
158 done = true;
159 let cc = request.QueryInterface(Ci.nsIHttpChannel);
160 if (!cancelled && callback) {
161 try {
162 callback(cc, (status == 0 && !cancelled), destfname, aurl, cancelled);
163 } catch (e) {
164 logException("downloadFile error", e);
169 let dldr = Cc["@mozilla.org/network/downloader;1"].createInstance(Ci.nsIDownloader);
170 dldr.init(downObserver, fl);
171 let httpChan = ioSrv.newChannel(aurl, "", null).QueryInterface(Ci.nsIHttpChannel);
172 httpChan.setRequestHeader("User-Agent", "GuerillaScript/0.666", false);
173 httpChan.redirectionLimit = 16;
174 httpChan.asyncOpen(dldr, fl);
175 //conlog("downloading...");
176 return {
177 get done () done,
178 get filename () destfname,
179 get url () aurl,
180 cancel: function () {
181 if (!done && !cancelled) {
182 cancelled = true;
183 httpChan.cancel(Cr.NS_BINDING_ABORTED);
190 PkgDownloader.prototype.cancel = function () {
191 if (this.state != 1) throw new Error("can't cancel from invalid state");
192 if (this.state == 1 && this.curdown) {
193 this.curdown.cancel();
194 this.curdown = null;
196 this.state = -1;
197 if ("pkg" in this) delete this.pkg;
198 if ("curres" in this) delete this.pkg;
199 this.onCancel();
203 PkgDownloader.prototype.start = function () {
204 if (this.state != 0) throw new Error("can't start from invalid state");
205 this.state = 1;
206 let fname = this.genFileName();
207 //conlog("fname: [", fname, "]");
208 try {
209 this.curdown = this.downloadFile(fname, this.mainURL, tieto(this, "downCBMain"));
210 } catch (e) {
211 try { let fl = genFileObj(fname); fl.remove(false); } catch (e) {}
212 this.state = 2;
213 this.onError();
218 PkgDownloader.prototype.__abort = function (skipOnError) {
219 this.state = 2;
220 if ("pkg" in this) delete this.pkg;
221 if ("curres" in this) delete this.pkg;
222 if (!skipOnError) try { this.onError(); } catch (e) { logException("onError", e); }
226 PkgDownloader.prototype._abort = function (filename, cancelled) {
227 if (typeof(filename) === "string") {
228 try { let fl = genFileObj(fname); fl.remove(false); } catch (e) {}
230 this.__abort(cancelled);
234 PkgDownloader.prototype.downCBMain = function (chan, success, filename, url, cancelled) {
235 //conlog("downCBMain: [", filename, "] : ", success, " : ", chan.responseStatus, " : ", chan.requestSucceeded);
236 this.curdown = null;
237 try {
238 if (!success || !chan.requestSucceeded) { this._abort(filename, cancelled); return; } // failed to download main file
239 // main file downloaded
240 let ct = (chan.getResponseHeader("Content-Type")||"");
241 if (!(/^text\/javascript(?:;|$)/.test(ct)) && !(/^text\/plain(?:;|$)/.test(ct))) {
242 // invalid content type
243 this._abort(filename, cancelled);
244 return;
246 // parse metadata
247 let meta = parseMeta(fileReadText(genFileObj(filename)));
248 if (meta.length == 0) { this._abort(filename, cancelled); return; }
249 this.pkg = {};
250 this.pkg.downurl = this.mainURL;
251 this.pkg.meta = meta;
252 // get package name
253 let name = meta.getField("name");
254 if (name) {
255 this.pkg.name = name.value;
256 } else {
257 name = diskname.split("/");
258 this.pkg.name = name[name.length-1];
260 // get package version
261 let ver = meta.getField("version");
262 this.pkg.version = (ver ? ver.value : "");
263 // diskname and sum
264 this.pkg.diskname = filename;
265 this.pkg.sha512 = calcFileSha512(this.pkg.diskname);
266 // now build download list
267 this.pkg.resources = new Array();
268 let resList = new Array(); // resources to download: {name, url}
269 // js files
270 let rqf = {};
271 meta.forEachField("require", function (kv) {
272 if (!rqf[kv.value]) {
273 if (/^https?:/.test(kv.value)) resList.push({name:null, url:kv.value});
274 rqf[kv.value] = true;
277 // resources
278 rqf = {};
279 meta.forEachField("resource", function (kv) {
280 let rn = kv.value.name;
281 let ru = kv.value.url;
282 if (!rqf[rn] && (/^https?:/.test(ru))) rqf[rn] = ru;
284 for (let [rn, ru] in Iterator(rqf)) resList.push({name:rn, url:ru});
285 this.reslist = resList;
287 if (!this.onMainReceived()) {
288 // cancel downloading
289 this.onCancel();
290 return;
293 this.downloadNextResource();
294 } catch (e) {
295 logException("DOWN", e);
296 this.__abort(false);
301 PkgDownloader.prototype.downloadNextResource = function () {
302 this.curdown = null;
303 if (this.reslist.length == 0) {
304 conlog("package download complete");
305 this.state = 3;
306 try { this.onComplete(); } catch (e) { logException("DOWN", e); }
307 return;
309 // we have some resource to download, do it
310 try {
311 let res = this.reslist[0];
312 conlog("downloading resource '", res.name, "' from: ", res.url);
313 let ext = this.getExtFromUrl(res.url);
314 if (!ext) { this._abort(filename, cancelled); return; }
315 this.curres = res;
316 this.reslist = this.reslist.slice(1);
317 let fname = this.genFileName(ext);
318 this.curdown = this.downloadFile(fname, res.url, tieto(this, "downCBNextRes"));
319 } catch (e) {
320 logException("DOWN", e);
321 // just in case
322 this.__abort(false);
327 PkgDownloader.prototype.downCBNextRes = function (chan, success, filename, url, cancelled) {
328 this.curdown = null;
329 if (!success || !chan.requestSucceeded) { this._abort(filename, cancelled); return; } // failed to download resource file
330 try {
331 let ct = (chan.getResponseHeader("Content-Type")||"application/octet-stream");
332 let resobj = {};
333 resobj.url = url;
334 resobj.diskname = filename;
335 resobj.resname = this.curres.name; // string or null
336 resobj.sha512 = calcFileSha512(resobj.diskname);
337 resobj.contenttype = ct;
338 this.pkg.resources.push(resobj);
339 this.downloadNextResource();
340 } catch (e) {
341 logException("DOWN", e);
342 // just in case
343 this.__abort(false);
348 ////////////////////////////////////////////////////////////////////////////////
349 exports.PkgDownloader = PkgDownloader;