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.
11 var EXPORTED_SYMBOLS
= [
15 let {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)
35 function ImgDetectListener (isoctet
, blacklisted
, doSizeBlock
) {
37 this.receivedData
= ""; // string, incoming data; will become 0
38 this.cancelled
= false;
40 this.stopSent
= false;
41 this.octets
= !!isoctet
;
42 this.blacklisted
= !!blacklisted
;
43 this.maxLength
= PREFS
.maxLength
;
44 this.checker
= new FormatChecker();
46 this.imageInfo
= null;
47 this.doSizeBlock
= !!doSizeBlock
;
49 this.mainURI
= null; // URI of the main window
53 ImgDetectListener
.prototype = {
54 get debugLogOpt () (PREFS
.debugLog
),
56 get minWidthOpt () (this.rule
? this.rule
.minWidth
: PREFS
.minWidth
),
57 get minHeightOpt () (this.rule
? this.rule
.minHeight
: PREFS
.minHeight
),
58 get maxWidthOpt () (this.rule
? this.rule
.maxWidth
: PREFS
.maxWidth
),
59 get maxHeightOpt () (this.rule
? this.rule
.maxHeight
: PREFS
.maxHeight
),
60 get showPlaceholderOpt () (this.rule
? this.rule
.showPlaceholder
: PREFS
.showPlaceholder
),
61 get allowUnknownFormatsOpt () (this.rule
? this.rule
.allowUnknownFormats
: PREFS
.allowUnknownFormats
),
62 get maxLengthOpt () (this.rule
? this.rule
.maxLength
: PREFS
.maxLength
),
63 get allowFirstPartyImagesOpt () (this.rule
? this.rule
.allowFirstPartyImages
: PREFS
.allowFirstPartyImages
),
67 if (!this.debugLogOpt
) return;
68 if (arguments
.length
) {
69 let s
= ""+this.id
+":: ";
70 for (let idx
= 0; idx
< arguments
.length
; ++idx
) s
+= arguments
[idx
];
71 Services
.console
.logStringMessage(s
);
75 logError: function () {
76 if (!this.debugLogOpt
) return;
77 if (arguments
.length
) {
78 let s
= ""+this.id
+":: ";
79 for (let idx
= 0; idx
< arguments
.length
; ++idx
) s
+= arguments
[idx
];
84 _markImage: function (request
) {
85 let srcchan
= request
.QueryInterface(Ci
.nsIChannel
);
86 let url
= srcchan
.URI
.spec
;
87 let dw
= getDomWindowForChannel(srcchan
);
89 //Services.console.logStringMessage("==================== <"+url+">");
92 title
= this.imageInfo
.name
+" "+(this.imageInfo
.valid
? ""+this.imageInfo
.width
+"x"+this.imageInfo
.height
: "<INVALID>");
94 title
= (this.blacklisted
? "<BLACKLISTED>" : "<INVALID>");
96 //for (let img of dw.document.querySelectorAll("img:not([k8imago-mark])[src=\""+cssEscape(url)+"\"]")) {
97 for (let img
of dw
.document
.querySelectorAll("img")) {
98 //for (let iidx = 0; iidx < dw.document.images.length; ++iidx) {
99 //let img = dw.document.images[iidx];
100 if (img
.src
== url
) {
101 img
.setAttribute("k8imago-saved-title", (img
.getAttribute("title")||""));
102 img
.setAttribute("k8imago-saved-alt", (img
.getAttribute("alt")||""));
103 img
.setAttribute("title", title
);
104 img
.setAttribute("alt", title
);
105 img
.setAttribute("k8imago-mark", "tan");
106 //if (PREFS.debugLog) conlog("!!! setting title for <"+url+">");
107 //Services.console.logStringMessage("!!! setting title for <"+url+">");
111 Services.console.logStringMessage("!!! SKIPPING title for <"+img.src+">");
115 //Services.console.logStringMessage("=======================================================");
119 Services.console.logStringMessage("+++***+++ NO DOM WINDOW FOR <"+url+">");
125 _cancel: function (request
, context
, total
, data
) {
126 if (typeof(total
) !== "number") total
= "???";
127 // abort this request
128 this.cancelled
= true;
129 this.receivedData
= null; // just in case
131 this._markImage(request
);
132 if (this.debugLogOpt
) {
133 let srcchan
= request
.QueryInterface(Ci
.nsIChannel
);
134 let url
= srcchan
.URI
.spec
;
135 this.log("[", url
, "] total: ", total
, ": CANCELLING!");
137 if (typeof(data
) === "string") {
138 if (this.showPlaceholderOpt
) {
139 this.log(" sending HAZARD");
140 this.olst
.onDataAvailable(request
, context
, createInputStreamFromString(data
), 0, data
.length
);
142 this.log(" sending HAZARD is blocked");
144 this.stopSent
= true;
145 this.olst
.onStopRequest(request
, context
, 0);
147 // just in case that double cancel will throw
149 request
.cancel(Cr
.NS_BINDING_ABORTED
);
153 _pipe: function (request
, context
) {
155 this.receivedData
= null;
159 url: function (request
) {
160 if (!request
) return "<no-url>";
161 let srcchan
= request
.QueryInterface(Ci
.nsIChannel
);
162 if (!srcchan
) return "<no-url>";
163 return srcchan
.URI
.spec
;
166 // object initialization finished
167 inited: function () {
170 QueryInterface: function (aIID
) {
171 if (aIID
.equals(Ci
.nsIStreamListener
) || aIID
.equals(Ci
.nsISupports
)) return this;
172 throw Components
.results
.NS_NOINTERFACE
;
175 onDataAvailable: function (request
, context
, inputStream
, offset
, count
) {
176 if (this.cancelled
) return;
178 if (!this.piping
&& this.debugLogOpt
) this.log("[", this.url(request
), "] offset=", offset
, "; count=", count
);
180 // if we aren't piping...
182 // ...and there's no more formats...
183 if (this.checker
.done
) {
185 if (!this.allowUnknownFormatsOpt
) {
186 this._cancel(request
, context
, offset
+count
, hazardPNG
);
189 // send collected data
190 if (this.receivedData
) {
191 if (this.receivedData
.length
) {
192 if (offset
== 0) this.logError("WTF000?! offset is zero, but we have receivedData!");
193 this.olst
.onDataAvailable(request
, context
, createInputStreamFromString(this.receivedData
), 0, this.receivedData
.length
);
194 if (offset
!= this.receivedData
.length
) this.logError("WTF001?! offset is not equal to receivedData length! offset=", offset
, "; length=", this.receivedData
.length
);
196 } else if (offset
> 0) {
197 this.logError("WTF002?! offset is not zero, but we have no receivedData!");
199 this._pipe(request
, context
);
204 // pipe data if we are piping
206 // check if we hit the limit
207 if (this.doSizeBlock
) {
208 let srcchan
= request
.QueryInterface(Ci
.nsIChannel
);
209 if (this.maxTotalBytesForDocOpt
> 0 && !this.octets
) {
210 let dw
= getDomWindowForChannel(srcchan
);
211 let isize
= getByteCounterForDomWindow(dw
)+count
;
212 if (isize
>= this.maxTotalBytesForDocOpt
) {
213 if (this.debugLogOpt
) conlog("*** [", httpChannel
.URI
.spec
, "]: blocked due to bytes limit: isize="+isize
+"; max="+this.maxTotalBytesForDocOpt
+"; count="+count
);
214 this._cancel(request
, context
, offset
+count
);
217 setByteCounterForDomWindow(dw
, isize
);
221 if (this.maxLength
> 0 && !this.octets
) {
222 if (offset
+count
> this.maxLength
) {
223 this._cancel(request
, context
, offset
+count
);
227 this.olst
.onDataAvailable(request
, context
, inputStream
, offset
, count
);
231 // collecting header data and analyzing image formats
232 let ist
= Cc
["@mozilla.org/binaryinputstream;1"].createInstance(Ci
.nsIBinaryInputStream
);
233 ist
.setInputStream(inputStream
);
235 // read up to `this.checker.maxHeaderBytes` kb
236 let maxb
= this.checker
.maxHeaderBytes
;
237 if (this.receivedData
.length
+count
< maxb
) {
238 this.log(" reading ", count
, " bytes (maxb=", maxb
, ")");
239 this.receivedData
+= ist
.readBytes(count
);
242 let left
= maxb
-this.receivedData
.length
;
244 Cu
.reportError("internal error (something is heavily fucked, #000)");
245 throw new Error("internal error (something is heavily fucked, #000)");
248 this.receivedData
+= ist
.readBytes(left
);
251 this.log(" read ", left
, " bytes, ", count
, " bytes left");
254 let res
= this.checker
.process(this.receivedData
);
255 this.log(" checker returned `", res
, "`, done is: ", this.checker
.done
);
257 // no successfull detection yet, need more spice
258 if (this.receivedData
.length
>= maxb
) this._cancel(request
, context
, offset
+count
, hazardPNG
);
262 // detected something
263 this.imageInfo
= res
;
265 this._cancel(request
, context
, offset
+count
, hazardPNG
);
268 this.log(" image: ", res
.name
, ": ", res
.width
, "x", res
.height
);
269 // valid image, check dimensions
270 if (res
.width
< this.minWidthOpt
|| res
.height
< this.minHeightOpt
||
271 res
.width
> this.maxWidthOpt
|| res
.height
> this.maxHeightOpt
)
273 this.log(" allowed size: min=", this.minWidthOpt
, "x", this.minHeightOpt
, "; max=", this.maxWidthOpt
, "x", this.maxHeightOpt
);
274 this._cancel(request
, context
, offset
+count
, hazardPNG
);
279 if (!this.allowUnknownFormatsOpt
) {
280 this.log(" unknown format");
281 this._cancel(request
, context
, offset
+count
, hazardPNG
);
286 // here we got something good, transfer collected header and start piping
287 this.olst
.onDataAvailable(request
, context
, createInputStreamFromString(this.receivedData
), 0, this.receivedData
.length
);
290 offset
+= this.receivedData
.length
;
291 this.olst
.onDataAvailable(request
, context
, inputStream
, offset
, count
);
293 this._pipe(request
, context
);
296 onStartRequest: function (request
, context
) {
297 if (this.debugLogOpt
) this.log("onStartRequest() [", this.url(request
), "]");
298 this.olst
.onStartRequest(request
, context
);
299 if (this.blacklisted
) {
300 this.log("image is blacklisted");
301 this._cancel(request
, context
, 0, hazardPNG
);
306 onStopRequest: function (request
, context
, statusCode
) {
307 if (this.debugLogOpt
) this.log("onStopRequest() [", this.url(request
), "]: status code=", statusCode
);
308 //this.log("[", this.url(request), "]: status code=", statusCode);
309 if (!this.stopSent
&& this.receivedData
!== null) {
310 // wow, we have some buffered data, check and send it!
311 let res
= this.checker
.process(this.receivedData
);
312 if (typeof(res
) === "object") {
314 this._cancel(request
, context
, 0, hazardPNG
);
317 if (res
.width
< this.minWidthOpt
|| res
.height
< this.minHeightOpt
||
318 res
.width
> this.maxWidthOpt
|| res
.height
> this.maxHeightOpt
) {
319 this._cancel(request
, context
, 0, hazardPNG
);
324 if (!this.allowUnknownFormatsOpt
) {
325 this._cancel(request
, context
, 0, hazardPNG
);
329 this.olst
.onDataAvailable(request
, context
, createInputStreamFromString(this.receivedData
), 0, this.receivedData
.length
);
331 this.receivedData
= null; // just in case
333 if (!this.stopSent
) {
334 this.olst
.onStopRequest(request
, context
, statusCode
);