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