зеркало из https://github.com/mozilla/gecko-dev.git
815 строки
26 KiB
JavaScript
815 строки
26 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} = 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.parentOuterWindowID == loadInfo.outerWindowID) {
|
|
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];
|
|
}
|