summaryrefslogtreecommitdiffstats
path: root/devtools/shared/webconsole/network-helper.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/webconsole/network-helper.js')
-rw-r--r--devtools/shared/webconsole/network-helper.js814
1 files changed, 814 insertions, 0 deletions
diff --git a/devtools/shared/webconsole/network-helper.js b/devtools/shared/webconsole/network-helper.js
new file mode 100644
index 000000000..af6a2e55b
--- /dev/null
+++ b/devtools/shared/webconsole/network-helper.js
@@ -0,0 +1,814 @@
+/* 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} = require("chrome");
+loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const Services = require("Services");
+
+// The cache used in the `nsIURL` function.
+const gNSURLStore = new Map();
+
+/**
+ * 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.
+ */
+var NetworkHelper = {
+ /**
+ * Converts text with a given charset to unicode.
+ *
+ * @param string text
+ * Text to convert.
+ * @param string charset
+ * Charset to convert the text to.
+ * @returns string
+ * Converted text.
+ */
+ convertToUnicode: function (text, charset) {
+ let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ try {
+ conv.charset = charset || "UTF-8";
+ return conv.ConvertToUnicode(text);
+ } catch (ex) {
+ return text;
+ }
+ },
+
+ /**
+ * Reads all available bytes from stream and converts them to charset.
+ *
+ * @param nsIInputStream stream
+ * @param string charset
+ * @returns string
+ * UTF-16 encoded string based on the content of stream and charset.
+ */
+ readAndConvertFromStream: function (stream, charset) {
+ let text = null;
+ try {
+ text = NetUtil.readInputStreamToString(stream, stream.available());
+ return this.convertToUnicode(text, charset);
+ } catch (err) {
+ return text;
+ }
+ },
+
+ /**
+ * Reads the posted text from request.
+ *
+ * @param nsIHttpChannel request
+ * @param string charset
+ * 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 request
+ * otherwise null.
+ */
+ readPostTextFromRequest: function (request, charset) {
+ if (request instanceof Ci.nsIUploadChannel) {
+ let iStream = request.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, charset);
+
+ // 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 docShell
+ * @param string charset
+ * @returns string or null
+ * Returns the posted string if it was possible to read from
+ * docShell otherwise null.
+ */
+ readPostTextFromPage: function (docShell, charset) {
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ return this.readPostTextFromPageViaWebNav(webNav, charset);
+ },
+
+ /**
+ * Reads the posted text from the page's cache, given an nsIWebNavigation
+ * object.
+ *
+ * @param nsIWebNavigation webNav
+ * @param string charset
+ * @returns string or null
+ * Returns the posted string if it was possible to read from
+ * webNav, otherwise null.
+ */
+ readPostTextFromPageViaWebNav: function (webNav, charset) {
+ if (webNav instanceof Ci.nsIWebPageDescriptor) {
+ let descriptor = webNav.currentDescriptor;
+
+ if (descriptor instanceof Ci.nsISHEntry && descriptor.postData &&
+ descriptor instanceof Ci.nsISeekableStream) {
+ descriptor.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+
+ return this.readAndConvertFromStream(descriptor, charset);
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Gets the web appId that is associated with request.
+ *
+ * @param nsIHttpChannel request
+ * @returns number|null
+ * The appId for the given request, if available.
+ */
+ getAppIdForRequest: function (request) {
+ try {
+ return this.getRequestLoadContext(request).appId;
+ } catch (ex) {
+ // request loadContent is not always available.
+ }
+ return null;
+ },
+
+ /**
+ * Gets the topFrameElement that is associated with request. This
+ * works in single-process and multiprocess contexts. It may cross
+ * the content/chrome boundary.
+ *
+ * @param nsIHttpChannel request
+ * @returns nsIDOMElement|null
+ * The top frame element for the given request.
+ */
+ getTopFrameForRequest: function (request) {
+ try {
+ return this.getRequestLoadContext(request).topFrameElement;
+ } catch (ex) {
+ // request loadContent is not always available.
+ }
+ return null;
+ },
+
+ /**
+ * Gets the nsIDOMWindow that is associated with request.
+ *
+ * @param nsIHttpChannel request
+ * @returns nsIDOMWindow or null
+ */
+ getWindowForRequest: function (request) {
+ try {
+ return this.getRequestLoadContext(request).associatedWindow;
+ } catch (ex) {
+ // TODO: bug 802246 - getWindowForRequest() throws on b2g: there is no
+ // associatedWindow property.
+ }
+ return null;
+ },
+
+ /**
+ * Gets the nsILoadContext that is associated with request.
+ *
+ * @param nsIHttpChannel request
+ * @returns nsILoadContext or null
+ */
+ getRequestLoadContext: function (request) {
+ try {
+ return request.notificationCallbacks.getInterface(Ci.nsILoadContext);
+ } catch (ex) {
+ // Ignore.
+ }
+
+ try {
+ return request.loadGroup.notificationCallbacks
+ .getInterface(Ci.nsILoadContext);
+ } catch (ex) {
+ // Ignore.
+ }
+
+ return null;
+ },
+
+ /**
+ * Determines whether the request has been made for the top level document.
+ *
+ * @param nsIHttpChannel request
+ * @returns Boolean True if the request represents the top level document.
+ */
+ isTopLevelLoad: function (request) {
+ if (request instanceof Ci.nsIChannel) {
+ let loadInfo = request.loadInfo;
+ if (loadInfo && loadInfo.isTopLevelLoad) {
+ return (request.loadFlags & Ci.nsIChannel.LOAD_DOCUMENT_URI);
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Loads the content of url from the cache.
+ *
+ * @param string url
+ * URL to load the cached content for.
+ * @param string charset
+ * Assumed charset of the cached content. Used if there is no charset
+ * on the channel directly.
+ * @param function callback
+ * Callback that is called with the loaded cached content if available
+ * or null if something failed while getting the cached content.
+ */
+ loadFromCache: function (url, charset, callback) {
+ let channel = NetUtil.newChannel({uri: url,
+ loadUsingSystemPrincipal: true});
+
+ // 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,
+ (inputStream, statusCode, request) => {
+ if (!components.isSuccessCode(statusCode)) {
+ callback(null);
+ return;
+ }
+
+ // Try to get the encoding from the channel. If there is none, then use
+ // the passed assumed charset.
+ let requestChannel = request.QueryInterface(Ci.nsIChannel);
+ let contentCharset = requestChannel.contentCharset || charset;
+
+ // Read the content of the stream using contentCharset as encoding.
+ callback(this.readAndConvertFromStream(inputStream, contentCharset));
+ });
+ },
+
+ /**
+ * Parse a raw Cookie header value.
+ *
+ * @param string header
+ * 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 (header) {
+ let cookies = header.split(";");
+ let result = [];
+
+ cookies.forEach(function (cookie) {
+ let equal = cookie.indexOf("=");
+ let name = cookie.substr(0, equal);
+ let value = cookie.substr(equal + 1);
+ result.push({name: unescape(name.trim()),
+ value: unescape(value.trim())});
+ });
+
+ return result;
+ },
+
+ /**
+ * Parse a raw Set-Cookie header value.
+ *
+ * @param string header
+ * 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 (header) {
+ let rawCookies = header.split(/\r\n|\n|\r/);
+ let cookies = [];
+
+ rawCookies.forEach(function (cookie) {
+ let equal = cookie.indexOf("=");
+ let name = unescape(cookie.substr(0, equal).trim());
+ let parts = cookie.substr(equal + 1).split(";");
+ let value = unescape(parts.shift().trim());
+
+ cookie = {name: name, value: value};
+
+ parts.forEach(function (part) {
+ part = part.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) {
+ // Ignore.
+ }
+ }
+ }
+ });
+
+ 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",
+ "application/x-mpegurl": "txt",
+ "application/vnd.apple.mpegurl": "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",
+ "video/vnd.mpeg.dash.mpd": "xml",
+ "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",
+ "application/manifest+json": "json"
+ },
+
+ /**
+ * Check if the given MIME type is a text-only MIME type.
+ *
+ * @param string mimeType
+ * @return boolean
+ */
+ isTextMimeType: function (mimeType) {
+ if (mimeType.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(mimeType)) {
+ return true;
+ }
+
+ let category = this.mimeCategoryMap[mimeType] || null;
+ switch (category) {
+ case "txt":
+ case "js":
+ case "json":
+ case "css":
+ case "html":
+ case "svg":
+ case "xml":
+ return true;
+
+ default:
+ return false;
+ }
+ },
+
+ /**
+ * Takes a securityInfo object of nsIRequest, the nsIRequest itself and
+ * extracts security information from them.
+ *
+ * @param object securityInfo
+ * The securityInfo object of a request. If null channel is assumed
+ * to be insecure.
+ * @param object httpActivity
+ * The httpActivity object for the request with at least members
+ * { private, hostname }.
+ *
+ * @return object
+ * Returns an object containing following members:
+ * - state: The security of the connection used to fetch this
+ * request. Has one of following string values:
+ * * "insecure": the connection was not secure (only http)
+ * * "weak": the connection has minor security issues
+ * * "broken": secure connection failed (e.g. expired cert)
+ * * "secure": the connection was properly secured.
+ * If state == broken:
+ * - errorMessage: full error message from
+ * nsITransportSecurityInfo.
+ * If state == secure:
+ * - protocolVersion: one of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3.
+ * - cipherSuite: the cipher suite used in this connection.
+ * - cert: information about certificate used in this connection.
+ * See parseCertificateInfo for the contents.
+ * - hsts: true if host uses Strict Transport Security,
+ * false otherwise
+ * - hpkp: true if host uses Public Key Pinning, false otherwise
+ * If state == weak: Same as state == secure and
+ * - weaknessReasons: list of reasons that cause the request to be
+ * considered weak. See getReasonsForWeakness.
+ */
+ parseSecurityInfo: function (securityInfo, httpActivity) {
+ const info = {
+ state: "insecure",
+ };
+
+ // The request did not contain any security info.
+ if (!securityInfo) {
+ return info;
+ }
+
+ /**
+ * Different scenarios to consider here and how they are handled:
+ * - request is HTTP, the connection is not secure
+ * => securityInfo is null
+ * => state === "insecure"
+ *
+ * - request is HTTPS, the connection is secure
+ * => .securityState has STATE_IS_SECURE flag
+ * => state === "secure"
+ *
+ * - request is HTTPS, the connection has security issues
+ * => .securityState has STATE_IS_INSECURE flag
+ * => .errorCode is an NSS error code.
+ * => state === "broken"
+ *
+ * - request is HTTPS, the connection was terminated before the security
+ * could be validated
+ * => .securityState has STATE_IS_INSECURE flag
+ * => .errorCode is NOT an NSS error code.
+ * => .errorMessage is not available.
+ * => state === "insecure"
+ *
+ * - request is HTTPS but it uses a weak cipher or old protocol, see
+ * http://hg.mozilla.org/mozilla-central/annotate/def6ed9d1c1a/
+ * security/manager/ssl/nsNSSCallbacks.cpp#l1233
+ * - request is mixed content (which makes no sense whatsoever)
+ * => .securityState has STATE_IS_BROKEN flag
+ * => .errorCode is NOT an NSS error code
+ * => .errorMessage is not available
+ * => state === "weak"
+ */
+
+ securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+ securityInfo.QueryInterface(Ci.nsISSLStatusProvider);
+
+ const wpl = Ci.nsIWebProgressListener;
+ const NSSErrorsService = Cc["@mozilla.org/nss_errors_service;1"]
+ .getService(Ci.nsINSSErrorsService);
+ const SSLStatus = securityInfo.SSLStatus;
+ if (!NSSErrorsService.isNSSErrorCode(securityInfo.errorCode)) {
+ const state = securityInfo.securityState;
+
+ let uri = null;
+ if (httpActivity.channel && httpActivity.channel.URI) {
+ uri = httpActivity.channel.URI;
+ }
+ if (uri && !uri.schemeIs("https") && !uri.schemeIs("wss")) {
+ // it is not enough to look at the transport security info -
+ // schemes other than https and wss are subject to
+ // downgrade/etc at the scheme level and should always be
+ // considered insecure
+ info.state = "insecure";
+ } else if (state & wpl.STATE_IS_SECURE) {
+ // The connection is secure if the scheme is sufficient
+ info.state = "secure";
+ } else if (state & wpl.STATE_IS_BROKEN) {
+ // The connection is not secure, there was no error but there's some
+ // minor security issues.
+ info.state = "weak";
+ info.weaknessReasons = this.getReasonsForWeakness(state);
+ } else if (state & wpl.STATE_IS_INSECURE) {
+ // This was most likely an https request that was aborted before
+ // validation. Return info as info.state = insecure.
+ return info;
+ } else {
+ DevToolsUtils.reportException("NetworkHelper.parseSecurityInfo",
+ "Security state " + state + " has no known STATE_IS_* flags.");
+ return info;
+ }
+
+ // Cipher suite.
+ info.cipherSuite = SSLStatus.cipherName;
+
+ // Protocol version.
+ info.protocolVersion =
+ this.formatSecurityProtocol(SSLStatus.protocolVersion);
+
+ // Certificate.
+ info.cert = this.parseCertificateInfo(SSLStatus.serverCert);
+
+ // HSTS and HPKP if available.
+ if (httpActivity.hostname) {
+ const sss = Cc["@mozilla.org/ssservice;1"]
+ .getService(Ci.nsISiteSecurityService);
+
+ // SiteSecurityService uses different storage if the channel is
+ // private. Thus we must give isSecureHost correct flags or we
+ // might get incorrect results.
+ let flags = (httpActivity.private) ?
+ Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
+
+ let host = httpActivity.hostname;
+
+ info.hsts = sss.isSecureHost(sss.HEADER_HSTS, host, flags);
+ info.hpkp = sss.isSecureHost(sss.HEADER_HPKP, host, flags);
+ } else {
+ DevToolsUtils.reportException("NetworkHelper.parseSecurityInfo",
+ "Could not get HSTS/HPKP status as hostname is not available.");
+ info.hsts = false;
+ info.hpkp = false;
+ }
+ } else {
+ // The connection failed.
+ info.state = "broken";
+ info.errorMessage = securityInfo.errorMessage;
+ }
+
+ return info;
+ },
+
+ /**
+ * Takes an nsIX509Cert and returns an object with certificate information.
+ *
+ * @param nsIX509Cert cert
+ * The certificate to extract the information from.
+ * @return object
+ * An object with following format:
+ * {
+ * subject: { commonName, organization, organizationalUnit },
+ * issuer: { commonName, organization, organizationUnit },
+ * validity: { start, end },
+ * fingerprint: { sha1, sha256 }
+ * }
+ */
+ parseCertificateInfo: function (cert) {
+ let info = {};
+ if (cert) {
+ info.subject = {
+ commonName: cert.commonName,
+ organization: cert.organization,
+ organizationalUnit: cert.organizationalUnit,
+ };
+
+ info.issuer = {
+ commonName: cert.issuerCommonName,
+ organization: cert.issuerOrganization,
+ organizationUnit: cert.issuerOrganizationUnit,
+ };
+
+ info.validity = {
+ start: cert.validity.notBeforeLocalDay,
+ end: cert.validity.notAfterLocalDay,
+ };
+
+ info.fingerprint = {
+ sha1: cert.sha1Fingerprint,
+ sha256: cert.sha256Fingerprint,
+ };
+ } else {
+ DevToolsUtils.reportException("NetworkHelper.parseCertificateInfo",
+ "Secure connection established without certificate.");
+ }
+
+ return info;
+ },
+
+ /**
+ * Takes protocolVersion of SSLStatus object and returns human readable
+ * description.
+ *
+ * @param Number version
+ * One of nsISSLStatus version constants.
+ * @return string
+ * One of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 if @param version
+ * is valid, Unknown otherwise.
+ */
+ formatSecurityProtocol: function (version) {
+ switch (version) {
+ case Ci.nsISSLStatus.TLS_VERSION_1:
+ return "TLSv1";
+ case Ci.nsISSLStatus.TLS_VERSION_1_1:
+ return "TLSv1.1";
+ case Ci.nsISSLStatus.TLS_VERSION_1_2:
+ return "TLSv1.2";
+ case Ci.nsISSLStatus.TLS_VERSION_1_3:
+ return "TLSv1.3";
+ default:
+ DevToolsUtils.reportException("NetworkHelper.formatSecurityProtocol",
+ "protocolVersion " + version + " is unknown.");
+ return "Unknown";
+ }
+ },
+
+ /**
+ * Takes the securityState bitfield and returns reasons for weak connection
+ * as an array of strings.
+ *
+ * @param Number state
+ * nsITransportSecurityInfo.securityState.
+ *
+ * @return Array[String]
+ * List of weakness reasons. A subset of { cipher } where
+ * * cipher: The cipher suite is consireded to be weak (RC4).
+ */
+ getReasonsForWeakness: function (state) {
+ const wpl = Ci.nsIWebProgressListener;
+
+ // If there's non-fatal security issues the request has STATE_IS_BROKEN
+ // flag set. See http://hg.mozilla.org/mozilla-central/file/44344099d119
+ // /security/manager/ssl/nsNSSCallbacks.cpp#l1233
+ let reasons = [];
+
+ if (state & wpl.STATE_IS_BROKEN) {
+ let isCipher = state & wpl.STATE_USES_WEAK_CRYPTO;
+
+ if (isCipher) {
+ reasons.push("cipher");
+ }
+
+ if (!isCipher) {
+ DevToolsUtils.reportException("NetworkHelper.getReasonsForWeakness",
+ "STATE_IS_BROKEN without a known reason. Full state was: " + state);
+ }
+ }
+
+ return reasons;
+ },
+
+ /**
+ * Parse a url's query string into its components
+ *
+ * @param string queryString
+ * The query part of a url
+ * @return array
+ * Array of query params {name, value}
+ */
+ parseQueryString: function (queryString) {
+ // Make sure there's at least one param available.
+ // Be careful here, params don't necessarily need to have values, so
+ // no need to verify the existence of a "=".
+ if (!queryString) {
+ return null;
+ }
+
+ // Turn the params string into an array containing { name: value } tuples.
+ let paramsArray = queryString.replace(/^[?&]/, "").split("&").map(e => {
+ let param = e.split("=");
+ return {
+ name: param[0] ?
+ NetworkHelper.convertToUnicode(unescape(param[0])) : "",
+ value: param[1] ?
+ NetworkHelper.convertToUnicode(unescape(param[1])) : ""
+ };
+ });
+
+ return paramsArray;
+ },
+
+ /**
+ * Helper for getting an nsIURL instance out of a string.
+ */
+ nsIURL: function (url, store = gNSURLStore) {
+ if (store.has(url)) {
+ return store.get(url);
+ }
+
+ let uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
+ store.set(url, uri);
+ return uri;
+ }
+};
+
+for (let prop of Object.getOwnPropertyNames(NetworkHelper)) {
+ exports[prop] = NetworkHelper[prop];
+}