1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 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/. */
7 #include "mozilla/Components.h"
8 #include "mozilla/ClearOnShutdown.h"
9 #include "mozilla/TimeStamp.h"
10 #include "mozilla/glean/GleanMetrics.h"
11 #include "mozilla/NullPrincipal.h"
12 #include "mozilla/StaticPrefs_dom.h"
13 #include "mozilla/net/DNS.h"
14 #include "nsContentUtils.h"
15 #include "nsHTTPSOnlyUtils.h"
16 #include "nsIConsoleService.h"
17 #include "nsIHttpChannel.h"
18 #include "nsIHttpChannelInternal.h"
19 #include "nsIHttpsOnlyModePermission.h"
20 #include "nsILoadInfo.h"
21 #include "nsIPermissionManager.h"
22 #include "nsIPrincipal.h"
23 #include "nsIRedirectHistoryEntry.h"
24 #include "nsIScriptError.h"
25 #include "nsIURIMutator.h"
26 #include "nsNetUtil.h"
30 bool nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(bool aFromPrivateWindow
) {
31 // if the general pref is set to true, then we always return
32 if (mozilla::StaticPrefs::dom_security_https_only_mode()) {
36 // otherwise we check if executing in private browsing mode and return true
37 // if the PBM pref for HTTPS-Only is set.
38 if (aFromPrivateWindow
&&
39 mozilla::StaticPrefs::dom_security_https_only_mode_pbm()) {
46 bool nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(bool aFromPrivateWindow
) {
47 // HTTPS-Only takes priority over HTTPS-First
48 if (IsHttpsOnlyModeEnabled(aFromPrivateWindow
)) {
52 // if the general pref is set to true, then we always return
53 if (mozilla::StaticPrefs::dom_security_https_first()) {
57 // otherwise we check if executing in private browsing mode and return true
58 // if the PBM pref for HTTPS-First is set.
59 if (aFromPrivateWindow
&&
60 mozilla::StaticPrefs::dom_security_https_first_pbm()) {
67 void nsHTTPSOnlyUtils::PotentiallyFireHttpRequestToShortenTimout(
68 mozilla::net::DocumentLoadListener
* aDocumentLoadListener
) {
69 // only send http background request to counter timeouts if the
70 // pref allows us to do that.
71 if (!mozilla::StaticPrefs::
72 dom_security_https_only_mode_send_http_background_request()) {
76 nsCOMPtr
<nsIChannel
> channel
= aDocumentLoadListener
->GetChannel();
81 nsCOMPtr
<nsILoadInfo
> loadInfo
= channel
->LoadInfo();
82 bool isPrivateWin
= loadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
84 // if neither HTTPS-Only nor HTTPS-First mode is enabled, then there is
85 // nothing to do here.
86 if ((!IsHttpsOnlyModeEnabled(isPrivateWin
) &&
87 !IsHttpsFirstModeEnabled(isPrivateWin
)) &&
88 !(loadInfo
->GetWasSchemelessInput() &&
89 mozilla::StaticPrefs::dom_security_https_first_schemeless())) {
93 // if we are not dealing with a top-level load, then there is nothing to do
95 if (loadInfo
->GetExternalContentPolicyType() !=
96 ExtContentPolicy::TYPE_DOCUMENT
) {
100 // if the load is exempt, then there is nothing to do here.
101 uint32_t httpsOnlyStatus
= loadInfo
->GetHttpsOnlyStatus();
102 if (httpsOnlyStatus
& nsILoadInfo::nsILoadInfo::HTTPS_ONLY_EXEMPT
) {
106 // if it's not an http channel, then there is nothing to do here.
107 nsCOMPtr
<nsIHttpChannel
> httpChannel(do_QueryInterface(channel
));
112 // if it's not a GET method, then there is nothing to do here either.
113 nsAutoCString method
;
114 mozilla::Unused
<< httpChannel
->GetRequestMethod(method
);
115 if (!method
.EqualsLiteral("GET")) {
119 // if it's already an https channel, then there is nothing to do here.
120 nsCOMPtr
<nsIURI
> channelURI
;
121 channel
->GetURI(getter_AddRefs(channelURI
));
122 if (!channelURI
->SchemeIs("http")) {
126 // HTTPS-First only applies to standard ports but HTTPS-Only brute forces
127 // all http connections to be https and overrules HTTPS-First. In case
128 // HTTPS-First is enabled, but HTTPS-Only is not enabled, we might return
129 // early if attempting to send a background request to a non standard port.
130 if ((IsHttpsFirstModeEnabled(isPrivateWin
) ||
131 (loadInfo
->GetWasSchemelessInput() &&
132 mozilla::StaticPrefs::dom_security_https_first_schemeless()))) {
134 nsresult rv
= channelURI
->GetPort(&port
);
135 int defaultPortforScheme
= NS_GetDefaultPort("http");
136 if (NS_SUCCEEDED(rv
) && port
!= defaultPortforScheme
&& port
!= -1) {
141 // Check for general exceptions
142 if (OnionException(channelURI
) || LoopbackOrLocalException(channelURI
)) {
146 RefPtr
<nsIRunnable
> task
=
147 new TestHTTPAnswerRunnable(channelURI
, aDocumentLoadListener
);
148 NS_DispatchToMainThread(task
.forget());
152 bool nsHTTPSOnlyUtils::ShouldUpgradeRequest(nsIURI
* aURI
,
153 nsILoadInfo
* aLoadInfo
) {
154 // 1. Check if the HTTPS-Only Mode is even enabled, before we do anything else
155 bool isPrivateWin
= aLoadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
156 if (!IsHttpsOnlyModeEnabled(isPrivateWin
)) {
160 // 2. Check for general exceptions
161 if (OnionException(aURI
) || LoopbackOrLocalException(aURI
)) {
165 // 3. Check if NoUpgrade-flag is set in LoadInfo
166 uint32_t httpsOnlyStatus
= aLoadInfo
->GetHttpsOnlyStatus();
167 if (httpsOnlyStatus
& nsILoadInfo::HTTPS_ONLY_EXEMPT
) {
168 AutoTArray
<nsString
, 1> params
= {
169 NS_ConvertUTF8toUTF16(aURI
->GetSpecOrDefault())};
170 nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyNoUpgradeException", params
,
171 nsIScriptError::infoFlag
, aLoadInfo
,
176 // All subresources of an exempt triggering principal are also exempt
177 ExtContentPolicyType contentType
= aLoadInfo
->GetExternalContentPolicyType();
178 if (contentType
!= ExtContentPolicy::TYPE_DOCUMENT
) {
179 if (!aLoadInfo
->TriggeringPrincipal()->IsSystemPrincipal() &&
180 TestIfPrincipalIsExempt(aLoadInfo
->TriggeringPrincipal())) {
185 // We can not upgrade "Save-As" downloads, since we have no way of detecting
186 // if the upgrade failed (Bug 1674859). For now we will just allow the
187 // download, since there will still be a visual warning about the download
189 if (contentType
== ExtContentPolicyType::TYPE_SAVEAS_DOWNLOAD
) {
193 // We can upgrade the request - let's log it to the console
194 // Appending an 's' to the scheme for the logging. (http -> https)
195 nsAutoCString scheme
;
196 aURI
->GetScheme(scheme
);
197 scheme
.AppendLiteral("s");
198 NS_ConvertUTF8toUTF16
reportSpec(aURI
->GetSpecOrDefault());
199 NS_ConvertUTF8toUTF16
reportScheme(scheme
);
201 bool isSpeculative
= aLoadInfo
->GetExternalContentPolicyType() ==
202 ExtContentPolicy::TYPE_SPECULATIVE
;
203 AutoTArray
<nsString
, 2> params
= {reportSpec
, reportScheme
};
204 nsHTTPSOnlyUtils::LogLocalizedString(
205 isSpeculative
? "HTTPSOnlyUpgradeSpeculativeConnection"
206 : "HTTPSOnlyUpgradeRequest",
207 params
, nsIScriptError::warningFlag
, aLoadInfo
, aURI
);
209 // If the status was not determined before, we now indicate that the request
210 // will get upgraded, but no event-listener has been registered yet.
211 if (httpsOnlyStatus
& nsILoadInfo::HTTPS_ONLY_UNINITIALIZED
) {
212 httpsOnlyStatus
^= nsILoadInfo::HTTPS_ONLY_UNINITIALIZED
;
213 httpsOnlyStatus
|= nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED
;
214 aLoadInfo
->SetHttpsOnlyStatus(httpsOnlyStatus
);
220 bool nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(nsIURI
* aURI
,
221 nsILoadInfo
* aLoadInfo
) {
222 // 1. Check if the HTTPS-Only Mode is even enabled, before we do anything else
223 bool isPrivateWin
= aLoadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
224 if (!IsHttpsOnlyModeEnabled(isPrivateWin
)) {
228 // 2. Check for general exceptions
229 if (OnionException(aURI
) || LoopbackOrLocalException(aURI
)) {
233 // 3. Check if NoUpgrade-flag is set in LoadInfo
234 uint32_t httpsOnlyStatus
= aLoadInfo
->GetHttpsOnlyStatus();
235 if (httpsOnlyStatus
& nsILoadInfo::HTTPS_ONLY_EXEMPT
) {
236 // Let's log to the console, that we didn't upgrade this request
237 AutoTArray
<nsString
, 1> params
= {
238 NS_ConvertUTF8toUTF16(aURI
->GetSpecOrDefault())};
239 nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyNoUpgradeException", params
,
240 nsIScriptError::infoFlag
, aLoadInfo
,
245 // All subresources of an exempt triggering principal are also exempt.
246 if (!aLoadInfo
->TriggeringPrincipal()->IsSystemPrincipal() &&
247 TestIfPrincipalIsExempt(aLoadInfo
->TriggeringPrincipal())) {
251 // We can upgrade the request - let's log it to the console
252 // Appending an 's' to the scheme for the logging. (ws -> wss)
253 nsAutoCString scheme
;
254 aURI
->GetScheme(scheme
);
255 scheme
.AppendLiteral("s");
256 NS_ConvertUTF8toUTF16
reportSpec(aURI
->GetSpecOrDefault());
257 NS_ConvertUTF8toUTF16
reportScheme(scheme
);
259 AutoTArray
<nsString
, 2> params
= {reportSpec
, reportScheme
};
260 nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyUpgradeRequest", params
,
261 nsIScriptError::warningFlag
, aLoadInfo
,
267 bool nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
268 nsIURI
* aURI
, nsILoadInfo
* aLoadInfo
,
269 const mozilla::EnumSet
<UpgradeDowngradeEndlessLoopOptions
>& aOptions
) {
270 // 1. Check if the HTTPS-Only/HTTPS-First is even enabled, before doing
272 bool isPrivateWin
= aLoadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
273 bool enforceForHTTPSOnlyMode
=
274 IsHttpsOnlyModeEnabled(isPrivateWin
) &&
276 UpgradeDowngradeEndlessLoopOptions::EnforceForHTTPSOnlyMode
);
277 bool enforceForHTTPSFirstMode
=
278 IsHttpsFirstModeEnabled(isPrivateWin
) &&
280 UpgradeDowngradeEndlessLoopOptions::EnforceForHTTPSFirstMode
);
281 bool enforceForHTTPSRR
=
282 aOptions
.contains(UpgradeDowngradeEndlessLoopOptions::EnforceForHTTPSRR
);
283 if (!enforceForHTTPSOnlyMode
&& !enforceForHTTPSFirstMode
&&
284 !enforceForHTTPSRR
) {
288 // 2. Check if the upgrade downgrade pref even wants us to try to break the
289 // cycle. In the case that HTTPS RR is presented, we ignore this pref.
290 if (!mozilla::StaticPrefs::
291 dom_security_https_only_mode_break_upgrade_downgrade_endless_loop() &&
292 !enforceForHTTPSRR
) {
296 // 3. If it's not a top-level load, then there is nothing to do here either.
297 if (aLoadInfo
->GetExternalContentPolicyType() !=
298 ExtContentPolicy::TYPE_DOCUMENT
) {
302 // 4. If the load is exempt, then it's defintely not related to https-only
303 uint32_t httpsOnlyStatus
= aLoadInfo
->GetHttpsOnlyStatus();
304 if ((httpsOnlyStatus
& nsILoadInfo::HTTPS_ONLY_EXEMPT
) &&
305 !enforceForHTTPSRR
) {
309 // 5. If the URI to be loaded is not http, then it's defnitely no endless
310 // loop caused by https-only.
311 if (!aURI
->SchemeIs("http")) {
315 nsAutoCString uriHost
;
316 aURI
->GetAsciiHost(uriHost
);
318 auto uriAndPrincipalComparator
= [&](nsIPrincipal
* aPrincipal
) {
319 nsAutoCString principalHost
;
320 aPrincipal
->GetAsciiHost(principalHost
);
321 bool checkPath
= mozilla::StaticPrefs::
322 dom_security_https_only_check_path_upgrade_downgrade_endless_loop();
324 return uriHost
.Equals(principalHost
);
327 nsAutoCString uriPath
;
328 nsresult rv
= aURI
->GetFilePath(uriPath
);
332 nsAutoCString principalPath
;
333 aPrincipal
->GetFilePath(principalPath
);
334 return uriHost
.Equals(principalHost
) && uriPath
.Equals(principalPath
);
337 // 6. Check actual redirects. If the Principal that kicked off the
338 // load/redirect is not https, then it's definitely not a redirect cause by
339 // https-only. If the scheme of the principal however is https and the
340 // asciiHost of the URI to be loaded and the asciiHost of the Principal are
341 // identical, then we are dealing with an upgrade downgrade scenario and we
342 // have to break the cycle.
343 if (!aLoadInfo
->RedirectChain().IsEmpty()) {
344 nsCOMPtr
<nsIPrincipal
> redirectPrincipal
;
345 for (nsIRedirectHistoryEntry
* entry
: aLoadInfo
->RedirectChain()) {
346 entry
->GetPrincipal(getter_AddRefs(redirectPrincipal
));
347 if (redirectPrincipal
&& redirectPrincipal
->SchemeIs("https") &&
348 uriAndPrincipalComparator(redirectPrincipal
)) {
353 // 6.1 We should only check if this load is triggered by a user gesture
354 // when the redirect chain is empty, since this information is only useful
355 // in our case here. When the redirect chain is not empty, this load is
356 // defnitely triggered by redirection, not a user gesture.
357 if (aLoadInfo
->GetHasValidUserGestureActivation()) {
362 // 7. Meta redirects and JS based redirects (win.location). If the security
363 // context that triggered the load is not https, then it's defnitely no
364 // endless loop caused by https-only. If the scheme is http however and the
365 // asciiHost of the URI to be loaded matches the asciiHost of the Principal,
366 // then we are dealing with an upgrade downgrade scenario and we have to break
368 nsCOMPtr
<nsIPrincipal
> triggeringPrincipal
= aLoadInfo
->TriggeringPrincipal();
369 if (!triggeringPrincipal
->SchemeIs("https")) {
373 return uriAndPrincipalComparator(triggeringPrincipal
);
377 bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI
* aURI
,
378 nsILoadInfo
* aLoadInfo
) {
379 // 1. Check if HTTPS-First Mode is enabled
380 bool isPrivateWin
= aLoadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
381 if (!IsHttpsFirstModeEnabled(isPrivateWin
) &&
382 !(aLoadInfo
->GetWasSchemelessInput() &&
383 mozilla::StaticPrefs::dom_security_https_first_schemeless())) {
387 // 2. HTTPS-First only upgrades top-level loads (and speculative connections)
388 ExtContentPolicyType contentType
= aLoadInfo
->GetExternalContentPolicyType();
389 if (contentType
!= ExtContentPolicy::TYPE_DOCUMENT
&&
390 contentType
!= ExtContentPolicy::TYPE_SPECULATIVE
) {
394 // 3. Check for general exceptions
395 if (OnionException(aURI
) || LoopbackOrLocalException(aURI
)) {
399 // 4. Don't upgrade if upgraded previously or exempt from upgrades
400 uint32_t httpsOnlyStatus
= aLoadInfo
->GetHttpsOnlyStatus();
401 if (httpsOnlyStatus
& nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST
||
402 httpsOnlyStatus
& nsILoadInfo::HTTPS_ONLY_EXEMPT
) {
406 // 5. HTTPS-First Mode only upgrades default ports - do not upgrade the
407 // request to https if port is specified and not the default port of 80.
408 MOZ_ASSERT(aURI
->SchemeIs("http"), "how come the request is not 'http'?");
409 int defaultPortforScheme
= NS_GetDefaultPort("http");
410 // If no port is specified, then the API returns -1 to indicate the default
413 nsresult rv
= aURI
->GetPort(&port
);
414 NS_ENSURE_SUCCESS(rv
, false);
415 if (port
!= defaultPortforScheme
&& port
!= -1) {
418 // 6. Do not upgrade form submissions (for now), revisit within
419 // Bug 1720500: Revisit upgrading form submissions.
420 if (aLoadInfo
->GetIsFormSubmission()) {
424 // https-first needs to account for breaking upgrade-downgrade endless
425 // loops at this point because this function is called before we
426 // check the redirect limit in HttpBaseChannel. If we encounter
427 // a same-origin server side downgrade from e.g https://example.com
428 // to http://example.com then we simply not annotating the loadinfo
429 // and returning false from within this function. Please note that
430 // the handling for https-only mode is different from https-first mode,
431 // because https-only mode results in an exception page in case
432 // we encounter and endless upgrade downgrade loop.
433 bool isUpgradeDowngradeEndlessLoop
= IsUpgradeDowngradeEndlessLoop(
435 {UpgradeDowngradeEndlessLoopOptions::EnforceForHTTPSFirstMode
});
436 if (isUpgradeDowngradeEndlessLoop
) {
440 // We can upgrade the request - let's log to the console and set the status
441 // so we know that we upgraded the request.
442 if (aLoadInfo
->GetWasSchemelessInput() &&
443 !IsHttpsFirstModeEnabled(isPrivateWin
)) {
444 nsAutoCString urlCString
;
445 aURI
->GetSpec(urlCString
);
446 NS_ConvertUTF8toUTF16
urlString(urlCString
);
448 AutoTArray
<nsString
, 1> params
= {urlString
};
449 nsHTTPSOnlyUtils::LogLocalizedString("HTTPSFirstSchemeless", params
,
450 nsIScriptError::warningFlag
, aLoadInfo
,
453 mozilla::glean::httpsfirst::upgraded_schemeless
.Add();
455 nsAutoCString scheme
;
457 aURI
->GetScheme(scheme
);
458 scheme
.AppendLiteral("s");
459 NS_ConvertUTF8toUTF16
reportSpec(aURI
->GetSpecOrDefault());
460 NS_ConvertUTF8toUTF16
reportScheme(scheme
);
462 bool isSpeculative
= contentType
== ExtContentPolicy::TYPE_SPECULATIVE
;
463 AutoTArray
<nsString
, 2> params
= {reportSpec
, reportScheme
};
464 nsHTTPSOnlyUtils::LogLocalizedString(
465 isSpeculative
? "HTTPSOnlyUpgradeSpeculativeConnection"
466 : "HTTPSOnlyUpgradeRequest",
467 params
, nsIScriptError::warningFlag
, aLoadInfo
, aURI
, true);
469 if (!isSpeculative
) {
470 mozilla::glean::httpsfirst::upgraded
.Add();
474 // Set flag so we know that we upgraded the request
475 httpsOnlyStatus
|= nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST
;
476 aLoadInfo
->SetHttpsOnlyStatus(httpsOnlyStatus
);
481 already_AddRefed
<nsIURI
>
482 nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(
483 mozilla::net::DocumentLoadListener
* aDocumentLoadListener
,
485 nsCOMPtr
<nsIChannel
> channel
= aDocumentLoadListener
->GetChannel();
486 nsCOMPtr
<nsILoadInfo
> loadInfo
= channel
->LoadInfo();
487 uint32_t httpsOnlyStatus
= loadInfo
->GetHttpsOnlyStatus();
488 // Only downgrade if we this request was upgraded using HTTPS-First Mode
489 if (!(httpsOnlyStatus
& nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST
)) {
492 // Once loading is in progress we set that flag so that timeout counter
493 // measures do not kick in.
494 loadInfo
->SetHttpsOnlyStatus(
495 httpsOnlyStatus
| nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS
);
497 nsresult status
= aStatus
;
498 // Since 4xx and 5xx errors return NS_OK instead of NS_ERROR_*, we need
499 // to check each NS_OK for those errors.
500 // Only downgrade an NS_OK status if it is an 4xx or 5xx error.
501 if (NS_SUCCEEDED(aStatus
)) {
502 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(channel
);
503 // If no httpChannel exists we have nothing to do here.
507 uint32_t responseStatus
= 0;
508 if (NS_FAILED(httpChannel
->GetResponseStatus(&responseStatus
))) {
512 // In case we found one 4xx or 5xx error we need to log it later on,
513 // for that reason we flip the nsresult 'status' from 'NS_OK' to the
514 // corresponding NS_ERROR_*.
515 // To do so we convert the response status to an nsresult error
516 // Every NS_OK that is NOT an 4xx or 5xx error code won't get downgraded.
517 if (responseStatus
>= 400 && responseStatus
< 600) {
518 // HttpProxyResponseToErrorCode() maps 400 and 404 on
519 // the same error as a 500 status which would lead to no downgrade
520 // later on. For that reason we explicit filter for 400 and 404 status
521 // codes to log them correctly and to downgrade them if possible.
522 switch (responseStatus
) {
524 status
= NS_ERROR_PROXY_BAD_REQUEST
;
527 status
= NS_ERROR_PROXY_NOT_FOUND
;
530 status
= mozilla::net::HttpProxyResponseToErrorCode(responseStatus
);
534 if (NS_SUCCEEDED(status
)) {
539 // We're only downgrading if it's possible that the error was
540 // caused by the upgrade.
541 if (HttpsUpgradeUnrelatedErrorCode(status
)) {
545 nsCOMPtr
<nsIURI
> uri
;
546 nsresult rv
= channel
->GetURI(getter_AddRefs(uri
));
547 NS_ENSURE_SUCCESS(rv
, nullptr);
550 nsCOMPtr
<nsIURI
> newURI
;
552 // Only downgrade if the current scheme is (a) https or (b) view-source:https
553 if (uri
->SchemeIs("https")) {
554 rv
= uri
->GetSpec(spec
);
555 NS_ENSURE_SUCCESS(rv
, nullptr);
557 rv
= NS_NewURI(getter_AddRefs(newURI
), spec
);
558 NS_ENSURE_SUCCESS(rv
, nullptr);
560 rv
= NS_MutateURI(newURI
).SetScheme("http"_ns
).Finalize(
561 getter_AddRefs(newURI
));
562 NS_ENSURE_SUCCESS(rv
, nullptr);
563 } else if (uri
->SchemeIs("view-source")) {
564 nsCOMPtr
<nsINestedURI
> nestedURI
= do_QueryInterface(uri
);
568 nsCOMPtr
<nsIURI
> innerURI
;
569 rv
= nestedURI
->GetInnerURI(getter_AddRefs(innerURI
));
570 NS_ENSURE_SUCCESS(rv
, nullptr);
571 if (!innerURI
|| !innerURI
->SchemeIs("https")) {
574 rv
= NS_MutateURI(innerURI
).SetScheme("http"_ns
).Finalize(
575 getter_AddRefs(innerURI
));
576 NS_ENSURE_SUCCESS(rv
, nullptr);
578 nsAutoCString innerSpec
;
579 rv
= innerURI
->GetSpec(innerSpec
);
580 NS_ENSURE_SUCCESS(rv
, nullptr);
582 spec
.Append("view-source:");
583 spec
.Append(innerSpec
);
585 rv
= NS_NewURI(getter_AddRefs(newURI
), spec
);
586 NS_ENSURE_SUCCESS(rv
, nullptr);
591 // Log downgrade to console
592 NS_ConvertUTF8toUTF16
reportSpec(uri
->GetSpecOrDefault());
593 AutoTArray
<nsString
, 1> params
= {reportSpec
};
594 nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyFailedDowngradeAgain", params
,
595 nsIScriptError::warningFlag
, loadInfo
,
599 nsDOMNavigationTiming
* timing
= aDocumentLoadListener
->GetTiming();
601 mozilla::TimeStamp navigationStart
= timing
->GetNavigationStartTimeStamp();
602 if (navigationStart
) {
603 mozilla::TimeDuration duration
=
604 mozilla::TimeStamp::Now() - navigationStart
;
606 loadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
608 if (loadInfo
->GetWasSchemelessInput() &&
609 !IsHttpsFirstModeEnabled(isPrivateWin
)) {
610 mozilla::glean::httpsfirst::downgraded_schemeless
.Add();
612 mozilla::glean::httpsfirst::downgrade_time_schemeless
613 .AccumulateRawDuration(duration
);
616 mozilla::glean::httpsfirst::downgraded
.Add();
618 mozilla::glean::httpsfirst::downgrade_time
.AccumulateRawDuration(
625 return newURI
.forget();
629 bool nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(nsIChannel
* aChannel
,
631 // If there is no failed channel, then there is nothing to do here.
636 // If HTTPS-Only Mode is not enabled, then there is nothing to do here.
637 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
638 bool isPrivateWin
= loadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
639 if (!IsHttpsOnlyModeEnabled(isPrivateWin
)) {
643 // If the load is exempt or did not get upgraded,
644 // then there is nothing to do here.
645 uint32_t httpsOnlyStatus
= loadInfo
->GetHttpsOnlyStatus();
646 if (httpsOnlyStatus
& nsILoadInfo::HTTPS_ONLY_EXEMPT
||
647 httpsOnlyStatus
& nsILoadInfo::HTTPS_ONLY_UNINITIALIZED
) {
651 // If it's one of those errors, then most likely it's not a HTTPS-Only error
652 // (This list of errors is largely drawn from nsDocShell::DisplayLoadError())
653 return !HttpsUpgradeUnrelatedErrorCode(aError
);
657 bool nsHTTPSOnlyUtils::TestIfPrincipalIsExempt(nsIPrincipal
* aPrincipal
) {
658 static nsCOMPtr
<nsIPermissionManager
> sPermMgr
;
660 sPermMgr
= mozilla::components::PermissionManager::Service();
661 mozilla::ClearOnShutdown(&sPermMgr
);
663 NS_ENSURE_TRUE(sPermMgr
, false);
666 nsresult rv
= sPermMgr
->TestExactPermissionFromPrincipal(
667 aPrincipal
, "https-only-load-insecure"_ns
, &perm
);
668 NS_ENSURE_SUCCESS(rv
, false);
670 return perm
== nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW
||
671 perm
== nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW_SESSION
;
675 void nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(
676 nsIChannel
* aChannel
) {
677 NS_ENSURE_TRUE_VOID(aChannel
);
679 // If HTTPS-Only or HTTPS-First Mode is not enabled, then there is nothing to
681 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
682 bool isPrivateWin
= loadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
683 bool isHttpsOnly
= IsHttpsOnlyModeEnabled(isPrivateWin
);
684 bool isHttpsFirst
= IsHttpsFirstModeEnabled(isPrivateWin
);
685 bool isSchemelessHttpsFirst
=
686 (loadInfo
->GetWasSchemelessInput() &&
687 mozilla::StaticPrefs::dom_security_https_first_schemeless());
688 if (!isHttpsOnly
&& !isHttpsFirst
&& !isSchemelessHttpsFirst
) {
692 // if it's not a top-level load then there is nothing to here.
693 ExtContentPolicyType type
= loadInfo
->GetExternalContentPolicyType();
694 if (type
!= ExtContentPolicy::TYPE_DOCUMENT
) {
698 // it it's not an http channel, then there is nothing to do here.
699 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aChannel
);
704 nsCOMPtr
<nsIPrincipal
> principal
;
705 nsresult rv
= nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
706 aChannel
, getter_AddRefs(principal
));
707 NS_ENSURE_SUCCESS_VOID(rv
);
709 uint32_t httpsOnlyStatus
= loadInfo
->GetHttpsOnlyStatus();
710 bool isPrincipalExempt
= TestIfPrincipalIsExempt(principal
);
711 if (isPrincipalExempt
) {
712 httpsOnlyStatus
|= nsILoadInfo::HTTPS_ONLY_EXEMPT
;
714 // We explicitly remove the exemption flag, because this
715 // function is also consulted after redirects.
716 httpsOnlyStatus
&= ~nsILoadInfo::HTTPS_ONLY_EXEMPT
;
718 if (httpsOnlyStatus
& nsILoadInfo::HTTPS_FIRST_EXEMPT_NEXT_LOAD
&&
720 httpsOnlyStatus
&= ~nsILoadInfo::HTTPS_FIRST_EXEMPT_NEXT_LOAD
;
721 httpsOnlyStatus
|= nsILoadInfo::HTTPS_ONLY_EXEMPT
;
723 loadInfo
->SetHttpsOnlyStatus(httpsOnlyStatus
);
727 bool nsHTTPSOnlyUtils::IsSafeToAcceptCORSOrMixedContent(
728 nsILoadInfo
* aLoadInfo
) {
729 // Check if the request is exempt from upgrades
730 if ((aLoadInfo
->GetHttpsOnlyStatus() & nsILoadInfo::HTTPS_ONLY_EXEMPT
)) {
733 // Check if HTTPS-Only Mode is enabled for this request
734 bool isPrivateWin
= aLoadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
735 return nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin
);
739 bool nsHTTPSOnlyUtils::HttpsUpgradeUnrelatedErrorCode(nsresult aError
) {
740 return NS_ERROR_UNKNOWN_PROTOCOL
== aError
||
741 NS_ERROR_FILE_NOT_FOUND
== aError
||
742 NS_ERROR_FILE_ACCESS_DENIED
== aError
||
743 NS_ERROR_UNKNOWN_HOST
== aError
|| NS_ERROR_PHISHING_URI
== aError
||
744 NS_ERROR_MALWARE_URI
== aError
|| NS_ERROR_UNWANTED_URI
== aError
||
745 NS_ERROR_HARMFUL_URI
== aError
|| NS_ERROR_CONTENT_CRASHED
== aError
||
746 NS_ERROR_FRAME_CRASHED
== aError
|| NS_ERROR_SUPERFLUOS_AUTH
== aError
;
749 /* ------ Logging ------ */
752 void nsHTTPSOnlyUtils::LogLocalizedString(const char* aName
,
753 const nsTArray
<nsString
>& aParams
,
755 nsILoadInfo
* aLoadInfo
, nsIURI
* aURI
,
756 bool aUseHttpsFirst
) {
758 nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES
,
759 aName
, aParams
, logMsg
);
760 LogMessage(logMsg
, aFlags
, aLoadInfo
, aURI
, aUseHttpsFirst
);
764 void nsHTTPSOnlyUtils::LogMessage(const nsAString
& aMessage
, uint32_t aFlags
,
765 nsILoadInfo
* aLoadInfo
, nsIURI
* aURI
,
766 bool aUseHttpsFirst
) {
767 // do not log to the console if the loadinfo says we should not!
768 uint32_t httpsOnlyStatus
= aLoadInfo
->GetHttpsOnlyStatus();
769 if (httpsOnlyStatus
& nsILoadInfo::HTTPS_ONLY_DO_NOT_LOG_TO_CONSOLE
) {
773 // Prepending HTTPS-Only to the outgoing console message
775 message
.Append(aUseHttpsFirst
? u
"HTTPS-First Mode: "_ns
776 : u
"HTTPS-Only Mode: "_ns
);
777 message
.Append(aMessage
);
779 // Allow for easy distinction in devtools code.
780 auto category
= aUseHttpsFirst
? "HTTPSFirst"_ns
: "HTTPSOnly"_ns
;
782 uint64_t windowId
= aLoadInfo
->GetInnerWindowID();
784 windowId
= aLoadInfo
->GetTriggeringWindowId();
787 // Send to content console
788 nsContentUtils::ReportToConsoleByWindowID(message
, aFlags
, category
,
791 // Send to browser console
792 bool isPrivateWin
= aLoadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
793 nsContentUtils::LogSimpleConsoleError(message
, category
, isPrivateWin
,
794 true /* from chrome context */,
799 /* ------ Exceptions ------ */
802 bool nsHTTPSOnlyUtils::OnionException(nsIURI
* aURI
) {
803 // Onion-host exception can get disabled with a pref
804 if (mozilla::StaticPrefs::dom_security_https_only_mode_upgrade_onion()) {
809 return StringEndsWith(host
, ".onion"_ns
);
813 bool nsHTTPSOnlyUtils::LoopbackOrLocalException(nsIURI
* aURI
) {
814 nsAutoCString asciiHost
;
815 nsresult rv
= aURI
->GetAsciiHost(asciiHost
);
816 NS_ENSURE_SUCCESS(rv
, false);
818 // Let's make a quick check if the host matches these loopback strings
819 // before we do anything else
820 if (asciiHost
.EqualsLiteral("localhost") || asciiHost
.EqualsLiteral("::1")) {
824 mozilla::net::NetAddr addr
;
825 if (NS_FAILED(addr
.InitFromString(asciiHost
))) {
828 // Loopback IPs are always exempt
829 if (addr
.IsLoopbackAddr()) {
833 // Local IP exception can get disabled with a pref
835 mozilla::StaticPrefs::dom_security_https_only_mode_upgrade_local();
836 return (!upgradeLocal
&& addr
.IsIPAddrLocal());
840 bool nsHTTPSOnlyUtils::IsEqualURIExceptSchemeAndRef(nsIURI
* aHTTPSSchemeURI
,
842 nsILoadInfo
* aLoadInfo
) {
843 // 1. Check if one of parameters is null then webpage can't be loaded yet
844 // and no further inspections are needed
845 if (!aHTTPSSchemeURI
|| !aOtherURI
|| !aLoadInfo
) {
849 // 2. If the URI to be loaded is not http, then same origin will be detected
851 if (!mozilla::net::SchemeIsHTTP(aOtherURI
)) {
855 // 3. Check if the HTTPS-Only Mode is even enabled, before we do anything else
856 bool isPrivateWin
= aLoadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
857 if (!IsHttpsOnlyModeEnabled(isPrivateWin
) &&
858 !IsHttpsFirstModeEnabled(isPrivateWin
)) {
862 // 4. If the load is exempt, then it's defintely not related to https-only
863 uint32_t httpsOnlyStatus
= aLoadInfo
->GetHttpsOnlyStatus();
864 if (httpsOnlyStatus
& nsILoadInfo::HTTPS_ONLY_EXEMPT
) {
868 // 5. Create a new target URI with 'https' instead of 'http' and compare it
869 // to the current URI
871 nsresult rv
= aOtherURI
->GetPort(&port
);
872 NS_ENSURE_SUCCESS(rv
, false);
873 // a port of -1 indicates the default port, hence we upgrade from port 80 to
875 // otherwise we keep the port.
877 port
= NS_GetDefaultPort("https");
879 nsCOMPtr
<nsIURI
> newHTTPSchemeURI
;
880 rv
= NS_MutateURI(aOtherURI
)
881 .SetScheme("https"_ns
)
883 .Finalize(newHTTPSchemeURI
);
884 NS_ENSURE_SUCCESS(rv
, false);
886 bool uriEquals
= false;
888 aHTTPSSchemeURI
->EqualsExceptRef(newHTTPSchemeURI
, &uriEquals
))) {
894 /////////////////////////////////////////////////////////////////////
895 // Implementation of TestHTTPAnswerRunnable
897 NS_IMPL_ISUPPORTS_INHERITED(TestHTTPAnswerRunnable
, mozilla::Runnable
,
898 nsIStreamListener
, nsIInterfaceRequestor
,
901 TestHTTPAnswerRunnable::TestHTTPAnswerRunnable(
902 nsIURI
* aURI
, mozilla::net::DocumentLoadListener
* aDocumentLoadListener
)
903 : mozilla::Runnable("TestHTTPAnswerRunnable"),
905 mDocumentLoadListener(aDocumentLoadListener
) {}
908 bool TestHTTPAnswerRunnable::IsBackgroundRequestRedirected(
909 nsIHttpChannel
* aChannel
) {
910 // If there is no background request (aChannel), then there is nothing
915 // If the request was not redirected, then there is nothing to do here.
916 nsCOMPtr
<nsILoadInfo
> loadinfo
= aChannel
->LoadInfo();
917 if (loadinfo
->RedirectChain().IsEmpty()) {
921 // If the final URI is not targeting an https scheme, then we definitely not
922 // dealing with a 'same-origin' redirect.
923 nsCOMPtr
<nsIURI
> finalURI
;
924 nsresult rv
= NS_GetFinalChannelURI(aChannel
, getter_AddRefs(finalURI
));
925 NS_ENSURE_SUCCESS(rv
, false);
926 if (!finalURI
->SchemeIs("https")) {
930 // If the background request was not http, then there is nothing to do here.
931 nsCOMPtr
<nsIPrincipal
> firstURIPrincipal
;
932 loadinfo
->RedirectChain()[0]->GetPrincipal(getter_AddRefs(firstURIPrincipal
));
933 if (!firstURIPrincipal
|| !firstURIPrincipal
->SchemeIs("http")) {
937 // By now we have verified that the inital background request was http and
938 // that the redirected scheme is https. We want to find the following case
939 // where the background channel redirects to the https version of the
940 // top-level request.
941 // --> background channel: http://example.com
942 // |--> redirects to: https://example.com
943 // Now we have to check that the hosts are 'same-origin'.
944 nsAutoCString redirectHost
;
945 nsAutoCString finalHost
;
946 firstURIPrincipal
->GetAsciiHost(redirectHost
);
947 finalURI
->GetAsciiHost(finalHost
);
948 return finalHost
.Equals(redirectHost
);
952 TestHTTPAnswerRunnable::OnStartRequest(nsIRequest
* aRequest
) {
953 // If the request status is not OK, it means it encountered some
954 // kind of error in which case we do not want to do anything.
955 nsresult requestStatus
;
956 aRequest
->GetStatus(&requestStatus
);
957 if (requestStatus
!= NS_OK
) {
961 // Check if the original top-level channel which https-only is trying
962 // to upgrade is already in progress or if the channel is an auth channel.
963 // If it is in progress or Auth is in progress, then all good, if not
964 // then let's cancel that channel so we can dispaly the exception page.
965 nsCOMPtr
<nsIChannel
> docChannel
= mDocumentLoadListener
->GetChannel();
966 nsCOMPtr
<nsIHttpChannel
> httpsOnlyChannel
= do_QueryInterface(docChannel
);
967 if (httpsOnlyChannel
) {
968 nsCOMPtr
<nsILoadInfo
> loadInfo
= httpsOnlyChannel
->LoadInfo();
969 uint32_t topLevelLoadInProgress
=
970 loadInfo
->GetHttpsOnlyStatus() &
971 nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS
;
973 nsCOMPtr
<nsIHttpChannelInternal
> httpChannelInternal
=
974 do_QueryInterface(httpsOnlyChannel
);
975 bool isAuthChannel
= false;
976 mozilla::Unused
<< httpChannelInternal
->GetIsAuthChannel(&isAuthChannel
);
977 // some server configurations need a long time to respond to an https
978 // connection, but also redirect any http connection to the https version of
979 // it. If the top-level load has not started yet, but the http background
980 // request redirects to https, then do not show the error page, but keep
981 // waiting for the https response of the upgraded top-level request.
982 if (!topLevelLoadInProgress
) {
983 nsCOMPtr
<nsIHttpChannel
> backgroundHttpChannel
=
984 do_QueryInterface(aRequest
);
985 topLevelLoadInProgress
=
986 IsBackgroundRequestRedirected(backgroundHttpChannel
);
988 if (!topLevelLoadInProgress
&& !isAuthChannel
) {
989 // Only really cancel the original top-level channel if it's
990 // status is still NS_OK, otherwise it might have already
991 // encountered some other error and was cancelled.
992 nsresult httpsOnlyChannelStatus
;
993 httpsOnlyChannel
->GetStatus(&httpsOnlyChannelStatus
);
994 if (httpsOnlyChannelStatus
== NS_OK
) {
996 loadInfo
->GetOriginAttributes().mPrivateBrowsingId
> 0;
997 if (!nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin
)) {
998 // Record HTTPS-First Telemetry
999 if (loadInfo
->GetWasSchemelessInput() &&
1000 !nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin
)) {
1001 mozilla::glean::httpsfirst::downgraded_on_timer_schemeless
1004 mozilla::glean::httpsfirst::downgraded_on_timer
.AddToNumerator();
1008 httpsOnlyChannel
->Cancel(NS_ERROR_NET_TIMEOUT_EXTERNAL
);
1013 // Cancel this http request because it has reached the end of it's
1014 // lifetime at this point.
1015 aRequest
->Cancel(NS_ERROR_ABORT
);
1016 return NS_ERROR_ABORT
;
1020 TestHTTPAnswerRunnable::OnDataAvailable(nsIRequest
* aRequest
,
1021 nsIInputStream
* aStream
,
1022 uint64_t aOffset
, uint32_t aCount
) {
1023 // TestHTTPAnswerRunnable only cares about ::OnStartRequest which
1024 // will also cancel the request, so we should in fact never even
1026 MOZ_ASSERT(false, "how come we get to ::OnDataAvailable");
1031 TestHTTPAnswerRunnable::OnStopRequest(nsIRequest
* aRequest
,
1032 nsresult aStatusCode
) {
1033 // TestHTTPAnswerRunnable only cares about ::OnStartRequest
1038 TestHTTPAnswerRunnable::GetInterface(const nsIID
& aIID
, void** aResult
) {
1039 return QueryInterface(aIID
, aResult
);
1043 TestHTTPAnswerRunnable::Run() {
1044 // Wait N milliseconds to give the original https request a heads start
1045 // before firing up this http request in the background. By default the
1046 // timer is set to 3 seconds. If the https request has not received
1047 // any signal from the server during that time, than it's almost
1048 // certain the upgraded request will result in time out.
1049 uint32_t background_timer_ms
= mozilla::StaticPrefs::
1050 dom_security_https_only_fire_http_request_background_timer_ms();
1052 return NS_NewTimerWithCallback(getter_AddRefs(mTimer
), this,
1053 background_timer_ms
, nsITimer::TYPE_ONE_SHOT
);
1057 TestHTTPAnswerRunnable::Notify(nsITimer
* aTimer
) {
1063 // If the original channel has already started loading at this point
1064 // then there is no need to do the dance.
1065 nsCOMPtr
<nsIChannel
> origChannel
= mDocumentLoadListener
->GetChannel();
1066 nsCOMPtr
<nsILoadInfo
> origLoadInfo
= origChannel
->LoadInfo();
1067 uint32_t origHttpsOnlyStatus
= origLoadInfo
->GetHttpsOnlyStatus();
1068 uint32_t topLevelLoadInProgress
=
1069 origHttpsOnlyStatus
& nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS
;
1070 uint32_t downloadInProgress
=
1071 origHttpsOnlyStatus
& nsILoadInfo::HTTPS_ONLY_DOWNLOAD_IN_PROGRESS
;
1072 if (topLevelLoadInProgress
|| downloadInProgress
) {
1076 mozilla::OriginAttributes attrs
= origLoadInfo
->GetOriginAttributes();
1077 RefPtr
<nsIPrincipal
> nullPrincipal
= mozilla::NullPrincipal::Create(attrs
);
1079 uint32_t loadFlags
=
1080 nsIRequest::LOAD_ANONYMOUS
| nsIRequest::INHIBIT_CACHING
|
1081 nsIRequest::INHIBIT_PERSISTENT_CACHING
| nsIRequest::LOAD_BYPASS_CACHE
|
1082 nsIChannel::LOAD_BYPASS_SERVICE_WORKER
;
1084 // No need to connect to the URI including the path because we only care about
1085 // the round trip time if a server responds to an http request.
1086 nsCOMPtr
<nsIURI
> backgroundChannelURI
;
1087 nsAutoCString prePathStr
;
1088 nsresult rv
= mURI
->GetPrePath(prePathStr
);
1089 if (NS_WARN_IF(NS_FAILED(rv
))) {
1092 rv
= NS_NewURI(getter_AddRefs(backgroundChannelURI
), prePathStr
);
1093 if (NS_WARN_IF(NS_FAILED(rv
))) {
1097 // we are using TYPE_OTHER because TYPE_DOCUMENT might have side effects
1098 nsCOMPtr
<nsIChannel
> testHTTPChannel
;
1099 rv
= NS_NewChannel(getter_AddRefs(testHTTPChannel
), backgroundChannelURI
,
1101 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
1102 nsIContentPolicy::TYPE_OTHER
, nullptr, nullptr, nullptr,
1103 nullptr, loadFlags
);
1105 if (NS_WARN_IF(NS_FAILED(rv
))) {
1109 // We have exempt that load from HTTPS-Only to avoid getting upgraded
1110 // to https as well. Additonally let's not log that request to the console
1111 // because it might confuse end users.
1112 nsCOMPtr
<nsILoadInfo
> loadInfo
= testHTTPChannel
->LoadInfo();
1113 uint32_t httpsOnlyStatus
= loadInfo
->GetHttpsOnlyStatus();
1114 httpsOnlyStatus
|= nsILoadInfo::HTTPS_ONLY_EXEMPT
|
1115 nsILoadInfo::HTTPS_ONLY_DO_NOT_LOG_TO_CONSOLE
|
1116 nsILoadInfo::HTTPS_ONLY_BYPASS_ORB
;
1117 loadInfo
->SetHttpsOnlyStatus(httpsOnlyStatus
);
1119 testHTTPChannel
->SetNotificationCallbacks(this);
1120 testHTTPChannel
->AsyncOpen(this);