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