From 6ee5f4b5c14f5ed6ae329ed3f27bfb9e29c35307 Mon Sep 17 00:00:00 2001 From: Andrew Sutherland Date: Mon, 19 Oct 2020 07:00:41 +0000 Subject: [PATCH] Bug 1669355 - Refactor MIME type warnings into base class. r=necko-reviewers,valentin Differential Revision: https://phabricator.services.mozilla.com/D93906 --- netwerk/protocol/http/HttpBaseChannel.cpp | 338 ++++++++++++++++++ netwerk/protocol/http/HttpBaseChannel.h | 2 + .../protocol/http/InterceptedHttpChannel.cpp | 6 + netwerk/protocol/http/nsHttpChannel.cpp | 331 +---------------- 4 files changed, 347 insertions(+), 330 deletions(-) diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index cdd7cc80d98c..f87273293f7c 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -25,10 +25,12 @@ #include "mozilla/NullPrincipal.h" #include "mozilla/Services.h" #include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_security.h" #include "mozilla/Telemetry.h" #include "mozilla/Tokenizer.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/Document.h" #include "mozilla/dom/Performance.h" #include "mozilla/dom/PerformanceStorage.h" #include "mozilla/dom/WindowGlobalParent.h" @@ -2424,6 +2426,342 @@ nsresult HttpBaseChannel::ComputeCrossOriginOpenerPolicyMismatch() { return NS_OK; } +enum class Report { Error, Warning }; + +// Helper Function to report messages to the console when the loaded +// script had a wrong MIME type. +void ReportMimeTypeMismatch(HttpBaseChannel* aChannel, const char* aMessageName, + nsIURI* aURI, const nsACString& aContentType, + Report report) { + NS_ConvertUTF8toUTF16 spec(aURI->GetSpecOrDefault()); + NS_ConvertUTF8toUTF16 contentType(aContentType); + + aChannel->LogMimeTypeMismatch(nsCString(aMessageName), + report == Report::Warning, spec, contentType); +} + +// Check and potentially enforce X-Content-Type-Options: nosniff +nsresult ProcessXCTO(HttpBaseChannel* aChannel, nsIURI* aURI, + nsHttpResponseHead* aResponseHead, + nsILoadInfo* aLoadInfo) { + if (!aURI || !aResponseHead || !aLoadInfo) { + // if there is no uri, no response head or no loadInfo, then there is + // nothing to do + return NS_OK; + } + + // 1) Query the XCTO header and check if 'nosniff' is the first value. + nsAutoCString contentTypeOptionsHeader; + if (!aResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader)) { + // if failed to get XCTO header, then there is nothing to do. + return NS_OK; + } + + // let's compare the header (ignoring case) + // e.g. "NoSniFF" -> "nosniff" + // if it's not 'nosniff' then there is nothing to do here + if (!contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) { + // since we are getting here, the XCTO header was sent; + // a non matching value most likely means a mistake happenend; + // e.g. sending 'nosnif' instead of 'nosniff', let's log a warning. + AutoTArray params; + CopyUTF8toUTF16(contentTypeOptionsHeader, *params.AppendElement()); + RefPtr doc; + aLoadInfo->GetLoadingDocument(getter_AddRefs(doc)); + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "XCTO"_ns, doc, + nsContentUtils::eSECURITY_PROPERTIES, + "XCTOHeaderValueMissing", params); + return NS_OK; + } + + // 2) Query the content type from the channel + nsAutoCString contentType; + aResponseHead->ContentType(contentType); + + // 3) Compare the expected MIME type with the actual type + if (aLoadInfo->GetExternalContentPolicyType() == + nsIContentPolicy::TYPE_STYLESHEET) { + if (contentType.EqualsLiteral(TEXT_CSS)) { + return NS_OK; + } + ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType, + Report::Error); + return NS_ERROR_CORRUPTED_CONTENT; + } + + if (aLoadInfo->GetExternalContentPolicyType() == + nsIContentPolicy::TYPE_SCRIPT) { + if (nsContentUtils::IsJavascriptMIMEType( + NS_ConvertUTF8toUTF16(contentType))) { + return NS_OK; + } + ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType, + Report::Error); + return NS_ERROR_CORRUPTED_CONTENT; + } + + auto policyType = aLoadInfo->GetExternalContentPolicyType(); + if (policyType == nsIContentPolicy::TYPE_DOCUMENT || + policyType == nsIContentPolicy::TYPE_SUBDOCUMENT) { + // If the header XCTO nosniff is set for any browsing context, then + // we set the skipContentSniffing flag on the Loadinfo. Within + // GetMIMETypeFromContent we then bail early and do not do any sniffing. + aLoadInfo->SetSkipContentSniffing(true); + return NS_OK; + } + + return NS_OK; +} + +// Ensure that a load of type script has correct MIME type +nsresult EnsureMIMEOfScript(HttpBaseChannel* aChannel, nsIURI* aURI, + nsHttpResponseHead* aResponseHead, + nsILoadInfo* aLoadInfo) { + if (!aURI || !aResponseHead || !aLoadInfo) { + // if there is no uri, no response head or no loadInfo, then there is + // nothing to do + return NS_OK; + } + + if (aLoadInfo->GetExternalContentPolicyType() != + nsIContentPolicy::TYPE_SCRIPT) { + // if this is not a script load, then there is nothing to do + return NS_OK; + } + + nsAutoCString contentType; + aResponseHead->ContentType(contentType); + NS_ConvertUTF8toUTF16 typeString(contentType); + + if (nsContentUtils::IsJavascriptMIMEType(typeString)) { + // script load has type script + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::javaScript); + return NS_OK; + } + + switch (aLoadInfo->InternalContentPolicyType()) { + case nsIContentPolicy::TYPE_SCRIPT: + case nsIContentPolicy::TYPE_INTERNAL_SCRIPT: + case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD: + case nsIContentPolicy::TYPE_INTERNAL_MODULE: + case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD: + case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT: + case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT: + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::script_load); + break; + case nsIContentPolicy::TYPE_INTERNAL_WORKER: + case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER: + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worker_load); + break; + case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER: + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::serviceworker_load); + break; + case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS: + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::importScript_load); + break; + case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET: + case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET: + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worklet_load); + break; + default: + MOZ_ASSERT_UNREACHABLE("unexpected script type"); + break; + } + + bool isPrivateWin = aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; + bool isSameOrigin = false; + aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(aURI, isPrivateWin, + &isSameOrigin); + if (isSameOrigin) { + // same origin + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::same_origin); + } else { + bool cors = false; + nsAutoCString corsOrigin; + nsresult rv = aResponseHead->GetHeader( + nsHttp::ResolveAtom("Access-Control-Allow-Origin"), corsOrigin); + if (NS_SUCCEEDED(rv)) { + if (corsOrigin.Equals("*")) { + cors = true; + } else { + nsCOMPtr corsOriginURI; + rv = NS_NewURI(getter_AddRefs(corsOriginURI), corsOrigin); + if (NS_SUCCEEDED(rv)) { + bool isPrivateWin = + aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; + bool isSameOrigin = false; + aLoadInfo->GetLoadingPrincipal()->IsSameOrigin( + corsOriginURI, isPrivateWin, &isSameOrigin); + if (isSameOrigin) { + cors = true; + } + } + } + } + if (cors) { + // cors origin + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::CORS_origin); + } else { + // cross origin + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::cross_origin); + } + } + + bool block = false; + if (StringBeginsWith(contentType, "image/"_ns)) { + // script load has type image + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::image); + block = true; + } else if (StringBeginsWith(contentType, "audio/"_ns)) { + // script load has type audio + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::audio); + block = true; + } else if (StringBeginsWith(contentType, "video/"_ns)) { + // script load has type video + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::video); + block = true; + } else if (StringBeginsWith(contentType, "text/csv"_ns)) { + // script load has type text/csv + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_csv); + block = true; + } + + if (block) { + ReportMimeTypeMismatch(aChannel, "BlockScriptWithWrongMimeType2", aURI, + contentType, Report::Error); + return NS_ERROR_CORRUPTED_CONTENT; + } + + if (StringBeginsWith(contentType, "text/plain"_ns)) { + // script load has type text/plain + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_plain); + } else if (StringBeginsWith(contentType, "text/xml"_ns)) { + // script load has type text/xml + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_xml); + } else if (StringBeginsWith(contentType, "application/octet-stream"_ns)) { + // script load has type application/octet-stream + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_octet_stream); + } else if (StringBeginsWith(contentType, "application/xml"_ns)) { + // script load has type application/xml + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_xml); + } else if (StringBeginsWith(contentType, "application/json"_ns)) { + // script load has type application/json + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_json); + } else if (StringBeginsWith(contentType, "text/json"_ns)) { + // script load has type text/json + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_json); + } else if (StringBeginsWith(contentType, "text/html"_ns)) { + // script load has type text/html + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_html); + } else if (contentType.IsEmpty()) { + // script load has no type + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::empty); + } else { + // script load has unknown type + AccumulateCategorical( + Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::unknown); + } + + // We restrict importScripts() in worker code to JavaScript MIME types. + nsContentPolicyType internalType = aLoadInfo->InternalContentPolicyType(); + if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS) { + ReportMimeTypeMismatch(aChannel, "BlockImportScriptsWithWrongMimeType", + aURI, contentType, Report::Error); + return NS_ERROR_CORRUPTED_CONTENT; + } + + if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER || + internalType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER) { + // Do not block the load if the feature is not enabled. + if (!StaticPrefs::security_block_Worker_with_wrong_mime()) { + return NS_OK; + } + + ReportMimeTypeMismatch(aChannel, "BlockWorkerWithWrongMimeType", aURI, + contentType, Report::Error); + return NS_ERROR_CORRUPTED_CONTENT; + } + + // ES6 modules require a strict MIME type check. + if (internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE || + internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD) { + ReportMimeTypeMismatch(aChannel, "BlockModuleWithWrongMimeType", aURI, + contentType, Report::Error); + return NS_ERROR_CORRUPTED_CONTENT; + } + + return NS_OK; +} + +// Warn when a load of type script uses a wrong MIME type and +// wasn't blocked by EnsureMIMEOfScript or ProcessXCTO. +void WarnWrongMIMEOfScript(HttpBaseChannel* aChannel, nsIURI* aURI, + nsHttpResponseHead* aResponseHead, + nsILoadInfo* aLoadInfo) { + if (!aURI || !aResponseHead || !aLoadInfo) { + // If there is no uri, no response head or no loadInfo, then there is + // nothing to do. + return; + } + + if (aLoadInfo->GetExternalContentPolicyType() != + nsIContentPolicy::TYPE_SCRIPT) { + // If this is not a script load, then there is nothing to do. + return; + } + + bool succeeded; + MOZ_ALWAYS_SUCCEEDS(aChannel->GetRequestSucceeded(&succeeded)); + if (!succeeded) { + // Do not warn for failed loads: HTTP error pages are usually in HTML. + return; + } + + nsAutoCString contentType; + aResponseHead->ContentType(contentType); + NS_ConvertUTF8toUTF16 typeString(contentType); + if (!nsContentUtils::IsJavascriptMIMEType(typeString)) { + ReportMimeTypeMismatch(aChannel, "WarnScriptWithWrongMimeType", aURI, + contentType, Report::Warning); + } +} + +nsresult HttpBaseChannel::ValidateMIMEType() { + nsresult rv = EnsureMIMEOfScript(this, mURI, mResponseHead.get(), mLoadInfo); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ProcessXCTO(this, mURI, mResponseHead.get(), mLoadInfo); + if (NS_FAILED(rv)) { + return rv; + } + + WarnWrongMIMEOfScript(this, mURI, mResponseHead.get(), mLoadInfo); + return NS_OK; +} + NS_IMETHODIMP HttpBaseChannel::SetCookie(const nsACString& aCookieHeader) { if (mLoadFlags & LOAD_ANONYMOUS) return NS_OK; diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index 97cf37ab0cfc..1cefab9d1382 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -615,6 +615,8 @@ class HttpBaseChannel : public nsHashPropertyBag, nsresult ComputeCrossOriginOpenerPolicyMismatch(); + nsresult ValidateMIMEType(); + friend class PrivateBrowsingChannel; friend class InterceptFailedOnStop; diff --git a/netwerk/protocol/http/InterceptedHttpChannel.cpp b/netwerk/protocol/http/InterceptedHttpChannel.cpp index 86dfc4c832ab..df524acc2f95 100644 --- a/netwerk/protocol/http/InterceptedHttpChannel.cpp +++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp @@ -1031,6 +1031,12 @@ InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest) { Cancel(mStatus); } + rv = ValidateMIMEType(); + if (NS_FAILED(rv)) { + mStatus = rv; + Cancel(mStatus); + } + mOnStartRequestCalled = true; if (mListener) { return mListener->OnStartRequest(this); diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 64337c141390..be6c4b6169ab 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -1525,327 +1525,6 @@ HttpTrafficCategory nsHttpChannel::CreateTrafficCategory() { NS_UsePrivateBrowsing(this), isSystemPrincipal, isThirdParty, cos, tc); } -enum class Report { Error, Warning }; - -// Helper Function to report messages to the console when the loaded -// script had a wrong MIME type. -void ReportMimeTypeMismatch(nsHttpChannel* aChannel, const char* aMessageName, - nsIURI* aURI, const nsACString& aContentType, - Report report) { - NS_ConvertUTF8toUTF16 spec(aURI->GetSpecOrDefault()); - NS_ConvertUTF8toUTF16 contentType(aContentType); - - aChannel->LogMimeTypeMismatch(nsCString(aMessageName), - report == Report::Warning, spec, contentType); -} - -// Check and potentially enforce X-Content-Type-Options: nosniff -nsresult ProcessXCTO(nsHttpChannel* aChannel, nsIURI* aURI, - nsHttpResponseHead* aResponseHead, - nsILoadInfo* aLoadInfo) { - if (!aURI || !aResponseHead || !aLoadInfo) { - // if there is no uri, no response head or no loadInfo, then there is - // nothing to do - return NS_OK; - } - - // 1) Query the XCTO header and check if 'nosniff' is the first value. - nsAutoCString contentTypeOptionsHeader; - if (!aResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader)) { - // if failed to get XCTO header, then there is nothing to do. - return NS_OK; - } - - // let's compare the header (ignoring case) - // e.g. "NoSniFF" -> "nosniff" - // if it's not 'nosniff' then there is nothing to do here - if (!contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) { - // since we are getting here, the XCTO header was sent; - // a non matching value most likely means a mistake happenend; - // e.g. sending 'nosnif' instead of 'nosniff', let's log a warning. - AutoTArray params; - CopyUTF8toUTF16(contentTypeOptionsHeader, *params.AppendElement()); - RefPtr doc; - aLoadInfo->GetLoadingDocument(getter_AddRefs(doc)); - nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "XCTO"_ns, doc, - nsContentUtils::eSECURITY_PROPERTIES, - "XCTOHeaderValueMissing", params); - return NS_OK; - } - - // 2) Query the content type from the channel - nsAutoCString contentType; - aResponseHead->ContentType(contentType); - - // 3) Compare the expected MIME type with the actual type - if (aLoadInfo->GetExternalContentPolicyType() == - nsIContentPolicy::TYPE_STYLESHEET) { - if (contentType.EqualsLiteral(TEXT_CSS)) { - return NS_OK; - } - ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType, - Report::Error); - return NS_ERROR_CORRUPTED_CONTENT; - } - - if (aLoadInfo->GetExternalContentPolicyType() == - nsIContentPolicy::TYPE_SCRIPT) { - if (nsContentUtils::IsJavascriptMIMEType( - NS_ConvertUTF8toUTF16(contentType))) { - return NS_OK; - } - ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType, - Report::Error); - return NS_ERROR_CORRUPTED_CONTENT; - } - - auto policyType = aLoadInfo->GetExternalContentPolicyType(); - if (policyType == nsIContentPolicy::TYPE_DOCUMENT || - policyType == nsIContentPolicy::TYPE_SUBDOCUMENT) { - // If the header XCTO nosniff is set for any browsing context, then - // we set the skipContentSniffing flag on the Loadinfo. Within - // GetMIMETypeFromContent we then bail early and do not do any sniffing. - aLoadInfo->SetSkipContentSniffing(true); - return NS_OK; - } - - return NS_OK; -} - -// Ensure that a load of type script has correct MIME type -nsresult EnsureMIMEOfScript(nsHttpChannel* aChannel, nsIURI* aURI, - nsHttpResponseHead* aResponseHead, - nsILoadInfo* aLoadInfo) { - if (!aURI || !aResponseHead || !aLoadInfo) { - // if there is no uri, no response head or no loadInfo, then there is - // nothing to do - return NS_OK; - } - - if (aLoadInfo->GetExternalContentPolicyType() != - nsIContentPolicy::TYPE_SCRIPT) { - // if this is not a script load, then there is nothing to do - return NS_OK; - } - - nsAutoCString contentType; - aResponseHead->ContentType(contentType); - NS_ConvertUTF8toUTF16 typeString(contentType); - - if (nsContentUtils::IsJavascriptMIMEType(typeString)) { - // script load has type script - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::javaScript); - return NS_OK; - } - - switch (aLoadInfo->InternalContentPolicyType()) { - case nsIContentPolicy::TYPE_SCRIPT: - case nsIContentPolicy::TYPE_INTERNAL_SCRIPT: - case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD: - case nsIContentPolicy::TYPE_INTERNAL_MODULE: - case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD: - case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT: - case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT: - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::script_load); - break; - case nsIContentPolicy::TYPE_INTERNAL_WORKER: - case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER: - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worker_load); - break; - case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER: - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::serviceworker_load); - break; - case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS: - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::importScript_load); - break; - case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET: - case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET: - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worklet_load); - break; - default: - MOZ_ASSERT_UNREACHABLE("unexpected script type"); - break; - } - - bool isPrivateWin = aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; - bool isSameOrigin = false; - aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(aURI, isPrivateWin, - &isSameOrigin); - if (isSameOrigin) { - // same origin - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::same_origin); - } else { - bool cors = false; - nsAutoCString corsOrigin; - nsresult rv = aResponseHead->GetHeader( - nsHttp::ResolveAtom("Access-Control-Allow-Origin"), corsOrigin); - if (NS_SUCCEEDED(rv)) { - if (corsOrigin.Equals("*")) { - cors = true; - } else { - nsCOMPtr corsOriginURI; - rv = NS_NewURI(getter_AddRefs(corsOriginURI), corsOrigin); - if (NS_SUCCEEDED(rv)) { - bool isPrivateWin = - aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; - bool isSameOrigin = false; - aLoadInfo->GetLoadingPrincipal()->IsSameOrigin( - corsOriginURI, isPrivateWin, &isSameOrigin); - if (isSameOrigin) { - cors = true; - } - } - } - } - if (cors) { - // cors origin - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::CORS_origin); - } else { - // cross origin - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::cross_origin); - } - } - - bool block = false; - if (StringBeginsWith(contentType, "image/"_ns)) { - // script load has type image - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::image); - block = true; - } else if (StringBeginsWith(contentType, "audio/"_ns)) { - // script load has type audio - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::audio); - block = true; - } else if (StringBeginsWith(contentType, "video/"_ns)) { - // script load has type video - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::video); - block = true; - } else if (StringBeginsWith(contentType, "text/csv"_ns)) { - // script load has type text/csv - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_csv); - block = true; - } - - if (block) { - ReportMimeTypeMismatch(aChannel, "BlockScriptWithWrongMimeType2", aURI, - contentType, Report::Error); - return NS_ERROR_CORRUPTED_CONTENT; - } - - if (StringBeginsWith(contentType, "text/plain"_ns)) { - // script load has type text/plain - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_plain); - } else if (StringBeginsWith(contentType, "text/xml"_ns)) { - // script load has type text/xml - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_xml); - } else if (StringBeginsWith(contentType, "application/octet-stream"_ns)) { - // script load has type application/octet-stream - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_octet_stream); - } else if (StringBeginsWith(contentType, "application/xml"_ns)) { - // script load has type application/xml - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_xml); - } else if (StringBeginsWith(contentType, "application/json"_ns)) { - // script load has type application/json - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_json); - } else if (StringBeginsWith(contentType, "text/json"_ns)) { - // script load has type text/json - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_json); - } else if (StringBeginsWith(contentType, "text/html"_ns)) { - // script load has type text/html - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_html); - } else if (contentType.IsEmpty()) { - // script load has no type - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::empty); - } else { - // script load has unknown type - AccumulateCategorical( - Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::unknown); - } - - // We restrict importScripts() in worker code to JavaScript MIME types. - nsContentPolicyType internalType = aLoadInfo->InternalContentPolicyType(); - if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS) { - ReportMimeTypeMismatch(aChannel, "BlockImportScriptsWithWrongMimeType", - aURI, contentType, Report::Error); - return NS_ERROR_CORRUPTED_CONTENT; - } - - if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER || - internalType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER) { - // Do not block the load if the feature is not enabled. - if (!StaticPrefs::security_block_Worker_with_wrong_mime()) { - return NS_OK; - } - - ReportMimeTypeMismatch(aChannel, "BlockWorkerWithWrongMimeType", aURI, - contentType, Report::Error); - return NS_ERROR_CORRUPTED_CONTENT; - } - - // ES6 modules require a strict MIME type check. - if (internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE || - internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD) { - ReportMimeTypeMismatch(aChannel, "BlockModuleWithWrongMimeType", aURI, - contentType, Report::Error); - return NS_ERROR_CORRUPTED_CONTENT; - } - - return NS_OK; -} - -// Warn when a load of type script uses a wrong MIME type and -// wasn't blocked by EnsureMIMEOfScript or ProcessXCTO. -void WarnWrongMIMEOfScript(nsHttpChannel* aChannel, nsIURI* aURI, - nsHttpResponseHead* aResponseHead, - nsILoadInfo* aLoadInfo) { - if (!aURI || !aResponseHead || !aLoadInfo) { - // If there is no uri, no response head or no loadInfo, then there is - // nothing to do. - return; - } - - if (aLoadInfo->GetExternalContentPolicyType() != - nsIContentPolicy::TYPE_SCRIPT) { - // If this is not a script load, then there is nothing to do. - return; - } - - bool succeeded; - MOZ_ALWAYS_SUCCEEDS(aChannel->GetRequestSucceeded(&succeeded)); - if (!succeeded) { - // Do not warn for failed loads: HTTP error pages are usually in HTML. - return; - } - - nsAutoCString contentType; - aResponseHead->ContentType(contentType); - NS_ConvertUTF8toUTF16 typeString(contentType); - if (!nsContentUtils::IsJavascriptMIMEType(typeString)) { - ReportMimeTypeMismatch(aChannel, "WarnScriptWithWrongMimeType", aURI, - contentType, Report::Warning); - } -} - void nsHttpChannel::SetCachedContentType() { if (!mResponseHead) { return; @@ -1913,7 +1592,7 @@ nsresult nsHttpChannel::CallOnStartRequest() { mOnStartRequestCalled = true; }); - nsresult rv = EnsureMIMEOfScript(this, mURI, mResponseHead.get(), mLoadInfo); + nsresult rv = ValidateMIMEType(); // Since ODA and OnStopRequest could be sent from socket process directly, we // need to update the channel status before calling mListener->OnStartRequest. // This is the only way to let child process discard the already received ODA @@ -1923,14 +1602,6 @@ nsresult nsHttpChannel::CallOnStartRequest() { return mStatus; } - rv = ProcessXCTO(this, mURI, mResponseHead.get(), mLoadInfo); - if (NS_FAILED(rv)) { - mStatus = rv; - return mStatus; - } - - WarnWrongMIMEOfScript(this, mURI, mResponseHead.get(), mLoadInfo); - // Allow consumers to override our content type if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) { // NOTE: We can have both a txn pump and a cache pump when the cache