tor-browser

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

AlternateServices.cpp (48276B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set sw=2 ts=8 et 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 "HttpLog.h"
      8 
      9 #include "AlternateServices.h"
     10 #include <algorithm>
     11 #include "LoadInfo.h"
     12 #include "mozilla/Atomics.h"
     13 #include "mozilla/StaticPrefs_network.h"
     14 #include "mozilla/SyncRunnable.h"
     15 #include "mozilla/dom/PContent.h"
     16 #include "mozilla/net/AltSvcTransactionChild.h"
     17 #include "mozilla/net/AltSvcTransactionParent.h"
     18 #include "mozilla/SyncRunnable.h"
     19 #include "nsComponentManagerUtils.h"
     20 #include "nsEscape.h"
     21 #include "nsHttpChannel.h"
     22 #include "nsHttpConnectionInfo.h"
     23 #include "nsHttpHandler.h"
     24 #include "nsHttpTransaction.h"
     25 #include "nsIOService.h"
     26 #include "nsITLSSocketControl.h"
     27 #include "nsIWellKnownOpportunisticUtils.h"
     28 #include "nsThreadUtils.h"
     29 #include "xpcpublic.h"
     30 
     31 /* RFC 7838 Alternative Services
     32   http://httpwg.org/http-extensions/opsec.html
     33    note that connections currently do not do mixed-scheme (the I attribute
     34    in the ConnectionInfo prevents it) but could, do not honor tls-commit and
     35   should not, and always require authentication
     36 */
     37 
     38 namespace mozilla {
     39 namespace net {
     40 
     41 // function places true in outIsHTTPS if scheme is https, false if
     42 // http, and returns an error if neither. originScheme passed into
     43 // alternate service should already be normalized to those lower case
     44 // strings by the URI parser (and so there is an assert)- this is an extra
     45 // check.
     46 static nsresult SchemeIsHTTPS(const nsACString& originScheme,
     47                              bool& outIsHTTPS) {
     48  outIsHTTPS = originScheme.EqualsLiteral("https");
     49 
     50  if (!outIsHTTPS && !originScheme.EqualsLiteral("http")) {
     51    MOZ_ASSERT(!originScheme.LowerCaseEqualsLiteral("https") &&
     52                   !originScheme.LowerCaseEqualsLiteral("http"),
     53               "The scheme should already be lowercase");
     54    return NS_ERROR_UNEXPECTED;
     55  }
     56  return NS_OK;
     57 }
     58 
     59 bool AltSvcMapping::AcceptableProxy(nsProxyInfo* proxyInfo) {
     60  // TODO: We also need to make sure the inner connection will connect to the
     61  // routed host.
     62  return !proxyInfo || proxyInfo->IsDirect() || proxyInfo->IsSOCKS() ||
     63         proxyInfo->IsHttp3Proxy();
     64 }
     65 
     66 void AltSvcMapping::ProcessHeader(
     67    const nsCString& buf, const nsCString& originScheme,
     68    const nsCString& originHost, int32_t originPort, const nsACString& username,
     69    bool privateBrowsing, nsIInterfaceRequestor* callbacks,
     70    nsProxyInfo* proxyInfo, uint32_t caps,
     71    const OriginAttributes& originAttributes,
     72    nsHttpConnectionInfo* aTransConnInfo, bool aDontValidate /* = false */) {
     73  LOG(("AltSvcMapping::ProcessHeader: %s\n", buf.get()));
     74  // In tests, this might be called off the main thread. If so, dispatch it
     75  // synchronously to the main thread.
     76  if (!NS_IsMainThread() && xpc::AreNonLocalConnectionsDisabled()) {
     77    nsCOMPtr<nsIThread> mainThread;
     78    nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
     79    if (NS_FAILED(rv)) {
     80      return;
     81    }
     82 
     83    nsCString userName(username);
     84    nsCOMPtr<nsIInterfaceRequestor> cb = callbacks;
     85    RefPtr<nsProxyInfo> info = proxyInfo;
     86    RefPtr<nsHttpConnectionInfo> connInfo = aTransConnInfo;
     87    // Forward to the main thread synchronously.
     88    mozilla::SyncRunnable::DispatchToThread(
     89        mainThread,
     90        NS_NewRunnableFunction(
     91            "AltSvcMapping::ProcessHeader",
     92            [buf(buf), originScheme(originScheme), originHost(originHost),
     93             originPort, userName, privateBrowsing, cb, info, caps,
     94             originAttributes, connInfo, aDontValidate]() {
     95              AltSvcMapping::ProcessHeader(
     96                  buf, originScheme, originHost, originPort, userName,
     97                  privateBrowsing, cb, info, caps, originAttributes, connInfo,
     98                  aDontValidate);
     99            }));
    100 
    101    return;
    102  }
    103 
    104  // AltSvcMapping::ProcessHeader is not thread-safe.
    105  if (!NS_IsMainThread()) {
    106    return;
    107  }
    108 
    109  if (StaticPrefs::network_http_altsvc_proxy_checks() &&
    110      !AcceptableProxy(proxyInfo)) {
    111    LOG(("AltSvcMapping::ProcessHeader ignoring due to proxy\n"));
    112    return;
    113  }
    114 
    115  bool isHTTPS;
    116  if (NS_FAILED(SchemeIsHTTPS(originScheme, isHTTPS))) {
    117    return;
    118  }
    119  if (!isHTTPS && !gHttpHandler->AllowAltSvcOE()) {
    120    LOG(("Alt-Svc Response Header for http:// origin but OE disabled\n"));
    121    return;
    122  }
    123 
    124  LOG(("Alt-Svc Response Header %s\n", buf.get()));
    125  ParsedHeaderValueListList parsedAltSvc(buf);
    126 
    127  nsTArray<RefPtr<AltSvcMapping>> h3Mappings;
    128  nsTArray<RefPtr<AltSvcMapping>> otherMappings;
    129  for (uint32_t index = 0; index < parsedAltSvc.mValues.Length(); ++index) {
    130    uint32_t maxage = 86400;  // default
    131    nsAutoCString hostname;
    132    nsAutoCString npnToken;
    133    int32_t portno = originPort;
    134    bool clearEntry = false;
    135    SupportedAlpnRank alpnRank = SupportedAlpnRank::NOT_SUPPORTED;
    136 
    137    for (uint32_t pairIndex = 0;
    138         pairIndex < parsedAltSvc.mValues[index].mValues.Length();
    139         ++pairIndex) {
    140      nsDependentCSubstring& currentName =
    141          parsedAltSvc.mValues[index].mValues[pairIndex].mName;
    142      nsDependentCSubstring& currentValue =
    143          parsedAltSvc.mValues[index].mValues[pairIndex].mValue;
    144 
    145      if (!pairIndex) {
    146        if (currentName.EqualsLiteral("clear")) {
    147          clearEntry = true;
    148          break;
    149        }
    150 
    151        // h2=[hostname]:443 or h3-xx=[hostname]:port
    152        // XX is current version we support and it is define in nsHttp.h.
    153        alpnRank = IsAlpnSupported(currentName);
    154        npnToken = currentName;
    155 
    156        int32_t colonIndex = currentValue.FindChar(':');
    157        if (colonIndex >= 0) {
    158          portno =
    159              atoi(PromiseFlatCString(currentValue).get() + colonIndex + 1);
    160        } else {
    161          colonIndex = 0;
    162        }
    163        hostname.Assign(currentValue.BeginReading(), colonIndex);
    164      } else if (currentName.EqualsLiteral("ma")) {
    165        maxage = atoi(PromiseFlatCString(currentValue).get());
    166      } else {
    167        LOG(("Alt Svc ignoring parameter %s", currentName.BeginReading()));
    168      }
    169    }
    170 
    171    if (clearEntry) {
    172      nsCString suffix;
    173      originAttributes.CreateSuffix(suffix);
    174      LOG(("Alt Svc clearing mapping for %s:%d:%s", originHost.get(),
    175           originPort, suffix.get()));
    176      gHttpHandler->AltServiceCache()->ClearHostMapping(originHost, originPort,
    177                                                        originAttributes);
    178      continue;
    179    }
    180 
    181    if (NS_FAILED(NS_CheckPortSafety(portno, originScheme.get()))) {
    182      LOG(("Alt Svc doesn't allow port %d, ignoring", portno));
    183      continue;
    184    }
    185 
    186    // unescape modifies a c string in place, so afterwards
    187    // update nsCString length
    188    nsUnescape(npnToken.BeginWriting());
    189    npnToken.SetLength(strlen(npnToken.BeginReading()));
    190    bool isHttp3 = net::IsHttp3(alpnRank);
    191    SpdyInformation* spdyInfo = gHttpHandler->SpdyInfo();
    192    if (!(npnToken.Equals(spdyInfo->VersionString) &&
    193          StaticPrefs::network_http_http2_enabled()) &&
    194        !(isHttp3 && nsHttpHandler::IsHttp3Enabled() &&
    195          !gHttpHandler->IsHttp3Excluded(hostname.IsEmpty() ? originHost
    196                                                            : hostname))) {
    197      LOG(("Alt Svc unknown protocol %s, ignoring", npnToken.get()));
    198      continue;
    199    }
    200 
    201    LOG(("AltSvcMapping created npnToken=%s", npnToken.get()));
    202    RefPtr<AltSvcMapping> mapping = new AltSvcMapping(
    203        gHttpHandler->AltServiceCache()->GetStoragePtr(),
    204        gHttpHandler->AltServiceCache()->StorageEpoch(), originScheme,
    205        originHost, originPort, username, privateBrowsing,
    206        NowInSeconds() + maxage, hostname, portno, npnToken, originAttributes,
    207        isHttp3, alpnRank);
    208    if (mapping->TTL() <= 0) {
    209      LOG(("Alt Svc invalid map"));
    210      mapping = nullptr;
    211      // since this isn't a parse error, let's clear any existing mapping
    212      // as that would have happened if we had accepted the parameters.
    213      gHttpHandler->AltServiceCache()->ClearHostMapping(originHost, originPort,
    214                                                        originAttributes);
    215    } else {
    216      if (isHttp3) {
    217        h3Mappings.AppendElement(std::move(mapping));
    218      } else {
    219        otherMappings.AppendElement(std::move(mapping));
    220      }
    221    }
    222  }
    223 
    224  auto doUpdateAltSvcMapping = [&](AltSvcMapping* aMapping) {
    225    if (aTransConnInfo) {
    226      if (!aTransConnInfo->GetEchConfig().IsEmpty()) {
    227        LOG(("Server has ECH, use HTTPS RR to connect instead"));
    228        return;
    229      }
    230      if (StaticPrefs::network_http_skip_alt_svc_validation_on_https_rr()) {
    231        RefPtr<nsHttpConnectionInfo> ci;
    232        aMapping->GetConnectionInfo(getter_AddRefs(ci), proxyInfo,
    233                                    originAttributes);
    234        if (ci->HashKey().Equals(aTransConnInfo->HashKey())) {
    235          LOG(("The transaction's conninfo is the same, no need to validate"));
    236          aDontValidate = true;
    237        }
    238      }
    239    }
    240    if (!aDontValidate) {
    241      gHttpHandler->UpdateAltServiceMapping(aMapping, proxyInfo, callbacks,
    242                                            caps, originAttributes);
    243    } else {
    244      gHttpHandler->UpdateAltServiceMappingWithoutValidation(
    245          aMapping, proxyInfo, callbacks, caps, originAttributes);
    246    }
    247  };
    248 
    249  if (!h3Mappings.IsEmpty()) {
    250    // Select the HTTP/3 (h3) AltSvcMapping with the highest ALPN rank from
    251    // h3Mappings.
    252    RefPtr<AltSvcMapping> latestH3Mapping = *std::max_element(
    253        h3Mappings.begin(), h3Mappings.end(),
    254        [](const RefPtr<AltSvcMapping>& a, const RefPtr<AltSvcMapping>& b) {
    255          return a->AlpnRank() < b->AlpnRank();
    256        });
    257    doUpdateAltSvcMapping(latestH3Mapping);
    258  }
    259 
    260  std::for_each(otherMappings.begin(), otherMappings.end(),
    261                doUpdateAltSvcMapping);
    262 }
    263 
    264 AltSvcMapping::AltSvcMapping(nsIDataStorage* storage, int32_t epoch,
    265                             const nsACString& originScheme,
    266                             const nsACString& originHost, int32_t originPort,
    267                             const nsACString& username, bool privateBrowsing,
    268                             uint32_t expiresAt,
    269                             const nsACString& alternateHost,
    270                             int32_t alternatePort, const nsACString& npnToken,
    271                             const OriginAttributes& originAttributes,
    272                             bool aIsHttp3, SupportedAlpnRank aRank)
    273    : mStorage(storage),
    274      mStorageEpoch(epoch),
    275      mAlternateHost(alternateHost),
    276      mAlternatePort(alternatePort),
    277      mOriginHost(originHost),
    278      mOriginPort(originPort),
    279      mUsername(username),
    280      mPrivate(privateBrowsing),
    281      mExpiresAt(expiresAt),
    282      mNPNToken(npnToken),
    283      mOriginAttributes(originAttributes),
    284      mIsHttp3(aIsHttp3),
    285      mAlpnRank(aRank) {
    286  MOZ_ASSERT(NS_IsMainThread());
    287 
    288  if (NS_FAILED(SchemeIsHTTPS(originScheme, mHttps))) {
    289    LOG(("AltSvcMapping ctor %p invalid scheme\n", this));
    290    mExpiresAt = 0;  // invalid
    291  }
    292 
    293  if (mAlternatePort == -1) {
    294    mAlternatePort = mHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
    295  }
    296  if (mOriginPort == -1) {
    297    mOriginPort = mHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
    298  }
    299 
    300  LOG(("AltSvcMapping ctor %p %s://%s:%d to %s:%d\n", this,
    301       nsCString(originScheme).get(), mOriginHost.get(), mOriginPort,
    302       mAlternateHost.get(), mAlternatePort));
    303 
    304  if (mAlternateHost.IsEmpty()) {
    305    mAlternateHost = mOriginHost;
    306  }
    307 
    308  if ((mAlternatePort == mOriginPort) &&
    309      mAlternateHost.EqualsIgnoreCase(mOriginHost.get()) && !mIsHttp3) {
    310    // Http2 on the same host:port does not make sense because we are
    311    // connecting to the same end point over the same protocol (TCP) as with
    312    // original host. On the other hand, for Http3 alt-svc can be hosted on
    313    // the same host:port because protocol(UDP vs. TCP) is always different and
    314    // we are not connecting to the same end point.
    315    LOG(("Alt Svc is also origin Svc - ignoring\n"));
    316    mExpiresAt = 0;  // invalid
    317  }
    318 
    319  if (mExpiresAt) {
    320    MakeHashKey(mHashKey, originScheme, mOriginHost, mOriginPort, mPrivate,
    321                mOriginAttributes, mIsHttp3);
    322  }
    323 }
    324 
    325 void AltSvcMapping::MakeHashKey(nsCString& outKey,
    326                                const nsACString& originScheme,
    327                                const nsACString& originHost,
    328                                int32_t originPort, bool privateBrowsing,
    329                                const OriginAttributes& originAttributes,
    330                                bool aHttp3) {
    331  outKey.Truncate();
    332 
    333  if (originPort == -1) {
    334    bool isHttps = originScheme.EqualsLiteral("https");
    335    originPort = isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
    336  }
    337 
    338  outKey.Append(originScheme);
    339  outKey.Append(':');
    340  outKey.Append(originHost);
    341  outKey.Append(':');
    342  outKey.AppendInt(originPort);
    343  outKey.Append(':');
    344  outKey.Append(privateBrowsing ? 'P' : '.');
    345  outKey.Append(':');
    346  nsAutoCString suffix;
    347  originAttributes.CreateSuffix(suffix);
    348  outKey.Append(suffix);
    349  outKey.Append(':');
    350 
    351  outKey.Append(aHttp3 ? '3' : '.');
    352 }
    353 
    354 int32_t AltSvcMapping::TTL() { return mExpiresAt - NowInSeconds(); }
    355 
    356 void AltSvcMapping::SyncString(const nsCString& str) {
    357  MOZ_ASSERT(NS_IsMainThread());
    358  (void)mStorage->Put(HashKey(), str,
    359                      mPrivate ? nsIDataStorage::DataType::Private
    360                               : nsIDataStorage::DataType::Persistent);
    361 }
    362 
    363 void AltSvcMapping::Sync() {
    364  if (!mStorage) {
    365    return;
    366  }
    367  if (mSyncOnlyOnSuccess && !mValidated) {
    368    return;
    369  }
    370  nsCString value;
    371  Serialize(value);
    372 
    373  if (!NS_IsMainThread()) {
    374    nsCOMPtr<nsIRunnable> r;
    375    r = NewRunnableMethod<nsCString>("net::AltSvcMapping::SyncString", this,
    376                                     &AltSvcMapping::SyncString, value);
    377    NS_DispatchToMainThread(r, NS_DISPATCH_NORMAL);
    378    return;
    379  }
    380 
    381  (void)mStorage->Put(HashKey(), value,
    382                      mPrivate ? nsIDataStorage::DataType::Private
    383                               : nsIDataStorage::DataType::Persistent);
    384 }
    385 
    386 void AltSvcMapping::SetValidated(bool val) {
    387  mValidated = val;
    388  Sync();
    389 }
    390 
    391 void AltSvcMapping::SetMixedScheme(bool val) {
    392  mMixedScheme = val;
    393  Sync();
    394 }
    395 
    396 void AltSvcMapping::SetExpiresAt(int32_t val) {
    397  mExpiresAt = val;
    398  Sync();
    399 }
    400 
    401 void AltSvcMapping::SetExpired() {
    402  LOG(("AltSvcMapping SetExpired %p origin %s alternate %s\n", this,
    403       mOriginHost.get(), mAlternateHost.get()));
    404  mExpiresAt = NowInSeconds() - 1;
    405  Sync();
    406 }
    407 
    408 bool AltSvcMapping::RouteEquals(AltSvcMapping* map) {
    409  MOZ_ASSERT(map->mHashKey.Equals(mHashKey));
    410  return mAlternateHost.Equals(map->mAlternateHost) &&
    411         (mAlternatePort == map->mAlternatePort) &&
    412         mNPNToken.Equals(map->mNPNToken);
    413 }
    414 
    415 void AltSvcMapping::GetConnectionInfo(
    416    nsHttpConnectionInfo** outCI, nsProxyInfo* pi,
    417    const OriginAttributes& originAttributes) {
    418  RefPtr<nsHttpConnectionInfo> ci = new nsHttpConnectionInfo(
    419      mOriginHost, mOriginPort, mNPNToken, mUsername, pi, originAttributes,
    420      mAlternateHost, mAlternatePort, mIsHttp3, false);
    421 
    422  // http:// without the mixed-scheme attribute needs to be segmented in the
    423  // connection manager connection information hash with this attribute
    424  if (!mHttps && !mMixedScheme) {
    425    ci->SetInsecureScheme(true);
    426  }
    427  ci->SetPrivate(mPrivate);
    428  ci.forget(outCI);
    429 }
    430 
    431 void AltSvcMapping::Serialize(nsCString& out) {
    432  // Be careful, when serializing new members, add them to the end of this list.
    433  out = mHttps ? "https:"_ns : "http:"_ns;
    434  out.Append(mOriginHost);
    435  out.Append(':');
    436  out.AppendInt(mOriginPort);
    437  out.Append(':');
    438  out.Append(mAlternateHost);
    439  out.Append(':');
    440  out.AppendInt(mAlternatePort);
    441  out.Append(':');
    442  out.Append(mUsername);
    443  out.Append(':');
    444  out.Append(mPrivate ? 'y' : 'n');
    445  out.Append(':');
    446  out.AppendInt(mExpiresAt);
    447  out.Append(':');
    448  out.Append(mNPNToken);
    449  out.Append(':');
    450  out.Append(mValidated ? 'y' : 'n');
    451  out.Append(':');
    452  out.AppendInt(mStorageEpoch);
    453  out.Append(':');
    454  out.Append(mMixedScheme ? 'y' : 'n');
    455  out.Append(':');
    456  nsAutoCString suffix;
    457  mOriginAttributes.CreateSuffix(suffix);
    458  out.Append(suffix);
    459  out.Append(':');
    460  out.Append(""_ns);  // Formerly topWindowOrigin. Now unused empty string.
    461  out.Append('|');    // Be careful, the top window origin may contain colons!
    462  out.Append('n');  // Formerly mIsolated. Now always 'n'. Should remove someday
    463  out.Append(':');
    464  out.Append(mIsHttp3 ? 'y' : 'n');
    465  out.Append(':');
    466  // Add code to serialize new members here!
    467 }
    468 
    469 AltSvcMapping::AltSvcMapping(nsIDataStorage* storage, int32_t epoch,
    470                             const nsCString& str)
    471    : mStorage(storage), mStorageEpoch(epoch) {
    472  mValidated = false;
    473  nsresult code;
    474  char separator = ':';
    475 
    476  // The the do {} while(0) loop acts like try/catch(e){} with the break in
    477  // _NS_NEXT_TOKEN
    478  do {
    479 #ifdef _NS_NEXT_TOKEN
    480    COMPILER ERROR
    481 #endif
    482 #define _NS_NEXT_TOKEN                  \
    483  start = idx + 1;                      \
    484  idx = str.FindChar(separator, start); \
    485  if (idx < 0) break;
    486        int32_t start = 0;
    487    int32_t idx;
    488    idx = str.FindChar(separator, start);
    489    if (idx < 0) break;
    490    // Be careful, when deserializing new members, add them to the end of this
    491    // list.
    492    mHttps = Substring(str, start, idx - start).EqualsLiteral("https");
    493    _NS_NEXT_TOKEN;
    494    mOriginHost = Substring(str, start, idx - start);
    495    _NS_NEXT_TOKEN;
    496    mOriginPort =
    497        nsCString(Substring(str, start, idx - start)).ToInteger(&code);
    498    _NS_NEXT_TOKEN;
    499    mAlternateHost = Substring(str, start, idx - start);
    500    _NS_NEXT_TOKEN;
    501    mAlternatePort =
    502        nsCString(Substring(str, start, idx - start)).ToInteger(&code);
    503    _NS_NEXT_TOKEN;
    504    mUsername = Substring(str, start, idx - start);
    505    _NS_NEXT_TOKEN;
    506    mPrivate = Substring(str, start, idx - start).EqualsLiteral("y");
    507    _NS_NEXT_TOKEN;
    508    mExpiresAt = nsCString(Substring(str, start, idx - start)).ToInteger(&code);
    509    _NS_NEXT_TOKEN;
    510    mNPNToken = Substring(str, start, idx - start);
    511    _NS_NEXT_TOKEN;
    512    mValidated = Substring(str, start, idx - start).EqualsLiteral("y");
    513    _NS_NEXT_TOKEN;
    514    mStorageEpoch =
    515        nsCString(Substring(str, start, idx - start)).ToInteger(&code);
    516    _NS_NEXT_TOKEN;
    517    mMixedScheme = Substring(str, start, idx - start).EqualsLiteral("y");
    518    _NS_NEXT_TOKEN;
    519    (void)mOriginAttributes.PopulateFromSuffix(
    520        Substring(str, start, idx - start));
    521    // The separator after the top window origin is a pipe character since the
    522    // origin string can contain colons.
    523    separator = '|';
    524    _NS_NEXT_TOKEN;
    525    // TopWindowOrigin used to be encoded here. Now it's unused.
    526    separator = ':';
    527    _NS_NEXT_TOKEN;
    528    // mIsolated used to be encoded here. Now it's unused.
    529    _NS_NEXT_TOKEN;
    530    mIsHttp3 = Substring(str, start, idx - start).EqualsLiteral("y");
    531    // Add code to deserialize new members here!
    532 #undef _NS_NEXT_TOKEN
    533 
    534    MakeHashKey(mHashKey, mHttps ? "https"_ns : "http"_ns, mOriginHost,
    535                mOriginPort, mPrivate, mOriginAttributes, mIsHttp3);
    536  } while (false);
    537 }
    538 
    539 AltSvcMappingValidator::AltSvcMappingValidator(AltSvcMapping* aMap)
    540    : mMapping(aMap) {
    541  LOG(("AltSvcMappingValidator ctor %p map %p [%s -> %s]", this, aMap,
    542       aMap->OriginHost().get(), aMap->AlternateHost().get()));
    543  MOZ_ASSERT(mMapping);
    544  MOZ_ASSERT(mMapping->HTTPS());  // http:// uses the .wk path
    545 }
    546 
    547 void AltSvcMappingValidator::OnTransactionDestroy(bool aValidateResult) {
    548  mMapping->SetValidated(aValidateResult);
    549  if (!mMapping->Validated()) {
    550    // try again later
    551    mMapping->SetExpiresAt(NowInSeconds() + 2);
    552  }
    553  LOG(
    554      ("AltSvcMappingValidator::OnTransactionDestroy %p map %p validated %d "
    555       "[%s]",
    556       this, mMapping.get(), mMapping->Validated(), mMapping->HashKey().get()));
    557 }
    558 
    559 void AltSvcMappingValidator::OnTransactionClose(bool aValidateResult) {
    560  mMapping->SetValidated(aValidateResult);
    561  LOG(
    562      ("AltSvcMappingValidator::OnTransactionClose %p map %p validated %d "
    563       "[%s]",
    564       this, mMapping.get(), mMapping->Validated(), mMapping->HashKey().get()));
    565 }
    566 
    567 template <class Validator>
    568 AltSvcTransaction<Validator>::AltSvcTransaction(
    569    nsHttpConnectionInfo* ci, nsIInterfaceRequestor* callbacks, uint32_t caps,
    570    Validator* aValidator, bool aIsHttp3)
    571    : SpeculativeTransaction(ci, callbacks, caps),
    572      mValidator(aValidator),
    573      mIsHttp3(aIsHttp3),
    574      mRunning(true),
    575      mTriedToValidate(false),
    576      mTriedToWrite(false),
    577      mValidatedResult(false) {
    578  MOZ_ASSERT_IF(nsIOService::UseSocketProcess(), XRE_IsSocketProcess());
    579  MOZ_ASSERT_IF(!nsIOService::UseSocketProcess(), XRE_IsParentProcess());
    580  // We don't want to let this transaction use consistent connection.
    581  mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
    582 }
    583 
    584 template <class Validator>
    585 AltSvcTransaction<Validator>::~AltSvcTransaction() {
    586  LOG(("AltSvcTransaction dtor %p running %d", this, mRunning));
    587 
    588  if (mRunning) {
    589    mValidatedResult = MaybeValidate(NS_OK);
    590    mValidator->OnTransactionDestroy(mValidatedResult);
    591  }
    592 }
    593 
    594 template <class Validator>
    595 bool AltSvcTransaction<Validator>::MaybeValidate(nsresult reason) {
    596  if (mTriedToValidate) {
    597    return mValidatedResult;
    598  }
    599  mTriedToValidate = true;
    600 
    601  LOG(("AltSvcTransaction::MaybeValidate() %p reason=%" PRIx32
    602       " running=%d conn=%p write=%d",
    603       this, static_cast<uint32_t>(reason), mRunning, mConnection.get(),
    604       mTriedToWrite));
    605 
    606  if (mTriedToWrite && reason == NS_BASE_STREAM_CLOSED) {
    607    // The normal course of events is to cause the transaction to fail with
    608    // CLOSED on a write - so that's a success that means the HTTP/2 session
    609    // is setup.
    610    reason = NS_OK;
    611  }
    612 
    613  if (NS_FAILED(reason) || !mRunning || !mConnection) {
    614    LOG(("AltSvcTransaction::MaybeValidate %p Failed due to precondition",
    615         this));
    616    return false;
    617  }
    618 
    619  // insist on >= http/2
    620  HttpVersion version = mConnection->Version();
    621  LOG(("AltSvcTransaction::MaybeValidate() %p version %d\n", this,
    622       static_cast<int32_t>(version)));
    623  if ((!mIsHttp3 && (version != HttpVersion::v2_0)) ||
    624      (mIsHttp3 && (version != HttpVersion::v3_0))) {
    625    LOG(
    626        ("AltSvcTransaction::MaybeValidate %p Failed due to protocol version"
    627         " expacted %s.",
    628         this, mIsHttp3 ? "Http3" : "Http2"));
    629    return false;
    630  }
    631 
    632  nsCOMPtr<nsITLSSocketControl> socketControl;
    633  mConnection->GetTLSSocketControl(getter_AddRefs(socketControl));
    634 
    635  LOG(("AltSvcTransaction::MaybeValidate() %p socketControl=%p\n", this,
    636       socketControl.get()));
    637 
    638  if (socketControl->GetFailedVerification()) {
    639    LOG(
    640        ("AltSvcTransaction::MaybeValidate() %p "
    641         "not validated due to auth error",
    642         this));
    643    return false;
    644  }
    645 
    646  LOG(
    647      ("AltSvcTransaction::MaybeValidate() %p "
    648       "validating alternate service with successful auth check",
    649       this));
    650 
    651  return true;
    652 }
    653 
    654 template <class Validator>
    655 void AltSvcTransaction<Validator>::Close(nsresult reason) {
    656  LOG(("AltSvcTransaction::Close() %p reason=%" PRIx32 " running %d", this,
    657       static_cast<uint32_t>(reason), mRunning));
    658 
    659  mValidatedResult = MaybeValidate(reason);
    660  mValidator->OnTransactionClose(mValidatedResult);
    661  if (!mValidatedResult && mConnection) {
    662    mConnection->DontReuse();
    663  }
    664  NullHttpTransaction::Close(reason);
    665 }
    666 
    667 template <class Validator>
    668 nsresult AltSvcTransaction<Validator>::ReadSegments(
    669    nsAHttpSegmentReader* reader, uint32_t count, uint32_t* countRead) {
    670  LOG(("AltSvcTransaction::ReadSegements() %p\n", this));
    671  mTriedToWrite = true;
    672  return NullHttpTransaction::ReadSegments(reader, count, countRead);
    673 }
    674 
    675 class WellKnownChecker {
    676 public:
    677  WellKnownChecker(nsIURI* uri, const nsCString& origin, uint32_t caps,
    678                   nsHttpConnectionInfo* ci, AltSvcMapping* mapping)
    679      : mWaiting(
    680            2)  // waiting for 2 channels (default and alternate) to complete
    681        ,
    682        mOrigin(origin),
    683        mAlternatePort(ci->RoutedPort()),
    684        mMapping(mapping),
    685        mCI(ci),
    686        mURI(uri),
    687        mCaps(caps) {
    688    LOG(("WellKnownChecker ctor %p\n", this));
    689    MOZ_ASSERT(!mMapping->HTTPS());
    690  }
    691 
    692  nsresult Start() {
    693    LOG(("WellKnownChecker::Start %p\n", this));
    694    nsCOMPtr<nsILoadInfo> loadInfo = MOZ_TRY(LoadInfo::Create(
    695        nsContentUtils::GetSystemPrincipal(), nullptr, nullptr,
    696        nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
    697        nsIContentPolicy::TYPE_OTHER));
    698    loadInfo->SetOriginAttributes(mCI->GetOriginAttributes());
    699    // allow deprecated HTTP request from SystemPrincipal
    700    loadInfo->SetAllowDeprecatedSystemRequests(true);
    701 
    702    RefPtr<nsHttpChannel> chan = new nsHttpChannel();
    703    nsresult rv;
    704 
    705    mTransactionAlternate = new TransactionObserver(chan, this);
    706    RefPtr<nsHttpConnectionInfo> newCI = mCI->Clone();
    707    rv = MakeChannel(chan, mTransactionAlternate, newCI, mURI, mCaps, loadInfo);
    708    if (NS_FAILED(rv)) {
    709      return rv;
    710    }
    711    chan = new nsHttpChannel();
    712    mTransactionOrigin = new TransactionObserver(chan, this);
    713    newCI = nullptr;
    714    return MakeChannel(chan, mTransactionOrigin, newCI, mURI, mCaps, loadInfo);
    715  }
    716 
    717  void Done(TransactionObserver* finished) {
    718    MOZ_ASSERT(NS_IsMainThread());
    719    LOG(("WellKnownChecker::Done %p waiting for %d\n", this, mWaiting));
    720 
    721    mWaiting--;       // another channel is complete
    722    if (!mWaiting) {  // there are all complete!
    723      nsAutoCString mAlternateCT, mOriginCT;
    724      mTransactionOrigin->mChannel->GetContentType(mOriginCT);
    725      mTransactionAlternate->mChannel->GetContentType(mAlternateCT);
    726      nsCOMPtr<nsIWellKnownOpportunisticUtils> uu =
    727          do_CreateInstance(NS_WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID);
    728      bool accepted = false;
    729 
    730      if (!mTransactionOrigin->mStatusOK) {
    731        LOG(("WellKnownChecker::Done %p origin was not 200 response code\n",
    732             this));
    733      } else if (!mTransactionAlternate->mAuthOK) {
    734        LOG(("WellKnownChecker::Done %p alternate was not TLS authenticated\n",
    735             this));
    736      } else if (!mTransactionAlternate->mStatusOK) {
    737        LOG(("WellKnownChecker::Done %p alternate was not 200 response code\n",
    738             this));
    739      } else if (!mTransactionAlternate->mVersionOK) {
    740        LOG(("WellKnownChecker::Done %p alternate was not at least h2 or h3\n",
    741             this));
    742      } else if (!mTransactionAlternate->mWKResponse.Equals(
    743                     mTransactionOrigin->mWKResponse)) {
    744        LOG(
    745            ("WellKnownChecker::Done %p alternate and origin "
    746             ".wk representations don't match\norigin: %s\alternate:%s\n",
    747             this, mTransactionOrigin->mWKResponse.get(),
    748             mTransactionAlternate->mWKResponse.get()));
    749      } else if (!mAlternateCT.Equals(mOriginCT)) {
    750        LOG(
    751            ("WellKnownChecker::Done %p alternate and origin content types "
    752             "dont match\n",
    753             this));
    754      } else if (!mAlternateCT.EqualsLiteral("application/json")) {
    755        LOG(("WellKnownChecker::Done %p .wk content type is %s\n", this,
    756             mAlternateCT.get()));
    757      } else if (!uu) {
    758        LOG(("WellKnownChecker::Done %p json parser service unavailable\n",
    759             this));
    760      } else {
    761        accepted = true;
    762      }
    763 
    764      if (accepted) {
    765        MOZ_ASSERT(!mMapping->HTTPS());  // https:// does not use .wk
    766 
    767        nsresult rv = uu->Verify(mTransactionAlternate->mWKResponse, mOrigin);
    768        if (NS_SUCCEEDED(rv)) {
    769          bool validWK = false;
    770          (void)uu->GetValid(&validWK);
    771          if (!validWK) {
    772            LOG(("WellKnownChecker::Done %p json parser declares invalid\n%s\n",
    773                 this, mTransactionAlternate->mWKResponse.get()));
    774            accepted = false;
    775          }
    776        } else {
    777          LOG(("WellKnownChecker::Done %p .wk jason eval failed to run\n",
    778               this));
    779          accepted = false;
    780        }
    781      }
    782 
    783      MOZ_ASSERT(!mMapping->Validated());
    784      if (accepted) {
    785        LOG(("WellKnownChecker::Done %p Alternate for %s ACCEPTED\n", this,
    786             mOrigin.get()));
    787        mMapping->SetValidated(true);
    788      } else {
    789        LOG(("WellKnownChecker::Done %p Alternate for %s FAILED\n", this,
    790             mOrigin.get()));
    791        // try again soon
    792        mMapping->SetExpiresAt(NowInSeconds() + 2);
    793      }
    794 
    795      delete this;
    796    }
    797  }
    798 
    799  ~WellKnownChecker() { LOG(("WellKnownChecker dtor %p\n", this)); }
    800 
    801 private:
    802  nsresult MakeChannel(nsHttpChannel* chan, TransactionObserver* obs,
    803                       nsHttpConnectionInfo* ci, nsIURI* uri, uint32_t caps,
    804                       nsILoadInfo* loadInfo) {
    805    nsLoadFlags flags;
    806 
    807    uint64_t channelId = gHttpHandler->NewChannelId();
    808    if (NS_FAILED(
    809            chan->Init(uri, caps, nullptr, 0, nullptr, channelId, loadInfo)) ||
    810        NS_FAILED(chan->SetAllowAltSvc(false)) ||
    811        NS_FAILED(chan->SetRedirectMode(
    812            nsIHttpChannelInternal::REDIRECT_MODE_ERROR)) ||
    813        NS_FAILED(chan->GetLoadFlags(&flags))) {
    814      return NS_ERROR_FAILURE;
    815    }
    816    flags |= HttpBaseChannel::LOAD_BYPASS_CACHE;
    817    if (NS_FAILED(chan->SetLoadFlags(flags))) {
    818      return NS_ERROR_FAILURE;
    819    }
    820    chan->SetTransactionObserver(obs);
    821    chan->SetConnectionInfo(ci);
    822    return chan->AsyncOpen(obs);
    823  }
    824 
    825  RefPtr<TransactionObserver> mTransactionAlternate;
    826  RefPtr<TransactionObserver> mTransactionOrigin;
    827  uint32_t mWaiting;  // semaphore
    828  nsCString mOrigin;
    829  int32_t mAlternatePort;
    830  RefPtr<AltSvcMapping> mMapping;
    831  RefPtr<nsHttpConnectionInfo> mCI;
    832  nsCOMPtr<nsIURI> mURI;
    833  uint32_t mCaps;
    834 };
    835 
    836 NS_IMPL_ISUPPORTS(TransactionObserver, nsIStreamListener)
    837 
    838 TransactionObserver::TransactionObserver(nsHttpChannel* channel,
    839                                         WellKnownChecker* checker)
    840    : mChannel(channel),
    841      mChecker(checker),
    842      mRanOnce(false),
    843      mStatusOK(false),
    844      mAuthOK(false),
    845      mVersionOK(false) {
    846  LOG(("TransactionObserver ctor %p channel %p checker %p\n", this, channel,
    847       checker));
    848  mChannelRef = do_QueryInterface((nsIHttpChannel*)channel);
    849 }
    850 
    851 void TransactionObserver::Complete(bool versionOK, bool authOK,
    852                                   nsresult reason) {
    853  if (mRanOnce) {
    854    return;
    855  }
    856  mRanOnce = true;
    857 
    858  mVersionOK = versionOK;
    859  mAuthOK = authOK;
    860 
    861  LOG(
    862      ("TransactionObserve::Complete %p authOK %d versionOK %d"
    863       " reason %" PRIx32,
    864       this, authOK, versionOK, static_cast<uint32_t>(reason)));
    865 }
    866 
    867 #define MAX_WK 32768
    868 
    869 NS_IMETHODIMP
    870 TransactionObserver::OnStartRequest(nsIRequest* aRequest) {
    871  MOZ_ASSERT(NS_IsMainThread());
    872  // only consider the first 32KB.. because really.
    873  mWKResponse.SetCapacity(MAX_WK);
    874  return NS_OK;
    875 }
    876 
    877 NS_IMETHODIMP
    878 TransactionObserver::OnDataAvailable(nsIRequest* aRequest,
    879                                     nsIInputStream* aStream, uint64_t aOffset,
    880                                     uint32_t aCount) {
    881  MOZ_ASSERT(NS_IsMainThread());
    882  uint64_t oldLen = static_cast<uint64_t>(mWKResponse.Length());
    883  uint64_t newLen = static_cast<uint64_t>(aCount) + oldLen;
    884  if (newLen < MAX_WK) {
    885    auto handleOrErr = mWKResponse.BulkWrite(newLen, oldLen, false);
    886    if (handleOrErr.isErr()) {
    887      return handleOrErr.unwrapErr();
    888    }
    889    auto handle = handleOrErr.unwrap();
    890    uint32_t amtRead;
    891    if (NS_SUCCEEDED(
    892            aStream->Read(handle.Elements() + oldLen, aCount, &amtRead))) {
    893      MOZ_ASSERT(oldLen + amtRead <= newLen);
    894      handle.Finish(oldLen + amtRead, false);
    895      LOG(("TransactionObserver onDataAvailable %p read %d of .wk [%zd]\n",
    896           this, amtRead, mWKResponse.Length()));
    897    } else {
    898      LOG(("TransactionObserver onDataAvailable %p read error\n", this));
    899    }
    900  }
    901  return NS_OK;
    902 }
    903 
    904 NS_IMETHODIMP
    905 TransactionObserver::OnStopRequest(nsIRequest* aRequest, nsresult code) {
    906  MOZ_ASSERT(NS_IsMainThread());
    907  LOG(("TransactionObserver onStopRequest %p code %" PRIx32 "\n", this,
    908       static_cast<uint32_t>(code)));
    909  if (NS_SUCCEEDED(code)) {
    910    nsHttpResponseHead* hdrs = mChannel->GetResponseHead();
    911    LOG(("TransactionObserver onStopRequest %p http resp %d\n", this,
    912         hdrs ? hdrs->Status() : -1));
    913    mStatusOK = hdrs && (hdrs->Status() == 200);
    914  }
    915  if (mChecker) {
    916    mChecker->Done(this);
    917  }
    918  return NS_OK;
    919 }
    920 
    921 void AltSvcCache::EnsureStorageInited() {
    922  static Atomic<bool> initialized(false);
    923 
    924  if (initialized) {
    925    return;
    926  }
    927 
    928  auto initTask = [&]() {
    929    MOZ_ASSERT(NS_IsMainThread());
    930 
    931    // nsIDataStorage gives synchronous access to a memory based hash table
    932    // that is backed by disk where those writes are done asynchronously
    933    // on another thread
    934    nsCOMPtr<nsIDataStorageManager> dataStorageManager(
    935        do_GetService("@mozilla.org/security/datastoragemanager;1"));
    936    if (!dataStorageManager) {
    937      LOG(("AltSvcCache::EnsureStorageInited WARN NO STORAGE MANAGER\n"));
    938      return;
    939    }
    940    nsresult rv = dataStorageManager->Get(
    941        nsIDataStorageManager::AlternateServices, getter_AddRefs(mStorage));
    942    if (NS_FAILED(rv) || !mStorage) {
    943      LOG(("AltSvcCache::EnsureStorageInited WARN NO STORAGE\n"));
    944      return;
    945    }
    946    initialized = true;
    947 
    948    mStorageEpoch = NowInSeconds();
    949  };
    950 
    951  if (NS_IsMainThread()) {
    952    initTask();
    953    return;
    954  }
    955 
    956  nsCOMPtr<nsIEventTarget> main = GetMainThreadSerialEventTarget();
    957  if (!main) {
    958    return;
    959  }
    960 
    961  SyncRunnable::DispatchToThread(
    962      main,
    963      NS_NewRunnableFunction("AltSvcCache::EnsureStorageInited", initTask));
    964 }
    965 
    966 already_AddRefed<AltSvcMapping> AltSvcCache::LookupMapping(
    967    const nsCString& key, bool privateBrowsing) {
    968  LOG(("AltSvcCache::LookupMapping %p %s\n", this, key.get()));
    969  if (!mStorage) {
    970    LOG(("AltSvcCache::LookupMapping %p no backing store\n", this));
    971    return nullptr;
    972  }
    973 
    974  if (NS_IsMainThread()) {
    975    bool isReady;
    976    nsresult rv = mStorage->IsReady(&isReady);
    977    if (NS_FAILED(rv)) {
    978      LOG(("AltSvcCache::LookupMapping %p mStorage->IsReady failed\n", this));
    979      return nullptr;
    980    }
    981    if (!isReady) {
    982      LOG(("AltSvcCache::LookupMapping %p skip when storage is not ready\n",
    983           this));
    984      return nullptr;
    985    }
    986  }
    987 
    988  nsAutoCString val;
    989  nsresult rv =
    990      mStorage->Get(key,
    991                    privateBrowsing ? nsIDataStorage::DataType::Private
    992                                    : nsIDataStorage::DataType::Persistent,
    993                    val);
    994  if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
    995    LOG(("AltSvcCache::LookupMapping %p mStorage->Get failed \n", this));
    996    return nullptr;
    997  }
    998  if (rv == NS_ERROR_NOT_AVAILABLE || val.IsEmpty()) {
    999    LOG(("AltSvcCache::LookupMapping %p MISS\n", this));
   1000    return nullptr;
   1001  }
   1002  RefPtr<AltSvcMapping> mapping =
   1003      new AltSvcMapping(mStorage, mStorageEpoch, val);
   1004  if (!mapping->Validated() && (mapping->StorageEpoch() != mStorageEpoch)) {
   1005    // this was an in progress validation abandoned in a different session
   1006    // rare edge case will not detect session change - that's ok as only impact
   1007    // will be loss of alt-svc to this origin for this session.
   1008    LOG(("AltSvcCache::LookupMapping %p invalid hit - MISS\n", this));
   1009    (void)mStorage->Remove(key, mapping->Private()
   1010                                    ? nsIDataStorage::DataType::Private
   1011                                    : nsIDataStorage::DataType::Persistent);
   1012    return nullptr;
   1013  }
   1014 
   1015  if (mapping->IsHttp3() &&
   1016      (!nsHttpHandler::IsHttp3Enabled() ||
   1017       !gHttpHandler->IsHttp3VersionSupported(mapping->NPNToken()) ||
   1018       gHttpHandler->IsHttp3Excluded(mapping->AlternateHost()))) {
   1019    // If Http3 is disabled or the version not supported anymore, remove the
   1020    // mapping.
   1021    (void)mStorage->Remove(key, mapping->Private()
   1022                                    ? nsIDataStorage::DataType::Private
   1023                                    : nsIDataStorage::DataType::Persistent);
   1024    return nullptr;
   1025  }
   1026 
   1027  if (mapping->TTL() <= 0) {
   1028    LOG(("AltSvcCache::LookupMapping %p expired hit - MISS\n", this));
   1029    (void)mStorage->Remove(key, mapping->Private()
   1030                                    ? nsIDataStorage::DataType::Private
   1031                                    : nsIDataStorage::DataType::Persistent);
   1032    return nullptr;
   1033  }
   1034 
   1035  MOZ_ASSERT(mapping->Private() == privateBrowsing);
   1036  LOG(("AltSvcCache::LookupMapping %p HIT %p\n", this, mapping.get()));
   1037  return mapping.forget();
   1038 }
   1039 
   1040 // For cases where the connection's hash key matches the hash key generated
   1041 // from the alt-svc header, validation is skipped since an equivalent connection
   1042 // already exists.
   1043 void AltSvcCache::UpdateAltServiceMappingWithoutValidation(
   1044    AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor* aCallbacks,
   1045    uint32_t caps, const OriginAttributes& originAttributes) {
   1046  MOZ_ASSERT(NS_IsMainThread());
   1047  if (!mStorage) {
   1048    return;
   1049  }
   1050  RefPtr<AltSvcMapping> existing =
   1051      LookupMapping(map->HashKey(), map->Private());
   1052  LOG(
   1053      ("AltSvcCache::UpdateAltServiceMappingWithoutValidation %p map %p "
   1054       "existing %p %s",
   1055       this, map, existing.get(), map->AlternateHost().get()));
   1056  if (!existing) {
   1057    map->SetValidated(true);
   1058  }
   1059 }
   1060 
   1061 void AltSvcCache::UpdateAltServiceMapping(
   1062    AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor* aCallbacks,
   1063    uint32_t caps, const OriginAttributes& originAttributes) {
   1064  MOZ_ASSERT(NS_IsMainThread());
   1065  if (!mStorage) {
   1066    return;
   1067  }
   1068  RefPtr<AltSvcMapping> existing =
   1069      LookupMapping(map->HashKey(), map->Private());
   1070  LOG(
   1071      ("AltSvcCache::UpdateAltServiceMapping %p map %p existing %p %s "
   1072       "validated=%d",
   1073       this, map, existing.get(), map->AlternateHost().get(),
   1074       existing ? existing->Validated() : 0));
   1075 
   1076  if (existing && existing->Validated()) {
   1077    if (existing->RouteEquals(map)) {
   1078      // update expires in storage
   1079      // if this is http:// then a ttl can only be extended via .wk, so ignore
   1080      // this header path unless it is making things shorter
   1081      if (existing->HTTPS()) {
   1082        LOG(
   1083            ("AltSvcCache::UpdateAltServiceMapping %p map %p updates ttl of "
   1084             "%p\n",
   1085             this, map, existing.get()));
   1086        existing->SetExpiresAt(map->GetExpiresAt());
   1087      } else {
   1088        if (map->GetExpiresAt() < existing->GetExpiresAt()) {
   1089          LOG(
   1090              ("AltSvcCache::UpdateAltServiceMapping %p map %p reduces ttl of "
   1091               "%p\n",
   1092               this, map, existing.get()));
   1093          existing->SetExpiresAt(map->GetExpiresAt());
   1094        } else {
   1095          LOG(
   1096              ("AltSvcCache::UpdateAltServiceMapping %p map %p tries to extend "
   1097               "%p but"
   1098               " cannot as without .wk\n",
   1099               this, map, existing.get()));
   1100        }
   1101      }
   1102      glean::http::altsvc_mapping_changed_target
   1103          .EnumGet(glean::http::AltsvcMappingChangedTargetLabel::eFalse)
   1104          .Add();
   1105      return;
   1106    }
   1107 
   1108    if (map->GetExpiresAt() < existing->GetExpiresAt()) {
   1109      LOG(
   1110          ("AltSvcCache::UpdateAltServiceMapping %p map %p ttl shorter than "
   1111           "%p, ignoring",
   1112           this, map, existing.get()));
   1113      return;
   1114    }
   1115 
   1116    // new alternate. start new validation
   1117    LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p may overwrite %p\n",
   1118         this, map, existing.get()));
   1119    glean::http::altsvc_mapping_changed_target
   1120        .EnumGet(glean::http::AltsvcMappingChangedTargetLabel::eTrue)
   1121        .Add();
   1122  }
   1123 
   1124  if (existing && !existing->Validated()) {
   1125    LOG(
   1126        ("AltSvcCache::UpdateAltServiceMapping %p map %p ignored because %p "
   1127         "still in progress\n",
   1128         this, map, existing.get()));
   1129    return;
   1130  }
   1131 
   1132  if (map->IsHttp3()) {
   1133    bool isProxyAllowed = pi ? (pi->IsDirect() || pi->IsHttp3Proxy()) : true;
   1134    if (!isProxyAllowed) {
   1135      LOG(
   1136          ("AltSvcCache::UpdateAltServiceMapping %p map %p ignored h3 because "
   1137           "proxy is in use %p\n",
   1138           this, map, existing.get()));
   1139      return;
   1140    }
   1141  }
   1142 
   1143  // start new validation, but don't overwrite a valid existing mapping unless
   1144  // this completes successfully
   1145  MOZ_ASSERT(!map->Validated());
   1146  if (!existing) {
   1147    map->Sync();
   1148  } else {
   1149    map->SetSyncOnlyOnSuccess(true);
   1150  }
   1151 
   1152  RefPtr<nsHttpConnectionInfo> ci;
   1153  map->GetConnectionInfo(getter_AddRefs(ci), pi, originAttributes);
   1154  caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
   1155  caps |= NS_HTTP_ERROR_SOFTLY;
   1156 
   1157  if (map->HTTPS()) {
   1158    LOG(
   1159        ("AltSvcCache::UpdateAltServiceMapping %p validation via "
   1160         "speculative connect started\n",
   1161         this));
   1162    // for https resources we only establish a connection
   1163    nsCOMPtr<nsIInterfaceRequestor> callbacks = new AltSvcOverride(aCallbacks);
   1164    RefPtr<AltSvcMappingValidator> validator = new AltSvcMappingValidator(map);
   1165    RefPtr<SpeculativeTransaction> transaction;
   1166    if (nsIOService::UseSocketProcess()) {
   1167      RefPtr<AltSvcTransactionParent> parent =
   1168          new AltSvcTransactionParent(ci, aCallbacks, caps, validator);
   1169      if (!parent->Init()) {
   1170        return;
   1171      }
   1172      transaction = parent;
   1173    } else {
   1174      transaction = new AltSvcTransaction<AltSvcMappingValidator>(
   1175          ci, aCallbacks, caps, validator, map->IsHttp3());
   1176    }
   1177 
   1178    nsresult rv =
   1179        gHttpHandler->SpeculativeConnect(ci, callbacks, caps, transaction);
   1180    if (NS_FAILED(rv)) {
   1181      LOG(
   1182          ("AltSvcCache::UpdateAltServiceMapping %p "
   1183           "speculative connect failed with code %08x\n",
   1184           this, static_cast<uint32_t>(rv)));
   1185    }
   1186  } else {
   1187    // for http:// resources we fetch .well-known too
   1188    nsAutoCString origin("http://"_ns);
   1189 
   1190    // Check whether origin is an ipv6 address. In that case we need to add
   1191    // '[]'.
   1192    if (map->OriginHost().FindChar(':') != kNotFound) {
   1193      origin.Append('[');
   1194      origin.Append(map->OriginHost());
   1195      origin.Append(']');
   1196    } else {
   1197      origin.Append(map->OriginHost());
   1198    }
   1199    if (map->OriginPort() != NS_HTTP_DEFAULT_PORT) {
   1200      origin.Append(':');
   1201      origin.AppendInt(map->OriginPort());
   1202    }
   1203 
   1204    nsCOMPtr<nsIURI> wellKnown;
   1205    nsAutoCString uri(origin);
   1206    uri.AppendLiteral("/.well-known/http-opportunistic");
   1207    NS_NewURI(getter_AddRefs(wellKnown), uri);
   1208 
   1209    auto* checker = new WellKnownChecker(wellKnown, origin, caps, ci, map);
   1210    if (NS_FAILED(checker->Start())) {
   1211      LOG(
   1212          ("AltSvcCache::UpdateAltServiceMapping %p .wk checker failed to "
   1213           "start\n",
   1214           this));
   1215      map->SetExpired();
   1216      delete checker;
   1217      checker = nullptr;
   1218    } else {
   1219      // object deletes itself when done if started
   1220      LOG(("AltSvcCache::UpdateAltServiceMapping %p .wk checker started %p\n",
   1221           this, checker));
   1222    }
   1223  }
   1224 }
   1225 
   1226 already_AddRefed<AltSvcMapping> AltSvcCache::GetAltServiceMapping(
   1227    const nsACString& scheme, const nsACString& host, int32_t port,
   1228    bool privateBrowsing, const OriginAttributes& originAttributes,
   1229    bool aHttp2Allowed, bool aHttp3Allowed) {
   1230  EnsureStorageInited();
   1231 
   1232  bool isHTTPS;
   1233  if (NS_FAILED(SchemeIsHTTPS(scheme, isHTTPS))) {
   1234    return nullptr;
   1235  }
   1236  if (!gHttpHandler->AllowAltSvc()) {
   1237    return nullptr;
   1238  }
   1239  if (!gHttpHandler->AllowAltSvcOE() && !isHTTPS) {
   1240    return nullptr;
   1241  }
   1242 
   1243  // First look for HTTP3
   1244  if (aHttp3Allowed) {
   1245    nsAutoCString key;
   1246    AltSvcMapping::MakeHashKey(key, scheme, host, port, privateBrowsing,
   1247                               originAttributes, true);
   1248    RefPtr<AltSvcMapping> existing = LookupMapping(key, privateBrowsing);
   1249    LOG(
   1250        ("AltSvcCache::GetAltServiceMapping %p key=%s "
   1251         "existing=%p validated=%d ttl=%d",
   1252         this, key.get(), existing.get(), existing ? existing->Validated() : 0,
   1253         existing ? existing->TTL() : 0));
   1254    if (existing && existing->Validated()) {
   1255      return existing.forget();
   1256    }
   1257  }
   1258 
   1259  // Now look for HTTP2.
   1260  if (aHttp2Allowed) {
   1261    nsAutoCString key;
   1262    AltSvcMapping::MakeHashKey(key, scheme, host, port, privateBrowsing,
   1263                               originAttributes, false);
   1264    RefPtr<AltSvcMapping> existing = LookupMapping(key, privateBrowsing);
   1265    LOG(
   1266        ("AltSvcCache::GetAltServiceMapping %p key=%s "
   1267         "existing=%p validated=%d ttl=%d",
   1268         this, key.get(), existing.get(), existing ? existing->Validated() : 0,
   1269         existing ? existing->TTL() : 0));
   1270    if (existing && existing->Validated()) {
   1271      return existing.forget();
   1272    }
   1273  }
   1274 
   1275  return nullptr;
   1276 }
   1277 
   1278 class ProxyClearHostMapping : public Runnable {
   1279 public:
   1280  explicit ProxyClearHostMapping(const nsACString& host, int32_t port,
   1281                                 const OriginAttributes& originAttributes)
   1282      : Runnable("net::ProxyClearHostMapping"),
   1283        mHost(host),
   1284        mPort(port),
   1285        mOriginAttributes(originAttributes) {}
   1286 
   1287  NS_IMETHOD Run() override {
   1288    MOZ_ASSERT(NS_IsMainThread());
   1289    gHttpHandler->AltServiceCache()->ClearHostMapping(mHost, mPort,
   1290                                                      mOriginAttributes);
   1291    return NS_OK;
   1292  }
   1293 
   1294 private:
   1295  nsCString mHost;
   1296  int32_t mPort;
   1297  OriginAttributes mOriginAttributes;
   1298 };
   1299 
   1300 void AltSvcCache::ClearHostMapping(const nsACString& host, int32_t port,
   1301                                   const OriginAttributes& originAttributes) {
   1302  MOZ_ASSERT(XRE_IsParentProcess());
   1303 
   1304  if (!NS_IsMainThread()) {
   1305    nsCOMPtr<nsIRunnable> event =
   1306        new ProxyClearHostMapping(host, port, originAttributes);
   1307    if (event) {
   1308      NS_DispatchToMainThread(event);
   1309    }
   1310    return;
   1311  }
   1312  nsAutoCString key;
   1313  for (int secure = 0; secure < 2; ++secure) {
   1314    constexpr auto http = "http"_ns;
   1315    constexpr auto https = "https"_ns;
   1316    const nsLiteralCString& scheme = secure ? https : http;
   1317    for (int pb = 1; pb >= 0; --pb) {
   1318      AltSvcMapping::MakeHashKey(key, scheme, host, port, bool(pb),
   1319                                 originAttributes, false);
   1320      RefPtr<AltSvcMapping> existing = LookupMapping(key, bool(pb));
   1321      if (existing) {
   1322        existing->SetExpired();
   1323      }
   1324      AltSvcMapping::MakeHashKey(key, scheme, host, port, bool(pb),
   1325                                 originAttributes, true);
   1326      existing = LookupMapping(key, bool(pb));
   1327      if (existing) {
   1328        existing->SetExpired();
   1329      }
   1330    }
   1331  }
   1332 }
   1333 
   1334 void AltSvcCache::ClearHostMapping(nsHttpConnectionInfo* ci) {
   1335  if (!ci->GetOrigin().IsEmpty()) {
   1336    ClearHostMapping(ci->GetOrigin(), ci->OriginPort(),
   1337                     ci->GetOriginAttributes());
   1338  }
   1339 }
   1340 
   1341 void AltSvcCache::ClearAltServiceMappings() {
   1342  MOZ_ASSERT(NS_IsMainThread());
   1343  if (mStorage) {
   1344    (void)mStorage->Clear();
   1345  }
   1346 }
   1347 
   1348 nsresult AltSvcCache::GetAltSvcCacheKeys(nsTArray<nsCString>& value) {
   1349  MOZ_ASSERT(NS_IsMainThread());
   1350  if (gHttpHandler->AllowAltSvc() && mStorage) {
   1351    nsTArray<RefPtr<nsIDataStorageItem>> items;
   1352    nsresult rv = mStorage->GetAll(items);
   1353    if (NS_FAILED(rv)) {
   1354      return rv;
   1355    }
   1356 
   1357    for (const auto& item : items) {
   1358      nsAutoCString key;
   1359      rv = item->GetKey(key);
   1360      if (NS_FAILED(rv)) {
   1361        return rv;
   1362      }
   1363      value.AppendElement(key);
   1364    }
   1365  }
   1366  return NS_OK;
   1367 }
   1368 
   1369 NS_IMETHODIMP
   1370 AltSvcOverride::GetInterface(const nsIID& iid, void** result) {
   1371  if (NS_SUCCEEDED(QueryInterface(iid, result)) && *result) {
   1372    return NS_OK;
   1373  }
   1374 
   1375  if (mCallbacks) {
   1376    return mCallbacks->GetInterface(iid, result);
   1377  }
   1378 
   1379  return NS_ERROR_NO_INTERFACE;
   1380 }
   1381 
   1382 NS_IMETHODIMP
   1383 AltSvcOverride::GetIgnoreIdle(bool* ignoreIdle) {
   1384  *ignoreIdle = true;
   1385  return NS_OK;
   1386 }
   1387 
   1388 NS_IMETHODIMP
   1389 AltSvcOverride::GetParallelSpeculativeConnectLimit(
   1390    uint32_t* parallelSpeculativeConnectLimit) {
   1391  *parallelSpeculativeConnectLimit = 32;
   1392  return NS_OK;
   1393 }
   1394 
   1395 NS_IMETHODIMP
   1396 AltSvcOverride::GetAllow1918(bool* allow) {
   1397  // normally we don't do speculative connects to 1918.. and we use
   1398  // speculative connects for the mapping validation, so override
   1399  // that default here for alt-svc
   1400  *allow = true;
   1401  return NS_OK;
   1402 }
   1403 
   1404 template class AltSvcTransaction<AltSvcTransactionChild>;
   1405 
   1406 NS_IMPL_ISUPPORTS(AltSvcOverride, nsIInterfaceRequestor,
   1407                  nsISpeculativeConnectionOverrider)
   1408 
   1409 }  // namespace net
   1410 }  // namespace mozilla