Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

SSL request-utils.js

  Sprache: JAVA
 

/* 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/. */


"use strict";

const {
  getUnicodeUrl,
  getUnicodeUrlPath,
  getUnicodeHostname,
} = require("resource://devtools/client/shared/unicode-url.js");

const lazy = {};
ChromeUtils.defineESModuleGetters(
  lazy,
  {
    parseJsonLossless:
      "resource://devtools/client/shared/components/reps/reps/rep-utils.mjs",
    JSON_NUMBER:
      "resource://devtools/client/shared/components/reps/reps/constants.mjs",
  },
  { global: "contextual" }
);

loader.lazyRequireGetter(
  this,
  "L10N",
  "resource://devtools/client/netmonitor/src/utils/l10n.js",
  true
);

const {
  UPDATE_PROPS,
} = require("resource://devtools/client/netmonitor/src/constants.js");

const CONTENT_MIME_TYPE_ABBREVIATIONS = new Map([
  ["ecmascript""js"],
  ["javascript""js"],
  ["x-javascript""js"],
  ["event-stream""eventsource"],
]);

/**
 * Extracts any urlencoded form data sections (e.g. "?foo=bar&baz=42") from a
 * POST request.
 *
 * @param {object} headers - the "requestHeaders".
 * @param {object} uploadHeaders - the "requestHeadersFromUploadStream".
 * @param {object} postData - the "requestPostData".
 * @return {Array} a promise list that is resolved with the extracted form data.
 */

async function getFormDataSections(
  headers,
  uploadHeaders,
  postData,
  getLongString
) {
  const formDataSections = [];

  const requestHeaders = headers.headers;
  const payloadHeaders = uploadHeaders ? uploadHeaders.headers : [];
  const allHeaders = [...payloadHeaders, ...requestHeaders];

  const contentTypeHeader = allHeaders.find(e => {
    return e.name.toLowerCase() == "content-type";
  });

  const contentTypeLongString = contentTypeHeader
    ? contentTypeHeader.value
    : "";

  const contentType = await getLongString(contentTypeLongString);

  if (contentType && contentType.includes("x-www-form-urlencoded")) {
    const postDataLongString = postData.postData.text;
    const text = await getLongString(postDataLongString);

    for (const section of text.trim().split(/\r\n|\r|\n/)) {
      // Before displaying it, make sure this section of the POST data
      // isn't a line containing upload stream headers.
      if (payloadHeaders.every(header => !section.startsWith(header.name))) {
        formDataSections.push(section);
      }
    }
  }
  return formDataSections;
}

/**
 * Fetch headers full content from actor server
 *
 * @param {object} headers - a object presents headers data
 * @return {object} a headers object with updated content payload
 */

async function fetchHeaders(headers, getLongString) {
  for (const header of headers.headers) {
    header.value = await getLongString(header.value);
  }
  return headers;
}

/**
 * Fetch network event update packets from actor server
 * Expect to fetch a couple of network update packets from a given request.
 *
 * @param {function} requestData - requestData function for lazily fetch data
 * @param {object} request - request object
 * @param {Array} updateTypes - a list of network event update types
 */

function fetchNetworkUpdatePacket(requestData, request, updateTypes) {
  const promises = [];
  if (request) {
    updateTypes.forEach(updateType => {
      // stackTrace needs to be handled specially as the property to lookup
      // on the request object follows a slightly different convention.
      // i.e `stacktrace` not `stackTrace`
      if (updateType === "stackTrace") {
        if (request.cause.stacktraceAvailable && !request.stacktrace) {
          promises.push(requestData(request.id, updateType));
        }
        return;
      }
      // responseContent only checks the availiability flag as there can
      // be multiple response content events
      if (updateType === "responseContent") {
        if (request.responseContentAvailable) {
          promises.push(requestData(request.id, updateType));
        }
        return;
      }

      if (request[`${updateType}Available`] && !request[updateType]) {
        promises.push(requestData(request.id, updateType));
      }
    });
  }

  return Promise.all(promises);
}

/**
 * Form a data: URI given a mime type, encoding, and some text.
 *
 * @param {string} mimeType - mime type
 * @param {string} encoding - encoding to use; if not set, the
 *                            text will be base64-encoded.
 * @param {string} text - text of the URI.
 * @return {string} a data URI
 */

function formDataURI(mimeType, encoding, text) {
  if (!encoding) {
    encoding = "base64";
    text = btoa(unescape(encodeURIComponent(text)));
  }
  return "data:" + mimeType + ";" + encoding + "," + text;
}

/**
 * Write out a list of headers into a chunk of text
 *
 * @param {Array} headers - array of headers info { name, value }
 * @param {string} preHeaderText - first line of the headers request/response
 * @return {string} list of headers in text format
 */

function writeHeaderText(headers, preHeaderText) {
  let result = "";
  if (preHeaderText) {
    result += preHeaderText + "\r\n";
  }
  result += headers.map(({ name, value }) => name + ": " + value).join("\r\n");
  result += "\r\n\r\n";
  return result;
}

/**
 * Decode base64 string.
 *
 * @param {string} url - a string
 * @return {string} decoded string
 */

function decodeUnicodeBase64(string) {
  try {
    return decodeURIComponent(atob(string));
  } catch (err) {
    // Ignore error and return input string directly.
  }
  return string;
}

/**
 * Helper for getting an abbreviated string for a mime type.
 *
 * @param {string} mimeType - mime type
 * @return {string} abbreviated mime type
 */

function getAbbreviatedMimeType(mimeType) {
  if (!mimeType) {
    return "";
  }
  const abbrevType = (
    mimeType.toLowerCase().split(";")[0].split("/")[1] || ""
  ).split("+")[0];
  return CONTENT_MIME_TYPE_ABBREVIATIONS.get(abbrevType) || abbrevType;
}

/**
 * Helpers for getting a filename from a mime type.
 *
 * @param {string} baseNameWithQuery - unicode basename and query of a url
 * @return {string} unicode filename portion of a url
 */

function getFileName(baseNameWithQuery) {
  const basename = baseNameWithQuery && baseNameWithQuery.split("?")[0];
  return basename && basename.includes(".") ? basename : null;
}

/**
 * Helpers for retrieving a URL object from a string
 *
 * @param {string|URL} url - unvalidated url string or already a URL object
 * @return {URL} The URL object
 */

function getUrl(url) {
  if (URL.isInstance(url)) {
    return url;
  }
  return URL.parse(url);
}

/**
 * Helpers for retrieving the value of a URL object property
 *
 * @param {string|URL} input - unvalidated url string or URL instance
 * @param {string} string - desired property in the URL object
 * @return {string} unicode query of a url
 */

function getUrlProperty(input, property) {
  const url = getUrl(input);
  return url?.[property] ?? "";
}

/**
 * Helpers for getting the last portion of a url.
 * For example helper returns "basename" from http://domain.com/path/basename
 * If basename portion is empty, it returns the url pathname.
 *
 * @param {string|URL} url - unvalidated url string or URL instance
 * @return {string} unicode basename of a url
 */

function getUrlBaseName(url) {
  const pathname = getUrlProperty(url, "pathname");
  return getUnicodeUrlPath(pathname.replace(/\S*\//, "") || pathname || "/");
}

/**
 * Helpers for getting the query portion of a url.
 *
 * @param {string|URL} url - unvalidated url string or URL instance
 * @return {string} unicode query of a url
 */

function getUrlQuery(url) {
  return getUrlProperty(url, "search").replace(/^\?/, "");
}

/**
 * Helpers for getting unicode name and query portions of a url.
 *
 * @param {URL} urlObject - the URL instance
 * @return {string} unicode basename and query portions of a url
 */

function getUrlBaseNameWithQuery(urlObject) {
  if (urlObject.href.startsWith("data:")) {
    // For data URIs, no basename can be extracted from the URL so just reuse
    // the full url.
    return urlObject.href;
  }

  const basename = getUrlBaseName(urlObject);
  const search = getUrlProperty(urlObject, "search");
  return basename + getUnicodeUrlPath(search);
}

/**
 * Helpers for getting hostname portion of an URL.
 *
 * @param {string|URL} url - unvalidated url string or URL instance
 * @return {string} unicode hostname of a url
 */

function getUrlHostName(url) {
  return getUrlProperty(url, "hostname");
}

/**
 * Helpers for getting host portion of an URL.
 *
 * @param {string|URL} url - unvalidated url string or URL instance
 * @return {string} unicode host of a url
 */

function getUrlHost(url) {
  return getUrlProperty(url, "host");
}

/**
 * Helpers for getting the shceme portion of a url.
 * For example helper returns "http" from http://domain.com/path/basename
 *
 * @param {string|URL} url - unvalidated url string or URL instance
 * @return {string} string scheme of a url
 */

function getUrlScheme(url) {
  const protocol = getUrlProperty(url, "protocol");
  return protocol.replace(":""").toLowerCase();
}

/**
 * Helpers for getting the full path portion of a url.
 *
 * @param {string|URL} url - unvalidated url string or URL instance
 * @return {string} string path of a url
 */

function getUrlPath(url) {
  const href = getUrlProperty(url, "href");
  const origin = getUrlProperty(url, "origin");
  return href.replace(origin, "");
}

/**
 * Extract several details fields from a URL at once.
 */

function getUrlDetails(url) {
  const urlObject = getUrl(url);
  const baseNameWithQuery = getUrlBaseNameWithQuery(urlObject);
  let host = getUrlHost(urlObject);
  const hostname = getUrlHostName(urlObject);
  const unicodeUrl = getUnicodeUrl(urlObject);
  const scheme = getUrlScheme(urlObject);
  const path = getUrlPath(urlObject);

  // If the hostname contains unreadable ASCII characters, we need to do the
  // following two steps:
  // 1. Converting the unreadable hostname to a readable Unicode domain name.
  //    For example, converting xn--g6w.xn--8pv into a Unicode domain name.
  // 2. Replacing the unreadable hostname portion in the `host` with the
  //    readable hostname.
  //    For example, replacing xn--g6w.xn--8pv:8000 with [Unicode domain]:8000
  // After finishing the two steps, we get a readable `host`.
  const unicodeHostname = getUnicodeHostname(hostname);
  if (unicodeHostname !== hostname) {
    host = host.replace(hostname, unicodeHostname);
  }

  // Mark local hosts specially, where "local" is  as defined in the W3C
  // spec for secure contexts.
  // http://www.w3.org/TR/powerful-features/
  //
  //  * If the name falls under 'localhost'
  //  * If the name is an IPv4 address within 127.0.0.0/8
  //  * If the name is an IPv6 address within ::1/128
  //
  // IPv6 parsing is a little sloppy; it assumes that the address has
  // been validated before it gets here.
  const isLocal =
    /^(.+\.)?localhost$/.test(hostname) ||
    /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname) ||
    /^\[[0:]+1\]$/.test(hostname);

  return {
    baseNameWithQuery,
    host,
    scheme,
    unicodeUrl,
    isLocal,
    url,
    path,
  };
}

/**
 * Helpers for retrieving the value of a URL tooltip
 *
 * @param {object} urlDetails - a urlDetails object
 * @returns
 */

function getUrlToolTip(urlDetails) {
  const url = urlDetails.url;
  const decodedURL = urlDetails.unicodeUrl;

  // The `originalFileURL` below refers to "File" because it was initially created for use in the File column.
  // Now it is also being used in the Path and URL columns, while retaining the original name.
  const ORIGINAL_URL = L10N.getFormatStr(
    "netRequest.originalFileURL.tooltip",
    url
  );
  const DECODED_URL = L10N.getFormatStr(
    "netRequest.decodedFileURL.tooltip",
    decodedURL
  );
  const toolTip =
    url === decodedURL ? url : ORIGINAL_URL + "\n\n" + DECODED_URL;
  return toolTip;
}

/**
 * Parse a url's query string into its components
 *
 * @param {string} query - query string of a url portion
 * @return {Array} array of query params { name, value }
 */

function parseQueryString(query) {
  if (!query) {
    return null;
  }
  return query
    .replace(/^[?&]/, "")
    .split("&")
    .map(e => {
      const param = e.split("=");
      return {
        name: param[0] ? getUnicodeUrlPath(param[0].replace(/\+/g, " ")) : "",
        value: param[1]
          ? getUnicodeUrlPath(param.slice(1).join("=").replace(/\+/g, " "))
          : "",
      };
    });
}

/**
 * Parse a string of formdata sections into its components
 *
 * @param {Array<string>} sections Array of sections of formdata
 *                                 e.g ["", "a=x&b=y", "c=z"]
 * @return {Array<object>}  Array of formdata params
 *                          e.g [{ name: 'a', value: 'x' }, { name: 'b', value: 'y'}, { name: 'c', value: 'z'}]
 */
function parseFormData(sections) {
  if (!sections || !sections.length) {
    return [];
  }
  const formDataParams = [];
  const searchStr = sections
    // Filter out empty sections
    .filter(str => /\S/.test(str))
    .join("&");

  const params = new URLSearchParams(searchStr);
  for (const [key, value] of params) {
    formDataParams.push({
      name: getUnicodeUrlPath(key),
      value: getUnicodeUrlPath(value),
    });
  }
  return formDataParams;
}

/**
 * Reduces an IP address into a number for easier sorting
 *
 * @param {string} ip - IP address to reduce
 * @return {number} the number representing the IP address
 */

function ipToLong(ip) {
  if (!ip) {
    // Invalid IP
    return -1;
  }

  let base;
  let octets = ip.split(".");

  if (octets.length === 4) {
    // IPv4
    base = 10;
  } else if (ip.includes(":")) {
    // IPv6
    const numberOfZeroSections =
      8 - ip.replace(/^:+|:+$/g, "").split(/:+/g).length;
    octets = ip
      .replace("::", `:${"0:".repeat(numberOfZeroSections)}`)
      .replace(/^:|:$/g, "")
      .split(":");
    base = 16;
  } else {
    // Invalid IP
    return -1;
  }
  return octets
    .map((val, ix, arr) => {
      return parseInt(val, base) * Math.pow(256, arr.length - 1 - ix);
    })
    .reduce((sum, val) => {
      return sum + val;
    }, 0);
}

/**
 * Compare two objects on a subset of their properties
 */

function propertiesEqual(props, item1, item2) {
  return item1 === item2 || props.every(p => item1[p] === item2[p]);
}

/**
 * Calculate the start time of a request, which is the time from start
 * of 1st request until the start of this request.
 *
 * Without a firstRequestStartedMs argument the wrong time will be returned.
 * However, it can be omitted when comparing two start times and neither supplies
 * a firstRequestStartedMs.
 */

function getStartTime(item, firstRequestStartedMs = 0) {
  return item.startedMs - firstRequestStartedMs;
}

/**
 * Calculate the end time of a request, which is the time from start
 * of 1st request until the end of this response.
 *
 * Without a firstRequestStartedMs argument the wrong time will be returned.
 * However, it can be omitted when comparing two end times and neither supplies
 * a firstRequestStartedMs.
 */

function getEndTime(item, firstRequestStartedMs = 0) {
  const { startedMs, totalTime } = item;
  return startedMs + totalTime - firstRequestStartedMs;
}

/**
 * Calculate the response time of a request, which is the time from start
 * of 1st request until the beginning of download of this response.
 *
 * Without a firstRequestStartedMs argument the wrong time will be returned.
 * However, it can be omitted when comparing two response times and neither supplies
 * a firstRequestStartedMs.
 */

function getResponseTime(item, firstRequestStartedMs = 0) {
  const { startedMs, totalTime, eventTimings = { timings: {} } } = item;
  return (
    startedMs + totalTime - firstRequestStartedMs - eventTimings.timings.receive
  );
}

/**
 * Format the protocols used by the request.
 */

function getFormattedProtocol(item) {
  const { httpVersion = "", responseHeaders = { headers: [] } } = item;
  const protocol = [httpVersion];
  responseHeaders.headers.some(h => {
    if (h.hasOwnProperty("name") && h.name.toLowerCase() === "x-firefox-spdy") {
      /**
       * First we make sure h.value is defined and not an empty string.
       * Then check that HTTP version and x-firefox-spdy == "http/1.1".
       * If not, check that HTTP version and x-firefox-spdy have the same
       * numeric value when of the forms "http/<x>" and "h<x>" respectively.
       * If not, will push to protocol the non-standard x-firefox-spdy value.
       *
       * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1501357
       */
      if (h.value !== undefined && h.value.length) {
        if (
          h.value.toLowerCase() !== "http/1.1" ||
          protocol[0].toLowerCase() !== "http/1.1"
        ) {
          if (
            parseFloat(h.value.toLowerCase().split("")[1]) !==
            parseFloat(protocol[0].toLowerCase().split("/")[1])
          ) {
            protocol.push(h.value);
            return true;
          }
        }
      }
    }
    return false;
  });
  return protocol.join("+");
}

/**
 * Get the value of a particular response header, or null if not
 * present.
 */

function getResponseHeader(item, header) {
  const { responseHeaders } = item;
  if (!responseHeaders?.headers?.length) {
    return null;
  }
  header = header.toLowerCase();
  for (const responseHeader of responseHeaders.headers) {
    if (responseHeader.name.toLowerCase() == header) {
      return responseHeader.value;
    }
  }
  return null;
}

/**
 * Get the value of a particular request header, or null if not
 * present.
 */

function getRequestHeader(item, header) {
  const { requestHeaders } = item;
  if (!requestHeaders?.headers?.length) {
    return null;
  }
  header = header.toLowerCase();
  for (const requestHeader of requestHeaders.headers) {
    if (requestHeader.name.toLowerCase() == header) {
      return requestHeader.value;
    }
  }
  return null;
}

/**
 * Extracts any urlencoded form data sections from a POST request.
 */

async function updateFormDataSections(props) {
  const { connector, request = {}, updateRequest } = props;
  let {
    id,
    formDataSections,
    requestHeaders,
    requestHeadersAvailable,
    requestHeadersFromUploadStream,
    requestPostData,
    requestPostDataAvailable,
  } = request;

  if (requestHeadersAvailable && !requestHeaders) {
    requestHeaders = await connector.requestData(id, "requestHeaders");
  }

  if (requestPostDataAvailable && !requestPostData) {
    requestPostData = await connector.requestData(id, "requestPostData");
  }

  if (
    !formDataSections &&
    requestHeaders &&
    requestPostData &&
    requestHeadersFromUploadStream
  ) {
    formDataSections = await getFormDataSections(
      requestHeaders,
      requestHeadersFromUploadStream,
      requestPostData,
      connector.getLongString
    );

    updateRequest(request.id, { formDataSections }, true);
  }
}

/**
 * This helper function helps to resolve the full payload of a message
 * that is wrapped in a LongStringActor object.
 */

async function getMessagePayload(payload, getLongString) {
  const result = await getLongString(payload);
  return result;
}

/**
 * This helper function is used for additional processing of
 * incoming network update packets. It makes sure the only valid
 * update properties and the values are correct.
 * It's used by Network and Console panel reducers.
 *
 * @param {object} update
 *        The new update payload
 * @param {object} request
 *        The current request in the state
 */

function processNetworkUpdates(update) {
  const newRequest = {};
  for (const [key, value] of Object.entries(update)) {
    if (UPDATE_PROPS.includes(key)) {
      newRequest[key] = value;
      if (key == "requestPostData") {
        newRequest.requestHeadersFromUploadStream = value.uploadHeaders;
      }
    }
  }
  return newRequest;
}

/**
 * This method checks that the response is base64 encoded by
 * comparing these 2 values:
 * 1. The original response
 * 2. The value of doing a base64 decode on the
 * response and then base64 encoding the result.
 * If the values are different or an error is thrown,
 * the method will return false.
 */

function isBase64(payload) {
  try {
    return btoa(atob(payload)) == payload;
  } catch (err) {
    return false;
  }
}

/**
 * Checks if the payload is of JSON type.
 * This function also handles JSON with XSSI-escaping characters by stripping them
 * and returning the stripped chars in the strippedChars property
 * This function also handles Base64 encoded JSON.
 *
 * @returns {object} shape:
 *  {Object} json: parsed JSON object
 *  {Error} error: JSON parsing error
 *  {string} strippedChars: XSSI stripped chars removed from JSON payload
 */

function parseJSON(payloadUnclean) {
  let json;
  const jsonpRegex = /^\s*([\w$]+)\s*\(\s*([^]*)\s*\)\s*;?\s*$/;
  const [, jsonpCallback, jsonp] = payloadUnclean.match(jsonpRegex) || [];
  if (jsonpCallback && jsonp) {
    let error;
    try {
      json = parseJSON(jsonp).json;
    } catch (err) {
      error = err;
    }
    return { json, error, jsonpCallback };
  }

  let { payload, strippedChars, error } = removeXSSIString(payloadUnclean);

  try {
    json = lazy.parseJsonLossless(payload);
  } catch (err) {
    if (isBase64(payload)) {
      try {
        json = JSON.parse(atob(payload));
      } catch (err64) {
        error = err64;
      }
    } else {
      error = err;
    }
  }

  // Do not present JSON primitives (e.g. boolean, strings in quotes, numbers)
  // as JSON expandable tree.
  if (
    !error &&
    (typeof json !== "object" ||
      json === null ||
      // Parsed JSON numbers might be different than the source, for example
      // JSON.parse("1516340399466235648") returns 1516340399466235600. In such case,
      // parseJsonLossless will return an object with `type: JSON_NUMBER` property.
      // We still want to display those numbers as the other numbers here.
      json?.type === lazy.JSON_NUMBER)
  ) {
    return {};
  }

  return {
    json,
    error,
    strippedChars,
  };
}

/**
 * Removes XSSI prevention sequences from JSON payloads
 *
 * @param {string} payloadUnclean: JSON payload that may or may have a
 *                                 XSSI prevention sequence
 * @returns {object} Shape:
 *   {string} payload: the JSON witht the XSSI prevention sequence removed
 *   {string} strippedChars: XSSI string that was removed, null if no XSSI
 *                           prevention sequence was found
 *   {Error} error: error attempting to strip XSSI prevention sequence
 */

function removeXSSIString(payloadUnclean) {
  // Regex that finds the XSSI protection sequences )]}'\n for(;;); and while(1);
  const xssiRegex = /(^\)\]\}',?\n)|(^for ?\(;;\);?)|(^while ?\(1\);?)/;
  let payload, strippedChars, error;
  const xssiRegexMatch = payloadUnclean.match(xssiRegex);

  // Remove XSSI string if there was one found
  if (xssiRegexMatch?.length) {
    const xssiLen = xssiRegexMatch[0].length;
    try {
      // substring the payload by the length of the XSSI match to remove it
      // and save the match to report
      payload = payloadUnclean.substring(xssiLen);
      strippedChars = xssiRegexMatch[0];
    } catch (err) {
      error = err;
      payload = payloadUnclean;
    }
  } else {
    // if there was no XSSI match just return the raw payload
    payload = payloadUnclean;
  }
  return {
    payload,
    strippedChars,
    error,
  };
}

/**
 * Computes the request headers of an HTTP request
 *
 * @param {string} method: request method
 * @param {string} httpVersion: request http version
 * @param {object} requestHeaders: request headers
 * @param {object} urlDetails: request url details
 *
 * @return {string} the request headers
 */

function getRequestHeadersRawText(
  method,
  httpVersion,
  requestHeaders,
  urlDetails
) {
  const url = getUrl(urlDetails.url);
  const path = url ? `${url.pathname}${url.search}` : "<unknown>";
  const preHeaderText = `${method} ${path} ${httpVersion}`;
  return writeHeaderText(requestHeaders.headers, preHeaderText).trim();
}

/**
 * Checks if the "Expiration Calculations" defined in section 13.2.4 of the
 * "HTTP/1.1: Caching in HTTP" spec holds true for a collection of headers.
 *
 * @param object
 *        An object containing the { responseHeaders, status } properties.
 * @return boolean
 *         True if the response is fresh and loaded from cache.
 */

function responseIsFresh({ responseHeaders, status }) {
  // Check for a "304 Not Modified" status and response headers availability.
  if (status != 304 || !responseHeaders) {
    return false;
  }

  const list = responseHeaders.headers;
  const cacheControl = list.find(e => e.name.toLowerCase() === "cache-control");
  const expires = list.find(e => e.name.toLowerCase() === "expires");

  // Check the "Cache-Control" header for a maximum age value.
  if (cacheControl) {
    const maxAgeMatch =
      cacheControl.value.match(/s-maxage\s*=\s*(\d+)/) ||
      cacheControl.value.match(/max-age\s*=\s*(\d+)/);

    if (maxAgeMatch && maxAgeMatch.pop() > 0) {
      return true;
    }
  }

  // Check the "Expires" header for a valid date.
  if (expires && Date.parse(expires.value)) {
    return true;
  }

  return false;
}

module.exports = {
  decodeUnicodeBase64,
  getFormDataSections,
  fetchHeaders,
  fetchNetworkUpdatePacket,
  formDataURI,
  writeHeaderText,
  getAbbreviatedMimeType,
  getFileName,
  getEndTime,
  getFormattedProtocol,
  getMessagePayload,
  getRequestHeader,
  getResponseHeader,
  getResponseTime,
  getStartTime,
  getUrl,
  getUrlBaseName,
  getUrlDetails,
  getUrlHost,
  getUrlHostName,
  getUrlQuery,
  getUrlScheme,
  getUrlToolTip,
  parseQueryString,
  parseFormData,
  updateFormDataSections,
  processNetworkUpdates,
  propertiesEqual,
  ipToLong,
  parseJSON,
  getRequestHeadersRawText,
  responseIsFresh,
};

Messung V0.5 in Prozent
C=88 H=93 G=90

¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.138Bemerkung:  ¤

*Bot Zugriff






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=752002