gecko/toolkit/devtools/webconsole/network-helper.js

487 lines
15 KiB
JavaScript

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