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


Quelle  URIFixup.sys.mjs   Sprache: unbekannt

 
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 expandtab
 * 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/. */

/**
 * This component handles fixing up URIs, by correcting obvious typos and adding
 * missing schemes.
 * URI references:
 *   http://www.faqs.org/rfcs/rfc1738.html
 *   http://www.faqs.org/rfcs/rfc2396.html
 */

// TODO (Bug 1641220) getFixupURIInfo has a complex logic, that likely could be
// simplified, but the risk of regressing its behavior is high.
/* eslint complexity: ["error", 43] */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "externalProtocolService",
  "@mozilla.org/uriloader/external-protocol-service;1",
  "nsIExternalProtocolService"
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "defaultProtocolHandler",
  "@mozilla.org/network/protocol;1?name=default",
  "nsIProtocolHandler"
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "fileProtocolHandler",
  "@mozilla.org/network/protocol;1?name=file",
  "nsIFileProtocolHandler"
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "handlerService",
  "@mozilla.org/uriloader/handler-service;1",
  "nsIHandlerService"
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "fixupSchemeTypos",
  "browser.fixup.typo.scheme",
  true
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "dnsFirstForSingleWords",
  "browser.fixup.dns_first_for_single_words",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "keywordEnabled",
  "keyword.enabled",
  true
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "alternateProtocol",
  "browser.fixup.alternate.protocol",
  "https"
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "dnsResolveFullyQualifiedNames",
  "browser.urlbar.dnsResolveFullyQualifiedNames",
  true
);

const {
  FIXUP_FLAG_NONE,
  FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP,
  FIXUP_FLAGS_MAKE_ALTERNATE_URI,
  FIXUP_FLAG_PRIVATE_CONTEXT,
  FIXUP_FLAG_FIX_SCHEME_TYPOS,
} = Ci.nsIURIFixup;

const COMMON_PROTOCOLS = ["http", "https", "file"];

const HTTPISH = new Set(["http", "https"]);

// Regex used to identify user:password tokens in url strings.
// This is not a strict valid characters check, because we try to fixup this
// part of the url too.
ChromeUtils.defineLazyGetter(
  lazy,
  "userPasswordRegex",
  () => /^([a-z+.-]+:\/{0,3})*([^\/@]+@).+/i
);

// Regex used to identify the string that starts with port expression.
ChromeUtils.defineLazyGetter(lazy, "portRegex", () => /^:\d{1,5}([?#/]|$)/);

// Regex used to identify numbers.
ChromeUtils.defineLazyGetter(lazy, "numberRegex", () => /^[0-9]+(\.[0-9]+)?$/);

// Regex used to identify tab separated content (having at least 2 tabs).
ChromeUtils.defineLazyGetter(lazy, "maxOneTabRegex", () => /^[^\t]*\t?[^\t]*$/);

// Regex used to test if a string with a protocol might instead be a url
// without a protocol but with a port:
//
//   <hostname>:<port> or
//   <hostname>:<port>/
//
// Where <hostname> is a string of alphanumeric characters and dashes
// separated by dots.
// and <port> is a 5 or less digits. This actually breaks the rfc2396
// definition of a scheme which allows dots in schemes.
//
// Note:
//   People expecting this to work with
//   <user>:<password>@<host>:<port>/<url-path> will be disappointed!
//
// Note: Parser could be a lot tighter, tossing out silly hostnames
//       such as those containing consecutive dots and so on.
ChromeUtils.defineLazyGetter(
  lazy,
  "possiblyHostPortRegex",
  () => /^[a-z0-9-]+(\.[a-z0-9-]+)*:[0-9]{1,5}([/?#]|$)/i
);

// Regex used to strip newlines.
ChromeUtils.defineLazyGetter(lazy, "newLinesRegex", () => /[\r\n]/g);

// Regex used to match a possible protocol.
// This resembles the logic in Services.io.extractScheme, thus \t is admitted
// and stripped later. We don't use Services.io.extractScheme because of
// performance bottleneck caused by crossing XPConnect.
ChromeUtils.defineLazyGetter(
  lazy,
  "possibleProtocolRegex",
  () => /^([a-z][a-z0-9.+\t-]*)(:|;)?(\/\/)?/i
);

// Regex used to match IPs. Note that these are not made to validate IPs, but
// just to detect strings that look like an IP. They also skip protocol.
// For IPv4 this also accepts a shorthand format with just 2 dots.
ChromeUtils.defineLazyGetter(
  lazy,
  "IPv4LikeRegex",
  () => /^(?:[a-z+.-]+:\/*(?!\/))?(?:\d{1,3}\.){2,3}\d{1,3}(?::\d+|\/)?/i
);
ChromeUtils.defineLazyGetter(
  lazy,
  "IPv6LikeRegex",
  () =>
    /^(?:[a-z+.-]+:\/*(?!\/))?\[(?:[0-9a-f]{0,4}:){0,7}[0-9a-f]{0,4}\]?(?::\d+|\/)?/i
);

// Cache of known domains.
ChromeUtils.defineLazyGetter(lazy, "knownDomains", () => {
  const branch = "browser.fixup.domainwhitelist.";
  let domains = new Set(
    Services.prefs
      .getChildList(branch)
      .filter(p => Services.prefs.getBoolPref(p, false))
      .map(p => p.substring(branch.length))
  );
  // Hold onto the observer to avoid it being GC-ed.
  domains._observer = {
    observe(subject, topic, data) {
      let domain = data.substring(branch.length);
      if (Services.prefs.getBoolPref(data, false)) {
        domains.add(domain);
      } else {
        domains.delete(domain);
      }
    },
    QueryInterface: ChromeUtils.generateQI([
      "nsIObserver",
      "nsISupportsWeakReference",
    ]),
  };
  Services.prefs.addObserver(branch, domains._observer, true);
  return domains;
});

// Cache of known suffixes.
// This works differently from the known domains, because when we examine a
// domain we can't tell how many dot-separated parts constitute the suffix.
// We create a Map keyed by the last dotted part, containing a Set of
// all the suffixes ending with that part:
//   "two" => ["two"]
//   "three" => ["some.three", "three"]
// When searching we can restrict the linear scan based on the last part.
// The ideal structure for this would be a Directed Acyclic Word Graph, but
// since we expect this list to be small it's not worth the complication.
ChromeUtils.defineLazyGetter(lazy, "knownSuffixes", () => {
  const branch = "browser.fixup.domainsuffixwhitelist.";
  let suffixes = new Map();
  let prefs = Services.prefs
    .getChildList(branch)
    .filter(p => Services.prefs.getBoolPref(p, false));
  for (let pref of prefs) {
    let suffix = pref.substring(branch.length);
    let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1);
    if (lastPart) {
      let entries = suffixes.get(lastPart);
      if (!entries) {
        entries = new Set();
        suffixes.set(lastPart, entries);
      }
      entries.add(suffix);
    }
  }
  // Hold onto the observer to avoid it being GC-ed.
  suffixes._observer = {
    observe(subject, topic, data) {
      let suffix = data.substring(branch.length);
      let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1);
      let entries = suffixes.get(lastPart);
      if (Services.prefs.getBoolPref(data, false)) {
        // Add the suffix.
        if (!entries) {
          entries = new Set();
          suffixes.set(lastPart, entries);
        }
        entries.add(suffix);
      } else if (entries) {
        // Remove the suffix.
        entries.delete(suffix);
        if (!entries.size) {
          suffixes.delete(lastPart);
        }
      }
    },
    QueryInterface: ChromeUtils.generateQI([
      "nsIObserver",
      "nsISupportsWeakReference",
    ]),
  };
  Services.prefs.addObserver(branch, suffixes._observer, true);
  return suffixes;
});

export function URIFixup() {
  // There are cases that nsIExternalProtocolService.externalProtocolHandlerExists() does
  // not work well and returns always true due to flatpak. In this case, in order to
  // fallback to nsIHandlerService.exits(), we test whether can trust
  // nsIExternalProtocolService here.
  this._trustExternalProtocolService =
    !lazy.externalProtocolService.externalProtocolHandlerExists(
      `__dummy${Date.now()}__`
    );
}

URIFixup.prototype = {
  get FIXUP_FLAG_NONE() {
    return FIXUP_FLAG_NONE;
  },
  get FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP() {
    return FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
  },
  get FIXUP_FLAGS_MAKE_ALTERNATE_URI() {
    return FIXUP_FLAGS_MAKE_ALTERNATE_URI;
  },
  get FIXUP_FLAG_PRIVATE_CONTEXT() {
    return FIXUP_FLAG_PRIVATE_CONTEXT;
  },
  get FIXUP_FLAG_FIX_SCHEME_TYPOS() {
    return FIXUP_FLAG_FIX_SCHEME_TYPOS;
  },

  getFixupURIInfo(uriString, fixupFlags = FIXUP_FLAG_NONE) {
    let isPrivateContext = fixupFlags & FIXUP_FLAG_PRIVATE_CONTEXT;
    let untrimmedURIString = uriString;

    // Eliminate embedded newlines, which single-line text fields now allow,
    // and cleanup the empty spaces and tabs that might be on each end.
    uriString = uriString.trim().replace(lazy.newLinesRegex, "");

    if (!uriString) {
      throw new Components.Exception(
        "Should pass a non-null uri",
        Cr.NS_ERROR_FAILURE
      );
    }

    let info = new URIFixupInfo(uriString);

    const { scheme, fixedSchemeUriString, fixupChangedProtocol } =
      extractScheme(uriString, fixupFlags);
    uriString = fixedSchemeUriString;
    info.fixupChangedProtocol = fixupChangedProtocol;

    if (scheme == "view-source") {
      let { preferredURI, postData } = fixupViewSource(uriString, fixupFlags);
      info.preferredURI = info.fixedURI = preferredURI;
      info.postData = postData;
      return info;
    }

    if (scheme.length < 2) {
      // Check if it is a file path. We skip most schemes because the only case
      // where a file path may look like having a scheme is "X:" on Windows.
      let fileURI = fileURIFixup(uriString);
      if (fileURI) {
        info.preferredURI = info.fixedURI = fileURI;
        info.fixupChangedProtocol = true;
        return info;
      }
    }

    const isCommonProtocol = COMMON_PROTOCOLS.includes(scheme);

    let canHandleProtocol =
      scheme &&
      (isCommonProtocol ||
        Services.io.getProtocolHandler(scheme) != lazy.defaultProtocolHandler ||
        this._isKnownExternalProtocol(scheme));

    if (
      canHandleProtocol ||
      // If it's an unknown handler and the given URL looks like host:port or
      // has a user:password we can't pass it to the external protocol handler.
      // We'll instead try fixing it with http later.
      (!lazy.possiblyHostPortRegex.test(uriString) &&
        !lazy.userPasswordRegex.test(uriString))
    ) {
      // Just try to create a URL out of it.
      try {
        info.fixedURI = makeURIWithFixedLocalHosts(uriString, fixupFlags);
      } catch (ex) {
        if (ex.result != Cr.NS_ERROR_MALFORMED_URI) {
          throw ex;
        }
      }
    }

    // We're dealing with a theoretically valid URI but we have no idea how to
    // load it. (e.g. "christmas:humbug")
    // It's more likely the user wants to search, and so we chuck this over to
    // their preferred search provider.
    // TODO (Bug 1588118): Should check FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP
    // instead of FIXUP_FLAG_FIX_SCHEME_TYPOS.
    if (
      info.fixedURI &&
      lazy.keywordEnabled &&
      fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS &&
      scheme &&
      !canHandleProtocol
    ) {
      tryKeywordFixupForURIInfo(uriString, info, isPrivateContext);
    }

    if (info.fixedURI) {
      if (!info.preferredURI) {
        maybeSetAlternateFixedURI(info, fixupFlags);
        info.preferredURI = info.fixedURI;
      }
      fixupConsecutiveDotsHost(info);
      return info;
    }

    // Fix up protocol string before calling KeywordURIFixup, because
    // it cares about the hostname of such URIs.
    // Prune duff protocol schemes:
    //   ://totallybroken.url.com
    //   //shorthand.url.com
    let inputHadDuffProtocol =
      uriString.startsWith("://") || uriString.startsWith("//");
    if (inputHadDuffProtocol) {
      uriString = uriString.replace(/^:?\/\//, "");
    }

    let detectSpaceInCredentials = val => {
      // Only search the first 512 chars for performance reasons.
      let firstChars = val.slice(0, 512);
      if (!firstChars.includes("@")) {
        return false;
      }
      let credentials = firstChars.split("@")[0];
      return !credentials.includes("/") && /\s/.test(credentials);
    };

    // Avoid fixing up content that looks like tab-separated values.
    // Assume that 1 tab is accidental, but more than 1 implies this is
    // supposed to be tab-separated content.
    if (
      !isCommonProtocol &&
      lazy.maxOneTabRegex.test(uriString) &&
      !detectSpaceInCredentials(untrimmedURIString)
    ) {
      let uriWithProtocol = fixupURIProtocol(uriString, fixupFlags);
      if (uriWithProtocol) {
        info.fixedURI = uriWithProtocol;
        info.fixupChangedProtocol = true;
        info.schemelessInput = Ci.nsILoadInfo.SchemelessInputTypeSchemeless;
        maybeSetAlternateFixedURI(info, fixupFlags);
        info.preferredURI = info.fixedURI;
        // Check if it's a forced visit. The user can enforce a visit by
        // appending a slash, but the string must be in a valid uri format.
        if (uriString.endsWith("/")) {
          fixupConsecutiveDotsHost(info);
          return info;
        }
      }
    }

    // Handle "www.<something>" as a URI.
    const asciiHost = info.fixedURI?.asciiHost;
    if (
      asciiHost?.length > 4 &&
      asciiHost?.startsWith("www.") &&
      asciiHost?.lastIndexOf(".") == 3
    ) {
      return info;
    }

    // Memoize the public suffix check, since it may be expensive and should
    // only run once when necessary.
    let suffixInfo;
    function checkSuffix(i) {
      if (!suffixInfo) {
        suffixInfo = checkAndFixPublicSuffix(i);
      }
      return suffixInfo;
    }

    // See if it is a keyword and whether a keyword must be fixed up.
    if (
      lazy.keywordEnabled &&
      fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP &&
      !inputHadDuffProtocol &&
      !checkSuffix(info).suffix &&
      keywordURIFixup(uriString, info, isPrivateContext)
    ) {
      fixupConsecutiveDotsHost(info);
      return info;
    }

    if (
      info.fixedURI &&
      (!info.fixupChangedProtocol || !checkSuffix(info).hasUnknownSuffix)
    ) {
      fixupConsecutiveDotsHost(info);
      return info;
    }

    // If we still haven't been able to construct a valid URI, try to force a
    // keyword match.
    if (lazy.keywordEnabled && fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP) {
      tryKeywordFixupForURIInfo(info.originalInput, info, isPrivateContext);
    }

    if (!info.preferredURI) {
      // We couldn't salvage anything.
      throw new Components.Exception(
        "Couldn't build a valid uri",
        Cr.NS_ERROR_MALFORMED_URI
      );
    }

    fixupConsecutiveDotsHost(info);
    return info;
  },

  webNavigationFlagsToFixupFlags(href, navigationFlags) {
    try {
      Services.io.newURI(href);
      // Remove LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP for valid uris.
      navigationFlags &=
        ~Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
    } catch (ex) {}

    let fixupFlags = FIXUP_FLAG_NONE;
    if (
      navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP
    ) {
      fixupFlags |= FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
    }
    if (navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS) {
      fixupFlags |= FIXUP_FLAG_FIX_SCHEME_TYPOS;
    }
    return fixupFlags;
  },

  keywordToURI(keyword, isPrivateContext) {
    if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
      // There's no search service in the content process, thus all the calls
      // from it that care about keywords conversion should go through the
      // parent process.
      throw new Components.Exception(
        "Can't invoke URIFixup in the content process",
        Cr.NS_ERROR_NOT_AVAILABLE
      );
    }
    let info = new URIFixupInfo(keyword);

    // Strip leading "?" and leading/trailing spaces from aKeyword
    if (keyword.startsWith("?")) {
      keyword = keyword.substring(1);
    }
    keyword = keyword.trim();

    if (!Services.search.hasSuccessfullyInitialized) {
      return info;
    }

    // Try falling back to the search service's default search engine
    // We must use an appropriate search engine depending on the private
    // context.
    let engine = isPrivateContext
      ? Services.search.defaultPrivateEngine
      : Services.search.defaultEngine;

    // We allow default search plugins to specify alternate parameters that are
    // specific to keyword searches.
    let responseType = null;
    if (engine.supportsResponseType("application/x-moz-keywordsearch")) {
      responseType = "application/x-moz-keywordsearch";
    }
    let submission = engine.getSubmission(keyword, responseType, "keyword");
    if (
      !submission ||
      // For security reasons (avoid redirecting to file, data, or other unsafe
      // protocols) we only allow fixup to http/https search engines.
      !HTTPISH.has(submission.uri.scheme)
    ) {
      throw new Components.Exception(
        "Invalid search submission uri",
        Cr.NS_ERROR_NOT_AVAILABLE
      );
    }
    let submissionPostDataStream = submission.postData;
    if (submissionPostDataStream) {
      info.postData = submissionPostDataStream;
    }

    info.keywordProviderName = engine.name;
    info.keywordAsSent = keyword;
    info.preferredURI = submission.uri;
    return info;
  },

  forceHttpFixup(uriString) {
    if (!uriString) {
      throw new Components.Exception(
        "Should pass a non-null uri",
        Cr.NS_ERROR_FAILURE
      );
    }

    let info = new URIFixupInfo(uriString);
    let { scheme, fixedSchemeUriString, fixupChangedProtocol } = extractScheme(
      uriString,
      FIXUP_FLAG_FIX_SCHEME_TYPOS
    );

    if (!HTTPISH.has(scheme)) {
      throw new Components.Exception(
        "Scheme should be either http or https",
        Cr.NS_ERROR_FAILURE
      );
    }

    info.fixupChangedProtocol = fixupChangedProtocol;
    info.fixedURI = Services.io.newURI(fixedSchemeUriString);

    let host = info.fixedURI.host;
    if (!HTTPISH.has(host) && host != "localhost") {
      let modifiedHostname = maybeAddPrefixAndSuffix(host);
      updateHostAndScheme(info, modifiedHostname);
      info.preferredURI = info.fixedURI;
    }

    return info;
  },

  checkHost(uri, listener, originAttributes) {
    let { displayHost, asciiHost } = uri;
    if (!displayHost) {
      throw new Components.Exception(
        "URI must have displayHost",
        Cr.NS_ERROR_FAILURE
      );
    }
    if (!asciiHost) {
      throw new Components.Exception(
        "URI must have asciiHost",
        Cr.NS_ERROR_FAILURE
      );
    }

    let isIPv4Address = host => {
      let parts = host.split(".");
      if (parts.length != 4) {
        return false;
      }
      return parts.every(part => {
        let n = parseInt(part, 10);
        return n >= 0 && n <= 255;
      });
    };

    // Avoid showing fixup information if we're suggesting an IP. Note that
    // decimal representations of IPs are normalized to a 'regular'
    // dot-separated IP address by network code, but that only happens for
    // numbers that don't overflow. Longer numbers do not get normalized,
    // but still work to access IP addresses. So for instance,
    // 1097347366913 (ff7f000001) gets resolved by using the final bytes,
    // making it the same as 7f000001, which is 127.0.0.1 aka localhost.
    // While 2130706433 would get normalized by network, 1097347366913
    // does not, and we have to deal with both cases here:
    if (isIPv4Address(asciiHost) || /^(?:\d+|0x[a-f0-9]+)$/i.test(asciiHost)) {
      return;
    }

    // For dotless hostnames, we want to ensure this ends with a '.' but don't
    // want the . showing up in the UI if we end up notifying the user, so we
    // use a separate variable.
    let lookupName = displayHost;
    if (lazy.dnsResolveFullyQualifiedNames && !lookupName.includes(".")) {
      lookupName += ".";
    }

    Services.obs.notifyObservers(null, "uri-fixup-check-dns");
    Services.dns.asyncResolve(
      lookupName,
      Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
      0,
      null,
      listener,
      Services.tm.mainThread,
      originAttributes
    );
  },

  isDomainKnown,

  _isKnownExternalProtocol(scheme) {
    if (this._trustExternalProtocolService) {
      return lazy.externalProtocolService.externalProtocolHandlerExists(scheme);
    }

    try {
      // nsIExternalProtocolService.getProtocolHandlerInfo() on Android throws
      // error due to not implemented.
      return lazy.handlerService.exists(
        lazy.externalProtocolService.getProtocolHandlerInfo(scheme)
      );
    } catch (e) {
      return false;
    }
  },

  classID: Components.ID("{c6cf88b7-452e-47eb-bdc9-86e3561648ef}"),
  QueryInterface: ChromeUtils.generateQI(["nsIURIFixup"]),
};

export function URIFixupInfo(originalInput = "") {
  this._originalInput = originalInput;
}

URIFixupInfo.prototype = {
  set consumer(consumer) {
    this._consumer = consumer || null;
  },
  get consumer() {
    return this._consumer || null;
  },

  set preferredURI(uri) {
    this._preferredURI = uri;
  },
  get preferredURI() {
    return this._preferredURI || null;
  },

  set fixedURI(uri) {
    this._fixedURI = uri;
  },
  get fixedURI() {
    return this._fixedURI || null;
  },

  set keywordProviderName(name) {
    this._keywordProviderName = name;
  },
  get keywordProviderName() {
    return this._keywordProviderName || "";
  },

  set keywordAsSent(keyword) {
    this._keywordAsSent = keyword;
  },
  get keywordAsSent() {
    return this._keywordAsSent || "";
  },

  set schemelessInput(changed) {
    this._schemelessInput = changed;
  },
  get schemelessInput() {
    return this._schemelessInput ?? Ci.nsILoadInfo.SchemelessInputTypeUnset;
  },

  set fixupChangedProtocol(changed) {
    this._fixupChangedProtocol = changed;
  },
  get fixupChangedProtocol() {
    return !!this._fixupChangedProtocol;
  },

  set fixupCreatedAlternateURI(changed) {
    this._fixupCreatedAlternateURI = changed;
  },
  get fixupCreatedAlternateURI() {
    return !!this._fixupCreatedAlternateURI;
  },

  set originalInput(input) {
    this._originalInput = input;
  },
  get originalInput() {
    return this._originalInput || "";
  },

  set postData(postData) {
    this._postData = postData;
  },
  get postData() {
    return this._postData || null;
  },

  classID: Components.ID("{33d75835-722f-42c0-89cc-44f328e56a86}"),
  QueryInterface: ChromeUtils.generateQI(["nsIURIFixupInfo"]),
};

// Helpers

/**
 * Implementation of isDomainKnown, so we don't have to go through the
 * service.
 * @param {string} asciiHost
 * @returns {boolean} whether the domain is known
 */
function isDomainKnown(asciiHost) {
  if (lazy.dnsFirstForSingleWords) {
    return true;
  }
  // Check if this domain is known as an actual
  // domain (which will prevent a keyword query)
  // Note that any processing of the host here should stay in sync with
  // code in the front-end(s) that set the pref.
  let lastDotIndex = asciiHost.lastIndexOf(".");
  if (lastDotIndex == asciiHost.length - 1) {
    asciiHost = asciiHost.substring(0, asciiHost.length - 1);
    lastDotIndex = asciiHost.lastIndexOf(".");
  }
  if (lazy.knownDomains.has(asciiHost.toLowerCase())) {
    return true;
  }
  // If there's no dot or only a leading dot we are done, otherwise we'll check
  // against the known suffixes.
  if (lastDotIndex <= 0) {
    return false;
  }
  // Don't use getPublicSuffix here, since the suffix is not in the PSL,
  // thus it couldn't tell if the suffix is made up of one or multiple
  // dot-separated parts.
  let lastPart = asciiHost.substr(lastDotIndex + 1);
  let suffixes = lazy.knownSuffixes.get(lastPart);
  if (suffixes) {
    return Array.from(suffixes).some(s => asciiHost.endsWith(s));
  }
  return false;
}

/**
 * Checks the suffix of info.fixedURI against the Public Suffix List.
 * If the suffix is unknown due to a typo this will try to fix it up.
 * @param {URIFixupInfo} info about the uri to check.
 * @note this may modify the public suffix of info.fixedURI.
 * @returns {object} result The lookup result.
 * @returns {string} result.suffix The public suffix if one can be identified.
 * @returns {boolean} result.hasUnknownSuffix True when the suffix is not in the
 *     Public Suffix List and it's not in knownSuffixes. False in the other cases.
 */
function checkAndFixPublicSuffix(info) {
  let uri = info.fixedURI;
  let asciiHost = uri?.asciiHost;

  // If the original input ends in a "。" character (U+3002), we consider the
  // input a search query if there is no valid suffix.
  // While the "。" character is equivalent to a period in domains, it's more
  // commonly used to terminate search phrases. We're preserving the historical
  // behavior of the ascii period for now, as that may be more commonly expected
  // by technical users.
  if (
    !asciiHost ||
    !asciiHost.includes(".") ||
    (asciiHost.endsWith(".") && !info.originalInput.endsWith("。")) ||
    isDomainKnown(asciiHost)
  ) {
    return { suffix: "", hasUnknownSuffix: false };
  }

  // Quick bailouts for most common cases, according to Alexa Top 1 million.
  if (
    /^\w/.test(asciiHost) &&
    (asciiHost.endsWith(".com") ||
      asciiHost.endsWith(".net") ||
      asciiHost.endsWith(".org") ||
      asciiHost.endsWith(".ru") ||
      asciiHost.endsWith(".de"))
  ) {
    return {
      suffix: asciiHost.substring(asciiHost.lastIndexOf(".") + 1),
      hasUnknownSuffix: false,
    };
  }
  try {
    let suffix = Services.eTLD.getKnownPublicSuffix(uri);
    if (suffix) {
      return { suffix, hasUnknownSuffix: false };
    }
  } catch (ex) {
    return { suffix: "", hasUnknownSuffix: false };
  }
  // Suffix is unknown, try to fix most common 3 chars TLDs typos.
  // .com is the most commonly mistyped tld, so it has more cases.
  let suffix = Services.eTLD.getPublicSuffix(uri);
  if (!suffix || lazy.numberRegex.test(suffix)) {
    return { suffix: "", hasUnknownSuffix: false };
  }
  for (let [typo, fixed] of [
    ["ocm", "com"],
    ["con", "com"],
    ["cmo", "com"],
    ["xom", "com"],
    ["vom", "com"],
    ["cpm", "com"],
    ["com'", "com"],
    ["ent", "net"],
    ["ner", "net"],
    ["nte", "net"],
    ["met", "net"],
    ["rog", "org"],
    ["ogr", "org"],
    ["prg", "org"],
    ["orh", "org"],
  ]) {
    if (suffix == typo) {
      let host = uri.host.substring(0, uri.host.length - typo.length) + fixed;
      let updatePreferredURI = info.preferredURI == info.fixedURI;
      info.fixedURI = uri.mutate().setHost(host).finalize();
      if (updatePreferredURI) {
        info.preferredURI = info.fixedURI;
      }
      return { suffix: fixed, hasUnknownSuffix: false };
    }
  }
  return { suffix: "", hasUnknownSuffix: true };
}

function tryKeywordFixupForURIInfo(uriString, fixupInfo, isPrivateContext) {
  try {
    let keywordInfo = Services.uriFixup.keywordToURI(
      uriString,
      isPrivateContext
    );
    fixupInfo.keywordProviderName = keywordInfo.keywordProviderName;
    fixupInfo.keywordAsSent = keywordInfo.keywordAsSent;
    fixupInfo.preferredURI = keywordInfo.preferredURI;
    return true;
  } catch (ex) {}
  return false;
}

/**
 * This generates an alternate fixedURI, by adding a prefix and a suffix to
 * the fixedURI host, if and only if the protocol is http. It should _never_
 * modify URIs with other protocols.
 * @param {URIFixupInfo} info an URIInfo object
 * @param {integer} fixupFlags the fixup flags
 * @returns {boolean} Whether an alternate uri was generated
 */
function maybeSetAlternateFixedURI(info, fixupFlags) {
  let uri = info.fixedURI;
  if (
    !(fixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI) ||
    // Code only works for http. Not for any other protocol including https!
    !uri.schemeIs("http") ||
    // Security - URLs with user / password info should NOT be fixed up
    uri.userPass ||
    // Don't fix up hosts with ports
    uri.port != -1
  ) {
    return false;
  }

  let oldHost = uri.host;
  // Don't create an alternate uri for localhost, because it would be confusing.
  // Ditto for 'http' and 'https' as these are frequently the result of typos, e.g.
  // 'https//foo' (note missing : ).
  if (oldHost == "localhost" || HTTPISH.has(oldHost)) {
    return false;
  }

  // Get the prefix and suffix to stick onto the new hostname. By default these
  // are www. & .com but they could be any other value, e.g. www. & .org
  let newHost = maybeAddPrefixAndSuffix(oldHost);

  if (newHost == oldHost) {
    return false;
  }

  return updateHostAndScheme(info, newHost);
}

/**
 * Try to fixup a file URI.
 * @param {string} uriString The file URI to fix.
 * @returns {nsIURI} a fixed uri or null.
 * @note FileURIFixup only returns a URI if it has to add the file: protocol.
 */
function fileURIFixup(uriString) {
  let attemptFixup = false;
  let path = uriString;
  if (AppConstants.platform == "win") {
    // Check for "\"" in the url-string, just a drive (e.g. C:),
    // or 'A:/...' where the "protocol" is also a single letter.
    attemptFixup =
      uriString.includes("\\") ||
      (uriString[1] == ":" && (uriString.length == 2 || uriString[2] == "/"));
    if (uriString[1] == ":" && uriString[2] == "/") {
      path = uriString.replace(/\//g, "\\");
    }
  } else {
    // UNIX: Check if it starts with "/" or "~".
    attemptFixup = /^[~/]/.test(uriString);
  }
  if (attemptFixup) {
    try {
      // Test if this is a valid path by trying to create a local file
      // object. The URL of that is returned if successful.
      let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
      file.initWithPath(path);
      return Services.io.newURI(
        lazy.fileProtocolHandler.getURLSpecFromActualFile(file)
      );
    } catch (ex) {
      // Not a file uri.
    }
  }
  return null;
}

/**
 * Tries to fixup a string to an nsIURI by adding the default protocol.
 *
 * Should fix things like:
 *    no-scheme.com
 *    ftp.no-scheme.com
 *    ftp4.no-scheme.com
 *    no-scheme.com/query?foo=http://www.foo.com
 *    user:pass@no-scheme.com
 *
 * @param {string} uriString The string to fixup.
 * @param {Number} fixupFlags The fixup flags to use.
 * @returns {nsIURI} an nsIURI built adding the default protocol to the string,
 *          or null if fixing was not possible.
 */
function fixupURIProtocol(uriString, fixupFlags) {
  // The longest URI scheme on the IANA list is 36 chars + 3 for ://
  let schemeChars = uriString.slice(0, 39);

  let schemePos = schemeChars.indexOf("://");
  if (schemePos == -1 || schemePos > schemeChars.search(/[:\/]/)) {
    uriString = "http://" + uriString;
  }
  try {
    return makeURIWithFixedLocalHosts(uriString, fixupFlags);
  } catch (ex) {
    // We generated an invalid uri.
  }
  return null;
}

/**
 * A thin wrapper around `newURI` that fixes up the host if it's
 * 0.0.0.0 or ::, which are no longer valid. Aims to facilitate
 * user typos and/or "broken" links output by commandline tools.
 *
 * @param {string} uriString The string to make into a URI.
 * @param {Number} fixupFlags The fixup flags to use.
 * @throws NS_ERROR_MALFORMED_URI if the uri is invalid.
 */
function makeURIWithFixedLocalHosts(uriString, fixupFlags) {
  let uri = Services.io.newURI(uriString);

  // We only want to fix up 0.0.0.0 if the URL came from the user, either
  // from the address bar or as a commandline argument (ie clicking links
  // in other applications, terminal, etc.). We can't use
  // FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP for this as that isn't normally allowed
  // for external links, and the other flags are sometimes used for
  // web-provided content. So we cheat and use the scheme typo flag.
  if (fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS && HTTPISH.has(uri.scheme)) {
    if (uri.host == "0.0.0.0") {
      uri = uri.mutate().setHost("127.0.0.1").finalize();
    } else if (uri.host == "::") {
      uri = uri.mutate().setHost("[::1]").finalize();
    }
  }
  return uri;
}

/**
 * Tries to fixup a string to a search url.
 * @param {string} uriString the string to fixup.
 * @param {URIFixupInfo} fixupInfo The fixup info object, modified in-place.
 * @param {boolean} isPrivateContext Whether this happens in a private context.
 * @param {nsIInputStream} postData optional POST data for the search
 * @returns {boolean} Whether the keyword fixup was succesful.
 */
function keywordURIFixup(uriString, fixupInfo, isPrivateContext) {
  // Here is a few examples of strings that should be searched:
  // "what is mozilla"
  // "what is mozilla?"
  // "docshell site:mozilla.org" - has a space in the origin part
  // "?site:mozilla.org - anything that begins with a question mark
  // "mozilla'.org" - Things that have a quote before the first dot/colon
  // "mozilla/test" - unknown host
  // ".mozilla", "mozilla." - starts or ends with a dot ()
  // "user@nonQualifiedHost"

  // These other strings should not be searched, because they could be URIs:
  // "www.blah.com" - Domain with a standard or known suffix
  // "knowndomain" - known domain
  // "nonQualifiedHost:8888?something" - has a port
  // "user:pass@nonQualifiedHost"
  // "blah.com."

  // We do keyword lookups if the input starts with a question mark.
  if (uriString.startsWith("?")) {
    return tryKeywordFixupForURIInfo(
      fixupInfo.originalInput,
      fixupInfo,
      isPrivateContext
    );
  }

  // Check for IPs.
  const userPassword = lazy.userPasswordRegex.exec(uriString);
  const ipString = userPassword
    ? uriString.replace(userPassword[2], "")
    : uriString;
  if (lazy.IPv4LikeRegex.test(ipString) || lazy.IPv6LikeRegex.test(ipString)) {
    return false;
  }

  // Avoid keyword lookup if we can identify a host and it's known, or ends
  // with a dot and has some path.
  // Note that if dnsFirstForSingleWords is true isDomainKnown will always
  // return true, so we can avoid checking dnsFirstForSingleWords after this.
  let asciiHost = fixupInfo.fixedURI?.asciiHost;
  if (
    asciiHost &&
    (isDomainKnown(asciiHost) ||
      (asciiHost.endsWith(".") &&
        asciiHost.indexOf(".") != asciiHost.length - 1))
  ) {
    return false;
  }

  // Avoid keyword lookup if the url seems to have password.
  if (fixupInfo.fixedURI?.password) {
    return false;
  }

  // Even if the host is unknown, avoid keyword lookup if the string has
  // uri-like characteristics, unless it looks like "user@unknownHost".
  // Note we already excluded passwords at this point.
  if (
    !isURILike(uriString, fixupInfo.fixedURI?.displayHost) ||
    (fixupInfo.fixedURI?.userPass && fixupInfo.fixedURI?.pathQueryRef === "/")
  ) {
    return tryKeywordFixupForURIInfo(
      fixupInfo.originalInput,
      fixupInfo,
      isPrivateContext
    );
  }

  return false;
}

/**
 * Mimics the logic in Services.io.extractScheme, but avoids crossing XPConnect.
 * This also tries to fixup the scheme if it was clearly mistyped.
 * @param {string} uriString the string to examine
 * @param {integer} fixupFlags The original fixup flags
 * @returns {object}
 *          scheme: a typo fixed scheme or empty string if one could not be identified
 *          fixedSchemeUriString: uri string with a typo fixed scheme
 *          fixupChangedProtocol: true if the scheme is fixed up
 */
function extractScheme(uriString, fixupFlags = FIXUP_FLAG_NONE) {
  const matches = uriString.match(lazy.possibleProtocolRegex);
  const hasColon = matches?.[2] === ":";
  const hasSlash2 = matches?.[3] === "//";

  const isFixupSchemeTypos =
    lazy.fixupSchemeTypos && fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS;

  if (
    !matches ||
    (!hasColon && !hasSlash2) ||
    (!hasColon && !isFixupSchemeTypos)
  ) {
    return {
      scheme: "",
      fixedSchemeUriString: uriString,
      fixupChangedProtocol: false,
    };
  }

  let scheme = matches[1].replace("\t", "").toLowerCase();
  let fixedSchemeUriString = uriString;

  if (isFixupSchemeTypos && hasSlash2) {
    // Fix up typos for string that user would have intented as protocol.
    const afterProtocol = uriString.substring(matches[0].length);
    fixedSchemeUriString = `${scheme}://${afterProtocol}`;
  }

  let fixupChangedProtocol = false;

  if (isFixupSchemeTypos) {
    // Fix up common scheme typos.
    // TODO: Use levenshtein distance here?
    fixupChangedProtocol = [
      ["ttp", "http"],
      ["htp", "http"],
      ["ttps", "https"],
      ["tps", "https"],
      ["ps", "https"],
      ["htps", "https"],
      ["ile", "file"],
      ["le", "file"],
    ].some(([typo, fixed]) => {
      if (scheme === typo) {
        scheme = fixed;
        fixedSchemeUriString =
          scheme + fixedSchemeUriString.substring(typo.length);
        return true;
      }
      return false;
    });
  }

  return {
    scheme,
    fixedSchemeUriString,
    fixupChangedProtocol,
  };
}

/**
 * View-source is a pseudo scheme. We're interested in fixing up the stuff
 * after it. The easiest way to do that is to call this method again with
 * the "view-source:" lopped off and then prepend it again afterwards.
 * @param {string} uriString The original string to fixup
 * @param {integer} fixupFlags The original fixup flags
 * @param {nsIInputStream} postData Optional POST data for the search
 * @returns {object} {preferredURI, postData} The fixed URI and relative postData
 * @throws if it's not possible to fixup the url
 */
function fixupViewSource(uriString, fixupFlags) {
  // We disable keyword lookup and alternate URIs so that small typos don't
  // cause us to look at very different domains.
  let newFixupFlags = fixupFlags & ~FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
  let innerURIString = uriString.substring(12).trim();

  // Prevent recursion.
  const { scheme: innerScheme } = extractScheme(innerURIString);
  if (innerScheme == "view-source") {
    throw new Components.Exception(
      "Prevent view-source recursion",
      Cr.NS_ERROR_FAILURE
    );
  }

  let info = Services.uriFixup.getFixupURIInfo(innerURIString, newFixupFlags);
  if (!info.preferredURI) {
    throw new Components.Exception(
      "Couldn't build a valid uri",
      Cr.NS_ERROR_MALFORMED_URI
    );
  }
  return {
    preferredURI: Services.io.newURI("view-source:" + info.preferredURI.spec),
    postData: info.postData,
  };
}

/**
 * Fixup the host of fixedURI if it contains consecutive dots.
 * @param {URIFixupInfo} info an URIInfo object
 */
function fixupConsecutiveDotsHost(fixupInfo) {
  const uri = fixupInfo.fixedURI;

  try {
    if (!uri?.host.includes("..")) {
      return;
    }
  } catch (e) {
    return;
  }

  try {
    const isPreferredEqualsToFixed = fixupInfo.preferredURI?.equals(uri);

    fixupInfo.fixedURI = uri
      .mutate()
      .setHost(uri.host.replace(/\.+/g, "."))
      .finalize();

    if (isPreferredEqualsToFixed) {
      fixupInfo.preferredURI = fixupInfo.fixedURI;
    }
  } catch (e) {
    if (e.result !== Cr.NS_ERROR_MALFORMED_URI) {
      throw e;
    }
  }
}

/**
 * Return whether or not given string is uri like.
 * This function returns true like following strings.
 * - ":8080"
 * - "localhost:8080" (if given host is "localhost")
 * - "/foo?bar"
 * - "/foo#bar"
 * @param {string} uriString.
 * @param {string} host.
 * @param {boolean} true if uri like.
 */
function isURILike(uriString, host) {
  const indexOfSlash = uriString.indexOf("/");
  if (
    indexOfSlash >= 0 &&
    (indexOfSlash < uriString.indexOf("?", indexOfSlash) ||
      indexOfSlash < uriString.indexOf("#", indexOfSlash))
  ) {
    return true;
  }

  if (uriString.startsWith(host)) {
    uriString = uriString.substring(host.length);
  }

  return lazy.portRegex.test(uriString);
}

/**
 * Add prefix and suffix to a hostname if both are missing.
 *
 * If the host does not start with the prefix, add the prefix to
 * the hostname.
 *
 * By default the prefix and suffix are www. and .com but they could
 * be any value e.g. www. and .org as they use the preferences
 * "browser.fixup.alternate.prefix" and "browser.fixup.alternative.suffix"
 *
 * If no changes were made, it returns an empty string.
 *
 * @param {string} oldHost.
 * @return {String} Fixed up hostname or an empty string.
 */
function maybeAddPrefixAndSuffix(oldHost) {
  let prefix = Services.prefs.getCharPref(
    "browser.fixup.alternate.prefix",
    "www."
  );
  let suffix = Services.prefs.getCharPref(
    "browser.fixup.alternate.suffix",
    ".com"
  );
  let newHost = "";
  let numDots = (oldHost.match(/\./g) || []).length;
  if (numDots == 0) {
    newHost = prefix + oldHost + suffix;
  } else if (numDots == 1) {
    if (prefix && oldHost == prefix) {
      newHost = oldHost + suffix;
    } else if (suffix && !oldHost.startsWith(prefix)) {
      newHost = prefix + oldHost;
    }
  }
  return newHost ? newHost : oldHost;
}

/**
 * Given an instance of URIFixupInfo, update its fixedURI.
 *
 * First, change the protocol to the one stored in
 * "browser.fixup.alternate.protocol".
 *
 * Then, try to update fixedURI's host to newHost.
 *
 * @param {URIFixupInfo} info.
 * @param {string} newHost.
 * @return {boolean}
 *          True, if info was updated without any errors.
 *          False, if NS_ERROR_MALFORMED_URI error.
 * @throws If a non-NS_ERROR_MALFORMED_URI error occurs.
 */
function updateHostAndScheme(info, newHost) {
  let oldHost = info.fixedURI.host;
  let oldScheme = info.fixedURI.scheme;
  try {
    info.fixedURI = info.fixedURI
      .mutate()
      .setScheme(lazy.alternateProtocol)
      .setHost(newHost)
      .finalize();
  } catch (ex) {
    if (ex.result != Cr.NS_ERROR_MALFORMED_URI) {
      throw ex;
    }
    return false;
  }
  if (oldScheme != info.fixedURI.scheme) {
    info.fixupChangedProtocol = true;
  }
  if (oldHost != info.fixedURI.host) {
    info.fixupCreatedAlternateURI = true;
  }
  return true;
}

[ Dauer der Verarbeitung: 0.5 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge