better temp blacklister (earlier abort)
[k8imago.git] / code / modules / tamper.js
blobcedeefc08d30f50e030a5fd21bf4d9ec073ff14b
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 // traffic tamper
11 let EXPORTED_SYMBOLS = [
12 "ImgDetectListener"
15 const {utils:Cu, classes:Cc, interfaces:Ci, results:Cr} = Components;
18 //////////////////////////////////////////////////////////////////////////////
19 Cu.import("resource://gre/modules/Services.jsm");
21 Cu.import("chrome://k8-imago-code/content/modules/utils.js");
22 Cu.import(MODULE_PATH+"debuglog.js");
23 Cu.import(MODULE_PATH+"prefs.js");
24 Cu.import(MODULE_PATH+"detector.js");
25 Cu.import(MODULE_PATH+"hazard.js");
26 Cu.import(MODULE_PATH+"stoplist.js");
29 //////////////////////////////////////////////////////////////////////////////
30 // this listener will collect first several kb of image, detect it's type and
31 // size, and then will decide what to do with it (image, not size)
32 let tmpid = 0;
35 function ImgDetectListener (isoctet, blacklisted) {
36 this.olst = null;
37 this.receivedData = ""; // string, incoming data; will become 0
38 this.cancelled = false;
39 this.piping = false;
40 this.stopSent = false;
41 this.octets = !!isoctet;
42 this.blacklisted = !!blacklisted;
43 this.maxLength = PREFS.maxLength;
44 this.checker = new FormatChecker();
45 this.id = tmpid++;
46 this.imageInfo = null;
50 ImgDetectListener.prototype = {
51 log: function () {
52 if (!PREFS.debugLog) return;
53 if (arguments.length) {
54 let s = ""+this.id+":: ";
55 for (let idx = 0; idx < arguments.length; ++idx) s += arguments[idx];
56 Services.console.logStringMessage(s);
60 logError: function () {
61 if (!PREFS.debugLog) return;
62 if (arguments.length) {
63 let s = ""+this.id+":: ";
64 for (let idx = 0; idx < arguments.length; ++idx) s += arguments[idx];
65 Cu.reportError(s);
69 _markImage: function (request) {
70 let srcchan = request.QueryInterface(Ci.nsIChannel);
71 let url = srcchan.URI.spec;
72 let dw = getDomWindowForChannel(srcchan);
73 if (dw) {
74 let title;
75 if (this.imageInfo) {
76 title = this.imageInfo.name+" "+(this.imageInfo.valid ? ""+this.imageInfo.width+"x"+this.imageInfo.height : "<INVALID>");
77 } else {
78 title = (this.blacklisted ? "<BLACKLISTED>" : "<INVALID>");
80 for (let img of dw.document.querySelectorAll("img:not([k8imago-mark])[src=\""+cssEscape(url)+"\"]")) {
81 img.setAttribute("title", title);
82 img.setAttribute("k8imago-mark", "tan");
87 // cancel request
88 _cancel: function (request, context, total, data) {
89 if (typeof(total) !== "number") total = "???";
90 // abort this request
91 this.cancelled = true;
92 this.receivedData = null; // just in case
93 this.checker = null;
94 this._markImage(request);
95 if (PREFS.debugLog) {
96 let srcchan = request.QueryInterface(Ci.nsIChannel);
97 let url = srcchan.URI.spec;
98 this.log("[", url, "] total: ", total, ": CANCELLING!");
100 if (typeof(data) === "string") {
101 if (PREFS.showPlaceholder) {
102 this.log(" sending HAZARD");
103 this.olst.onDataAvailable(request, context, createInputStreamFromString(data), 0, data.length);
104 } else {
105 this.log(" sending HAZARD is blocked");
107 this.stopSent = true;
108 this.olst.onStopRequest(request, context, 0);
110 // just in case that double cancel will throw
111 try {
112 request.cancel(Cr.NS_BINDING_ABORTED);
113 } catch (e) {}
116 _pipe: function (request, context) {
117 this.piping = true;
118 this.receivedData = null;
119 this.checker = null;
122 url: function (request) {
123 if (!request) return "<no-url>";
124 let srcchan = request.QueryInterface(Ci.nsIChannel);
125 if (!srcchan) return "<no-url>";
126 return srcchan.URI.spec;
129 // object initialization finished
130 inited: function () {
133 QueryInterface: function (aIID) {
134 if (aIID.equals(Ci.nsIStreamListener) || aIID.equals(Ci.nsISupports)) return this;
135 throw Components.results.NS_NOINTERFACE;
138 onDataAvailable: function (request, context, inputStream, offset, count) {
139 if (this.cancelled) return;
141 if (!this.piping && PREFS.debugLog) this.log("[", this.url(request), "] offset=", offset, "; count=", count);
143 // if we aren't piping...
144 if (!this.piping) {
145 // ...and there's no more formats...
146 if (this.checker.done) {
147 // abort or pipe
148 if (!PREFS.allowUnknownFormats) {
149 this._cancel(request, context, offset+count, hazardPNG);
150 return;
152 // send collected data
153 if (this.receivedData) {
154 if (this.receivedData.length) {
155 if (offset == 0) this.logError("WTF000?! offset is zero, but we have receivedData!");
156 this.olst.onDataAvailable(request, context, createInputStreamFromString(this.receivedData), 0, this.receivedData.length);
157 if (offset != this.receivedData.length) this.logError("WTF001?! offset is not equal to receivedData length! offset=", offset, "; length=", this.receivedData.length);
159 } else if (offset > 0) {
160 this.logError("WTF002?! offset is not zero, but we have no receivedData!");
162 this._pipe(request, context);
163 // go on, do piping
167 // pipe data if we are piping
168 if (this.piping) {
169 // piping data
170 if (this.maxLength > 0 && !this.octets) {
171 if (offset+count > this.maxLength) {
172 this._cancel(request, context, offset+count);
173 return;
176 this.olst.onDataAvailable(request, context, inputStream, offset, count);
177 return;
180 // collecting header data and analyzing image formats
181 let ist = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
182 ist.setInputStream(inputStream);
184 // read up to `this.checker.maxHeaderBytes` kb
185 let maxb = this.checker.maxHeaderBytes;
186 if (this.receivedData.length+count < maxb) {
187 this.log(" reading ", count, " bytes (maxb=", maxb, ")");
188 this.receivedData += ist.readBytes(count);
189 count = 0;
190 } else {
191 let left = maxb-this.receivedData.length;
192 if (left > count) {
193 Cu.reportError("internal error (something is heavily fucked, #000)");
194 throw new Error("internal error (something is heavily fucked, #000)");
196 if (left > 0) {
197 this.receivedData += ist.readBytes(left);
198 count -= left;
200 this.log(" read ", left, " bytes, ", count, " bytes left");
203 let res = this.checker.process(this.receivedData);
204 this.log(" checker returned `", res, "`, done is: ", this.checker.done);
205 if (res === true) {
206 // no successfull detection yet, need more spice
207 if (this.receivedData.length >= maxb) this._cancel(request, context, offset+count, hazardPNG);
208 return;
210 if (res !== null) {
211 // detected something
212 this.imageInfo = res;
213 if (!res.valid) {
214 this._cancel(request, context, offset+count, hazardPNG);
215 return;
217 this.log(" image: ", res.name, ": ", res.width, "x", res.height);
218 // valid image, check dimensions
219 if (res.width < PREFS.minWidth || res.height < PREFS.minHeight ||
220 res.width > PREFS.maxWidth || res.height > PREFS.maxHeight)
222 this.log(" allowed size: min=", PREFS.minWidth, "x", PREFS.minHeight, "; max=", PREFS.maxWidth, "x", PREFS.maxHeight);
223 this._cancel(request, context, offset+count, hazardPNG);
224 return;
226 } else {
227 // no more formats
228 if (!PREFS.allowUnknownFormats) {
229 this.log(" unknown format");
230 this._cancel(request, context, offset+count, hazardPNG);
231 return;
235 // here we got something good, transfer collected header and start piping
236 this.olst.onDataAvailable(request, context, createInputStreamFromString(this.receivedData), 0, this.receivedData.length);
237 // pass what is left
238 if (count > 0) {
239 offset += this.receivedData.length;
240 this.olst.onDataAvailable(request, context, inputStream, offset, count);
242 this._pipe(request, context);
245 onStartRequest: function (request, context) {
246 if (PREFS.debugLog) this.log("onStartRequest() [", this.url(request), "]");
247 this.olst.onStartRequest(request, context);
248 if (this.blacklisted) {
249 this.log("image is blacklisted");
250 this._cancel(request, context, 0, hazardPNG);
251 return;
255 onStopRequest: function (request, context, statusCode) {
256 if (PREFS.debugLog) this.log("onStopRequest() [", this.url(request), "]: status code=", statusCode);
257 //this.log("[", this.url(request), "]: status code=", statusCode);
258 if (!this.stopSent && this.receivedData !== null) {
259 // wow, we have some buffered data, check and send it!
260 let res = this.checker.process(this.receivedData);
261 if (typeof(res) === "object") {
262 if (!res.valid) {
263 this._cancel(request, context, 0, hazardPNG);
264 return;
266 if (res.width < PREFS.minWidth || res.height < PREFS.minHeight ||
267 res.width > PREFS.maxWidth || res.height > PREFS.maxHeight) {
268 this._cancel(request, context, 0, hazardPNG);
269 return;
271 // ok
272 } else {
273 if (!PREFS.allowUnknownFormats) {
274 this._cancel(request, context, 0, hazardPNG);
275 return;
278 this.olst.onDataAvailable(request, context, createInputStreamFromString(this.receivedData), 0, this.receivedData.length);
280 this.receivedData = null; // just in case
281 this.checker = null;
282 if (!this.stopSent) {
283 this.olst.onStopRequest(request, context, statusCode);