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