1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/firebug",
  5     "firebug/lib/locale",
  6     "firebug/lib/events",
  7     "firebug/lib/url",
  8     "firebug/chrome/firefox",
  9     "firebug/lib/xpcom",
 10     "firebug/lib/http",
 11     "firebug/lib/string",
 12     "firebug/lib/xml"
 13 ],
 14 function(Firebug, Locale, Events, Url, Firefox, Xpcom, Http, Str, Xml) {
 15 
 16 // ********************************************************************************************* //
 17 // Constants
 18 
 19 const Cc = Components.classes;
 20 const Ci = Components.interfaces;
 21 const Cr = Components.results;
 22 
 23 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 24 
 25 const mimeExtensionMap =
 26 {
 27     "txt": "text/plain",
 28     "html": "text/html",
 29     "htm": "text/html",
 30     "xhtml": "text/html",
 31     "xml": "text/xml",
 32     "css": "text/css",
 33     "js": "application/x-javascript",
 34     "jss": "application/x-javascript",
 35     "jpg": "image/jpg",
 36     "jpeg": "image/jpeg",
 37     "gif": "image/gif",
 38     "png": "image/png",
 39     "bmp": "image/bmp",
 40     "swf": "application/x-shockwave-flash",
 41     "flv": "video/x-flv",
 42     "webm": "video/webm"
 43 };
 44 
 45 const mimeCategoryMap =
 46 {
 47     "text/plain": "txt",
 48     "application/octet-stream": "bin",
 49     "text/html": "html",
 50     "text/xml": "html",
 51     "application/rss+xml": "html",
 52     "application/atom+xml": "html",
 53     "application/xhtml+xml": "html",
 54     "application/mathml+xml": "html",
 55     "application/rdf+xml": "html",
 56     "text/css": "css",
 57     "application/x-javascript": "js",
 58     "text/javascript": "js",
 59     "application/javascript" : "js",
 60     "text/ecmascript": "js",
 61     "application/ecmascript" : "js", // RFC4329
 62     "image/jpeg": "image",
 63     "image/jpg": "image",
 64     "image/gif": "image",
 65     "image/png": "image",
 66     "image/bmp": "image",
 67     "application/x-shockwave-flash": "flash",
 68     "video/x-flv": "flash",
 69     "audio/mpeg3": "media",
 70     "audio/x-mpeg-3": "media",
 71     "video/mpeg": "media",
 72     "video/x-mpeg": "media",
 73     "video/webm": "media",
 74     "audio/ogg": "media",
 75     "application/ogg": "media",
 76     "application/x-ogg": "media",
 77     "application/x-midi": "media",
 78     "audio/midi": "media",
 79     "audio/x-mid": "media",
 80     "audio/x-midi": "media",
 81     "music/crescendo": "media",
 82     "audio/wav": "media",
 83     "audio/x-wav": "media"
 84 };
 85 
 86 const fileCategories =
 87 {
 88     "undefined": 1,
 89     "html": 1,
 90     "css": 1,
 91     "js": 1,
 92     "xhr": 1,
 93     "image": 1,
 94     "flash": 1,
 95     "media": 1,
 96     "txt": 1,
 97     "bin": 1
 98 };
 99 
100 const textFileCategories =
101 {
102     "txt": 1,
103     "html": 1,
104     "xhr": 1,
105     "css": 1,
106     "js": 1
107 };
108 
109 const binaryFileCategories =
110 {
111     "bin": 1,
112     "flash": 1,
113     "media": 1
114 };
115 
116 const binaryCategoryMap =
117 {
118     "image": 1,
119     "flash" : 1
120 };
121 
122 // ********************************************************************************************* //
123 
124 var NetUtils =
125 {
126     isXHR: Http.isXHR, // deprecated
127 
128     mimeExtensionMap: mimeExtensionMap,
129     mimeCategoryMap: mimeCategoryMap,
130     fileCategories: fileCategories,
131     textFileCategories: textFileCategories,
132     binaryFileCategories: binaryFileCategories,
133     binaryCategoryMap: binaryCategoryMap,
134 
135     now: function()
136     {
137         return (new Date()).getTime();
138     },
139 
140     getFrameLevel: function(win)
141     {
142         var level = 0;
143         for (; win && (win != win.parent) && (win.parent instanceof window.Window); win = win.parent)
144             ++level;
145         return level;
146     },
147 
148     findHeader: function(headers, name)
149     {
150         if (!headers)
151             return null;
152 
153         name = name.toLowerCase();
154         for (var i = 0; i < headers.length; ++i)
155         {
156             var headerName = headers[i].name.toLowerCase();
157             if (headerName == name)
158                 return headers[i].value;
159         }
160     },
161 
162     formatPostText: function(text)
163     {
164         if (text instanceof window.XMLDocument)
165             return Xml.getElementXML(text.documentElement);
166         else
167             return text;
168     },
169 
170     getPostText: function(file, context, noLimit)
171     {
172         if (!file.postText)
173         {
174             file.postText = Http.readPostTextFromRequest(file.request, context);
175 
176             if (!file.postText && context)
177                 file.postText = Http.readPostTextFromPage(file.href, context);
178         }
179 
180         if (!file.postText)
181             return file.postText;
182 
183         var limit = Firebug.netDisplayedPostBodyLimit;
184         if (file.postText.length > limit && !noLimit)
185         {
186             return Str.cropString(file.postText, limit,
187                 "\n\n... " + Locale.$STR("net.postDataSizeLimitMessage") + " ...\n\n");
188         }
189 
190         return file.postText;
191     },
192 
193     getResponseText: function(file, context)
194     {
195         // The response can be also empty string so, check agains "undefined".
196         return (typeof(file.responseText) != "undefined") ?
197             file.responseText :
198             context.sourceCache.loadText(file.href, file.method, file);
199     },
200 
201     matchesContentType: function(headerValue, contentType)
202     {
203         var contentTypes = (typeof contentType == "string" ? [contentType] : contentType);
204         for (var i = 0; i < contentTypes.length; ++i)
205         {
206             // The header value doesn't have to match the content type exactly;
207             // there can be a charset specified. So, test for a prefix instead.
208             if (Str.hasPrefix(headerValue, contentTypes[i]))
209                 return true;
210         }
211         return false;
212     },
213 
214     isURLEncodedRequest: function(file, context)
215     {
216         var text = NetUtils.getPostText(file, context);
217         if (text && Str.hasPrefix(text.toLowerCase(), "content-type: application/x-www-form-urlencoded"))
218             return true;
219 
220         var headerValue = NetUtils.findHeader(file.requestHeaders, "content-type");
221         return (headerValue &&
222                 NetUtils.matchesContentType(headerValue, "application/x-www-form-urlencoded"));
223     },
224 
225     isMultiPartRequest: function(file, context)
226     {
227         var text = NetUtils.getPostText(file, context);
228         if (text && Str.hasPrefix(text.toLowerCase(), "content-type: multipart/form-data"))
229             return true;
230         return false;
231     },
232 
233     getMimeType: function(mimeType, uri)
234     {
235         if (!mimeType || !(mimeCategoryMap.hasOwnProperty(mimeType)))
236         {
237             var ext = Url.getFileExtension(uri);
238             if (!ext)
239                 return mimeType;
240             else
241             {
242                 var extMimeType = mimeExtensionMap[ext.toLowerCase()];
243                 return extMimeType ? extMimeType : mimeType;
244             }
245         }
246         else
247             return mimeType;
248     },
249 
250     getDateFromSeconds: function(s)
251     {
252         var d = new Date();
253         d.setTime(s*1000);
254         return d;
255     },
256 
257     getHttpHeaders: function(request, file, context)
258     {
259         if (!(request instanceof Ci.nsIHttpChannel))
260             return;
261 
262         // xxxHonza: is there any problem to do this in requestedFile method?
263         file.method = request.requestMethod;
264         file.urlParams = Url.parseURLParams(file.href);
265 
266         try
267         {
268             file.status = request.responseStatus;
269         }
270         catch (e) { }
271 
272         try
273         {
274             file.mimeType = NetUtils.getMimeType(request.contentType, request.name);
275         }
276         catch (e) { }
277 
278         try
279         {
280             if (!file.requestHeaders)
281             {
282                 var requestHeaders = [];
283                 request.visitRequestHeaders({
284                     visitHeader: function(name, value)
285                     {
286                         requestHeaders.push({name: name, value: value});
287                     }
288                 });
289                 file.requestHeaders = requestHeaders;
290             }
291         }
292         catch (e) { }
293 
294         try
295         {
296             if (!file.responseHeaders)
297             {
298                 var responseHeaders = [];
299                 request.visitResponseHeaders({
300                     visitHeader: function(name, value)
301                     {
302                         responseHeaders.push({name: name, value: value});
303                     }
304                 });
305                 file.responseHeaders = responseHeaders;
306 
307                 if (context)
308                 {
309                     // Response haeaders are available now, dispatch an event to listeners
310                     Events.dispatch(Firebug.NetMonitor.fbListeners, "onResponseHeaders",
311                         [context, file]);
312                 }
313             }
314         }
315         catch (e) { }
316     },
317 
318     getFileCategory: function(file)
319     {
320         if (file.category)
321         {
322             if (FBTrace.DBG_NET)
323                 FBTrace.sysout("net.getFileCategory; current: " + file.category + " for: " +
324                     file.href, file);
325             return file.category;
326         }
327 
328         if (file.isXHR)
329         {
330             if (FBTrace.DBG_NET)
331                 FBTrace.sysout("net.getFileCategory; XHR for: " + file.href, file);
332             return file.category = "xhr";
333         }
334 
335         if (!file.mimeType)
336         {
337             var ext = Url.getFileExtension(file.href);
338             if (ext)
339                 file.mimeType = mimeExtensionMap[ext.toLowerCase()];
340         }
341 
342         /*if (FBTrace.DBG_NET)
343             FBTrace.sysout("net.getFileCategory; " + mimeCategoryMap[file.mimeType] +
344                 ", mimeType: " + file.mimeType + " for: " + file.href, file);*/
345 
346         if (!file.mimeType)
347             return "";
348 
349         // Solve cases when charset is also specified, eg "text/html; charset=UTF-8".
350         var mimeType = file.mimeType;
351         if (mimeType)
352             mimeType = mimeType.split(";")[0];
353 
354         return (file.category = mimeCategoryMap[mimeType]);
355     },
356 
357     getPageTitle: function(context)
358     {
359         var title = context.getTitle();
360         return (title) ? title : context.getName();
361     },
362 
363     getBlockingEndTime: function(file)
364     {
365         //var blockingEnd = (file.sendingTime > file.startTime) ? file.sendingTime : file.waitingForTime;
366 
367         if (file.resolveStarted && file.connectStarted)
368             return file.resolvingTime;
369 
370         if (file.connectStarted)
371             return file.connectingTime;
372 
373         if (file.sendStarted)
374             return file.sendingTime;
375 
376         return file.waitingForTime;
377     },
378 
379     getTimeLabelFromMs: function(ms)
380     {
381         var time = new Date();
382         time.setTime(ms);
383         return this.getTimeLabel(time);
384     },
385 
386     getTimeLabel: function(date)
387     {
388         var m = date.getMinutes() + "";
389         var s = date.getSeconds() + "";
390         var ms = date.getMilliseconds() + "";
391         return "[" + ((m.length > 1) ? m : "0" + m) + ":" +
392             ((s.length > 1) ? s : "0" + s) + "." +
393             ((ms.length > 2) ? ms : ((ms.length > 1) ? "0" + ms : "00" + ms)) + "]";
394     },
395 
396     openResponseInTab: function(file)
397     {
398         try
399         {
400             var response = NetUtils.getResponseText(file, this.context);
401             var inputStream = Http.getInputStreamFromString(response);
402             var stream = Xpcom.CCIN("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream");
403             stream.setInputStream(inputStream);
404             var encodedResponse = btoa(stream.readBytes(stream.available()));
405             var dataURI = "data:" + file.request.contentType + ";base64," + encodedResponse;
406         
407             var tabBrowser = Firefox.getTabBrowser();
408             tabBrowser.selectedTab = tabBrowser.addTab(dataURI);
409         }
410         catch (err)
411         {
412             if (FBTrace.DBG_ERRORS)
413                 FBTrace.sysout("net.openResponseInTab EXCEPTION", err);
414         }
415     },
416 
417     traceRequestTiming: function(msg, file)
418     {
419         var blockingEnd = this.getBlockingEndTime(file);
420 
421         //Helper log for debugging timing problems.
422         var timeLog = {};
423         timeLog.startTime = this.getTimeLabelFromMs(file.startTime);
424         timeLog.resolvingTime = this.getTimeLabelFromMs(file.resolvingTime);
425         timeLog.connectingTime = this.getTimeLabelFromMs(file.connectingTime);
426         timeLog.connectedTime = this.getTimeLabelFromMs(file.connectedTime);
427         timeLog.blockingEnd = this.getTimeLabelFromMs(blockingEnd);
428         timeLog.sendingTime = this.getTimeLabelFromMs(file.sendingTime);
429         timeLog.waitingForTime = this.getTimeLabelFromMs(file.waitingForTime);
430         timeLog.respondedTime = this.getTimeLabelFromMs(file.respondedTime);
431         timeLog.endTime = this.getTimeLabelFromMs(file.endTime);
432 
433         if (file.request instanceof Ci.nsITimedChannel)
434         {
435             timeLog.startTime += " - " + this.getTimeLabelFromMs(file.request.channelCreationTime/1000);
436             timeLog.startTime += this.getTimeLabelFromMs(file.request.asyncOpenTime/1000);
437             timeLog.resolvingTime += " - " + this.getTimeLabelFromMs(file.request.domainLookupStartTime/1000);
438             timeLog.resolvingTime += this.getTimeLabelFromMs(file.request.domainLookupEndTime/1000);
439             timeLog.connectingTime += " - " + this.getTimeLabelFromMs(file.request.connectStartTime/1000);
440             timeLog.connectedTime += " - " + this.getTimeLabelFromMs(file.request.connectEndTime/1000);
441             timeLog.sendingTime += " - " + this.getTimeLabelFromMs(file.request.requestStartTime/1000);
442             timeLog.respondedTime += " - " + this.getTimeLabelFromMs(file.request.responseStartTime/1000);
443             timeLog.endTime += " - " + this.getTimeLabelFromMs(file.request.responseEndTime/1000);
444             timeLog.cacheReadStartTime = this.getTimeLabelFromMs(file.request.cacheReadStartTime/1000);
445             timeLog.cacheReadEndTime = this.getTimeLabelFromMs(file.request.cacheReadEndTime/1000);
446             timeLog.timingEnabled = file.request.timingEnabled;
447         }
448 
449         FBTrace.sysout(msg + " " + file.href, timeLog);
450     }
451 };
452 
453 // ********************************************************************************************* //
454 // Registration
455 
456 return NetUtils;
457 
458 // ********************************************************************************************* //
459 });
460