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