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