"chrome:" URIs have no host; check for that, and substitute "hohost" instead
[k8imago.git] / code / modules / httpobserver.js
bloba87d79921be8cb0abb05ca46cdf6c0ba7bef005c
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 // http observer
11 var EXPORTED_SYMBOLS = [
14 let {utils:Cu, classes:Cc, interfaces:Ci, results:Cr} = Components;
17 //////////////////////////////////////////////////////////////////////////////
18 Cu.import("resource://gre/modules/Services.jsm");
20 //////////////////////////////////////////////////////////////////////////////
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+"tamper.js");
25 Cu.import(MODULE_PATH+"stoplist.js");
26 Cu.import(MODULE_PATH+"imgreload.js");
27 Cu.import(MODULE_PATH+"hazard.js");
28 Cu.import(MODULE_PATH+"rule-engine.js");
31 //////////////////////////////////////////////////////////////////////////////
32 // init some functions from window
33 let btoa = null;
35 registerWindowHook("load", function (win) {
36   btoa = win.btoa;
37 });
39 registerWindowHook("unload", function (win) {
40   btoa = null;
41 });
44 //////////////////////////////////////////////////////////////////////////////
45 let tldsvc = Cc["@mozilla.org/network/effective-tld-service;1"].getService(Ci.nsIEffectiveTLDService);
48 //////////////////////////////////////////////////////////////////////////////
49 let imagectRE = /^image\//;
52 let httpRequestObserver = {
53   QueryInterface: function (aIID) {
54     if (aIID.equals(Ci.nsIObserver) || aIID.equals(Ci.nsISupports)) return this;
55     throw Components.results.NS_NOINTERFACE;
56   },
58   observe: function (aSubject, aTopic, aData) {
59     if (!(aSubject instanceof Ci.nsIHttpChannel)) return;
61     if (aTopic === "http-on-examine-response") {
62       // aSubject is request object
63       let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
65       let dw = getDomWindowForChannel(httpChannel), bro = null;
66       if (dw) {
67         bro = getBrowserForDomWindow(dw);
68         if (bro) {
69           if (PREFS.debugLog) conlog("[", httpChannel.URI.spec, "] browser: ", bro.currentURI.spec);
70         } else {
71           if (PREFS.debugLog) conlog("*** [", httpChannel.URI.spec, "] NO browser");
72           return;
73         }
74       } else {
75         if (PREFS.debugLog) conlog("*** [", httpChannel.URI.spec, "] NO dom window");
76         return;
77       }
78       let docURI = bro.currentURI.spec;
80       // process redirects for whitelisted and blacklisted images
81       if (httpChannel.responseStatus >= 300 && httpChannel.responseStatus <= 399) {
82         let wl = isUnblockedImage(httpChannel.URI, docURI);
83         let bl = isBlockedImage(httpChannel.URI, docURI);
84         if (wl || bl) {
85           imageLoaderActivated(httpChannel.URI, docURI);
86           if (PREFS.debugLog) conlog("***GOT IMAGE REDIRECT FOR ["+httpChannel.URI.spec+"]! responseStatus="+httpChannel.responseStatus);
87           let loc = httpChannel.getResponseHeader("Location");
88           if (loc) {
89             if (PREFS.debugLog) conlog("***REDIRECT TO: ["+loc+"]");
90             if (wl) {
91               unblockImage(loc, docURI);
92               registerManualReloadRedirectURL(httpChannel.URI.spec, loc);
93             } else {
94               blockImage(loc, docURI);
95             }
96           }
97         }
98       }
100       let ctype = httpChannel.contentType;
101       if (!ctype) return;
102       if (!imagectRE.test(ctype) /*&& ctype !== "application/octet-stream"*/) {
103         // if we got a non-redirecting reply, reset image status
104         resetImageStatus(httpChannel.URI, docURI);
105         //conlog("ctype: ", ctype);
106         return;
107       }
109       // intercept and count
110       if (Math.floor(httpChannel.responseStatus/100) === 2) {
111         imageLoaderActivated(httpChannel.URI, docURI);
112         let doSizeBlockInTamper = false;
114         if (PREFS.debugLog) conlog("*** CHECKING [", httpChannel.URI.spec, "]...");
115         let isoctet = false/*ctype === "application/octet-stream"*/;
116         // whitelisted?
117         if (isUnblockedImage(httpChannel.URI, docURI)) {
118           if (PREFS.debugLog) conlog("*** UNBLOCKED [", httpChannel.URI.spec, "]...");
119           return;
120         }
121         // blacklisted?
122         let blacklisted = (isBlockedImage(httpChannel.URI, docURI));
123         // global black/white lists
124         if (!blacklisted) {
125           //TODO: list priorities
126           // this can pappen for "chrome:", for exampe
127           let host = (httpChannel.URI.host ? httpChannel.URI.host : "nohost");
128           if (WHITE_HOSTS[host]) {
129             // whitelisted
130             if (PREFS.debugLog) conlog("*** WHITE HOST [", httpChannel.URI.spec, "]...");
131             return;
132           }
133           if (host in BLACK_HOSTS) blacklisted = BLACK_HOSTS[host];
134         }
135         if (!blacklisted && dw && PREFS.maxTotalBytesForDoc > 0) {
136           try {
137             let ctlen = parseInt(httpChannel.getResponseHeader("Content-Length"), 10);
138             if (isNaN(ctlen)) doSizeBlockInTamper = true;
139             if (!isNaN(ctlen) && ctlen > 0) {
140               let isize = getByteCounterForDomWindow(dw)+ctlen;
141               if (isize >= PREFS.maxTotalBytesForDoc) {
142                 if (PREFS.debugLog) conlog("*** [", httpChannel.URI.spec, "]: blacklisted due to bytes limit: isize="+isize+"; max="+PREFS.maxTotalBytesForDoc+"; ctlen="+ctlen);
143                 blacklisted = true;
144               } else {
145                 setByteCounterForDomWindow(dw, isize);
146               }
147             }
148           } catch (e) {
149             // httpChannel.getResponseHeader can throw when download is blocked (like font), just get out of here
150             return;
151           }
152         }
153         // check rules
154         let rule = null;
155         if (!blacklisted) {
156           // check if we have too much images for this DOM window
157           if (PREFS.debugLog) conlog("*** [", httpChannel.URI.spec, "] searching for rule...");
158           rule = getRuleForURI(httpChannel.URI, bro.currentURI);
159           if (rule) {
160             if (PREFS.debugLog) conlog("*** [", httpChannel.URI.spec, "] rule: ", rule);
161             if (rule.stopAction) {
162               if (rule.action === "deny") {
163                 if (PREFS.debugLog) conlog("*** [", httpChannel.URI.spec, "] blocked by 'deny' rule!");
164                 blacklisted = true;
165               }
166             }
167           }
168         }
169         //conlog("bl: [", httpChannel.URI.spec, "]: ", blacklisted);
170         // check for 1st-party images
171         if (!blacklisted && PREFS.allowFirstPartyImages && !bro.currentURI.equals(httpChannel.URI)) {
172           // i've seen "Error: NS_ERROR_FAILURE: Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIURI.host]" there, so...
173           try {
174             if (tldsvc.getBaseDomain(bro.currentURI) === tldsvc.getBaseDomain(httpChannel.URI)) {
175               if (PREFS.debugLog) conlog(httpChannel.URI.spec, ": first-party!");
176               return;
177             }
178           } catch (e) {}
179         }
180         //if (PREFS.debugLog) conlog("counting: ", httpChannel.URI.spec);
181         let nlst = new ImgDetectListener(isoctet, blacklisted, doSizeBlockInTamper);
182         nlst.rule = rule;
183         nlst.mainURI = bro.currentURI;
184         aSubject.QueryInterface(Ci.nsITraceableChannel);
185         nlst.olst = aSubject.setNewListener(nlst);
186         nlst.inited();
187         // onStartRequest() and onStopRequest() will be called anyway, and
188         // our tamper will process that correctly for blacklisted images
189         if (blacklisted) aSubject.cancel(Cr.NS_BINDING_ABORTED);
190       } else {
191         if (PREFS.debugLog) conlog("WTF: ", httpChannel.URI.spec, " (", httpChannel.responseStatus, ")");
192         //aSubject.cancel(Cr.NS_BINDING_ABORTED);
193         resetImageStatus(httpChannel.URI, docURI);
194       }
195     } else if (aTopic === "http-on-modify-request") {
196       let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
197       let url = httpChannel.URI.spec;
198       // WARNING! `isManualReloadURL()` modifies internal URL cache (i.e. it is destructive!)
199       if (isManualReloadURL(url)) {
200         // ah, ok, i took time from rfc
201         httpChannel.setRequestHeader("If-Modified-Since", "Sat, 29 Oct 1994 19:43:31 GMT", false);
202         httpChannel.setRequestHeader("ETag", "", false);
203         //delete reloadUrlList[url]; // no need to, `isManualReloadURL()` will do it for us
204       }
205     }
206   },
210 //////////////////////////////////////////////////////////////////////////////
211 let osvc = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
212 osvc.addObserver(httpRequestObserver, "http-on-examine-response", false);
213 osvc.addObserver(httpRequestObserver, "http-on-modify-request", false);