mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	The window property is never set on NetworkObserver, remove the related codepath. Also remove unused helpers. Differential Revision: https://phabricator.services.mozilla.com/D209523
		
			
				
	
	
		
			860 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			860 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
/*
 | 
						|
 * 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.)
 | 
						|
 */
 | 
						|
 | 
						|
const lazy = {};
 | 
						|
 | 
						|
ChromeUtils.defineESModuleGetters(
 | 
						|
  lazy,
 | 
						|
  {
 | 
						|
    DevToolsInfaillibleUtils:
 | 
						|
      "resource://devtools/shared/DevToolsInfaillibleUtils.sys.mjs",
 | 
						|
 | 
						|
    NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
 | 
						|
  },
 | 
						|
  { global: "contextual" }
 | 
						|
);
 | 
						|
 | 
						|
// It would make sense to put this in the above
 | 
						|
// ChromeUtils.defineESModuleGetters, but that doesn't seem to work.
 | 
						|
ChromeUtils.defineLazyGetter(lazy, "certDecoder", () => {
 | 
						|
  const { parse, pemToDER } = ChromeUtils.importESModule(
 | 
						|
    "chrome://global/content/certviewer/certDecoder.mjs",
 | 
						|
    { global: "contextual" }
 | 
						|
  );
 | 
						|
  return { parse, pemToDER };
 | 
						|
});
 | 
						|
 | 
						|
// "Lax", "Strict" and "None" are special values of the SameSite cookie
 | 
						|
// attribute that should not be translated.
 | 
						|
const COOKIE_SAMESITE = {
 | 
						|
  LAX: "Lax",
 | 
						|
  STRICT: "Strict",
 | 
						|
  NONE: "None",
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * 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.
 | 
						|
 */
 | 
						|
export 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(text, charset) {
 | 
						|
    // FIXME: We need to throw when text can't be converted e.g. the contents of
 | 
						|
    // an image. Until we have a way to do so with TextEncoder and TextDecoder
 | 
						|
    // we need to use nsIScriptableUnicodeConverter instead.
 | 
						|
    const 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(stream, charset) {
 | 
						|
    let text = null;
 | 
						|
    try {
 | 
						|
      text = lazy.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(request, charset) {
 | 
						|
    if (request instanceof Ci.nsIUploadChannel) {
 | 
						|
      const 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.
 | 
						|
      const 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;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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 Element|null
 | 
						|
   *          The top frame element for the given request.
 | 
						|
   */
 | 
						|
  getTopFrameForRequest(request) {
 | 
						|
    try {
 | 
						|
      return this.getRequestLoadContext(request).topFrameElement;
 | 
						|
    } catch (ex) {
 | 
						|
      // request loadContext is not always available.
 | 
						|
    }
 | 
						|
    return null;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the nsIDOMWindow that is associated with request.
 | 
						|
   *
 | 
						|
   * @param nsIHttpChannel request
 | 
						|
   * @returns nsIDOMWindow or null
 | 
						|
   */
 | 
						|
  getWindowForRequest(request) {
 | 
						|
    try {
 | 
						|
      return this.getRequestLoadContext(request).associatedWindow;
 | 
						|
    } catch (ex) {
 | 
						|
      // On some request notificationCallbacks and loadGroup are both null,
 | 
						|
      // so that we can't retrieve any nsILoadContext interface.
 | 
						|
      // Fallback on nsILoadInfo to try to retrieve the request's window.
 | 
						|
      // (this is covered by test_network_get.html and its CSS request)
 | 
						|
      return request.loadInfo.loadingDocument?.defaultView;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the nsILoadContext that is associated with request.
 | 
						|
   *
 | 
						|
   * @param nsIHttpChannel request
 | 
						|
   * @returns nsILoadContext or null
 | 
						|
   */
 | 
						|
  getRequestLoadContext(request) {
 | 
						|
    try {
 | 
						|
      if (request.loadInfo.workerAssociatedBrowsingContext) {
 | 
						|
        return request.loadInfo.workerAssociatedBrowsingContext;
 | 
						|
      }
 | 
						|
    } catch (ex) {
 | 
						|
      // Ignore.
 | 
						|
    }
 | 
						|
    try {
 | 
						|
      return request.notificationCallbacks.getInterface(Ci.nsILoadContext);
 | 
						|
    } catch (ex) {
 | 
						|
      // Ignore.
 | 
						|
    }
 | 
						|
 | 
						|
    try {
 | 
						|
      return request.loadGroup.notificationCallbacks.getInterface(
 | 
						|
        Ci.nsILoadContext
 | 
						|
      );
 | 
						|
    } catch (ex) {
 | 
						|
      // Ignore.
 | 
						|
    }
 | 
						|
 | 
						|
    return null;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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(url, charset, callback) {
 | 
						|
    const channel = lazy.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;
 | 
						|
 | 
						|
    lazy.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.
 | 
						|
      const requestChannel = request.QueryInterface(Ci.nsIChannel);
 | 
						|
      const 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(header) {
 | 
						|
    const cookies = header.split(";");
 | 
						|
    const result = [];
 | 
						|
 | 
						|
    cookies.forEach(function (cookie) {
 | 
						|
      const equal = cookie.indexOf("=");
 | 
						|
      const name = cookie.substr(0, equal);
 | 
						|
      const 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 array headers
 | 
						|
   *        Array of raw Set-Cookie header values.
 | 
						|
   * @return array
 | 
						|
   *         Array holding an object for each cookie. Each object holds the
 | 
						|
   *         following properties: name, value, secure (boolean), httpOnly
 | 
						|
   *         (boolean), path, domain, samesite and expires (ISO date string).
 | 
						|
   */
 | 
						|
  parseSetCookieHeaders(headers) {
 | 
						|
    function parseSameSiteAttribute(attribute) {
 | 
						|
      attribute = attribute.toLowerCase();
 | 
						|
      switch (attribute) {
 | 
						|
        case COOKIE_SAMESITE.LAX.toLowerCase():
 | 
						|
          return COOKIE_SAMESITE.LAX;
 | 
						|
        case COOKIE_SAMESITE.STRICT.toLowerCase():
 | 
						|
          return COOKIE_SAMESITE.STRICT;
 | 
						|
        default:
 | 
						|
          return COOKIE_SAMESITE.NONE;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    const cookies = [];
 | 
						|
 | 
						|
    for (const header of headers) {
 | 
						|
      const rawCookies = header.split(/\r\n|\n|\r/);
 | 
						|
 | 
						|
      rawCookies.forEach(function (cookie) {
 | 
						|
        const equal = cookie.indexOf("=");
 | 
						|
        const name = unescape(cookie.substr(0, equal).trim());
 | 
						|
        const parts = cookie.substr(equal + 1).split(";");
 | 
						|
        const value = unescape(parts.shift().trim());
 | 
						|
 | 
						|
        cookie = { name, 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) {
 | 
						|
            const pair = part.split("=");
 | 
						|
            pair[0] = pair[0].toLowerCase();
 | 
						|
            if (pair[0] == "path" || pair[0] == "domain") {
 | 
						|
              cookie[pair[0]] = pair[1];
 | 
						|
            } else if (pair[0] == "samesite") {
 | 
						|
              cookie[pair[0]] = parseSameSiteAttribute(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/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(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;
 | 
						|
    }
 | 
						|
 | 
						|
    const 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 originAttributes
 | 
						|
   *        The OriginAttributes of the request.
 | 
						|
   * @param object httpActivity
 | 
						|
   *        The httpActivity object for the request with at least members
 | 
						|
   *        { private, hostname }.
 | 
						|
   * @param Map decodedCertificateCache
 | 
						|
   *        A Map of certificate fingerprints to decoded certificates, to avoid
 | 
						|
   *        repeatedly decoding previously-seen certificates.
 | 
						|
   *
 | 
						|
   * @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: error code string.
 | 
						|
   *          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.
 | 
						|
   */
 | 
						|
  async parseSecurityInfo(
 | 
						|
    securityInfo,
 | 
						|
    originAttributes,
 | 
						|
    httpActivity,
 | 
						|
    decodedCertificateCache
 | 
						|
  ) {
 | 
						|
    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
 | 
						|
     *   https://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"
 | 
						|
     */
 | 
						|
 | 
						|
    const wpl = Ci.nsIWebProgressListener;
 | 
						|
    const NSSErrorsService = Cc["@mozilla.org/nss_errors_service;1"].getService(
 | 
						|
      Ci.nsINSSErrorsService
 | 
						|
    );
 | 
						|
    if (!NSSErrorsService.isNSSErrorCode(securityInfo.errorCode)) {
 | 
						|
      const state = securityInfo.securityState;
 | 
						|
 | 
						|
      let uri = null;
 | 
						|
      if (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 {
 | 
						|
        lazy.DevToolsInfaillibleUtils.reportException(
 | 
						|
          "NetworkHelper.parseSecurityInfo",
 | 
						|
          "Security state " + state + " has no known STATE_IS_* flags."
 | 
						|
        );
 | 
						|
        return info;
 | 
						|
      }
 | 
						|
 | 
						|
      // Cipher suite.
 | 
						|
      info.cipherSuite = securityInfo.cipherName;
 | 
						|
 | 
						|
      // Key exchange group name.
 | 
						|
      info.keaGroupName = securityInfo.keaGroupName;
 | 
						|
 | 
						|
      // Certificate signature scheme.
 | 
						|
      info.signatureSchemeName = securityInfo.signatureSchemeName;
 | 
						|
 | 
						|
      // Protocol version.
 | 
						|
      info.protocolVersion = this.formatSecurityProtocol(
 | 
						|
        securityInfo.protocolVersion
 | 
						|
      );
 | 
						|
 | 
						|
      // Certificate.
 | 
						|
      info.cert = await this.parseCertificateInfo(
 | 
						|
        securityInfo.serverCert,
 | 
						|
        decodedCertificateCache
 | 
						|
      );
 | 
						|
 | 
						|
      // Certificate transparency status.
 | 
						|
      info.certificateTransparency = securityInfo.certificateTransparencyStatus;
 | 
						|
 | 
						|
      // HSTS and HPKP if available.
 | 
						|
      if (httpActivity.hostname) {
 | 
						|
        const sss = Cc["@mozilla.org/ssservice;1"].getService(
 | 
						|
          Ci.nsISiteSecurityService
 | 
						|
        );
 | 
						|
        const pkps = Cc[
 | 
						|
          "@mozilla.org/security/publickeypinningservice;1"
 | 
						|
        ].getService(Ci.nsIPublicKeyPinningService);
 | 
						|
 | 
						|
        if (!uri) {
 | 
						|
          // isSecureURI only cares about the host, not the scheme.
 | 
						|
          const host = httpActivity.hostname;
 | 
						|
          uri = Services.io.newURI("https://" + host);
 | 
						|
        }
 | 
						|
 | 
						|
        info.hsts = sss.isSecureURI(uri, originAttributes);
 | 
						|
        info.hpkp = pkps.hostHasPins(uri);
 | 
						|
      } else {
 | 
						|
        lazy.DevToolsInfaillibleUtils.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.errorCodeString;
 | 
						|
    }
 | 
						|
 | 
						|
    // These values can be unset in rare cases, e.g. when stashed connection
 | 
						|
    // data is deseralized from an older version of Firefox.
 | 
						|
    try {
 | 
						|
      info.usedEch = securityInfo.isAcceptedEch;
 | 
						|
    } catch {
 | 
						|
      info.usedEch = false;
 | 
						|
    }
 | 
						|
    try {
 | 
						|
      info.usedDelegatedCredentials = securityInfo.isDelegatedCredential;
 | 
						|
    } catch {
 | 
						|
      info.usedDelegatedCredentials = false;
 | 
						|
    }
 | 
						|
    info.usedOcsp = securityInfo.madeOCSPRequests;
 | 
						|
    info.usedPrivateDns = securityInfo.usedPrivateDNS;
 | 
						|
 | 
						|
    return info;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Takes an nsIX509Cert and returns an object with certificate information.
 | 
						|
   *
 | 
						|
   * @param nsIX509Cert cert
 | 
						|
   *        The certificate to extract the information from.
 | 
						|
   * @param Map decodedCertificateCache
 | 
						|
   *        A Map of certificate fingerprints to decoded certificates, to avoid
 | 
						|
   *        repeatedly decoding previously-seen certificates.
 | 
						|
   * @return object
 | 
						|
   *         An object with following format:
 | 
						|
   *           {
 | 
						|
   *             subject: { commonName, organization, organizationalUnit },
 | 
						|
   *             issuer: { commonName, organization, organizationUnit },
 | 
						|
   *             validity: { start, end },
 | 
						|
   *             fingerprint: { sha1, sha256 }
 | 
						|
   *           }
 | 
						|
   */
 | 
						|
  async parseCertificateInfo(cert, decodedCertificateCache) {
 | 
						|
    function getDNComponent(dn, componentType) {
 | 
						|
      for (const [type, value] of dn.entries) {
 | 
						|
        if (type == componentType) {
 | 
						|
          return value;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      return undefined;
 | 
						|
    }
 | 
						|
 | 
						|
    const info = {};
 | 
						|
    if (cert) {
 | 
						|
      const certHash = cert.sha256Fingerprint;
 | 
						|
      let parsedCert = decodedCertificateCache.get(certHash);
 | 
						|
      if (!parsedCert) {
 | 
						|
        parsedCert = await lazy.certDecoder.parse(
 | 
						|
          lazy.certDecoder.pemToDER(cert.getBase64DERString())
 | 
						|
        );
 | 
						|
        decodedCertificateCache.set(certHash, parsedCert);
 | 
						|
      }
 | 
						|
      info.subject = {
 | 
						|
        commonName: getDNComponent(parsedCert.subject, "Common Name"),
 | 
						|
        organization: getDNComponent(parsedCert.subject, "Organization"),
 | 
						|
        organizationalUnit: getDNComponent(
 | 
						|
          parsedCert.subject,
 | 
						|
          "Organizational Unit"
 | 
						|
        ),
 | 
						|
      };
 | 
						|
 | 
						|
      info.issuer = {
 | 
						|
        commonName: getDNComponent(parsedCert.issuer, "Common Name"),
 | 
						|
        organization: getDNComponent(parsedCert.issuer, "Organization"),
 | 
						|
        organizationUnit: getDNComponent(
 | 
						|
          parsedCert.issuer,
 | 
						|
          "Organizational Unit"
 | 
						|
        ),
 | 
						|
      };
 | 
						|
 | 
						|
      info.validity = {
 | 
						|
        start: parsedCert.notBeforeUTC,
 | 
						|
        end: parsedCert.notAfterUTC,
 | 
						|
      };
 | 
						|
 | 
						|
      info.fingerprint = {
 | 
						|
        sha1: parsedCert.fingerprint.sha1,
 | 
						|
        sha256: parsedCert.fingerprint.sha256,
 | 
						|
      };
 | 
						|
    } else {
 | 
						|
      lazy.DevToolsInfaillibleUtils.reportException(
 | 
						|
        "NetworkHelper.parseCertificateInfo",
 | 
						|
        "Secure connection established without certificate."
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    return info;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Takes protocolVersion of TransportSecurityInfo object and returns
 | 
						|
   * human readable description.
 | 
						|
   *
 | 
						|
   * @param Number version
 | 
						|
   *        One of nsITransportSecurityInfo version constants.
 | 
						|
   * @return string
 | 
						|
   *         One of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 if @param version
 | 
						|
   *         is valid, Unknown otherwise.
 | 
						|
   */
 | 
						|
  formatSecurityProtocol(version) {
 | 
						|
    switch (version) {
 | 
						|
      case Ci.nsITransportSecurityInfo.TLS_VERSION_1:
 | 
						|
        return "TLSv1";
 | 
						|
      case Ci.nsITransportSecurityInfo.TLS_VERSION_1_1:
 | 
						|
        return "TLSv1.1";
 | 
						|
      case Ci.nsITransportSecurityInfo.TLS_VERSION_1_2:
 | 
						|
        return "TLSv1.2";
 | 
						|
      case Ci.nsITransportSecurityInfo.TLS_VERSION_1_3:
 | 
						|
        return "TLSv1.3";
 | 
						|
      default:
 | 
						|
        lazy.DevToolsInfaillibleUtils.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(state) {
 | 
						|
    const wpl = Ci.nsIWebProgressListener;
 | 
						|
 | 
						|
    // If there's non-fatal security issues the request has STATE_IS_BROKEN
 | 
						|
    // flag set. See https://hg.mozilla.org/mozilla-central/file/44344099d119
 | 
						|
    // /security/manager/ssl/nsNSSCallbacks.cpp#l1233
 | 
						|
    const reasons = [];
 | 
						|
 | 
						|
    if (state & wpl.STATE_IS_BROKEN) {
 | 
						|
      const isCipher = state & wpl.STATE_USES_WEAK_CRYPTO;
 | 
						|
 | 
						|
      if (isCipher) {
 | 
						|
        reasons.push("cipher");
 | 
						|
      }
 | 
						|
 | 
						|
      if (!isCipher) {
 | 
						|
        lazy.DevToolsInfaillibleUtils.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(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.
 | 
						|
    const paramsArray = queryString
 | 
						|
      .replace(/^[?&]/, "")
 | 
						|
      .split("&")
 | 
						|
      .map(e => {
 | 
						|
        const param = e.split("=");
 | 
						|
        return {
 | 
						|
          name: param[0]
 | 
						|
            ? NetworkHelper.convertToUnicode(unescape(param[0]))
 | 
						|
            : "",
 | 
						|
          value: param[1]
 | 
						|
            ? NetworkHelper.convertToUnicode(unescape(param[1]))
 | 
						|
            : "",
 | 
						|
        };
 | 
						|
      });
 | 
						|
 | 
						|
    return paramsArray;
 | 
						|
  },
 | 
						|
};
 |