|
1 /* vim:set ts=2 sw=2 sts=2 et: */ |
|
2 /* |
|
3 * Software License Agreement (BSD License) |
|
4 * |
|
5 * Copyright (c) 2007, Parakey Inc. |
|
6 * All rights reserved. |
|
7 * |
|
8 * Redistribution and use of this software in source and binary forms, with or without modification, |
|
9 * are permitted provided that the following conditions are met: |
|
10 * |
|
11 * * Redistributions of source code must retain the above |
|
12 * copyright notice, this list of conditions and the |
|
13 * following disclaimer. |
|
14 * |
|
15 * * Redistributions in binary form must reproduce the above |
|
16 * copyright notice, this list of conditions and the |
|
17 * following disclaimer in the documentation and/or other |
|
18 * materials provided with the distribution. |
|
19 * |
|
20 * * Neither the name of Parakey Inc. nor the names of its |
|
21 * contributors may be used to endorse or promote products |
|
22 * derived from this software without specific prior |
|
23 * written permission of Parakey Inc. |
|
24 * |
|
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR |
|
26 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
|
27 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
|
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER |
|
31 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
|
32 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
33 */ |
|
34 |
|
35 /* |
|
36 * Creator: |
|
37 * Joe Hewitt |
|
38 * Contributors |
|
39 * John J. Barton (IBM Almaden) |
|
40 * Jan Odvarko (Mozilla Corp.) |
|
41 * Max Stepanov (Aptana Inc.) |
|
42 * Rob Campbell (Mozilla Corp.) |
|
43 * Hans Hillen (Paciello Group, Mozilla) |
|
44 * Curtis Bartley (Mozilla Corp.) |
|
45 * Mike Collins (IBM Almaden) |
|
46 * Kevin Decker |
|
47 * Mike Ratcliffe (Comartis AG) |
|
48 * Hernan RodrÃguez Colmeiro |
|
49 * Austin Andrews |
|
50 * Christoph Dorn |
|
51 * Steven Roussey (AppCenter Inc, Network54) |
|
52 * Mihai Sucan (Mozilla Corp.) |
|
53 */ |
|
54 |
|
55 "use strict"; |
|
56 |
|
57 const {components, Cc, Ci, Cu} = require("chrome"); |
|
58 loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); |
|
59 |
|
60 /** |
|
61 * Helper object for networking stuff. |
|
62 * |
|
63 * Most of the following functions have been taken from the Firebug source. They |
|
64 * have been modified to match the Firefox coding rules. |
|
65 */ |
|
66 let NetworkHelper = { |
|
67 /** |
|
68 * Converts aText with a given aCharset to unicode. |
|
69 * |
|
70 * @param string aText |
|
71 * Text to convert. |
|
72 * @param string aCharset |
|
73 * Charset to convert the text to. |
|
74 * @returns string |
|
75 * Converted text. |
|
76 */ |
|
77 convertToUnicode: function NH_convertToUnicode(aText, aCharset) |
|
78 { |
|
79 let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. |
|
80 createInstance(Ci.nsIScriptableUnicodeConverter); |
|
81 try { |
|
82 conv.charset = aCharset || "UTF-8"; |
|
83 return conv.ConvertToUnicode(aText); |
|
84 } |
|
85 catch (ex) { |
|
86 return aText; |
|
87 } |
|
88 }, |
|
89 |
|
90 /** |
|
91 * Reads all available bytes from aStream and converts them to aCharset. |
|
92 * |
|
93 * @param nsIInputStream aStream |
|
94 * @param string aCharset |
|
95 * @returns string |
|
96 * UTF-16 encoded string based on the content of aStream and aCharset. |
|
97 */ |
|
98 readAndConvertFromStream: function NH_readAndConvertFromStream(aStream, aCharset) |
|
99 { |
|
100 let text = null; |
|
101 try { |
|
102 text = NetUtil.readInputStreamToString(aStream, aStream.available()) |
|
103 return this.convertToUnicode(text, aCharset); |
|
104 } |
|
105 catch (err) { |
|
106 return text; |
|
107 } |
|
108 }, |
|
109 |
|
110 /** |
|
111 * Reads the posted text from aRequest. |
|
112 * |
|
113 * @param nsIHttpChannel aRequest |
|
114 * @param string aCharset |
|
115 * The content document charset, used when reading the POSTed data. |
|
116 * @returns string or null |
|
117 * Returns the posted string if it was possible to read from aRequest |
|
118 * otherwise null. |
|
119 */ |
|
120 readPostTextFromRequest: function NH_readPostTextFromRequest(aRequest, aCharset) |
|
121 { |
|
122 if (aRequest instanceof Ci.nsIUploadChannel) { |
|
123 let iStream = aRequest.uploadStream; |
|
124 |
|
125 let isSeekableStream = false; |
|
126 if (iStream instanceof Ci.nsISeekableStream) { |
|
127 isSeekableStream = true; |
|
128 } |
|
129 |
|
130 let prevOffset; |
|
131 if (isSeekableStream) { |
|
132 prevOffset = iStream.tell(); |
|
133 iStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); |
|
134 } |
|
135 |
|
136 // Read data from the stream. |
|
137 let text = this.readAndConvertFromStream(iStream, aCharset); |
|
138 |
|
139 // Seek locks the file, so seek to the beginning only if necko hasn't |
|
140 // read it yet, since necko doesn't seek to 0 before reading (at lest |
|
141 // not till 459384 is fixed). |
|
142 if (isSeekableStream && prevOffset == 0) { |
|
143 iStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); |
|
144 } |
|
145 return text; |
|
146 } |
|
147 return null; |
|
148 }, |
|
149 |
|
150 /** |
|
151 * Reads the posted text from the page's cache. |
|
152 * |
|
153 * @param nsIDocShell aDocShell |
|
154 * @param string aCharset |
|
155 * @returns string or null |
|
156 * Returns the posted string if it was possible to read from |
|
157 * aDocShell otherwise null. |
|
158 */ |
|
159 readPostTextFromPage: function NH_readPostTextFromPage(aDocShell, aCharset) |
|
160 { |
|
161 let webNav = aDocShell.QueryInterface(Ci.nsIWebNavigation); |
|
162 return this.readPostTextFromPageViaWebNav(webNav, aCharset); |
|
163 }, |
|
164 |
|
165 /** |
|
166 * Reads the posted text from the page's cache, given an nsIWebNavigation |
|
167 * object. |
|
168 * |
|
169 * @param nsIWebNavigation aWebNav |
|
170 * @param string aCharset |
|
171 * @returns string or null |
|
172 * Returns the posted string if it was possible to read from |
|
173 * aWebNav, otherwise null. |
|
174 */ |
|
175 readPostTextFromPageViaWebNav: |
|
176 function NH_readPostTextFromPageViaWebNav(aWebNav, aCharset) |
|
177 { |
|
178 if (aWebNav instanceof Ci.nsIWebPageDescriptor) { |
|
179 let descriptor = aWebNav.currentDescriptor; |
|
180 |
|
181 if (descriptor instanceof Ci.nsISHEntry && descriptor.postData && |
|
182 descriptor instanceof Ci.nsISeekableStream) { |
|
183 descriptor.seek(NS_SEEK_SET, 0); |
|
184 |
|
185 return this.readAndConvertFromStream(descriptor, aCharset); |
|
186 } |
|
187 } |
|
188 return null; |
|
189 }, |
|
190 |
|
191 /** |
|
192 * Gets the web appId that is associated with aRequest. |
|
193 * |
|
194 * @param nsIHttpChannel aRequest |
|
195 * @returns number|null |
|
196 * The appId for the given request, if available. |
|
197 */ |
|
198 getAppIdForRequest: function NH_getAppIdForRequest(aRequest) |
|
199 { |
|
200 try { |
|
201 return this.getRequestLoadContext(aRequest).appId; |
|
202 } catch (ex) { |
|
203 // request loadContent is not always available. |
|
204 } |
|
205 return null; |
|
206 }, |
|
207 |
|
208 /** |
|
209 * Gets the topFrameElement that is associated with aRequest. |
|
210 * |
|
211 * @param nsIHttpChannel aRequest |
|
212 * @returns nsIDOMElement|null |
|
213 * The top frame element for the given request, if available. |
|
214 */ |
|
215 getTopFrameForRequest: function NH_getTopFrameForRequest(aRequest) |
|
216 { |
|
217 try { |
|
218 return this.getRequestLoadContext(aRequest).topFrameElement; |
|
219 } catch (ex) { |
|
220 // request loadContent is not always available. |
|
221 } |
|
222 return null; |
|
223 }, |
|
224 |
|
225 /** |
|
226 * Gets the nsIDOMWindow that is associated with aRequest. |
|
227 * |
|
228 * @param nsIHttpChannel aRequest |
|
229 * @returns nsIDOMWindow or null |
|
230 */ |
|
231 getWindowForRequest: function NH_getWindowForRequest(aRequest) |
|
232 { |
|
233 try { |
|
234 return this.getRequestLoadContext(aRequest).associatedWindow; |
|
235 } catch (ex) { |
|
236 // TODO: bug 802246 - getWindowForRequest() throws on b2g: there is no |
|
237 // associatedWindow property. |
|
238 } |
|
239 return null; |
|
240 }, |
|
241 |
|
242 /** |
|
243 * Gets the nsILoadContext that is associated with aRequest. |
|
244 * |
|
245 * @param nsIHttpChannel aRequest |
|
246 * @returns nsILoadContext or null |
|
247 */ |
|
248 getRequestLoadContext: function NH_getRequestLoadContext(aRequest) |
|
249 { |
|
250 try { |
|
251 return aRequest.notificationCallbacks.getInterface(Ci.nsILoadContext); |
|
252 } catch (ex) { } |
|
253 |
|
254 try { |
|
255 return aRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext); |
|
256 } catch (ex) { } |
|
257 |
|
258 return null; |
|
259 }, |
|
260 |
|
261 /** |
|
262 * Loads the content of aUrl from the cache. |
|
263 * |
|
264 * @param string aUrl |
|
265 * URL to load the cached content for. |
|
266 * @param string aCharset |
|
267 * Assumed charset of the cached content. Used if there is no charset |
|
268 * on the channel directly. |
|
269 * @param function aCallback |
|
270 * Callback that is called with the loaded cached content if available |
|
271 * or null if something failed while getting the cached content. |
|
272 */ |
|
273 loadFromCache: function NH_loadFromCache(aUrl, aCharset, aCallback) |
|
274 { |
|
275 let channel = NetUtil.newChannel(aUrl); |
|
276 |
|
277 // Ensure that we only read from the cache and not the server. |
|
278 channel.loadFlags = Ci.nsIRequest.LOAD_FROM_CACHE | |
|
279 Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE | |
|
280 Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY; |
|
281 |
|
282 NetUtil.asyncFetch(channel, (aInputStream, aStatusCode, aRequest) => { |
|
283 if (!components.isSuccessCode(aStatusCode)) { |
|
284 aCallback(null); |
|
285 return; |
|
286 } |
|
287 |
|
288 // Try to get the encoding from the channel. If there is none, then use |
|
289 // the passed assumed aCharset. |
|
290 let aChannel = aRequest.QueryInterface(Ci.nsIChannel); |
|
291 let contentCharset = aChannel.contentCharset || aCharset; |
|
292 |
|
293 // Read the content of the stream using contentCharset as encoding. |
|
294 aCallback(this.readAndConvertFromStream(aInputStream, contentCharset)); |
|
295 }); |
|
296 }, |
|
297 |
|
298 /** |
|
299 * Parse a raw Cookie header value. |
|
300 * |
|
301 * @param string aHeader |
|
302 * The raw Cookie header value. |
|
303 * @return array |
|
304 * Array holding an object for each cookie. Each object holds the |
|
305 * following properties: name and value. |
|
306 */ |
|
307 parseCookieHeader: function NH_parseCookieHeader(aHeader) |
|
308 { |
|
309 let cookies = aHeader.split(";"); |
|
310 let result = []; |
|
311 |
|
312 cookies.forEach(function(aCookie) { |
|
313 let equal = aCookie.indexOf("="); |
|
314 let name = aCookie.substr(0, equal); |
|
315 let value = aCookie.substr(equal + 1); |
|
316 result.push({name: unescape(name.trim()), |
|
317 value: unescape(value.trim())}); |
|
318 }); |
|
319 |
|
320 return result; |
|
321 }, |
|
322 |
|
323 /** |
|
324 * Parse a raw Set-Cookie header value. |
|
325 * |
|
326 * @param string aHeader |
|
327 * The raw Set-Cookie header value. |
|
328 * @return array |
|
329 * Array holding an object for each cookie. Each object holds the |
|
330 * following properties: name, value, secure (boolean), httpOnly |
|
331 * (boolean), path, domain and expires (ISO date string). |
|
332 */ |
|
333 parseSetCookieHeader: function NH_parseSetCookieHeader(aHeader) |
|
334 { |
|
335 let rawCookies = aHeader.split(/\r\n|\n|\r/); |
|
336 let cookies = []; |
|
337 |
|
338 rawCookies.forEach(function(aCookie) { |
|
339 let equal = aCookie.indexOf("="); |
|
340 let name = unescape(aCookie.substr(0, equal).trim()); |
|
341 let parts = aCookie.substr(equal + 1).split(";"); |
|
342 let value = unescape(parts.shift().trim()); |
|
343 |
|
344 let cookie = {name: name, value: value}; |
|
345 |
|
346 parts.forEach(function(aPart) { |
|
347 let part = aPart.trim(); |
|
348 if (part.toLowerCase() == "secure") { |
|
349 cookie.secure = true; |
|
350 } |
|
351 else if (part.toLowerCase() == "httponly") { |
|
352 cookie.httpOnly = true; |
|
353 } |
|
354 else if (part.indexOf("=") > -1) { |
|
355 let pair = part.split("="); |
|
356 pair[0] = pair[0].toLowerCase(); |
|
357 if (pair[0] == "path" || pair[0] == "domain") { |
|
358 cookie[pair[0]] = pair[1]; |
|
359 } |
|
360 else if (pair[0] == "expires") { |
|
361 try { |
|
362 pair[1] = pair[1].replace(/-/g, ' '); |
|
363 cookie.expires = new Date(pair[1]).toISOString(); |
|
364 } |
|
365 catch (ex) { } |
|
366 } |
|
367 } |
|
368 }); |
|
369 |
|
370 cookies.push(cookie); |
|
371 }); |
|
372 |
|
373 return cookies; |
|
374 }, |
|
375 |
|
376 // This is a list of all the mime category maps jviereck could find in the |
|
377 // firebug code base. |
|
378 mimeCategoryMap: { |
|
379 "text/plain": "txt", |
|
380 "text/html": "html", |
|
381 "text/xml": "xml", |
|
382 "text/xsl": "txt", |
|
383 "text/xul": "txt", |
|
384 "text/css": "css", |
|
385 "text/sgml": "txt", |
|
386 "text/rtf": "txt", |
|
387 "text/x-setext": "txt", |
|
388 "text/richtext": "txt", |
|
389 "text/javascript": "js", |
|
390 "text/jscript": "txt", |
|
391 "text/tab-separated-values": "txt", |
|
392 "text/rdf": "txt", |
|
393 "text/xif": "txt", |
|
394 "text/ecmascript": "js", |
|
395 "text/vnd.curl": "txt", |
|
396 "text/x-json": "json", |
|
397 "text/x-js": "txt", |
|
398 "text/js": "txt", |
|
399 "text/vbscript": "txt", |
|
400 "view-source": "txt", |
|
401 "view-fragment": "txt", |
|
402 "application/xml": "xml", |
|
403 "application/xhtml+xml": "xml", |
|
404 "application/atom+xml": "xml", |
|
405 "application/rss+xml": "xml", |
|
406 "application/vnd.mozilla.maybe.feed": "xml", |
|
407 "application/vnd.mozilla.xul+xml": "xml", |
|
408 "application/javascript": "js", |
|
409 "application/x-javascript": "js", |
|
410 "application/x-httpd-php": "txt", |
|
411 "application/rdf+xml": "xml", |
|
412 "application/ecmascript": "js", |
|
413 "application/http-index-format": "txt", |
|
414 "application/json": "json", |
|
415 "application/x-js": "txt", |
|
416 "multipart/mixed": "txt", |
|
417 "multipart/x-mixed-replace": "txt", |
|
418 "image/svg+xml": "svg", |
|
419 "application/octet-stream": "bin", |
|
420 "image/jpeg": "image", |
|
421 "image/jpg": "image", |
|
422 "image/gif": "image", |
|
423 "image/png": "image", |
|
424 "image/bmp": "image", |
|
425 "application/x-shockwave-flash": "flash", |
|
426 "video/x-flv": "flash", |
|
427 "audio/mpeg3": "media", |
|
428 "audio/x-mpeg-3": "media", |
|
429 "video/mpeg": "media", |
|
430 "video/x-mpeg": "media", |
|
431 "audio/ogg": "media", |
|
432 "application/ogg": "media", |
|
433 "application/x-ogg": "media", |
|
434 "application/x-midi": "media", |
|
435 "audio/midi": "media", |
|
436 "audio/x-mid": "media", |
|
437 "audio/x-midi": "media", |
|
438 "music/crescendo": "media", |
|
439 "audio/wav": "media", |
|
440 "audio/x-wav": "media", |
|
441 "text/json": "json", |
|
442 "application/x-json": "json", |
|
443 "application/json-rpc": "json", |
|
444 "application/x-web-app-manifest+json": "json", |
|
445 }, |
|
446 |
|
447 /** |
|
448 * Check if the given MIME type is a text-only MIME type. |
|
449 * |
|
450 * @param string aMimeType |
|
451 * @return boolean |
|
452 */ |
|
453 isTextMimeType: function NH_isTextMimeType(aMimeType) |
|
454 { |
|
455 if (aMimeType.indexOf("text/") == 0) { |
|
456 return true; |
|
457 } |
|
458 |
|
459 // XML and JSON often come with custom MIME types, so in addition to the |
|
460 // standard "application/xml" and "application/json", we also look for |
|
461 // variants like "application/x-bigcorp+xml". For JSON we allow "+json" and |
|
462 // "-json" as suffixes. |
|
463 if (/^application\/\w+(?:[\.-]\w+)*(?:\+xml|[-+]json)$/.test(aMimeType)) { |
|
464 return true; |
|
465 } |
|
466 |
|
467 let category = this.mimeCategoryMap[aMimeType] || null; |
|
468 switch (category) { |
|
469 case "txt": |
|
470 case "js": |
|
471 case "json": |
|
472 case "css": |
|
473 case "html": |
|
474 case "svg": |
|
475 case "xml": |
|
476 return true; |
|
477 |
|
478 default: |
|
479 return false; |
|
480 } |
|
481 }, |
|
482 }; |
|
483 |
|
484 for (let prop of Object.getOwnPropertyNames(NetworkHelper)) { |
|
485 exports[prop] = NetworkHelper[prop]; |
|
486 } |