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