tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsHTTPSOnlyUtils.cpp (45659B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "nsHTTPSOnlyUtils.h"
      8 
      9 #include "mozilla/ClearOnShutdown.h"
     10 #include "mozilla/Components.h"
     11 #include "mozilla/NullPrincipal.h"
     12 #include "mozilla/OriginAttributes.h"
     13 #include "mozilla/StaticPrefs_dom.h"
     14 #include "mozilla/TimeStamp.h"
     15 #include "mozilla/glean/DomSecurityMetrics.h"
     16 #include "mozilla/net/DNS.h"
     17 #include "nsContentUtils.h"
     18 #include "nsDNSPrefetch.h"
     19 #include "nsIEffectiveTLDService.h"
     20 #include "nsIHttpChannel.h"
     21 #include "nsIHttpChannelInternal.h"
     22 #include "nsIHttpsOnlyModePermission.h"
     23 #include "nsILoadInfo.h"
     24 #include "nsIPermissionManager.h"
     25 #include "nsIPrincipal.h"
     26 #include "nsIRedirectHistoryEntry.h"
     27 #include "nsIScriptError.h"
     28 #include "nsIURIMutator.h"
     29 #include "nsNetUtil.h"
     30 #include "prnetdb.h"
     31 
     32 /* static */
     33 nsHTTPSOnlyUtils::UpgradeMode nsHTTPSOnlyUtils::GetUpgradeMode(
     34    bool aFromPrivateWindow,
     35    nsILoadInfo::SchemelessInputType aSchemelessInputType) {
     36  if (mozilla::StaticPrefs::dom_security_https_only_mode() ||
     37      (aFromPrivateWindow &&
     38       mozilla::StaticPrefs::dom_security_https_only_mode_pbm())) {
     39    return nsHTTPSOnlyUtils::HTTPS_ONLY_MODE;
     40  }
     41 
     42  if (mozilla::StaticPrefs::dom_security_https_first() ||
     43      (aFromPrivateWindow &&
     44       mozilla::StaticPrefs::dom_security_https_first_pbm())) {
     45    return nsHTTPSOnlyUtils::HTTPS_FIRST_MODE;
     46  }
     47 
     48  if (mozilla::StaticPrefs::dom_security_https_first_schemeless() &&
     49      aSchemelessInputType == nsILoadInfo::SchemelessInputTypeSchemeless) {
     50    return nsHTTPSOnlyUtils::SCHEMELESS_HTTPS_FIRST_MODE;
     51  }
     52 
     53  return NO_UPGRADE_MODE;
     54 }
     55 
     56 /* static */
     57 nsHTTPSOnlyUtils::UpgradeMode nsHTTPSOnlyUtils::GetUpgradeMode(
     58    nsILoadInfo* aLoadInfo) {
     59  bool isPrivateWin = aLoadInfo->GetOriginAttributes().IsPrivateBrowsing();
     60  return GetUpgradeMode(isPrivateWin, aLoadInfo->GetSchemelessInput());
     61 }
     62 
     63 /* static */
     64 void nsHTTPSOnlyUtils::PotentiallyFireHttpRequestToShortenTimout(
     65    mozilla::net::DocumentLoadListener* aDocumentLoadListener) {
     66  // only send http background request to counter timeouts if the
     67  // pref allows us to do that.
     68  if (!mozilla::StaticPrefs::
     69          dom_security_https_only_mode_send_http_background_request()) {
     70    return;
     71  }
     72 
     73  nsCOMPtr<nsIChannel> channel = aDocumentLoadListener->GetChannel();
     74  if (!channel) {
     75    return;
     76  }
     77 
     78  nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
     79  UpgradeMode upgradeMode = GetUpgradeMode(loadInfo);
     80 
     81  // if neither HTTPS-Only nor HTTPS-First mode is enabled, then there is
     82  // nothing to do here.
     83  if (upgradeMode == NO_UPGRADE_MODE) {
     84    return;
     85  }
     86 
     87  // if we are not dealing with a top-level load, then there is nothing to do
     88  // here.
     89  if (loadInfo->GetExternalContentPolicyType() !=
     90      ExtContentPolicy::TYPE_DOCUMENT) {
     91    return;
     92  }
     93 
     94  // if the load is exempt, then there is nothing to do here.
     95  uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
     96  if (httpsOnlyStatus & nsILoadInfo::nsILoadInfo::HTTPS_ONLY_EXEMPT) {
     97    return;
     98  }
     99 
    100  // if it's not an http channel, then there is nothing to do here.
    101  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
    102  if (!httpChannel) {
    103    return;
    104  }
    105 
    106  // if it's not a GET method, then there is nothing to do here either.
    107  nsAutoCString method;
    108  (void)httpChannel->GetRequestMethod(method);
    109  if (!method.EqualsLiteral("GET")) {
    110    return;
    111  }
    112 
    113  // if it's already an https channel, then there is nothing to do here.
    114  nsCOMPtr<nsIURI> channelURI;
    115  channel->GetURI(getter_AddRefs(channelURI));
    116  if (!channelURI->SchemeIs("http")) {
    117    return;
    118  }
    119 
    120  // Upgrades for custom ports may be disabled in that case
    121  // HTTPS-First only applies to standard ports but HTTPS-Only brute forces
    122  // all http connections to be https and overrules HTTPS-First. In case
    123  // HTTPS-First is enabled, but HTTPS-Only is not enabled, we might return
    124  // early if attempting to send a background request to a non standard port.
    125  if (!mozilla::StaticPrefs::dom_security_https_first_for_custom_ports() &&
    126      (upgradeMode == HTTPS_FIRST_MODE ||
    127       upgradeMode == SCHEMELESS_HTTPS_FIRST_MODE)) {
    128    int32_t port = 0;
    129    nsresult rv = channelURI->GetPort(&port);
    130    int defaultPortforScheme = NS_GetDefaultPort("http");
    131    if (NS_SUCCEEDED(rv) && port != defaultPortforScheme && port != -1) {
    132      return;
    133    }
    134  }
    135 
    136  // Check for general exceptions
    137  if (OnionException(channelURI) || LoopbackOrLocalException(channelURI)) {
    138    return;
    139  }
    140 
    141  RefPtr<nsIRunnable> task =
    142      new TestHTTPAnswerRunnable(channelURI, aDocumentLoadListener);
    143  NS_DispatchToMainThread(task.forget());
    144 }
    145 
    146 /* static */
    147 bool nsHTTPSOnlyUtils::ShouldUpgradeRequest(nsIURI* aURI,
    148                                            nsILoadInfo* aLoadInfo) {
    149  // 1. Check if the HTTPS-Only Mode is even enabled, before we do anything else
    150  if (GetUpgradeMode(aLoadInfo) != HTTPS_ONLY_MODE) {
    151    return false;
    152  }
    153 
    154  // 2. Check for general exceptions
    155  if (OnionException(aURI) || LoopbackOrLocalException(aURI)) {
    156    return false;
    157  }
    158 
    159  // 3. Check if NoUpgrade-flag is set in LoadInfo
    160  uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
    161  if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) {
    162    AutoTArray<nsString, 1> params = {
    163        NS_ConvertUTF8toUTF16(aURI->GetSpecOrDefault())};
    164    nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyNoUpgradeException", params,
    165                                         nsIScriptError::infoFlag, aLoadInfo,
    166                                         aURI);
    167    return false;
    168  }
    169 
    170  // All subresources of an exempt triggering principal are also exempt
    171  ExtContentPolicyType contentType = aLoadInfo->GetExternalContentPolicyType();
    172  if (contentType != ExtContentPolicy::TYPE_DOCUMENT) {
    173    if (!aLoadInfo->TriggeringPrincipal()->IsSystemPrincipal() &&
    174        TestIfPrincipalIsExempt(aLoadInfo->TriggeringPrincipal(),
    175                                HTTPS_ONLY_MODE)) {
    176      return false;
    177    }
    178  }
    179 
    180  // We can not upgrade "Save-As" downloads, since we have no way of detecting
    181  // if the upgrade failed (Bug 1674859). For now we will just allow the
    182  // download, since there will still be a visual warning about the download
    183  // being insecure.
    184  if (contentType == ExtContentPolicyType::TYPE_SAVEAS_DOWNLOAD) {
    185    return false;
    186  }
    187 
    188  // We can upgrade the request - let's log it to the console
    189  // Appending an 's' to the scheme for the logging. (http -> https)
    190  nsAutoCString scheme;
    191  aURI->GetScheme(scheme);
    192  scheme.AppendLiteral("s");
    193  NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
    194  NS_ConvertUTF8toUTF16 reportScheme(scheme);
    195 
    196  bool isSpeculative = aLoadInfo->GetExternalContentPolicyType() ==
    197                       ExtContentPolicy::TYPE_SPECULATIVE;
    198  AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
    199  nsHTTPSOnlyUtils::LogLocalizedString(
    200      isSpeculative ? "HTTPSOnlyUpgradeSpeculativeConnection"
    201                    : "HTTPSOnlyUpgradeRequest",
    202      params, nsIScriptError::warningFlag, aLoadInfo, aURI);
    203 
    204  // If the status was not determined before, we now indicate that the request
    205  // will get upgraded, but no event-listener has been registered yet.
    206  if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UNINITIALIZED) {
    207    httpsOnlyStatus ^= nsILoadInfo::HTTPS_ONLY_UNINITIALIZED;
    208    httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED;
    209    aLoadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
    210  }
    211  return true;
    212 }
    213 
    214 /* static */
    215 bool nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(nsIURI* aURI,
    216                                              nsILoadInfo* aLoadInfo) {
    217  // 1. Check if the HTTPS-Only Mode is even enabled, before we do anything else
    218  if (GetUpgradeMode(aLoadInfo) != HTTPS_ONLY_MODE) {
    219    return false;
    220  }
    221 
    222  // 2. Check for general exceptions
    223  if (OnionException(aURI) || LoopbackOrLocalException(aURI)) {
    224    return false;
    225  }
    226 
    227  // 3. Check if NoUpgrade-flag is set in LoadInfo
    228  uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
    229  if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) {
    230    // Let's log to the console, that we didn't upgrade this request
    231    AutoTArray<nsString, 1> params = {
    232        NS_ConvertUTF8toUTF16(aURI->GetSpecOrDefault())};
    233    nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyNoUpgradeException", params,
    234                                         nsIScriptError::infoFlag, aLoadInfo,
    235                                         aURI);
    236    return false;
    237  }
    238 
    239  // All subresources of an exempt triggering principal are also exempt.
    240  if (!aLoadInfo->TriggeringPrincipal()->IsSystemPrincipal() &&
    241      TestIfPrincipalIsExempt(aLoadInfo->TriggeringPrincipal(),
    242                              HTTPS_ONLY_MODE)) {
    243    return false;
    244  }
    245 
    246  // We can upgrade the request - let's log it to the console
    247  // Appending an 's' to the scheme for the logging. (ws -> wss)
    248  nsAutoCString scheme;
    249  aURI->GetScheme(scheme);
    250  scheme.AppendLiteral("s");
    251  NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
    252  NS_ConvertUTF8toUTF16 reportScheme(scheme);
    253 
    254  AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
    255  nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyUpgradeRequest", params,
    256                                       nsIScriptError::warningFlag, aLoadInfo,
    257                                       aURI);
    258  return true;
    259 }
    260 
    261 /* static */
    262 bool nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
    263    nsIURI* aOldURI, nsIURI* aNewURI, nsILoadInfo* aLoadInfo,
    264    const mozilla::EnumSet<UpgradeDowngradeEndlessLoopOptions>& aOptions) {
    265  // 1. Check if the HTTPS-Only/HTTPS-First is even enabled, before doing
    266  // anything else
    267  UpgradeMode upgradeMode = GetUpgradeMode(aLoadInfo);
    268  bool enforceForHTTPSOnlyMode =
    269      upgradeMode == HTTPS_ONLY_MODE &&
    270      aOptions.contains(
    271          UpgradeDowngradeEndlessLoopOptions::EnforceForHTTPSOnlyMode);
    272  bool enforceForHTTPSFirstMode =
    273      upgradeMode == HTTPS_FIRST_MODE &&
    274      aOptions.contains(
    275          UpgradeDowngradeEndlessLoopOptions::EnforceForHTTPSFirstMode);
    276  bool enforceForHTTPSRR =
    277      aOptions.contains(UpgradeDowngradeEndlessLoopOptions::EnforceForHTTPSRR);
    278  if (!enforceForHTTPSOnlyMode && !enforceForHTTPSFirstMode &&
    279      !enforceForHTTPSRR) {
    280    return false;
    281  }
    282 
    283  // 2. Check if the upgrade downgrade pref even wants us to try to break the
    284  // cycle. In the case that HTTPS RR is presented, we ignore this pref.
    285  if (!mozilla::StaticPrefs::
    286          dom_security_https_only_mode_break_upgrade_downgrade_endless_loop() &&
    287      !enforceForHTTPSRR) {
    288    return false;
    289  }
    290 
    291  // 3. If it's not a top-level load, then there is nothing to do here either.
    292  if (aLoadInfo->GetExternalContentPolicyType() !=
    293      ExtContentPolicy::TYPE_DOCUMENT) {
    294    return false;
    295  }
    296 
    297  // 4. If the load is exempt, then it's defintely not related to https-only
    298  uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
    299  if ((httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) &&
    300      !enforceForHTTPSRR) {
    301    return false;
    302  }
    303 
    304  // 5. Check HTTP 3xx redirects. If the Principal that kicked off the
    305  // load/redirect is not https, then it's definitely not a redirect cause by
    306  // https-only. If the scheme of the principal however is https and the
    307  // asciiHost of the URI to be loaded and the asciiHost of the Principal are
    308  // identical, then we are dealing with an upgrade downgrade scenario and we
    309  // have to break the cycle.
    310  if (IsHttpDowngrade(aOldURI, aNewURI)) {
    311    return true;
    312  }
    313 
    314  // TODO(Bug 1896691): Don't depend on triggeringPrincipal for JS/Meta
    315  //     redirects. Call this function at the correct places instead
    316 
    317  // 6. Bug 1725026: Disable JS/Meta loop detection when the load was triggered
    318  // by a user gesture.
    319  if (aLoadInfo->GetHasValidUserGestureActivation()) {
    320    return false;
    321  }
    322 
    323  // 7. Meta redirects and JS based redirects (win.location). We detect them
    324  // during the https upgrade internal redirect.
    325  nsCOMPtr<nsIPrincipal> triggeringPrincipal = aLoadInfo->TriggeringPrincipal();
    326  if (!triggeringPrincipal->SchemeIs("https")) {
    327    return false;
    328  }
    329 
    330  // We detect Meta and JS based redirects during the upgrade. Check whether
    331  // we are currently in an upgrade situation here.
    332  if (!IsHttpDowngrade(aNewURI, aOldURI)) {
    333    return false;
    334  }
    335  // If we upgrade to the same URI that the load is origining from we are
    336  // creating a redirect loop.
    337  bool isLoop = false;
    338  nsresult rv = triggeringPrincipal->EqualsURI(aNewURI, &isLoop);
    339  NS_ENSURE_SUCCESS(rv, false);
    340  return isLoop;
    341 }
    342 
    343 /* static */
    344 bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI,
    345                                                      nsILoadInfo* aLoadInfo) {
    346  MOZ_ASSERT(aURI->SchemeIs("http"), "how come the request is not 'http'?");
    347 
    348  // 1. Check if HTTPS-First Mode is enabled
    349  UpgradeMode upgradeMode = GetUpgradeMode(aLoadInfo);
    350  if (upgradeMode != HTTPS_FIRST_MODE &&
    351      upgradeMode != SCHEMELESS_HTTPS_FIRST_MODE) {
    352    return false;
    353  }
    354  // 2. HTTPS-First only upgrades top-level loads (and speculative connections)
    355  ExtContentPolicyType contentType = aLoadInfo->GetExternalContentPolicyType();
    356  if (contentType != ExtContentPolicy::TYPE_DOCUMENT &&
    357      contentType != ExtContentPolicy::TYPE_SPECULATIVE) {
    358    return false;
    359  }
    360 
    361  // 3. Check for general exceptions
    362  if (OnionException(aURI) ||
    363      (!mozilla::StaticPrefs::dom_security_https_first_for_local_addresses() &&
    364       LoopbackOrLocalException(aURI)) ||
    365      (!mozilla::StaticPrefs::dom_security_https_first_for_unknown_suffixes() &&
    366       UnknownPublicSuffixException(aURI))) {
    367    return false;
    368  }
    369 
    370  // 4. Don't upgrade if upgraded previously or exempt from upgrades
    371  uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
    372  if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) {
    373    return false;
    374  }
    375 
    376  // 5. Don't upgrade if the user explicitly provided a scheme
    377  if (aLoadInfo->GetSchemelessInput() ==
    378          nsILoadInfo::SchemelessInputTypeSchemeful &&
    379      aLoadInfo->GetExternalContentPolicyType() !=
    380          ExtContentPolicy::TYPE_SPECULATIVE &&
    381      aURI->SchemeIs("http")) {
    382    AddHTTPSFirstException(aURI, aLoadInfo);
    383    return false;
    384  }
    385 
    386  // 6. Make sure HTTPS-First does not upgrade custom ports when it is disabled
    387  if (!mozilla::StaticPrefs::dom_security_https_first_for_custom_ports()) {
    388    int defaultPortforScheme = NS_GetDefaultPort("http");
    389    // If no port is specified, then the API returns -1 to indicate the default
    390    // port.
    391    int32_t port = 0;
    392    nsresult rv = aURI->GetPort(&port);
    393    NS_ENSURE_SUCCESS(rv, false);
    394    if (port != defaultPortforScheme && port != -1) {
    395      return false;
    396    }
    397  }
    398 
    399  // 7. Do not upgrade requests other than GET
    400  if (!aLoadInfo->GetIsGETRequest()) {
    401    return false;
    402  }
    403 
    404  // We can upgrade the request - let's log to the console and set the status
    405  // so we know that we upgraded the request.
    406  if (upgradeMode == SCHEMELESS_HTTPS_FIRST_MODE) {
    407    nsAutoCString urlCString;
    408    aURI->GetSpec(urlCString);
    409    NS_ConvertUTF8toUTF16 urlString(urlCString);
    410 
    411    AutoTArray<nsString, 1> params = {urlString};
    412    nsHTTPSOnlyUtils::LogLocalizedString("HTTPSFirstSchemeless", params,
    413                                         nsIScriptError::warningFlag, aLoadInfo,
    414                                         aURI, true);
    415  } else {
    416    nsAutoCString scheme;
    417 
    418    aURI->GetScheme(scheme);
    419    scheme.AppendLiteral("s");
    420    NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
    421    NS_ConvertUTF8toUTF16 reportScheme(scheme);
    422 
    423    bool isSpeculative = contentType == ExtContentPolicy::TYPE_SPECULATIVE;
    424    AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
    425    nsHTTPSOnlyUtils::LogLocalizedString(
    426        isSpeculative ? "HTTPSOnlyUpgradeSpeculativeConnection"
    427                      : "HTTPSOnlyUpgradeRequest",
    428        params, nsIScriptError::warningFlag, aLoadInfo, aURI, true);
    429  }
    430 
    431  // Set flag so we know that we upgraded the request
    432  httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST;
    433  aLoadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
    434  return true;
    435 }
    436 
    437 /* static */
    438 already_AddRefed<nsIURI>
    439 nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(
    440    mozilla::net::DocumentLoadListener* aDocumentLoadListener,
    441    nsresult aStatus) {
    442  nsCOMPtr<nsIChannel> channel = aDocumentLoadListener->GetChannel();
    443  nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
    444  uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
    445  // Only downgrade if we this request was upgraded using HTTPS-First Mode
    446  if (!(httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST)) {
    447    return nullptr;
    448  }
    449  // Once loading is in progress we set that flag so that timeout counter
    450  // measures do not kick in.
    451  loadInfo->SetHttpsOnlyStatus(
    452      httpsOnlyStatus | nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS);
    453 
    454  nsresult status = aStatus;
    455  // Since 4xx and 5xx errors return NS_OK instead of NS_ERROR_*, we need
    456  // to check each NS_OK for those errors.
    457  // Only downgrade an NS_OK status if it is an 4xx or 5xx error.
    458  if (NS_SUCCEEDED(aStatus)) {
    459    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
    460    // If no httpChannel exists we have nothing to do here.
    461    if (!httpChannel) {
    462      return nullptr;
    463    }
    464    uint32_t responseStatus = 0;
    465    if (NS_FAILED(httpChannel->GetResponseStatus(&responseStatus))) {
    466      return nullptr;
    467    }
    468 
    469    // In case we found one 4xx or 5xx error we need to log it later on,
    470    // for that reason we flip the nsresult 'status' from 'NS_OK' to the
    471    // corresponding NS_ERROR_*.
    472    // To do so we convert the response status to  an nsresult error
    473    // Every NS_OK that is NOT an 4xx or 5xx error code won't get downgraded.
    474    if (responseStatus >= 400 && responseStatus < 600) {
    475      // HttpProxyResponseToErrorCode() maps 400 and 404 on
    476      // the same error as a 500 status which would lead to no downgrade
    477      // later on. For that reason we explicit filter for 400 and 404 status
    478      // codes to log them correctly and to downgrade them if possible.
    479      switch (responseStatus) {
    480        case 400:
    481          status = NS_ERROR_PROXY_BAD_REQUEST;
    482          break;
    483        case 404:
    484          status = NS_ERROR_PROXY_NOT_FOUND;
    485          break;
    486        default:
    487          status = mozilla::net::HttpProxyResponseToErrorCode(responseStatus);
    488          break;
    489      }
    490    }
    491    if (NS_SUCCEEDED(status)) {
    492      return nullptr;
    493    }
    494  }
    495 
    496  // We're only downgrading if it's possible that the error was
    497  // caused by the upgrade.
    498  nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal(
    499      do_QueryInterface(channel));
    500  if (!httpChannelInternal) {
    501    return nullptr;
    502  }
    503  bool proxyUsed = false;
    504  nsresult rv = httpChannelInternal->GetIsProxyUsed(&proxyUsed);
    505  MOZ_ASSERT(NS_SUCCEEDED(rv));
    506  if (!(proxyUsed && status == nsresult::NS_ERROR_UNKNOWN_HOST)
    507      // When a proxy returns an error code it is converted by
    508      // HttpProxyResponseToErrorCode. We do want to downgrade in
    509      // that case. If the host is actually unreachable this will
    510      // show the same error page, but technically for the HTTP
    511      // site not the HTTPS site.
    512      && HttpsUpgradeUnrelatedErrorCode(status)) {
    513    return nullptr;
    514  }
    515 
    516  nsCOMPtr<nsIURI> uri;
    517  rv = channel->GetURI(getter_AddRefs(uri));
    518  NS_ENSURE_SUCCESS(rv, nullptr);
    519 
    520  nsAutoCString spec;
    521  nsCOMPtr<nsIURI> newURI;
    522 
    523  // Only downgrade if the current scheme is (a) https or (b) view-source:https
    524  if (uri->SchemeIs("https")) {
    525    rv = uri->GetSpec(spec);
    526    NS_ENSURE_SUCCESS(rv, nullptr);
    527 
    528    rv = NS_NewURI(getter_AddRefs(newURI), spec);
    529    NS_ENSURE_SUCCESS(rv, nullptr);
    530 
    531    rv = NS_MutateURI(newURI).SetScheme("http"_ns).Finalize(
    532        getter_AddRefs(newURI));
    533    NS_ENSURE_SUCCESS(rv, nullptr);
    534  } else if (uri->SchemeIs("view-source")) {
    535    nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri);
    536    if (!nestedURI) {
    537      return nullptr;
    538    }
    539    nsCOMPtr<nsIURI> innerURI;
    540    rv = nestedURI->GetInnerURI(getter_AddRefs(innerURI));
    541    NS_ENSURE_SUCCESS(rv, nullptr);
    542    if (!innerURI || !innerURI->SchemeIs("https")) {
    543      return nullptr;
    544    }
    545    rv = NS_MutateURI(innerURI).SetScheme("http"_ns).Finalize(
    546        getter_AddRefs(innerURI));
    547    NS_ENSURE_SUCCESS(rv, nullptr);
    548 
    549    nsAutoCString innerSpec;
    550    rv = innerURI->GetSpec(innerSpec);
    551    NS_ENSURE_SUCCESS(rv, nullptr);
    552 
    553    spec.Append("view-source:");
    554    spec.Append(innerSpec);
    555 
    556    rv = NS_NewURI(getter_AddRefs(newURI), spec);
    557    NS_ENSURE_SUCCESS(rv, nullptr);
    558  } else {
    559    return nullptr;
    560  }
    561 
    562  // Log downgrade to console
    563  NS_ConvertUTF8toUTF16 reportSpec(uri->GetSpecOrDefault());
    564  AutoTArray<nsString, 1> params = {reportSpec};
    565  nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyFailedDowngradeAgain", params,
    566                                       nsIScriptError::warningFlag, loadInfo,
    567                                       uri, true);
    568 
    569  if (mozilla::StaticPrefs::
    570          dom_security_https_first_add_exception_on_failure()) {
    571    AddHTTPSFirstException(uri, loadInfo);
    572  }
    573 
    574  return newURI.forget();
    575 }
    576 
    577 void nsHTTPSOnlyUtils::UpdateLoadStateAfterHTTPSFirstDowngrade(
    578    mozilla::net::DocumentLoadListener* aDocumentLoadListener,
    579    nsDocShellLoadState* aLoadState) {
    580  // We have to exempt the load from HTTPS-First to prevent a upgrade-downgrade
    581  // loop
    582  aLoadState->SetIsExemptFromHTTPSFirstMode(true);
    583 
    584  // we can safely set the flag to indicate the downgrade here and it will be
    585  // propagated all the way to nsHttpChannel::OnStopRequest() where we collect
    586  // the telemetry.
    587  nsCOMPtr<nsIChannel> channel = aDocumentLoadListener->GetChannel();
    588  nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
    589  if (loadInfo->GetSchemelessInput() ==
    590      nsILoadInfo::SchemelessInputTypeSchemeless) {
    591    aLoadState->SetHttpsUpgradeTelemetry(
    592        nsILoadInfo::HTTPS_FIRST_SCHEMELESS_UPGRADE_DOWNGRADE);
    593  } else {
    594    aLoadState->SetHttpsUpgradeTelemetry(
    595        nsILoadInfo::HTTPS_FIRST_UPGRADE_DOWNGRADE);
    596  }
    597 
    598  // Add downgrade data for later telemetry usage to load state
    599  nsDOMNavigationTiming* timing = aDocumentLoadListener->GetTiming();
    600  if (timing) {
    601    mozilla::TimeStamp navigationStart = timing->GetNavigationStartTimeStamp();
    602    if (navigationStart) {
    603      mozilla::TimeDuration duration =
    604          mozilla::TimeStamp::Now() - navigationStart;
    605 
    606      nsresult channelStatus;
    607      channel->GetStatus(&channelStatus);
    608 
    609      RefPtr downgradeData = mozilla::MakeRefPtr<HTTPSFirstDowngradeData>();
    610      downgradeData->downgradeTime = duration;
    611      downgradeData->isOnTimer = channelStatus == NS_ERROR_NET_TIMEOUT_EXTERNAL;
    612      downgradeData->isSchemeless =
    613          GetUpgradeMode(loadInfo) == SCHEMELESS_HTTPS_FIRST_MODE;
    614      aLoadState->SetHttpsFirstDowngradeData(downgradeData);
    615    }
    616  }
    617 }
    618 
    619 void nsHTTPSOnlyUtils::SubmitHTTPSFirstTelemetry(
    620    nsCOMPtr<nsILoadInfo> const& aLoadInfo,
    621    RefPtr<HTTPSFirstDowngradeData> const& aHttpsFirstDowngradeData) {
    622  using namespace mozilla::glean::httpsfirst;
    623  if (aHttpsFirstDowngradeData) {
    624    // Successfully downgraded load
    625 
    626    auto downgradedMetric = aHttpsFirstDowngradeData->isSchemeless
    627                                ? downgraded_schemeless
    628                                : downgraded;
    629    auto downgradedOnTimerMetric = aHttpsFirstDowngradeData->isSchemeless
    630                                       ? downgraded_on_timer_schemeless
    631                                       : downgraded_on_timer;
    632    auto downgradeTimeMetric = aHttpsFirstDowngradeData->isSchemeless
    633                                   ? downgrade_time_schemeless
    634                                   : downgrade_time;
    635 
    636    if (aHttpsFirstDowngradeData->isOnTimer) {
    637      downgradedOnTimerMetric.AddToNumerator();
    638    }
    639    downgradedMetric.Add();
    640    downgradeTimeMetric.AccumulateRawDuration(
    641        aHttpsFirstDowngradeData->downgradeTime);
    642  } else if (aLoadInfo->GetHttpsOnlyStatus() &
    643             nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST) {
    644    // Successfully upgraded load
    645 
    646    if (GetUpgradeMode(aLoadInfo) == SCHEMELESS_HTTPS_FIRST_MODE) {
    647      upgraded_schemeless.Add();
    648    } else {
    649      upgraded.Add();
    650    }
    651  }
    652 }
    653 
    654 /* static */
    655 bool nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(nsIChannel* aChannel,
    656                                             nsresult aError) {
    657  // If there is no failed channel, then there is nothing to do here.
    658  if (!aChannel) {
    659    return false;
    660  }
    661 
    662  // If HTTPS-Only Mode is not enabled, then there is nothing to do here.
    663  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    664  if (GetUpgradeMode(loadInfo) != HTTPS_ONLY_MODE) {
    665    return false;
    666  }
    667 
    668  // If the load is exempt or did not get upgraded,
    669  // then there is nothing to do here.
    670  uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
    671  if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT ||
    672      httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UNINITIALIZED) {
    673    return false;
    674  }
    675 
    676  // If it's one of those errors, then most likely it's not a HTTPS-Only error
    677  // (This list of errors is largely drawn from nsDocShell::DisplayLoadError())
    678  return !HttpsUpgradeUnrelatedErrorCode(aError);
    679 }
    680 
    681 /* static */
    682 bool nsHTTPSOnlyUtils::TestIfPrincipalIsExempt(nsIPrincipal* aPrincipal,
    683                                               UpgradeMode aUpgradeMode) {
    684  static nsCOMPtr<nsIPermissionManager> sPermMgr;
    685  if (!sPermMgr) {
    686    sPermMgr = mozilla::components::PermissionManager::Service();
    687    mozilla::ClearOnShutdown(&sPermMgr);
    688  }
    689  NS_ENSURE_TRUE(sPermMgr, false);
    690 
    691  uint32_t perm;
    692  nsresult rv = sPermMgr->TestExactPermissionFromPrincipal(
    693      aPrincipal, "https-only-load-insecure"_ns, &perm);
    694  NS_ENSURE_SUCCESS(rv, false);
    695 
    696  bool checkForHTTPSFirst = aUpgradeMode == HTTPS_FIRST_MODE ||
    697                            aUpgradeMode == SCHEMELESS_HTTPS_FIRST_MODE;
    698 
    699  return perm == nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW ||
    700         perm == nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW_SESSION ||
    701         (checkForHTTPSFirst &&
    702          perm == nsIHttpsOnlyModePermission::HTTPSFIRST_LOAD_INSECURE_ALLOW);
    703 }
    704 
    705 /* static */
    706 void nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(
    707    nsIChannel* aChannel) {
    708  NS_ENSURE_TRUE_VOID(aChannel);
    709 
    710  // If HTTPS-Only or HTTPS-First Mode is not enabled, then there is nothing to
    711  // do here.
    712  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    713  UpgradeMode upgradeMode = GetUpgradeMode(loadInfo);
    714 
    715  if (upgradeMode == NO_UPGRADE_MODE) {
    716    return;
    717  }
    718 
    719  // if it's not a top-level load then there is nothing to here.
    720  ExtContentPolicyType type = loadInfo->GetExternalContentPolicyType();
    721  if (type != ExtContentPolicy::TYPE_DOCUMENT) {
    722    return;
    723  }
    724 
    725  // it it's not an http channel, then there is nothing to do here.
    726  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
    727  if (!httpChannel) {
    728    return;
    729  }
    730 
    731  nsCOMPtr<nsIPrincipal> principal;
    732  nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
    733      aChannel, getter_AddRefs(principal));
    734  NS_ENSURE_SUCCESS_VOID(rv);
    735 
    736  uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
    737  bool isPrincipalExempt = TestIfPrincipalIsExempt(principal, upgradeMode);
    738  if (isPrincipalExempt) {
    739    httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT;
    740  }
    741  loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
    742 
    743  // For the telemetry we do not want downgrade values to be overwritten
    744  // in the loadinfo. We only want e.g. a reload() or a back() click
    745  // to carry the upgrade exception.
    746  if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) {
    747    nsILoadInfo::HTTPSUpgradeTelemetryType httpsTelemetry =
    748        nsILoadInfo::NOT_INITIALIZED;
    749    loadInfo->GetHttpsUpgradeTelemetry(&httpsTelemetry);
    750    if (httpsTelemetry != nsILoadInfo::HTTPS_ONLY_UPGRADE_DOWNGRADE &&
    751        httpsTelemetry != nsILoadInfo::HTTPS_FIRST_UPGRADE_DOWNGRADE &&
    752        httpsTelemetry !=
    753            nsILoadInfo::HTTPS_FIRST_SCHEMELESS_UPGRADE_DOWNGRADE) {
    754      loadInfo->SetHttpsUpgradeTelemetry(nsILoadInfo::UPGRADE_EXCEPTION);
    755    }
    756  }
    757 }
    758 
    759 /* static */
    760 bool nsHTTPSOnlyUtils::IsSafeToAcceptCORSOrMixedContent(
    761    nsILoadInfo* aLoadInfo) {
    762  // Check if the request is exempt from upgrades
    763  if ((aLoadInfo->GetHttpsOnlyStatus() & nsILoadInfo::HTTPS_ONLY_EXEMPT)) {
    764    return false;
    765  }
    766  // Check if HTTPS-Only Mode is enabled for this request
    767  return GetUpgradeMode(aLoadInfo) == HTTPS_ONLY_MODE;
    768 }
    769 
    770 /* static */
    771 bool nsHTTPSOnlyUtils::HttpsUpgradeUnrelatedErrorCode(nsresult aError) {
    772  return NS_ERROR_UNKNOWN_PROTOCOL == aError ||
    773         NS_ERROR_FILE_NOT_FOUND == aError ||
    774         NS_ERROR_FILE_ACCESS_DENIED == aError ||
    775         NS_ERROR_UNKNOWN_HOST == aError || NS_ERROR_PHISHING_URI == aError ||
    776         NS_ERROR_MALWARE_URI == aError || NS_ERROR_UNWANTED_URI == aError ||
    777         NS_ERROR_HARMFUL_URI == aError || NS_ERROR_CONTENT_CRASHED == aError ||
    778         NS_ERROR_FRAME_CRASHED == aError;
    779 }
    780 
    781 /* ------ Logging ------ */
    782 
    783 /* static */
    784 void nsHTTPSOnlyUtils::LogLocalizedString(const char* aName,
    785                                          const nsTArray<nsString>& aParams,
    786                                          uint32_t aFlags,
    787                                          nsILoadInfo* aLoadInfo, nsIURI* aURI,
    788                                          bool aUseHttpsFirst) {
    789  nsAutoString logMsg;
    790  nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES,
    791                                        aName, aParams, logMsg);
    792  LogMessage(logMsg, aFlags, aLoadInfo, aURI, aUseHttpsFirst);
    793 }
    794 
    795 /* static */
    796 void nsHTTPSOnlyUtils::LogMessage(const nsAString& aMessage, uint32_t aFlags,
    797                                  nsILoadInfo* aLoadInfo, nsIURI* aURI,
    798                                  bool aUseHttpsFirst) {
    799  // do not log to the console if the loadinfo says we should not!
    800  uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
    801  if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_DO_NOT_LOG_TO_CONSOLE) {
    802    return;
    803  }
    804 
    805  // Prepending HTTPS-Only to the outgoing console message
    806  nsString message;
    807  message.Append(aUseHttpsFirst ? u"HTTPS-First Mode: "_ns
    808                                : u"HTTPS-Only Mode: "_ns);
    809  message.Append(aMessage);
    810 
    811  // Allow for easy distinction in devtools code.
    812  auto category = aUseHttpsFirst ? "HTTPSFirst"_ns : "HTTPSOnly"_ns;
    813 
    814  uint64_t windowId = aLoadInfo->GetInnerWindowID();
    815  if (!windowId) {
    816    windowId = aLoadInfo->GetTriggeringWindowId();
    817  }
    818  if (windowId) {
    819    // Send to content console
    820    nsContentUtils::ReportToConsoleByWindowID(
    821        message, aFlags, category, windowId, mozilla::SourceLocation(aURI));
    822  } else {
    823    // Send to browser console
    824    bool isPrivateWin = aLoadInfo->GetOriginAttributes().IsPrivateBrowsing();
    825    nsContentUtils::LogSimpleConsoleError(message, category, isPrivateWin,
    826                                          true /* from chrome context */,
    827                                          aFlags);
    828  }
    829 }
    830 
    831 /* ------ Exceptions ------ */
    832 
    833 /* static */
    834 bool nsHTTPSOnlyUtils::OnionException(nsIURI* aURI) {
    835  // Onion-host exception can get disabled with a pref
    836  if (mozilla::StaticPrefs::dom_security_https_only_mode_upgrade_onion()) {
    837    return false;
    838  }
    839  nsAutoCString host;
    840  aURI->GetHost(host);
    841  return StringEndsWith(host, ".onion"_ns);
    842 }
    843 
    844 /* static */
    845 bool nsHTTPSOnlyUtils::LoopbackOrLocalException(nsIURI* aURI) {
    846  nsAutoCString asciiHost;
    847  nsresult rv = aURI->GetAsciiHost(asciiHost);
    848  NS_ENSURE_SUCCESS(rv, false);
    849 
    850  // Let's make a quick check if the host matches these loopback strings
    851  // before we do anything else
    852  if (asciiHost.EqualsLiteral("localhost") || asciiHost.EqualsLiteral("::1")) {
    853    return true;
    854  }
    855 
    856  mozilla::net::NetAddr addr;
    857  if (NS_FAILED(addr.InitFromString(asciiHost))) {
    858    return false;
    859  }
    860  // Loopback IPs are always exempt
    861  if (addr.IsLoopbackAddr()) {
    862    return true;
    863  }
    864 
    865  // Local IP exception can get disabled with a pref
    866  bool upgradeLocal =
    867      mozilla::StaticPrefs::dom_security_https_only_mode_upgrade_local();
    868  return (!upgradeLocal && addr.IsIPAddrLocal());
    869 }
    870 
    871 /* static */
    872 bool nsHTTPSOnlyUtils::UnknownPublicSuffixException(nsIURI* aURI) {
    873  nsCOMPtr<nsIEffectiveTLDService> tldService =
    874      do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
    875  NS_ENSURE_TRUE(tldService, false);
    876 
    877  bool hasKnownPublicSuffix;
    878  nsresult rv = tldService->HasKnownPublicSuffix(aURI, &hasKnownPublicSuffix);
    879  NS_ENSURE_SUCCESS(rv, false);
    880 
    881  return !hasKnownPublicSuffix;
    882 }
    883 
    884 /* static */
    885 bool nsHTTPSOnlyUtils::IsHttpDowngrade(nsIURI* aFromURI, nsIURI* aToURI) {
    886  MOZ_ASSERT(aFromURI);
    887  MOZ_ASSERT(aToURI);
    888 
    889  if (!aFromURI || !aToURI) {
    890    return false;
    891  }
    892 
    893  // 2. If the target URI is not http, then it's not a http downgrade
    894  if (!aToURI->SchemeIs("http")) {
    895    return false;
    896  }
    897 
    898  // 3. If the origin URI isn't https, then it's not a http downgrade either.
    899  if (!aFromURI->SchemeIs("https")) {
    900    return false;
    901  }
    902 
    903  // 4. Create a new target URI with 'https' instead of 'http' and compare it
    904  // to the origin URI
    905  int32_t port = 0;
    906  nsresult rv = aToURI->GetPort(&port);
    907  NS_ENSURE_SUCCESS(rv, false);
    908  // a port of -1 indicates the default port, hence we upgrade from port 80 to
    909  // port 443
    910  // otherwise we keep the port.
    911  if (port == -1) {
    912    port = NS_GetDefaultPort("https");
    913  }
    914  nsCOMPtr<nsIURI> newHTTPSchemeURI;
    915  rv = NS_MutateURI(aToURI)
    916           .SetScheme("https"_ns)
    917           .SetPort(port)
    918           .Finalize(newHTTPSchemeURI);
    919  NS_ENSURE_SUCCESS(rv, false);
    920 
    921  bool uriEquals = false;
    922  if (NS_FAILED(aFromURI->EqualsExceptRef(newHTTPSchemeURI, &uriEquals))) {
    923    return false;
    924  }
    925 
    926  return uriEquals;
    927 }
    928 
    929 /* static */
    930 nsresult nsHTTPSOnlyUtils::AddHTTPSFirstException(
    931    nsCOMPtr<nsIURI> aURI, nsILoadInfo* const aLoadInfo) {
    932  // We need to reconstruct a principal instead of taking one from the loadinfo,
    933  // as the permission needs a http scheme, while the passed URL or principals
    934  // on the loadinfo may have a https scheme.
    935  nsresult rv =
    936      NS_MutateURI(aURI).SetScheme("http"_ns).Finalize(getter_AddRefs(aURI));
    937  NS_ENSURE_SUCCESS(rv, rv);
    938 
    939  mozilla::OriginAttributes oa = aLoadInfo->GetOriginAttributes();
    940  oa.SetFirstPartyDomain(true, aURI);
    941 
    942  nsCOMPtr<nsIPermissionManager> permMgr =
    943      mozilla::components::PermissionManager::Service();
    944  NS_ENSURE_TRUE(permMgr, nsresult::NS_ERROR_SERVICE_NOT_AVAILABLE);
    945 
    946  nsCOMPtr<nsIPrincipal> principal =
    947      mozilla::BasePrincipal::CreateContentPrincipal(aURI, oa);
    948 
    949  nsCString host;
    950  aURI->GetHost(host);
    951  LogLocalizedString("HTTPSFirstAddingException", {NS_ConvertUTF8toUTF16(host)},
    952                     nsIScriptError::warningFlag, aLoadInfo, aURI, true);
    953 
    954  uint32_t lifetime =
    955      mozilla::StaticPrefs::dom_security_https_first_exception_lifetime();
    956  int64_t expirationTime = (PR_Now() / PR_USEC_PER_MSEC) + lifetime;
    957  rv = permMgr->AddFromPrincipal(
    958      principal, "https-only-load-insecure"_ns,
    959      nsIHttpsOnlyModePermission::HTTPSFIRST_LOAD_INSECURE_ALLOW,
    960      nsIPermissionManager::EXPIRE_TIME, expirationTime);
    961  NS_ENSURE_SUCCESS(rv, rv);
    962 
    963  return NS_OK;
    964 }
    965 
    966 /* static */
    967 uint32_t nsHTTPSOnlyUtils::GetStatusForSubresourceLoad(
    968    uint32_t aHttpsOnlyStatus) {
    969  return aHttpsOnlyStatus & ~nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST;
    970 }
    971 
    972 /////////////////////////////////////////////////////////////////////
    973 // Implementation of TestHTTPAnswerRunnable
    974 
    975 NS_IMPL_ISUPPORTS_INHERITED(TestHTTPAnswerRunnable, mozilla::Runnable,
    976                            nsIStreamListener, nsIInterfaceRequestor,
    977                            nsITimerCallback)
    978 
    979 TestHTTPAnswerRunnable::TestHTTPAnswerRunnable(
    980    nsIURI* aURI, mozilla::net::DocumentLoadListener* aDocumentLoadListener)
    981    : mozilla::Runnable("TestHTTPAnswerRunnable"),
    982      mURI(aURI),
    983      mDocumentLoadListener(aDocumentLoadListener) {}
    984 
    985 /* static */
    986 bool TestHTTPAnswerRunnable::IsBackgroundRequestRedirected(
    987    nsIHttpChannel* aChannel) {
    988  // If there is no background request (aChannel), then there is nothing
    989  // to do here.
    990  if (!aChannel) {
    991    return false;
    992  }
    993  // If the request was not redirected, then there is nothing to do here.
    994  nsCOMPtr<nsILoadInfo> loadinfo = aChannel->LoadInfo();
    995  if (loadinfo->RedirectChain().IsEmpty()) {
    996    return false;
    997  }
    998 
    999  // If the final URI is not targeting an https scheme, then we definitely not
   1000  // dealing with a 'same-origin' redirect.
   1001  nsCOMPtr<nsIURI> finalURI;
   1002  nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalURI));
   1003  NS_ENSURE_SUCCESS(rv, false);
   1004  if (!finalURI->SchemeIs("https")) {
   1005    return false;
   1006  }
   1007 
   1008  // If the background request was not http, then there is nothing to do here.
   1009  nsCOMPtr<nsIPrincipal> firstURIPrincipal;
   1010  loadinfo->RedirectChain()[0]->GetPrincipal(getter_AddRefs(firstURIPrincipal));
   1011  if (!firstURIPrincipal || !firstURIPrincipal->SchemeIs("http")) {
   1012    return false;
   1013  }
   1014 
   1015  // By now we have verified that the inital background request was http and
   1016  // that the redirected scheme is https. We want to find the following case
   1017  // where the background channel redirects to the https version of the
   1018  // top-level request.
   1019  // --> background channel: http://example.com
   1020  //      |--> redirects to: https://example.com
   1021  // Now we have to check that the hosts are 'same-origin'.
   1022  nsAutoCString redirectHost;
   1023  nsAutoCString finalHost;
   1024  firstURIPrincipal->GetAsciiHost(redirectHost);
   1025  finalURI->GetAsciiHost(finalHost);
   1026  return finalHost.Equals(redirectHost);
   1027 }
   1028 
   1029 NS_IMETHODIMP
   1030 TestHTTPAnswerRunnable::OnStartRequest(nsIRequest* aRequest) {
   1031  // If the request status is not OK, it means it encountered some
   1032  // kind of error in which case we do not want to do anything.
   1033  nsresult requestStatus;
   1034  aRequest->GetStatus(&requestStatus);
   1035  if (requestStatus != NS_OK) {
   1036    return NS_OK;
   1037  }
   1038 
   1039  // Check if the original top-level channel which https-only is trying
   1040  // to upgrade is already in progress or if the channel is an auth channel.
   1041  // If it is in progress or Auth is in progress, then all good, if not
   1042  // then let's cancel that channel so we can dispaly the exception page.
   1043  nsCOMPtr<nsIChannel> docChannel = mDocumentLoadListener->GetChannel();
   1044  nsCOMPtr<nsIHttpChannel> httpsOnlyChannel = do_QueryInterface(docChannel);
   1045  if (httpsOnlyChannel) {
   1046    nsCOMPtr<nsILoadInfo> loadInfo = httpsOnlyChannel->LoadInfo();
   1047    uint32_t topLevelLoadInProgress =
   1048        loadInfo->GetHttpsOnlyStatus() &
   1049        nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS;
   1050 
   1051    nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
   1052        do_QueryInterface(httpsOnlyChannel);
   1053    bool isAuthChannel = false;
   1054    (void)httpChannelInternal->GetIsAuthChannel(&isAuthChannel);
   1055    // some server configurations need a long time to respond to an https
   1056    // connection, but also redirect any http connection to the https version of
   1057    // it. If the top-level load has not started yet, but the http background
   1058    // request redirects to https, then do not show the error page, but keep
   1059    // waiting for the https response of the upgraded top-level request.
   1060    if (!topLevelLoadInProgress) {
   1061      nsCOMPtr<nsIHttpChannel> backgroundHttpChannel =
   1062          do_QueryInterface(aRequest);
   1063      topLevelLoadInProgress =
   1064          IsBackgroundRequestRedirected(backgroundHttpChannel);
   1065    }
   1066    if (!topLevelLoadInProgress && !isAuthChannel) {
   1067      // Only really cancel the original top-level channel if it's
   1068      // status is still NS_OK, otherwise it might have already
   1069      // encountered some other error and was cancelled.
   1070      nsresult httpsOnlyChannelStatus;
   1071      httpsOnlyChannel->GetStatus(&httpsOnlyChannelStatus);
   1072      if (httpsOnlyChannelStatus == NS_OK) {
   1073        httpsOnlyChannel->Cancel(NS_ERROR_NET_TIMEOUT_EXTERNAL);
   1074      }
   1075    }
   1076  }
   1077 
   1078  // Cancel this http request because it has reached the end of it's
   1079  // lifetime at this point.
   1080  aRequest->Cancel(NS_ERROR_ABORT);
   1081  return NS_ERROR_ABORT;
   1082 }
   1083 
   1084 NS_IMETHODIMP
   1085 TestHTTPAnswerRunnable::OnDataAvailable(nsIRequest* aRequest,
   1086                                        nsIInputStream* aStream,
   1087                                        uint64_t aOffset, uint32_t aCount) {
   1088  // TestHTTPAnswerRunnable only cares about ::OnStartRequest which
   1089  // will also cancel the request, so we should in fact never even
   1090  // get here.
   1091  MOZ_ASSERT(false, "how come we get to ::OnDataAvailable");
   1092  return NS_OK;
   1093 }
   1094 
   1095 NS_IMETHODIMP
   1096 TestHTTPAnswerRunnable::OnStopRequest(nsIRequest* aRequest,
   1097                                      nsresult aStatusCode) {
   1098  // TestHTTPAnswerRunnable only cares about ::OnStartRequest
   1099  return NS_OK;
   1100 }
   1101 
   1102 NS_IMETHODIMP
   1103 TestHTTPAnswerRunnable::GetInterface(const nsIID& aIID, void** aResult) {
   1104  return QueryInterface(aIID, aResult);
   1105 }
   1106 
   1107 NS_IMETHODIMP
   1108 TestHTTPAnswerRunnable::Run() {
   1109  {
   1110    // Before we start our timer we kick of a DNS request for HTTPS RR. If we
   1111    // find a HTTPS RR we will not downgrade later.
   1112    nsCOMPtr<nsIChannel> origChannel = mDocumentLoadListener->GetChannel();
   1113    mozilla::OriginAttributes originAttributes;
   1114    mozilla::StoragePrincipalHelper::GetOriginAttributesForHTTPSRR(
   1115        origChannel, originAttributes);
   1116    RefPtr<nsDNSPrefetch> resolver =
   1117        new nsDNSPrefetch(mURI, originAttributes, origChannel->GetTRRMode());
   1118    nsCOMPtr<nsIHttpChannelInternal> internalChannel =
   1119        do_QueryInterface(origChannel);
   1120    uint32_t caps;
   1121    if (NS_SUCCEEDED(internalChannel->GetCaps(&caps))) {
   1122      (void)resolver->FetchHTTPSSVC(
   1123          caps & NS_HTTP_REFRESH_DNS, false,
   1124          [self = RefPtr{this}](nsIDNSHTTPSSVCRecord* aRecord) {
   1125            self->mHasHTTPSRR = (aRecord != nullptr);
   1126          });
   1127    }
   1128  }
   1129 
   1130  // Wait N milliseconds to give the original https request a heads start
   1131  // before firing up this http request in the background. If the https request
   1132  // has not received any signal from the server during that time, than it's
   1133  // almost certain the upgraded request will result in time out.
   1134  uint32_t background_timer_ms = mozilla::StaticPrefs::
   1135      dom_security_https_only_fire_http_request_background_timer_ms();
   1136 
   1137  return NS_NewTimerWithCallback(getter_AddRefs(mTimer), this,
   1138                                 background_timer_ms, nsITimer::TYPE_ONE_SHOT);
   1139 }
   1140 
   1141 NS_IMETHODIMP
   1142 TestHTTPAnswerRunnable::Notify(nsITimer* aTimer) {
   1143  if (mTimer) {
   1144    mTimer->Cancel();
   1145    mTimer = nullptr;
   1146  }
   1147 
   1148  // If the original channel has already started loading at this point
   1149  // then there is no need to do the dance.
   1150  nsCOMPtr<nsIChannel> origChannel = mDocumentLoadListener->GetChannel();
   1151  nsCOMPtr<nsILoadInfo> origLoadInfo = origChannel->LoadInfo();
   1152  uint32_t origHttpsOnlyStatus = origLoadInfo->GetHttpsOnlyStatus();
   1153  uint32_t topLevelLoadInProgress =
   1154      origHttpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS;
   1155  uint32_t downloadInProgress =
   1156      origHttpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_DOWNLOAD_IN_PROGRESS;
   1157 
   1158  // If the upgrade is caused by HSTS or HTTPS RR we do not allow downgrades
   1159  // so we do not need to start a racing request.
   1160  bool isClientRequestedUpgrade =
   1161      origHttpsOnlyStatus &
   1162          (nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED |
   1163           nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED |
   1164           nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST) &&
   1165      !mHasHTTPSRR;
   1166 
   1167  if (topLevelLoadInProgress || downloadInProgress ||
   1168      !isClientRequestedUpgrade) {
   1169    return NS_OK;
   1170  }
   1171 
   1172  mozilla::OriginAttributes attrs = origLoadInfo->GetOriginAttributes();
   1173  RefPtr<nsIPrincipal> nullPrincipal = mozilla::NullPrincipal::Create(attrs);
   1174 
   1175  uint32_t loadFlags =
   1176      nsIRequest::LOAD_ANONYMOUS | nsIRequest::INHIBIT_CACHING |
   1177      nsIRequest::INHIBIT_PERSISTENT_CACHING | nsIRequest::LOAD_BYPASS_CACHE |
   1178      nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
   1179 
   1180  // No need to connect to the URI including the path because we only care about
   1181  // the round trip time if a server responds to an http request.
   1182  nsCOMPtr<nsIURI> backgroundChannelURI;
   1183  nsAutoCString prePathStr;
   1184  nsresult rv = mURI->GetPrePath(prePathStr);
   1185  if (NS_WARN_IF(NS_FAILED(rv))) {
   1186    return rv;
   1187  }
   1188  rv = NS_NewURI(getter_AddRefs(backgroundChannelURI), prePathStr);
   1189  if (NS_WARN_IF(NS_FAILED(rv))) {
   1190    return rv;
   1191  }
   1192 
   1193  // we are using TYPE_OTHER because TYPE_DOCUMENT might have side effects
   1194  nsCOMPtr<nsIChannel> testHTTPChannel;
   1195  rv = NS_NewChannel(getter_AddRefs(testHTTPChannel), backgroundChannelURI,
   1196                     nullPrincipal,
   1197                     nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
   1198                     nsIContentPolicy::TYPE_OTHER, nullptr, nullptr, nullptr,
   1199                     nullptr, loadFlags);
   1200 
   1201  if (NS_WARN_IF(NS_FAILED(rv))) {
   1202    return rv;
   1203  }
   1204 
   1205  // We have exempt that load from HTTPS-Only to avoid getting upgraded
   1206  // to https as well. Additonally let's not log that request to the console
   1207  // because it might confuse end users.
   1208  nsCOMPtr<nsILoadInfo> loadInfo = testHTTPChannel->LoadInfo();
   1209  uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
   1210  httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT |
   1211                     nsILoadInfo::HTTPS_ONLY_DO_NOT_LOG_TO_CONSOLE |
   1212                     nsILoadInfo::HTTPS_ONLY_BYPASS_ORB;
   1213  loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
   1214 
   1215  testHTTPChannel->SetNotificationCallbacks(this);
   1216  testHTTPChannel->AsyncOpen(this);
   1217  return NS_OK;
   1218 }