1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/object",
  5     "firebug/lib/xpcom",
  6     "firebug/cookies/baseObserver",
  7     "firebug/lib/http",
  8     "firebug/chrome/tabWatcher",
  9     "firebug/cookies/cookie",
 10     "firebug/lib/options",
 11     "firebug/cookies/cookieUtils",
 12     "firebug/lib/array",
 13     "firebug/chrome/firefox",
 14 ],
 15 function(Obj, Xpcom, BaseObserver, Http, TabWatcher, Cookie, Options, CookieUtils, Arr, Firefox) {
 16 
 17 // ********************************************************************************************* //
 18 // Constants
 19 
 20 var Cc = Components.classes;
 21 var Ci = Components.interfaces;
 22 
 23 const cookieService = Xpcom.CCSV("@mozilla.org/cookieService;1", "nsICookieService");
 24 
 25 const panelName = "cookies";
 26 
 27 // ********************************************************************************************* //
 28 // HTTP observer
 29 
 30 /**
 31  * @class Represents an observer for http-on-modify-request and
 32  * http-on-examine-response events that are dispatched
 33  * by Firefox when network request is executed and returned. 
 34  */
 35 var HttpObserver = Obj.extend(BaseObserver,
 36 /** @lends HttpObserver */
 37 {
 38     // nsIObserver
 39     observe: function(aSubject, aTopic, aData) 
 40     {
 41         try {
 42             aSubject = aSubject.QueryInterface(Ci.nsIHttpChannel);
 43             if (aTopic == "http-on-modify-request") {
 44                 this.onModifyRequest(aSubject);
 45             } else if (aTopic == "http-on-examine-response") {
 46                 this.onExamineResponse(aSubject);
 47             }
 48         }
 49         catch (err)
 50         {
 51             if (FBTrace.DBG_ERRORS)
 52                 FBTrace.sysout("cookies.HttpObserver.observe; EXCEPTION " + err, err);
 53         }
 54     },
 55 
 56     onModifyRequest: function(request) 
 57     {
 58         var name = request.URI.spec;
 59         var origName = request.originalURI.spec;
 60         var win = Http.getWindowForRequest(request);
 61         var tabId = Firebug.getTabIdForWindow(win);
 62 
 63         // Firebus's natures is to display information for a tab. So, if there
 64         // is no tab associated then end.
 65         if (!tabId)
 66             return;
 67 
 68         // Dump debug information to the console.
 69         if (FBTrace.DBG_COOKIES)
 70         {
 71             FBTrace.sysout("cookies.onModifyRequest: " + request.name);
 72             FBTrace.sysout("cookies.Cookies sent: " +
 73                 cookieService.getCookieString(request.URI, request));
 74         }
 75 
 76         // At this moment (specified by all the conditions) FB context doesn't exists yet.
 77         // But the page already started loading and there are things to monitor.
 78         // This is why the temporary context is created. It's used as a place where to 
 79         // store information (cookie events and hosts). All this info will be copied into
 80         // the real FB context when it's created (see initContext).
 81         if ((request.loadFlags & Ci.nsIHttpChannel.LOAD_DOCUMENT_URI) &&
 82             (request.loadGroup && request.loadGroup.groupObserver) &&
 83             (name == origName) && (win && win == win.parent))
 84         {
 85             var browser = Firefox.getBrowserForWindow(win);
 86 
 87             if (!Firebug.TabWatcher.shouldCreateContext(browser, name, null))
 88             {
 89                 if (FBTrace.DBG_COOKIES)
 90                     FBTrace.sysout("cookies.onModifyRequest; Activation logic says don't create " +
 91                         "temp context for: " + name);
 92                 return;
 93             }
 94 
 95             if (FBTrace.DBG_COOKIES && Firebug.CookieModule.contexts[tabId])
 96                 FBTrace.sysout("cookies.!!! Temporary context exists for: " + tabId);
 97 
 98             // Create temporary context
 99             if (!Firebug.CookieModule.contexts[tabId])
100             {
101                 var tempContext = new TempContext(tabId);
102                 Firebug.CookieModule.contexts[tabId] = tempContext;
103 
104                 if (FBTrace.DBG_COOKIES)
105                     FBTrace.sysout("cookies.INIT temporary context for: " + tempContext.tabId);
106 
107                 tempContext.href = name;
108                 Firebug.CookieModule.initTempContext(tempContext);
109             }
110         }
111 
112         // Use the temporary context first, if it exists. There could be an old
113         // context (associated with this tab) for the previous URL.
114         var context = Firebug.CookieModule.contexts[tabId];
115         context = context ? context : TabWatcher.getContextByWindow(win);
116 
117         // The context doesn't have to exist due to the activation support.
118         if (!context)
119         {
120             if (FBTrace.DBG_COOKIES) 
121                 FBTrace.sysout("cookies.onModifyRequest: context is NOT available for:" +
122                     request.URI.host + ", tabId: " + tabId);
123             return;
124         }
125 
126         // Collect all the host (redirects, iframes) as cookies for all of them
127         // will be displayed.
128         var activeHosts = context.cookies.activeHosts;
129         var host = request.URI.host;
130         if (!activeHosts[host])
131         {
132             activeHosts[host] = {host: host, path: request.URI.path};
133 
134             if (FBTrace.DBG_COOKIES)
135                 FBTrace.sysout("cookies.New host (on-modify-request): " +
136                     request.URI.host + ", tabId: " + tabId, activeHosts);
137 
138             // Refresh the panel asynchronously.
139             if (context instanceof Firebug.TabContext)
140                 context.invalidatePanels(panelName);
141         }
142     },
143 
144     onExamineResponse: function(request)
145     {
146         var win = Http.getWindowForRequest(request);
147         var tabId = Firebug.getTabIdForWindow(win);
148         if (!tabId)
149             return;
150 
151         if (FBTrace.DBG_COOKIES)
152             FBTrace.sysout("cookies.onExamineResponse: " + request.name);
153 
154         // Do not collect received cookies if they are not necessary.
155         if (!Options.get("cookies.logEvents") && !Options.get("cookies.showRejectedCookies"))
156             return;
157 
158         // If logging to console is on, remember the set-cookie string, so
159         // these cookies can be displayed together e.g. with rejected message.
160         var setCookie;
161         request.visitResponseHeaders({
162             visitHeader: function(header, value) {
163                 if (header == "Set-Cookie")
164                     setCookie = value;
165             }
166         });
167 
168         // Bail out if no cookies is received.
169         if (!setCookie)
170             return;
171 
172         // Try to get the context from the contexts array first. The TabWatacher
173         // could return context for the previous page in this tab.
174         var context = Firebug.CookieModule.contexts[tabId];
175         context = context ? context : TabWatcher.getContextByWindow(win);
176 
177         // The context doesn't have to exist due to the activation support.
178         if (!context)
179         {
180             if (FBTrace.DBG_COOKIES) 
181                 FBTrace.sysout("cookies.onExamineResponse: context is NOT available for:" +
182                     request.URI.host + ", tabId: " + tabId);
183             return;
184         }
185 
186         // Associate the setCookie string with proper active host (active
187         // host can be the page itself or an embedded iframe or a XHR).
188         // Also remember originalURI so, the info where the cookies comes
189         // from can be displayed to the user.
190         var activeHosts = context.cookies.activeHosts;
191         var host = request.URI.host;
192         var activeHost = activeHosts[host];
193 
194         // Map of all received cookies. The key is cookie-host the value is
195         // an array with all cookies with the same host.
196         if (!context.cookies.activeCookies)
197             context.cookies.activeCookies = [];
198 
199         if (!activeHost)
200             return;
201 
202         var activeCookies = context.cookies.activeCookies;
203 
204         // xxxHonza
205         // 1)the activeHost.receivedCookies array shouldn't be recreated
206         // if it's already there.
207         // 2) There can be more responses from the same domain (XHRs) and so,
208         // more received cookies within the page life.
209         // 3) The list should make sure that received cookies aren't duplicated.
210         // (the same cookie can be received multiple time).
211         // 4) Also, rejected cookies, are displayed in the cookie-list too and
212         // these shouldn't be duplicated.
213         // 5) This should be a map (key == the original host)
214         //if (!activeHost.receivedCookies)
215             activeHost.receivedCookies = [];
216 
217         // Parse all received cookies and store them into activeHost info.
218         var cookies = setCookie.split("\n");
219         for (var i=0; i<cookies.length; i++)
220         {
221             var cookie = CookieUtils.parseFromString(cookies[i]);
222             cookie.originalURI = request.originalURI;
223             if (!cookie.host)
224                 cookie.host = host;
225 
226             // Push into activeHosts
227             var cookieWrapper = new Cookie(CookieUtils.makeCookieObject(cookie));
228             activeHost.receivedCookies.push(cookieWrapper);
229 
230             // Push into activeCookies
231             if (!activeCookies[cookie.host])
232                 activeCookies[cookie.host] = [];
233 
234             var activeCookiesForHost = activeCookies[cookie.host];
235             activeCookiesForHost[CookieUtils.getCookieId(cookie)] = cookie;
236 
237             if (FBTrace.DBG_COOKIES)
238                 FBTrace.sysout("cookies.Cookie received: " +
239                     cookie.host + ", cookie: " + cookie.name, cookie);
240         }
241 
242         if (FBTrace.DBG_COOKIES)
243             FBTrace.sysout("cookies.Set-Cookie: " + setCookie, activeCookies);
244     }
245 });
246 
247 // ********************************************************************************************* //
248 // Temporary context
249 
250 function TempContext(tabId)
251 {
252     this.tabId = tabId;
253     this.events = [];
254 }
255 
256 TempContext.prototype.appendCookieEvent = function(subject, topic, data)
257 {
258     this.events.push({subject:subject, topic:topic, data:data});
259 }
260 
261 // ********************************************************************************************* //
262 
263 return HttpObserver;
264 
265 // ********************************************************************************************* //
266 });
267 
268