michael@0: /* vim:set ts=2 sw=2 sts=2 et: */ michael@0: /* michael@0: * Software License Agreement (BSD License) michael@0: * michael@0: * Copyright (c) 2007, Parakey Inc. michael@0: * All rights reserved. michael@0: * michael@0: * Redistribution and use of this software in source and binary forms, with or without modification, michael@0: * are permitted provided that the following conditions are met: michael@0: * michael@0: * * Redistributions of source code must retain the above michael@0: * copyright notice, this list of conditions and the michael@0: * following disclaimer. michael@0: * michael@0: * * Redistributions in binary form must reproduce the above michael@0: * copyright notice, this list of conditions and the michael@0: * following disclaimer in the documentation and/or other michael@0: * materials provided with the distribution. michael@0: * michael@0: * * Neither the name of Parakey Inc. nor the names of its michael@0: * contributors may be used to endorse or promote products michael@0: * derived from this software without specific prior michael@0: * written permission of Parakey Inc. michael@0: * michael@0: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR michael@0: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND michael@0: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR michael@0: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL michael@0: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER michael@0: * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT michael@0: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: */ michael@0: michael@0: /* michael@0: * Creator: michael@0: * Joe Hewitt michael@0: * Contributors michael@0: * John J. Barton (IBM Almaden) michael@0: * Jan Odvarko (Mozilla Corp.) michael@0: * Max Stepanov (Aptana Inc.) michael@0: * Rob Campbell (Mozilla Corp.) michael@0: * Hans Hillen (Paciello Group, Mozilla) michael@0: * Curtis Bartley (Mozilla Corp.) michael@0: * Mike Collins (IBM Almaden) michael@0: * Kevin Decker michael@0: * Mike Ratcliffe (Comartis AG) michael@0: * Hernan Rodríguez Colmeiro michael@0: * Austin Andrews michael@0: * Christoph Dorn michael@0: * Steven Roussey (AppCenter Inc, Network54) michael@0: * Mihai Sucan (Mozilla Corp.) michael@0: */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {components, Cc, Ci, Cu} = require("chrome"); michael@0: loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); michael@0: michael@0: /** michael@0: * Helper object for networking stuff. michael@0: * michael@0: * Most of the following functions have been taken from the Firebug source. They michael@0: * have been modified to match the Firefox coding rules. michael@0: */ michael@0: let NetworkHelper = { michael@0: /** michael@0: * Converts aText with a given aCharset to unicode. michael@0: * michael@0: * @param string aText michael@0: * Text to convert. michael@0: * @param string aCharset michael@0: * Charset to convert the text to. michael@0: * @returns string michael@0: * Converted text. michael@0: */ michael@0: convertToUnicode: function NH_convertToUnicode(aText, aCharset) michael@0: { michael@0: let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. michael@0: createInstance(Ci.nsIScriptableUnicodeConverter); michael@0: try { michael@0: conv.charset = aCharset || "UTF-8"; michael@0: return conv.ConvertToUnicode(aText); michael@0: } michael@0: catch (ex) { michael@0: return aText; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Reads all available bytes from aStream and converts them to aCharset. michael@0: * michael@0: * @param nsIInputStream aStream michael@0: * @param string aCharset michael@0: * @returns string michael@0: * UTF-16 encoded string based on the content of aStream and aCharset. michael@0: */ michael@0: readAndConvertFromStream: function NH_readAndConvertFromStream(aStream, aCharset) michael@0: { michael@0: let text = null; michael@0: try { michael@0: text = NetUtil.readInputStreamToString(aStream, aStream.available()) michael@0: return this.convertToUnicode(text, aCharset); michael@0: } michael@0: catch (err) { michael@0: return text; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Reads the posted text from aRequest. michael@0: * michael@0: * @param nsIHttpChannel aRequest michael@0: * @param string aCharset michael@0: * The content document charset, used when reading the POSTed data. michael@0: * @returns string or null michael@0: * Returns the posted string if it was possible to read from aRequest michael@0: * otherwise null. michael@0: */ michael@0: readPostTextFromRequest: function NH_readPostTextFromRequest(aRequest, aCharset) michael@0: { michael@0: if (aRequest instanceof Ci.nsIUploadChannel) { michael@0: let iStream = aRequest.uploadStream; michael@0: michael@0: let isSeekableStream = false; michael@0: if (iStream instanceof Ci.nsISeekableStream) { michael@0: isSeekableStream = true; michael@0: } michael@0: michael@0: let prevOffset; michael@0: if (isSeekableStream) { michael@0: prevOffset = iStream.tell(); michael@0: iStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); michael@0: } michael@0: michael@0: // Read data from the stream. michael@0: let text = this.readAndConvertFromStream(iStream, aCharset); michael@0: michael@0: // Seek locks the file, so seek to the beginning only if necko hasn't michael@0: // read it yet, since necko doesn't seek to 0 before reading (at lest michael@0: // not till 459384 is fixed). michael@0: if (isSeekableStream && prevOffset == 0) { michael@0: iStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); michael@0: } michael@0: return text; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Reads the posted text from the page's cache. michael@0: * michael@0: * @param nsIDocShell aDocShell michael@0: * @param string aCharset michael@0: * @returns string or null michael@0: * Returns the posted string if it was possible to read from michael@0: * aDocShell otherwise null. michael@0: */ michael@0: readPostTextFromPage: function NH_readPostTextFromPage(aDocShell, aCharset) michael@0: { michael@0: let webNav = aDocShell.QueryInterface(Ci.nsIWebNavigation); michael@0: return this.readPostTextFromPageViaWebNav(webNav, aCharset); michael@0: }, michael@0: michael@0: /** michael@0: * Reads the posted text from the page's cache, given an nsIWebNavigation michael@0: * object. michael@0: * michael@0: * @param nsIWebNavigation aWebNav michael@0: * @param string aCharset michael@0: * @returns string or null michael@0: * Returns the posted string if it was possible to read from michael@0: * aWebNav, otherwise null. michael@0: */ michael@0: readPostTextFromPageViaWebNav: michael@0: function NH_readPostTextFromPageViaWebNav(aWebNav, aCharset) michael@0: { michael@0: if (aWebNav instanceof Ci.nsIWebPageDescriptor) { michael@0: let descriptor = aWebNav.currentDescriptor; michael@0: michael@0: if (descriptor instanceof Ci.nsISHEntry && descriptor.postData && michael@0: descriptor instanceof Ci.nsISeekableStream) { michael@0: descriptor.seek(NS_SEEK_SET, 0); michael@0: michael@0: return this.readAndConvertFromStream(descriptor, aCharset); michael@0: } michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Gets the web appId that is associated with aRequest. michael@0: * michael@0: * @param nsIHttpChannel aRequest michael@0: * @returns number|null michael@0: * The appId for the given request, if available. michael@0: */ michael@0: getAppIdForRequest: function NH_getAppIdForRequest(aRequest) michael@0: { michael@0: try { michael@0: return this.getRequestLoadContext(aRequest).appId; michael@0: } catch (ex) { michael@0: // request loadContent is not always available. michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Gets the topFrameElement that is associated with aRequest. michael@0: * michael@0: * @param nsIHttpChannel aRequest michael@0: * @returns nsIDOMElement|null michael@0: * The top frame element for the given request, if available. michael@0: */ michael@0: getTopFrameForRequest: function NH_getTopFrameForRequest(aRequest) michael@0: { michael@0: try { michael@0: return this.getRequestLoadContext(aRequest).topFrameElement; michael@0: } catch (ex) { michael@0: // request loadContent is not always available. michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Gets the nsIDOMWindow that is associated with aRequest. michael@0: * michael@0: * @param nsIHttpChannel aRequest michael@0: * @returns nsIDOMWindow or null michael@0: */ michael@0: getWindowForRequest: function NH_getWindowForRequest(aRequest) michael@0: { michael@0: try { michael@0: return this.getRequestLoadContext(aRequest).associatedWindow; michael@0: } catch (ex) { michael@0: // TODO: bug 802246 - getWindowForRequest() throws on b2g: there is no michael@0: // associatedWindow property. michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Gets the nsILoadContext that is associated with aRequest. michael@0: * michael@0: * @param nsIHttpChannel aRequest michael@0: * @returns nsILoadContext or null michael@0: */ michael@0: getRequestLoadContext: function NH_getRequestLoadContext(aRequest) michael@0: { michael@0: try { michael@0: return aRequest.notificationCallbacks.getInterface(Ci.nsILoadContext); michael@0: } catch (ex) { } michael@0: michael@0: try { michael@0: return aRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext); michael@0: } catch (ex) { } michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Loads the content of aUrl from the cache. michael@0: * michael@0: * @param string aUrl michael@0: * URL to load the cached content for. michael@0: * @param string aCharset michael@0: * Assumed charset of the cached content. Used if there is no charset michael@0: * on the channel directly. michael@0: * @param function aCallback michael@0: * Callback that is called with the loaded cached content if available michael@0: * or null if something failed while getting the cached content. michael@0: */ michael@0: loadFromCache: function NH_loadFromCache(aUrl, aCharset, aCallback) michael@0: { michael@0: let channel = NetUtil.newChannel(aUrl); michael@0: michael@0: // Ensure that we only read from the cache and not the server. michael@0: channel.loadFlags = Ci.nsIRequest.LOAD_FROM_CACHE | michael@0: Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE | michael@0: Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY; michael@0: michael@0: NetUtil.asyncFetch(channel, (aInputStream, aStatusCode, aRequest) => { michael@0: if (!components.isSuccessCode(aStatusCode)) { michael@0: aCallback(null); michael@0: return; michael@0: } michael@0: michael@0: // Try to get the encoding from the channel. If there is none, then use michael@0: // the passed assumed aCharset. michael@0: let aChannel = aRequest.QueryInterface(Ci.nsIChannel); michael@0: let contentCharset = aChannel.contentCharset || aCharset; michael@0: michael@0: // Read the content of the stream using contentCharset as encoding. michael@0: aCallback(this.readAndConvertFromStream(aInputStream, contentCharset)); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Parse a raw Cookie header value. michael@0: * michael@0: * @param string aHeader michael@0: * The raw Cookie header value. michael@0: * @return array michael@0: * Array holding an object for each cookie. Each object holds the michael@0: * following properties: name and value. michael@0: */ michael@0: parseCookieHeader: function NH_parseCookieHeader(aHeader) michael@0: { michael@0: let cookies = aHeader.split(";"); michael@0: let result = []; michael@0: michael@0: cookies.forEach(function(aCookie) { michael@0: let equal = aCookie.indexOf("="); michael@0: let name = aCookie.substr(0, equal); michael@0: let value = aCookie.substr(equal + 1); michael@0: result.push({name: unescape(name.trim()), michael@0: value: unescape(value.trim())}); michael@0: }); michael@0: michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * Parse a raw Set-Cookie header value. michael@0: * michael@0: * @param string aHeader michael@0: * The raw Set-Cookie header value. michael@0: * @return array michael@0: * Array holding an object for each cookie. Each object holds the michael@0: * following properties: name, value, secure (boolean), httpOnly michael@0: * (boolean), path, domain and expires (ISO date string). michael@0: */ michael@0: parseSetCookieHeader: function NH_parseSetCookieHeader(aHeader) michael@0: { michael@0: let rawCookies = aHeader.split(/\r\n|\n|\r/); michael@0: let cookies = []; michael@0: michael@0: rawCookies.forEach(function(aCookie) { michael@0: let equal = aCookie.indexOf("="); michael@0: let name = unescape(aCookie.substr(0, equal).trim()); michael@0: let parts = aCookie.substr(equal + 1).split(";"); michael@0: let value = unescape(parts.shift().trim()); michael@0: michael@0: let cookie = {name: name, value: value}; michael@0: michael@0: parts.forEach(function(aPart) { michael@0: let part = aPart.trim(); michael@0: if (part.toLowerCase() == "secure") { michael@0: cookie.secure = true; michael@0: } michael@0: else if (part.toLowerCase() == "httponly") { michael@0: cookie.httpOnly = true; michael@0: } michael@0: else if (part.indexOf("=") > -1) { michael@0: let pair = part.split("="); michael@0: pair[0] = pair[0].toLowerCase(); michael@0: if (pair[0] == "path" || pair[0] == "domain") { michael@0: cookie[pair[0]] = pair[1]; michael@0: } michael@0: else if (pair[0] == "expires") { michael@0: try { michael@0: pair[1] = pair[1].replace(/-/g, ' '); michael@0: cookie.expires = new Date(pair[1]).toISOString(); michael@0: } michael@0: catch (ex) { } michael@0: } michael@0: } michael@0: }); michael@0: michael@0: cookies.push(cookie); michael@0: }); michael@0: michael@0: return cookies; michael@0: }, michael@0: michael@0: // This is a list of all the mime category maps jviereck could find in the michael@0: // firebug code base. michael@0: mimeCategoryMap: { michael@0: "text/plain": "txt", michael@0: "text/html": "html", michael@0: "text/xml": "xml", michael@0: "text/xsl": "txt", michael@0: "text/xul": "txt", michael@0: "text/css": "css", michael@0: "text/sgml": "txt", michael@0: "text/rtf": "txt", michael@0: "text/x-setext": "txt", michael@0: "text/richtext": "txt", michael@0: "text/javascript": "js", michael@0: "text/jscript": "txt", michael@0: "text/tab-separated-values": "txt", michael@0: "text/rdf": "txt", michael@0: "text/xif": "txt", michael@0: "text/ecmascript": "js", michael@0: "text/vnd.curl": "txt", michael@0: "text/x-json": "json", michael@0: "text/x-js": "txt", michael@0: "text/js": "txt", michael@0: "text/vbscript": "txt", michael@0: "view-source": "txt", michael@0: "view-fragment": "txt", michael@0: "application/xml": "xml", michael@0: "application/xhtml+xml": "xml", michael@0: "application/atom+xml": "xml", michael@0: "application/rss+xml": "xml", michael@0: "application/vnd.mozilla.maybe.feed": "xml", michael@0: "application/vnd.mozilla.xul+xml": "xml", michael@0: "application/javascript": "js", michael@0: "application/x-javascript": "js", michael@0: "application/x-httpd-php": "txt", michael@0: "application/rdf+xml": "xml", michael@0: "application/ecmascript": "js", michael@0: "application/http-index-format": "txt", michael@0: "application/json": "json", michael@0: "application/x-js": "txt", michael@0: "multipart/mixed": "txt", michael@0: "multipart/x-mixed-replace": "txt", michael@0: "image/svg+xml": "svg", michael@0: "application/octet-stream": "bin", michael@0: "image/jpeg": "image", michael@0: "image/jpg": "image", michael@0: "image/gif": "image", michael@0: "image/png": "image", michael@0: "image/bmp": "image", michael@0: "application/x-shockwave-flash": "flash", michael@0: "video/x-flv": "flash", michael@0: "audio/mpeg3": "media", michael@0: "audio/x-mpeg-3": "media", michael@0: "video/mpeg": "media", michael@0: "video/x-mpeg": "media", michael@0: "audio/ogg": "media", michael@0: "application/ogg": "media", michael@0: "application/x-ogg": "media", michael@0: "application/x-midi": "media", michael@0: "audio/midi": "media", michael@0: "audio/x-mid": "media", michael@0: "audio/x-midi": "media", michael@0: "music/crescendo": "media", michael@0: "audio/wav": "media", michael@0: "audio/x-wav": "media", michael@0: "text/json": "json", michael@0: "application/x-json": "json", michael@0: "application/json-rpc": "json", michael@0: "application/x-web-app-manifest+json": "json", michael@0: }, michael@0: michael@0: /** michael@0: * Check if the given MIME type is a text-only MIME type. michael@0: * michael@0: * @param string aMimeType michael@0: * @return boolean michael@0: */ michael@0: isTextMimeType: function NH_isTextMimeType(aMimeType) michael@0: { michael@0: if (aMimeType.indexOf("text/") == 0) { michael@0: return true; michael@0: } michael@0: michael@0: // XML and JSON often come with custom MIME types, so in addition to the michael@0: // standard "application/xml" and "application/json", we also look for michael@0: // variants like "application/x-bigcorp+xml". For JSON we allow "+json" and michael@0: // "-json" as suffixes. michael@0: if (/^application\/\w+(?:[\.-]\w+)*(?:\+xml|[-+]json)$/.test(aMimeType)) { michael@0: return true; michael@0: } michael@0: michael@0: let category = this.mimeCategoryMap[aMimeType] || null; michael@0: switch (category) { michael@0: case "txt": michael@0: case "js": michael@0: case "json": michael@0: case "css": michael@0: case "html": michael@0: case "svg": michael@0: case "xml": michael@0: return true; michael@0: michael@0: default: michael@0: return false; michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: for (let prop of Object.getOwnPropertyNames(NetworkHelper)) { michael@0: exports[prop] = NetworkHelper[prop]; michael@0: }