docshell/base/nsDefaultURIFixup.cpp
author Kris Maglione <maglione.k@gmail.com>
Mon, 28 Jan 2019 17:51:05 -0800
changeset 513684 d6528b01b83d071af1b6133a19d4050d7d0647ea
parent 511623 5f4630838d46dd81dadb13220a4af0da9e23a619
child 514440 add76bbdce4e452d1f921bcbc1224b30892d8393
permissions -rw-r--r--
Bug 1514594: Follow-up: Fix pageloader talos tests. r=bustage

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */

#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsIProtocolHandler.h"

#include "nsIFile.h"
#include <algorithm>

#ifdef MOZ_TOOLKIT_SEARCH
#  include "nsIBrowserSearchService.h"
#endif

#include "nsIURIFixup.h"
#include "nsIURIMutator.h"
#include "nsDefaultURIFixup.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/ipc/IPCStreamUtils.h"
#include "mozilla/ipc/URIUtils.h"
#include "mozilla/TextUtils.h"
#include "mozilla/Tokenizer.h"
#include "mozilla/Unused.h"
#include "nsIObserverService.h"
#include "nsXULAppAPI.h"

// Used to check if external protocol schemes are usable
#include "nsCExternalHandlerService.h"
#include "nsIExternalProtocolService.h"

using namespace mozilla;

/* Implementation file */
NS_IMPL_ISUPPORTS(nsDefaultURIFixup, nsIURIFixup)

static bool sInitializedPrefCaches = false;
static bool sFixTypos = true;
static bool sDNSFirstForSingleWords = false;
static bool sFixupKeywords = true;

nsDefaultURIFixup::nsDefaultURIFixup() {}

nsDefaultURIFixup::~nsDefaultURIFixup() {}

NS_IMETHODIMP
nsDefaultURIFixup::CreateExposableURI(nsIURI* aURI, nsIURI** aReturn) {
  NS_ENSURE_ARG_POINTER(aURI);
  NS_ENSURE_ARG_POINTER(aReturn);

  bool isWyciwyg = net::SchemeIsWYCIWYG(aURI);

  nsAutoCString userPass;
  aURI->GetUserPass(userPass);

  // most of the time we can just AddRef and return
  if (!isWyciwyg && userPass.IsEmpty()) {
    *aReturn = aURI;
    NS_ADDREF(*aReturn);
    return NS_OK;
  }

  // Rats, we have to massage the URI
  nsCOMPtr<nsIURI> uri;
  if (isWyciwyg) {
    nsresult rv =
        nsContentUtils::RemoveWyciwygScheme(aURI, getter_AddRefs(uri));
    NS_ENSURE_SUCCESS(rv, rv);
  } else {
    // No need to clone the URI as NS_MutateURI does that for us.
    uri = aURI;
  }

  Unused << NS_MutateURI(uri).SetUserPass(EmptyCString()).Finalize(uri);

  uri.forget(aReturn);
  return NS_OK;
}

NS_IMETHODIMP
nsDefaultURIFixup::CreateFixupURI(const nsACString& aStringURI,
                                  uint32_t aFixupFlags,
                                  nsIInputStream** aPostData, nsIURI** aURI) {
  nsCOMPtr<nsIURIFixupInfo> fixupInfo;
  nsresult rv = GetFixupURIInfo(aStringURI, aFixupFlags, aPostData,
                                getter_AddRefs(fixupInfo));
  NS_ENSURE_SUCCESS(rv, rv);

  fixupInfo->GetPreferredURI(aURI);
  return rv;
}

// Returns true if the URL contains a user:password@ or user@
static bool HasUserPassword(const nsACString& aStringURI) {
  mozilla::Tokenizer parser(aStringURI);
  mozilla::Tokenizer::Token token;

  // May start with any of "protocol:", "protocol://",  "//", "://"
  if (parser.Check(Tokenizer::TOKEN_WORD, token)) {  // Skip protocol if any
  }
  if (parser.CheckChar(':')) {  // Skip colon if found
  }
  while (parser.CheckChar('/')) {  // Skip all of the following slashes
  }

  while (parser.Next(token)) {
    if (token.Type() == Tokenizer::TOKEN_CHAR) {
      if (token.AsChar() == '/') {
        return false;
      }
      if (token.AsChar() == '@') {
        return true;
      }
    }
  }

  return false;
}

// Assume that 1 tab is accidental, but more than 1 implies this is
// supposed to be tab-separated content.
static bool MaybeTabSeparatedContent(const nsCString& aStringURI) {
  auto firstTab = aStringURI.FindChar('\t');
  return firstTab != kNotFound && aStringURI.RFindChar('\t') != firstTab;
}

NS_IMETHODIMP
nsDefaultURIFixup::GetFixupURIInfo(const nsACString& aStringURI,
                                   uint32_t aFixupFlags,
                                   nsIInputStream** aPostData,
                                   nsIURIFixupInfo** aInfo) {
  NS_ENSURE_ARG(!aStringURI.IsEmpty());

  nsresult rv;

  nsAutoCString uriString(aStringURI);

  // Eliminate embedded newlines, which single-line text fields now allow:
  uriString.StripCRLF();
  // Cleanup the empty spaces and tabs that might be on each end:
  uriString.Trim(" \t");

  NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE);

  RefPtr<nsDefaultURIFixupInfo> info = new nsDefaultURIFixupInfo(uriString);
  NS_ADDREF(*aInfo = info);

  nsCOMPtr<nsIIOService> ioService =
      do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  nsAutoCString scheme;
  ioService->ExtractScheme(aStringURI, scheme);

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

  if (scheme.EqualsLiteral("view-source")) {
    nsCOMPtr<nsIURIFixupInfo> uriInfo;
    // We disable keyword lookup and alternate URIs so that small typos don't
    // cause us to look at very different domains
    uint32_t newFixupFlags = aFixupFlags & ~FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP &
                             ~FIXUP_FLAGS_MAKE_ALTERNATE_URI;

    const uint32_t viewSourceLen = sizeof("view-source:") - 1;
    nsAutoCString innerURIString(Substring(uriString, viewSourceLen,
                                           uriString.Length() - viewSourceLen));
    // Prevent recursion:
    innerURIString.Trim(" ");
    nsAutoCString innerScheme;
    ioService->ExtractScheme(innerURIString, innerScheme);
    if (innerScheme.EqualsLiteral("view-source")) {
      return NS_ERROR_FAILURE;
    }

    rv = GetFixupURIInfo(innerURIString, newFixupFlags, aPostData,
                         getter_AddRefs(uriInfo));
    if (NS_FAILED(rv)) {
      return NS_ERROR_FAILURE;
    }
    nsAutoCString spec;
    nsCOMPtr<nsIURI> uri;
    uriInfo->GetPreferredURI(getter_AddRefs(uri));
    if (!uri) {
      return NS_ERROR_FAILURE;
    }
    uri->GetSpec(spec);
    uriString.AssignLiteral("view-source:");
    uriString.Append(spec);
  } else {
    // Check for if it is a file URL
    nsCOMPtr<nsIURI> uri;
    FileURIFixup(uriString, getter_AddRefs(uri));
    // NB: FileURIFixup only returns a URI if it had to fix the protocol to
    // do so, so passing in file:///foo/bar will not hit this path:
    if (uri) {
      uri.swap(info->mFixedURI);
      info->mPreferredURI = info->mFixedURI;
      info->mFixupChangedProtocol = true;
      return NS_OK;
    }
  }

  if (!sInitializedPrefCaches) {
    // Check if we want to fix up common scheme typos.
    rv = Preferences::AddBoolVarCache(&sFixTypos, "browser.fixup.typo.scheme",
                                      sFixTypos);
    MOZ_ASSERT(NS_SUCCEEDED(rv),
               "Failed to observe \"browser.fixup.typo.scheme\"");

    rv = Preferences::AddBoolVarCache(
        &sDNSFirstForSingleWords, "browser.fixup.dns_first_for_single_words",
        sDNSFirstForSingleWords);
    MOZ_ASSERT(
        NS_SUCCEEDED(rv),
        "Failed to observe \"browser.fixup.dns_first_for_single_words\"");

    rv = Preferences::AddBoolVarCache(&sFixupKeywords, "keyword.enabled",
                                      sFixupKeywords);
    MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to observe \"keyword.enabled\"");
    sInitializedPrefCaches = true;
  }

  // Fix up common scheme typos.
  if (sFixTypos && (aFixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS)) {
    // Fast-path for common cases.
    if (scheme.IsEmpty() || scheme.EqualsLiteral("http") ||
        scheme.EqualsLiteral("https") || scheme.EqualsLiteral("ftp") ||
        scheme.EqualsLiteral("file")) {
      // Do nothing.
    } else if (scheme.EqualsLiteral("ttp")) {
      // ttp -> http.
      uriString.ReplaceLiteral(0, 3, "http");
      scheme.AssignLiteral("http");
      info->mFixupChangedProtocol = true;
    } else if (scheme.EqualsLiteral("htp")) {
      // htp -> http.
      uriString.ReplaceLiteral(0, 3, "http");
      scheme.AssignLiteral("http");
      info->mFixupChangedProtocol = true;
    } else if (scheme.EqualsLiteral("ttps")) {
      // ttps -> https.
      uriString.ReplaceLiteral(0, 4, "https");
      scheme.AssignLiteral("https");
      info->mFixupChangedProtocol = true;
    } else if (scheme.EqualsLiteral("tps")) {
      // tps -> https.
      uriString.ReplaceLiteral(0, 3, "https");
      scheme.AssignLiteral("https");
      info->mFixupChangedProtocol = true;
    } else if (scheme.EqualsLiteral("ps")) {
      // ps -> https.
      uriString.ReplaceLiteral(0, 2, "https");
      scheme.AssignLiteral("https");
      info->mFixupChangedProtocol = true;
    } else if (scheme.EqualsLiteral("htps")) {
      // htps -> https.
      uriString.ReplaceLiteral(0, 4, "https");
      scheme.AssignLiteral("https");
      info->mFixupChangedProtocol = true;
    } else if (scheme.EqualsLiteral("ile")) {
      // ile -> file.
      uriString.ReplaceLiteral(0, 3, "file");
      scheme.AssignLiteral("file");
      info->mFixupChangedProtocol = true;
    } else if (scheme.EqualsLiteral("le")) {
      // le -> file.
      uriString.ReplaceLiteral(0, 2, "file");
      scheme.AssignLiteral("file");
      info->mFixupChangedProtocol = true;
    }
  }

  // Now we need to check whether "scheme" is something we don't
  // really know about.
  nsCOMPtr<nsIProtocolHandler> ourHandler, extHandler;

  extHandler = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "default");
  if (!scheme.IsEmpty()) {
    ioService->GetProtocolHandler(scheme.get(), getter_AddRefs(ourHandler));
  } else {
    ourHandler = extHandler;
  }

  if (ourHandler != extHandler || !PossiblyHostPortUrl(uriString)) {
    // Just try to create an URL out of it
    rv = NS_NewURI(getter_AddRefs(info->mFixedURI), uriString, nullptr);

    if (!info->mFixedURI && rv != NS_ERROR_MALFORMED_URI) {
      return rv;
    }
  }

  if (info->mFixedURI && ourHandler == extHandler && sFixupKeywords &&
      (aFixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS)) {
    nsCOMPtr<nsIExternalProtocolService> extProtService =
        do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
    if (extProtService) {
      bool handlerExists = false;
      rv = extProtService->ExternalProtocolHandlerExists(scheme.get(),
                                                         &handlerExists);
      if (NS_FAILED(rv)) {
        return rv;
      }
      // This basically means 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 instead:
      if (!handlerExists) {
        bool hasUserPassword = HasUserPassword(uriString);
        if (!hasUserPassword) {
          TryKeywordFixupForURIInfo(uriString, info, aPostData);
        } else {
          // If the given URL has a user:password we can't just pass it to the
          // external protocol handler; we'll try using it with http instead
          // later
          info->mFixedURI = nullptr;
        }
      }
    }
  }

  if (info->mFixedURI) {
    if (!info->mPreferredURI) {
      if (aFixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI) {
        info->mFixupCreatedAlternateURI = MakeAlternateURI(info->mFixedURI);
      }
      info->mPreferredURI = info->mFixedURI;
    }
    return NS_OK;
  }

  // Fix up protocol string before calling KeywordURIFixup, because
  // it cares about the hostname of such URIs:
  nsCOMPtr<nsIURI> uriWithProtocol;
  bool inputHadDuffProtocol = false;

  // Prune duff protocol schemes
  //
  //   ://totallybroken.url.com
  //   //shorthand.url.com
  //
  if (StringBeginsWith(uriString, NS_LITERAL_CSTRING("://"))) {
    uriString = StringTail(uriString, uriString.Length() - 3);
    inputHadDuffProtocol = true;
  } else if (StringBeginsWith(uriString, NS_LITERAL_CSTRING("//"))) {
    uriString = StringTail(uriString, uriString.Length() - 2);
    inputHadDuffProtocol = true;
  }

  // Note: this rv gets returned at the end of this method if we don't fix up
  // the protocol and don't do a keyword fixup after this (because the pref
  // or the flags passed might not let us).
  rv = NS_OK;
  // Avoid fixing up content that looks like tab-separated values
  if (!MaybeTabSeparatedContent(uriString)) {
    rv = FixupURIProtocol(uriString, info, getter_AddRefs(uriWithProtocol));
    if (uriWithProtocol) {
      info->mFixedURI = uriWithProtocol;
    }
  }

  // See if it is a keyword
  // Test whether keywords need to be fixed up
  if (sFixupKeywords && (aFixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP) &&
      !inputHadDuffProtocol) {
    if (NS_SUCCEEDED(KeywordURIFixup(uriString, info, aPostData)) &&
        info->mPreferredURI) {
      return NS_OK;
    }
  }

  // Did the caller want us to try an alternative URI?
  // If so, attempt to fixup http://foo into http://www.foo.com

  if (info->mFixedURI && aFixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI) {
    info->mFixupCreatedAlternateURI = MakeAlternateURI(info->mFixedURI);
  }

  if (info->mFixedURI) {
    info->mPreferredURI = info->mFixedURI;
    return NS_OK;
  }

  // If we still haven't been able to construct a valid URI, try to force a
  // keyword match.  This catches search strings with '.' or ':' in them.
  if (sFixupKeywords && (aFixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP)) {
    rv = TryKeywordFixupForURIInfo(aStringURI, info, aPostData);
  }

  return rv;
}

NS_IMETHODIMP
nsDefaultURIFixup::KeywordToURI(const nsACString& aKeyword,
                                nsIInputStream** aPostData,
                                nsIURIFixupInfo** aInfo) {
  RefPtr<nsDefaultURIFixupInfo> info = new nsDefaultURIFixupInfo(aKeyword);
  NS_ADDREF(*aInfo = info);

  if (aPostData) {
    *aPostData = nullptr;
  }
  NS_ENSURE_STATE(Preferences::GetRootBranch());

  // Strip leading "?" and leading/trailing spaces from aKeyword
  nsAutoCString keyword(aKeyword);
  if (StringBeginsWith(keyword, NS_LITERAL_CSTRING("?"))) {
    keyword.Cut(0, 1);
  }
  keyword.Trim(" ");

  if (XRE_IsContentProcess()) {
    dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
    if (!contentChild) {
      return NS_ERROR_NOT_AVAILABLE;
    }

    RefPtr<nsIInputStream> postData;
    ipc::OptionalURIParams uri;
    nsAutoString providerName;
    if (!contentChild->SendKeywordToURI(keyword, &providerName, &postData,
                                        &uri)) {
      return NS_ERROR_FAILURE;
    }

    CopyUTF8toUTF16(keyword, info->mKeywordAsSent);
    info->mKeywordProviderName = providerName;

    if (aPostData) {
      postData.forget(aPostData);
    }

    nsCOMPtr<nsIURI> temp = DeserializeURI(uri);
    info->mPreferredURI = temp.forget();
    return NS_OK;
  }

#ifdef MOZ_TOOLKIT_SEARCH
  // Try falling back to the search service's default search engine
  nsCOMPtr<nsIBrowserSearchService> searchSvc =
      do_GetService("@mozilla.org/browser/search-service;1");
  if (searchSvc) {
    nsCOMPtr<nsISearchEngine> defaultEngine;
    searchSvc->GetDefaultEngine(getter_AddRefs(defaultEngine));
    if (defaultEngine) {
      nsCOMPtr<nsISearchSubmission> submission;
      nsAutoString responseType;
      // We allow default search plugins to specify alternate
      // parameters that are specific to keyword searches.
      NS_NAMED_LITERAL_STRING(mozKeywordSearch,
                              "application/x-moz-keywordsearch");
      bool supportsResponseType = false;
      defaultEngine->SupportsResponseType(mozKeywordSearch,
                                          &supportsResponseType);
      if (supportsResponseType) {
        responseType.Assign(mozKeywordSearch);
      }

      NS_ConvertUTF8toUTF16 keywordW(keyword);
      defaultEngine->GetSubmission(keywordW, responseType,
                                   NS_LITERAL_STRING("keyword"),
                                   getter_AddRefs(submission));

      if (submission) {
        nsCOMPtr<nsIInputStream> postData;
        submission->GetPostData(getter_AddRefs(postData));
        if (aPostData) {
          postData.forget(aPostData);
        } else if (postData) {
          // The submission specifies POST data (i.e. the search
          // engine's "method" is POST), but our caller didn't allow
          // passing post data back. No point passing back a URL that
          // won't load properly.
          return NS_ERROR_FAILURE;
        }

        defaultEngine->GetName(info->mKeywordProviderName);
        info->mKeywordAsSent = keywordW;
        return submission->GetUri(getter_AddRefs(info->mPreferredURI));
      }
    }
  }
#endif

  // out of options
  return NS_ERROR_NOT_AVAILABLE;
}

// Helper to deal with passing around uri fixup stuff
nsresult nsDefaultURIFixup::TryKeywordFixupForURIInfo(
    const nsACString& aURIString, nsDefaultURIFixupInfo* aFixupInfo,
    nsIInputStream** aPostData) {
  nsCOMPtr<nsIURIFixupInfo> keywordInfo;
  nsresult rv =
      KeywordToURI(aURIString, aPostData, getter_AddRefs(keywordInfo));
  if (NS_SUCCEEDED(rv)) {
    keywordInfo->GetKeywordProviderName(aFixupInfo->mKeywordProviderName);
    keywordInfo->GetKeywordAsSent(aFixupInfo->mKeywordAsSent);
    keywordInfo->GetPreferredURI(getter_AddRefs(aFixupInfo->mPreferredURI));
  }
  return rv;
}

bool nsDefaultURIFixup::MakeAlternateURI(nsCOMPtr<nsIURI>& aURI) {
  if (!Preferences::GetRootBranch()) {
    return false;
  }
  if (!Preferences::GetBool("browser.fixup.alternate.enabled", true)) {
    return false;
  }

  // Code only works for http. Not for any other protocol including https!
  if (!net::SchemeIsHTTP(aURI)) {
    return false;
  }

  // Security - URLs with user / password info should NOT be fixed up
  nsAutoCString userpass;
  aURI->GetUserPass(userpass);
  if (!userpass.IsEmpty()) {
    return false;
  }
  // Don't fix up hosts with ports
  int32_t port;
  aURI->GetPort(&port);
  if (port != -1) {
    return false;
  }

  nsAutoCString oldHost;
  aURI->GetHost(oldHost);

  // Don't fix up 'localhost' because that's confusing:
  if (oldHost.EqualsLiteral("localhost")) {
    return false;
  }

  nsAutoCString newHost;
  // Count the dots
  int32_t numDots = 0;
  nsReadingIterator<char> iter;
  nsReadingIterator<char> iterEnd;
  oldHost.BeginReading(iter);
  oldHost.EndReading(iterEnd);
  while (iter != iterEnd) {
    if (*iter == '.') {
      numDots++;
    }
    ++iter;
  }

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

  nsAutoCString prefix("www.");
  nsAutoCString prefPrefix;
  nsresult rv =
      Preferences::GetCString("browser.fixup.alternate.prefix", prefPrefix);
  if (NS_SUCCEEDED(rv)) {
    prefix.Assign(prefPrefix);
  }

  nsAutoCString suffix(".com");
  nsAutoCString prefSuffix;
  rv = Preferences::GetCString("browser.fixup.alternate.suffix", prefSuffix);
  if (NS_SUCCEEDED(rv)) {
    suffix.Assign(prefSuffix);
  }

  if (numDots == 0) {
    newHost.Assign(prefix);
    newHost.Append(oldHost);
    newHost.Append(suffix);
  } else if (numDots == 1) {
    if (!prefix.IsEmpty() &&
        oldHost.EqualsIgnoreCase(prefix.get(), prefix.Length())) {
      newHost.Assign(oldHost);
      newHost.Append(suffix);
    } else if (!suffix.IsEmpty()) {
      newHost.Assign(prefix);
      newHost.Append(oldHost);
    } else {
      // Do nothing
      return false;
    }
  } else {
    // Do nothing
    return false;
  }

  if (newHost.IsEmpty()) {
    return false;
  }

  // Assign the new host string over the old one
  Unused << NS_MutateURI(aURI).SetHost(newHost).Finalize(aURI);

  return true;
}

nsresult nsDefaultURIFixup::FileURIFixup(const nsACString& aStringURI,
                                         nsIURI** aURI) {
  nsAutoCString uriSpecOut;

  nsresult rv = ConvertFileToStringURI(aStringURI, uriSpecOut);
  if (NS_SUCCEEDED(rv)) {
    // if this is file url, uriSpecOut is already in FS charset
    if (NS_SUCCEEDED(NS_NewURI(aURI, uriSpecOut.get(), nullptr))) {
      return NS_OK;
    }
  }
  return NS_ERROR_FAILURE;
}

nsresult nsDefaultURIFixup::ConvertFileToStringURI(const nsACString& aIn,
                                                   nsCString& aResult) {
  bool attemptFixup = false;

#if defined(XP_WIN)
  // Check for \ in the url-string or just a drive (PC)
  if (aIn.Contains('\\') ||
      (aIn.Length() == 2 && (aIn.Last() == ':' || aIn.Last() == '|'))) {
    attemptFixup = true;
  }
#elif defined(XP_UNIX)
  // Check if it starts with / (UNIX)
  if (aIn.First() == '/') {
    attemptFixup = true;
  }
#else
  // Do nothing (All others for now)
#endif

  if (attemptFixup) {
    // Test if this is a valid path by trying to create a local file
    // object. The URL of that is returned if successful.

    nsCOMPtr<nsIFile> filePath;
    nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(aIn), false,
                                  getter_AddRefs(filePath));

    if (NS_SUCCEEDED(rv)) {
      NS_GetURLSpecFromFile(filePath, aResult);
      return NS_OK;
    }
  }

  return NS_ERROR_FAILURE;
}

nsresult nsDefaultURIFixup::FixupURIProtocol(const nsACString& aURIString,
                                             nsDefaultURIFixupInfo* aFixupInfo,
                                             nsIURI** aURI) {
  nsAutoCString uriString(aURIString);
  *aURI = nullptr;

  // Add ftp:// or http:// to front of url if it has no spec
  //
  // Should fix:
  //
  //   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
  //
  int32_t schemeDelim = uriString.Find("://");
  int32_t firstDelim = uriString.FindCharInSet("/:");
  if (schemeDelim <= 0 || (firstDelim != -1 && schemeDelim > firstDelim)) {
    // find host name
    int32_t hostPos = uriString.FindCharInSet("/:?#");
    if (hostPos == -1) {
      hostPos = uriString.Length();
    }

    // extract host name
    nsAutoCString hostSpec;
    uriString.Left(hostSpec, hostPos);

    // insert url spec corresponding to host name
    uriString.InsertLiteral("http://", 0);
    aFixupInfo->mFixupChangedProtocol = true;
  }  // end if checkprotocol

  return NS_NewURI(aURI, uriString, nullptr);
}

bool nsDefaultURIFixup::PossiblyHostPortUrl(const nsACString& aUrl) {
  // Oh dear, the protocol is invalid. Test if the protocol might
  // actually be a url without a protocol:
  //
  //   http://www.faqs.org/rfcs/rfc1738.html
  //   http://www.faqs.org/rfcs/rfc2396.html
  //
  // e.g. Anything of the form:
  //
  //   <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.

  // Read the hostname which should of the form
  // [a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*:

  nsACString::const_iterator iterBegin;
  nsACString::const_iterator iterEnd;
  aUrl.BeginReading(iterBegin);
  aUrl.EndReading(iterEnd);
  nsACString::const_iterator iter = iterBegin;

  while (iter != iterEnd) {
    uint32_t chunkSize = 0;
    // Parse a chunk of the address
    while (iter != iterEnd &&
           (*iter == '-' || IsAsciiAlpha(*iter) || IsAsciiDigit(*iter))) {
      ++chunkSize;
      ++iter;
    }
    if (chunkSize == 0 || iter == iterEnd) {
      return false;
    }
    if (*iter == ':') {
      // Go onto checking the for the digits
      break;
    }
    if (*iter != '.') {
      // Whatever it is, it ain't a hostname!
      return false;
    }
    ++iter;
  }
  if (iter == iterEnd) {
    // No point continuing since there is no colon
    return false;
  }
  ++iter;

  // Count the number of digits after the colon and before the
  // next forward slash (or end of string)

  uint32_t digitCount = 0;
  while (iter != iterEnd && digitCount <= 5) {
    if (IsAsciiDigit(*iter)) {
      digitCount++;
    } else if (*iter == '/') {
      break;
    } else {
      // Whatever it is, it ain't a port!
      return false;
    }
    ++iter;
  }
  if (digitCount == 0 || digitCount > 5) {
    // No digits or more digits than a port would have.
    return false;
  }

  // Yes, it's possibly a host:port url
  return true;
}

nsresult nsDefaultURIFixup::KeywordURIFixup(const nsACString& aURIString,
                                            nsDefaultURIFixupInfo* aFixupInfo,
                                            nsIInputStream** aPostData) {
  // These are keyword formatted strings
  // "what is mozilla"
  // "what is mozilla?"
  // "docshell site:mozilla.org" - has no dot/colon in the first space-separated
  // substring
  // "?mozilla" - anything that begins with a question mark
  // "?site:mozilla.org docshell"
  // Things that have a quote before the first dot/colon
  // "mozilla" - checked against a whitelist to see if it's a host or not
  // ".mozilla", "mozilla." - ditto

  // These are not keyword formatted strings
  // "www.blah.com" - first space-separated substring contains a dot, doesn't
  // start with "?" "www.blah.com stuff" "nonQualifiedHost:80" - first
  // space-separated substring contains a colon, doesn't start with "?"
  // "nonQualifiedHost:80 args"
  // "nonQualifiedHost?"
  // "nonQualifiedHost?args"
  // "nonQualifiedHost?some args"
  // "blah.com."

  // Note: uint32_t(kNotFound) is greater than any actual location
  // in practice.  So if we cast all locations to uint32_t, then a <
  // b guarantees that either b is kNotFound and a is found, or both
  // are found and a found before b.

  uint32_t firstDotLoc = uint32_t(kNotFound);
  uint32_t lastDotLoc = uint32_t(kNotFound);
  uint32_t firstColonLoc = uint32_t(kNotFound);
  uint32_t firstQuoteLoc = uint32_t(kNotFound);
  uint32_t firstSpaceLoc = uint32_t(kNotFound);
  uint32_t firstQMarkLoc = uint32_t(kNotFound);
  uint32_t lastLSBracketLoc = uint32_t(kNotFound);
  uint32_t lastSlashLoc = uint32_t(kNotFound);
  uint32_t pos = 0;
  uint32_t foundDots = 0;
  uint32_t foundColons = 0;
  uint32_t foundDigits = 0;
  uint32_t foundRSBrackets = 0;
  bool looksLikeIpv6 = true;
  bool hasAsciiAlpha = false;

  nsACString::const_iterator iterBegin;
  nsACString::const_iterator iterEnd;
  aURIString.BeginReading(iterBegin);
  aURIString.EndReading(iterEnd);
  nsACString::const_iterator iter = iterBegin;

  while (iter != iterEnd) {
    if (pos >= 1 && foundRSBrackets == 0) {
      if (!(lastLSBracketLoc == 0 &&
            (*iter == ':' || *iter == '.' || *iter == ']' ||
             (*iter >= 'a' && *iter <= 'f') || (*iter >= 'A' && *iter <= 'F') ||
             IsAsciiDigit(*iter)))) {
        looksLikeIpv6 = false;
      }
    }

    // If we're at the end of the string or this is the first slash,
    // check if the thing before the slash looks like ipv4:
    if ((iterEnd - iter == 1 ||
         (lastSlashLoc == uint32_t(kNotFound) && *iter == '/')) &&
        // Need 2 or 3 dots + only digits
        (foundDots == 2 || foundDots == 3) &&
        // and they should be all that came before now:
        (foundDots + foundDigits == pos ||
         // or maybe there was also exactly 1 colon that came after the last
         // dot, and the digits, dots and colon were all that came before now:
         (foundColons == 1 && firstColonLoc > lastDotLoc &&
          foundDots + foundDigits + foundColons == pos))) {
      // Hurray, we got ourselves some ipv4!
      // At this point, there's no way we will do a keyword lookup, so just bail
      // immediately:
      return NS_OK;
    }

    if (*iter == '.') {
      ++foundDots;
      lastDotLoc = pos;
      if (firstDotLoc == uint32_t(kNotFound)) {
        firstDotLoc = pos;
      }
    } else if (*iter == ':') {
      ++foundColons;
      if (firstColonLoc == uint32_t(kNotFound)) {
        firstColonLoc = pos;
      }
    } else if (*iter == ' ' && firstSpaceLoc == uint32_t(kNotFound)) {
      firstSpaceLoc = pos;
    } else if (*iter == '?' && firstQMarkLoc == uint32_t(kNotFound)) {
      firstQMarkLoc = pos;
    } else if ((*iter == '\'' || *iter == '"') &&
               firstQuoteLoc == uint32_t(kNotFound)) {
      firstQuoteLoc = pos;
    } else if (*iter == '[') {
      lastLSBracketLoc = pos;
    } else if (*iter == ']') {
      foundRSBrackets++;
    } else if (*iter == '/') {
      lastSlashLoc = pos;
    } else if (IsAsciiAlpha(*iter)) {
      hasAsciiAlpha = true;
    } else if (IsAsciiDigit(*iter)) {
      ++foundDigits;
    }

    pos++;
    iter++;
  }

  if (lastLSBracketLoc > 0 || foundRSBrackets != 1) {
    looksLikeIpv6 = false;
  }

  // If there are only colons and only hexadecimal characters ([a-z][0-9])
  // enclosed in [], then don't do a keyword lookup
  if (looksLikeIpv6) {
    return NS_OK;
  }

  nsAutoCString asciiHost;
  nsAutoCString displayHost;

  bool isValidHost =
      aFixupInfo->mFixedURI &&
      NS_SUCCEEDED(aFixupInfo->mFixedURI->GetAsciiHost(asciiHost)) &&
      !asciiHost.IsEmpty();

  bool isValidDisplayHost =
      aFixupInfo->mFixedURI &&
      NS_SUCCEEDED(aFixupInfo->mFixedURI->GetDisplayHost(displayHost)) &&
      !displayHost.IsEmpty();

  nsresult rv = NS_OK;
  // We do keyword lookups if a space or quote preceded the dot, colon
  // or question mark (or if the latter is not found, or if the input starts
  // with a question mark)
  if (((firstSpaceLoc < firstDotLoc || firstQuoteLoc < firstDotLoc) &&
       (firstSpaceLoc < firstColonLoc || firstQuoteLoc < firstColonLoc) &&
       (firstSpaceLoc < firstQMarkLoc || firstQuoteLoc < firstQMarkLoc)) ||
      firstQMarkLoc == 0) {
    rv = TryKeywordFixupForURIInfo(aFixupInfo->mOriginalInput, aFixupInfo,
                                   aPostData);
    // ... or when the asciiHost is the same as displayHost and there are no
    // characters from [a-z][A-Z]
  } else if (isValidHost && isValidDisplayHost && !hasAsciiAlpha &&
             asciiHost.EqualsIgnoreCase(displayHost.get())) {
    if (!sDNSFirstForSingleWords) {
      rv = TryKeywordFixupForURIInfo(aFixupInfo->mOriginalInput, aFixupInfo,
                                     aPostData);
    }
  }
  // ... or if there is no question mark or colon, and there is either no
  // dot, or exactly 1 and it is the first or last character of the input:
  else if ((firstDotLoc == uint32_t(kNotFound) ||
            (foundDots == 1 &&
             (firstDotLoc == 0 || firstDotLoc == aURIString.Length() - 1))) &&
           firstColonLoc == uint32_t(kNotFound) &&
           firstQMarkLoc == uint32_t(kNotFound)) {
    if (isValidHost && IsDomainWhitelisted(asciiHost, firstDotLoc)) {
      return NS_OK;
    }

    // ... unless there are no dots, and a slash, and alpha characters, and
    // this is a valid host:
    if (firstDotLoc == uint32_t(kNotFound) &&
        lastSlashLoc != uint32_t(kNotFound) && hasAsciiAlpha && isValidHost) {
      return NS_OK;
    }

    // If we get here, we don't have a valid URI, or we did but the
    // host is not whitelisted, so we do a keyword search *anyway*:
    rv = TryKeywordFixupForURIInfo(aFixupInfo->mOriginalInput, aFixupInfo,
                                   aPostData);
  }
  return rv;
}

bool nsDefaultURIFixup::IsDomainWhitelisted(const nsACString& aAsciiHost,
                                            const uint32_t aDotLoc) {
  if (sDNSFirstForSingleWords) {
    return true;
  }
  // Check if this domain is whitelisted as an actual
  // domain (which will prevent a keyword query)
  // NB: any processing of the host here should stay in sync with
  // code in the front-end(s) that set the pref.

  nsAutoCString pref("browser.fixup.domainwhitelist.");

  if (aDotLoc == aAsciiHost.Length() - 1) {
    pref.Append(Substring(aAsciiHost, 0, aAsciiHost.Length() - 1));
  } else {
    pref.Append(aAsciiHost);
  }

  return Preferences::GetBool(pref.get(), false);
}

NS_IMETHODIMP
nsDefaultURIFixup::IsDomainWhitelisted(const nsACString& aDomain,
                                       const uint32_t aDotLoc, bool* aResult) {
  *aResult = IsDomainWhitelisted(aDomain, aDotLoc);
  return NS_OK;
}

/* Implementation of nsIURIFixupInfo */
NS_IMPL_ISUPPORTS(nsDefaultURIFixupInfo, nsIURIFixupInfo)

nsDefaultURIFixupInfo::nsDefaultURIFixupInfo(const nsACString& aOriginalInput)
    : mFixupChangedProtocol(false), mFixupCreatedAlternateURI(false) {
  mOriginalInput = aOriginalInput;
}

nsDefaultURIFixupInfo::~nsDefaultURIFixupInfo() {}

NS_IMETHODIMP
nsDefaultURIFixupInfo::GetConsumer(nsISupports** aConsumer) {
  *aConsumer = mConsumer;
  NS_IF_ADDREF(*aConsumer);
  return NS_OK;
}

NS_IMETHODIMP
nsDefaultURIFixupInfo::SetConsumer(nsISupports* aConsumer) {
  mConsumer = aConsumer;
  return NS_OK;
}

NS_IMETHODIMP
nsDefaultURIFixupInfo::GetPreferredURI(nsIURI** aPreferredURI) {
  *aPreferredURI = mPreferredURI;
  NS_IF_ADDREF(*aPreferredURI);
  return NS_OK;
}

NS_IMETHODIMP
nsDefaultURIFixupInfo::GetFixedURI(nsIURI** aFixedURI) {
  *aFixedURI = mFixedURI;
  NS_IF_ADDREF(*aFixedURI);
  return NS_OK;
}

NS_IMETHODIMP
nsDefaultURIFixupInfo::GetKeywordProviderName(nsAString& aResult) {
  aResult = mKeywordProviderName;
  return NS_OK;
}

NS_IMETHODIMP
nsDefaultURIFixupInfo::GetKeywordAsSent(nsAString& aResult) {
  aResult = mKeywordAsSent;
  return NS_OK;
}

NS_IMETHODIMP
nsDefaultURIFixupInfo::GetFixupChangedProtocol(bool* aResult) {
  *aResult = mFixupChangedProtocol;
  return NS_OK;
}

NS_IMETHODIMP
nsDefaultURIFixupInfo::GetFixupCreatedAlternateURI(bool* aResult) {
  *aResult = mFixupCreatedAlternateURI;
  return NS_OK;
}

NS_IMETHODIMP
nsDefaultURIFixupInfo::GetOriginalInput(nsACString& aResult) {
  aResult = mOriginalInput;
  return NS_OK;
}