diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:50 +0000 |
commit | def92d1b8e9d373e2f6f27c366d578d97d8960c6 (patch) | |
tree | 2ef34b9ad8bb9a9220e05d60352558b15f513894 /toolkit/components/contentanalysis | |
parent | Adding debian version 125.0.3-1. (diff) | |
download | firefox-def92d1b8e9d373e2f6f27c366d578d97d8960c6.tar.xz firefox-def92d1b8e9d373e2f6f27c366d578d97d8960c6.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/contentanalysis')
13 files changed, 1799 insertions, 137 deletions
diff --git a/toolkit/components/contentanalysis/ContentAnalysis.cpp b/toolkit/components/contentanalysis/ContentAnalysis.cpp index 1c71f5d986..2977072984 100644 --- a/toolkit/components/contentanalysis/ContentAnalysis.cpp +++ b/toolkit/components/contentanalysis/ContentAnalysis.cpp @@ -11,6 +11,7 @@ #include "base/process_util.h" #include "GMPUtils.h" // ToHexString #include "mozilla/Components.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/Logging.h" @@ -24,6 +25,9 @@ #include "nsIFile.h" #include "nsIGlobalObject.h" #include "nsIObserverService.h" +#include "nsIOutputStream.h" +#include "nsIPrintSettings.h" +#include "nsIStorageStream.h" #include "ScopedNSSTypes.h" #include "xpcpublic.h" @@ -35,6 +39,7 @@ # include <windows.h> # define SECURITY_WIN32 1 # include <security.h> +# include "mozilla/NativeNt.h" # include "mozilla/WinDllServices.h" #endif // XP_WIN @@ -114,6 +119,11 @@ nsIContentAnalysisAcknowledgement::FinalAction ConvertResult( } // anonymous namespace namespace mozilla::contentanalysis { +ContentAnalysisRequest::~ContentAnalysisRequest() { +#ifdef XP_WIN + CloseHandle(mPrintDataHandle); +#endif +} NS_IMETHODIMP ContentAnalysisRequest::GetAnalysisType(AnalysisType* aAnalysisType) { @@ -134,6 +144,34 @@ ContentAnalysisRequest::GetFilePath(nsAString& aFilePath) { } NS_IMETHODIMP +ContentAnalysisRequest::GetPrintDataHandle(uint64_t* aPrintDataHandle) { +#ifdef XP_WIN + uintptr_t printDataHandle = reinterpret_cast<uintptr_t>(mPrintDataHandle); + uint64_t printDataValue = static_cast<uint64_t>(printDataHandle); + *aPrintDataHandle = printDataValue; + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +NS_IMETHODIMP +ContentAnalysisRequest::GetPrinterName(nsAString& aPrinterName) { + aPrinterName = mPrinterName; + return NS_OK; +} + +NS_IMETHODIMP +ContentAnalysisRequest::GetPrintDataSize(uint64_t* aPrintDataSize) { +#ifdef XP_WIN + *aPrintDataSize = mPrintDataSize; + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +NS_IMETHODIMP ContentAnalysisRequest::GetUrl(nsIURI** aUrl) { NS_ENSURE_ARG_POINTER(aUrl); NS_IF_ADDREF(*aUrl = mUrl); @@ -234,6 +272,8 @@ ContentAnalysisRequest::ContentAnalysisRequest( mUrl(std::move(aUrl)), mSha256Digest(std::move(aSha256Digest)), mWindowGlobalParent(aWindowGlobalParent) { + MOZ_ASSERT(aAnalysisType != AnalysisType::ePrint, + "Print should use other ContentAnalysisRequest constructor!"); if (aStringIsFilePath) { mFilePath = std::move(aString); } else { @@ -251,6 +291,32 @@ ContentAnalysisRequest::ContentAnalysisRequest( mRequestToken = GenerateRequestToken(); } +ContentAnalysisRequest::ContentAnalysisRequest( + const nsTArray<uint8_t> aPrintData, nsCOMPtr<nsIURI> aUrl, + nsString aPrinterName, dom::WindowGlobalParent* aWindowGlobalParent) + : mAnalysisType(AnalysisType::ePrint), + mUrl(std::move(aUrl)), + mPrinterName(std::move(aPrinterName)), + mWindowGlobalParent(aWindowGlobalParent) { +#ifdef XP_WIN + LARGE_INTEGER dataContentLength; + dataContentLength.QuadPart = static_cast<LONGLONG>(aPrintData.Length()); + mPrintDataHandle = ::CreateFileMappingW( + INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, dataContentLength.HighPart, + dataContentLength.LowPart, nullptr); + if (mPrintDataHandle) { + mozilla::nt::AutoMappedView view(mPrintDataHandle, FILE_MAP_ALL_ACCESS); + memcpy(view.as<uint8_t>(), aPrintData.Elements(), aPrintData.Length()); + mPrintDataSize = aPrintData.Length(); + } +#else + MOZ_ASSERT_UNREACHABLE( + "Content Analysis is not supported on non-Windows platforms"); +#endif + mOperationTypeForDisplay = OperationType::eOperationPrint; + mRequestToken = GenerateRequestToken(); +} + nsresult ContentAnalysisRequest::GetFileDigest(const nsAString& aFilePath, nsCString& aDigestString) { MOZ_DIAGNOSTIC_ASSERT( @@ -366,22 +432,44 @@ static nsresult ConvertToProtobuf( requestData->set_digest(sha256Digest.get()); } - nsString filePath; - rv = aIn->GetFilePath(filePath); - NS_ENSURE_SUCCESS(rv, rv); - if (!filePath.IsEmpty()) { - std::string filePathStr = NS_ConvertUTF16toUTF8(filePath).get(); - aOut->set_file_path(filePathStr); - auto filename = filePathStr.substr(filePathStr.find_last_of("/\\") + 1); - if (!filename.empty()) { - requestData->set_filename(filename); + if (analysisType == nsIContentAnalysisRequest::AnalysisType::ePrint) { +#if XP_WIN + uint64_t printDataHandle; + MOZ_TRY(aIn->GetPrintDataHandle(&printDataHandle)); + if (!printDataHandle) { + return NS_ERROR_OUT_OF_MEMORY; } + aOut->mutable_print_data()->set_handle(printDataHandle); + + uint64_t printDataSize; + MOZ_TRY(aIn->GetPrintDataSize(&printDataSize)); + aOut->mutable_print_data()->set_size(printDataSize); + + nsString printerName; + MOZ_TRY(aIn->GetPrinterName(printerName)); + requestData->mutable_print_metadata()->set_printer_name( + NS_ConvertUTF16toUTF8(printerName).get()); +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif } else { - nsString textContent; - rv = aIn->GetTextContent(textContent); + nsString filePath; + rv = aIn->GetFilePath(filePath); NS_ENSURE_SUCCESS(rv, rv); - MOZ_ASSERT(!textContent.IsEmpty()); - aOut->set_text_content(NS_ConvertUTF16toUTF8(textContent).get()); + if (!filePath.IsEmpty()) { + std::string filePathStr = NS_ConvertUTF16toUTF8(filePath).get(); + aOut->set_file_path(filePathStr); + auto filename = filePathStr.substr(filePathStr.find_last_of("/\\") + 1); + if (!filename.empty()) { + requestData->set_filename(filename); + } + } else { + nsString textContent; + rv = aIn->GetTextContent(textContent); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(!textContent.IsEmpty()); + aOut->set_text_content(NS_ConvertUTF16toUTF8(textContent).get()); + } } #ifdef XP_WIN @@ -412,8 +500,31 @@ static nsresult ConvertToProtobuf( return NS_OK; } +namespace { +// We don't want this overload to be called for string parameters, so +// use std::enable_if +template <typename T> +typename std::enable_if_t<!std::is_same<std::string, std::decay_t<T>>::value, + void> +LogWithMaxLength(std::stringstream& ss, T value, size_t maxLength) { + ss << value; +} + +// 0 indicates no max length +template <typename T> +typename std::enable_if_t<std::is_same<std::string, std::decay_t<T>>::value, + void> +LogWithMaxLength(std::stringstream& ss, T value, size_t maxLength) { + if (!maxLength || value.length() < maxLength) { + ss << value; + } else { + ss << value.substr(0, maxLength) << " (truncated)"; + } +} +} // namespace + static void LogRequest( - content_analysis::sdk::ContentAnalysisRequest* aPbRequest) { + const content_analysis::sdk::ContentAnalysisRequest* aPbRequest) { // We cannot use Protocol Buffer's DebugString() because we optimize for // lite runtime. if (!static_cast<LogModule*>(gContentAnalysisLog) @@ -425,12 +536,13 @@ static void LogRequest( ss << "ContentAnalysisRequest:" << "\n"; -#define ADD_FIELD(PBUF, NAME, FUNC) \ - ss << " " << (NAME) << ": "; \ - if ((PBUF)->has_##FUNC()) \ - ss << (PBUF)->FUNC() << "\n"; \ - else \ - ss << "<none>" \ +#define ADD_FIELD(PBUF, NAME, FUNC) \ + ss << " " << (NAME) << ": "; \ + if ((PBUF)->has_##FUNC()) { \ + LogWithMaxLength(ss, (PBUF)->FUNC(), 500); \ + ss << "\n"; \ + } else \ + ss << "<none>" \ << "\n"; #define ADD_EXISTS(PBUF, NAME, FUNC) \ @@ -612,6 +724,12 @@ ContentAnalysisResponse::GetAction(Action* aAction) { return NS_OK; } +NS_IMETHODIMP +ContentAnalysisResponse::GetCancelError(CancelError* aCancelError) { + *aCancelError = mCancelError; + return NS_OK; +} + static void LogAcknowledgement( content_analysis::sdk::ContentAnalysisAcknowledgement* aPbAck) { if (!static_cast<LogModule*>(gContentAnalysisLog) @@ -643,6 +761,10 @@ void ContentAnalysisResponse::SetOwner(RefPtr<ContentAnalysis> aOwner) { mOwner = std::move(aOwner); } +void ContentAnalysisResponse::SetCancelError(CancelError aCancelError) { + mCancelError = aCancelError; +} + void ContentAnalysisResponse::ResolveWarnAction(bool aAllowContent) { MOZ_ASSERT(mAction == Action::eWarn); mAction = aAllowContent ? Action::eAllow : Action::eBlock; @@ -684,15 +806,17 @@ NS_IMETHODIMP ContentAnalysisResult::GetShouldAllowContent( if (mValue.is<NoContentAnalysisResult>()) { NoContentAnalysisResult result = mValue.as<NoContentAnalysisResult>(); if (Preferences::GetBool(kDefaultAllowPref)) { - *aShouldAllowContent = result != NoContentAnalysisResult::CANCELED; + *aShouldAllowContent = + result != NoContentAnalysisResult::DENY_DUE_TO_CANCELED; } else { // Note that we allow content if we're unable to get it (for example, if // there's clipboard content that is not text or file) *aShouldAllowContent = - result == NoContentAnalysisResult::CONTENT_ANALYSIS_NOT_ACTIVE || - result == - NoContentAnalysisResult::CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS || - result == NoContentAnalysisResult::ERROR_COULD_NOT_GET_DATA; + result == NoContentAnalysisResult:: + ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE || + result == NoContentAnalysisResult:: + ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS || + result == NoContentAnalysisResult::ALLOW_DUE_TO_COULD_NOT_GET_DATA; } } else { *aShouldAllowContent = @@ -735,8 +859,8 @@ ContentAnalysis::UrlFilterResult ContentAnalysis::FilterByUrlLists( nsIContentAnalysisRequest* aRequest) { EnsureParsedUrlFilters(); - nsIURI* nsiUrl = nullptr; - MOZ_ALWAYS_SUCCEEDS(aRequest->GetUrl(&nsiUrl)); + nsCOMPtr<nsIURI> nsiUrl; + MOZ_ALWAYS_SUCCEEDS(aRequest->GetUrl(getter_AddRefs(nsiUrl))); nsCString urlString; nsresult rv = nsiUrl->GetSpec(urlString); NS_ENSURE_SUCCESS(rv, UrlFilterResult::eDeny); @@ -825,6 +949,8 @@ NS_IMPL_ISUPPORTS(ContentAnalysisAcknowledgement, nsIContentAnalysisAcknowledgement); NS_IMPL_ISUPPORTS(ContentAnalysisCallback, nsIContentAnalysisCallback); NS_IMPL_ISUPPORTS(ContentAnalysisResult, nsIContentAnalysisResult); +NS_IMPL_ISUPPORTS(ContentAnalysisDiagnosticInfo, + nsIContentAnalysisDiagnosticInfo); NS_IMPL_ISUPPORTS(ContentAnalysis, nsIContentAnalysis, ContentAnalysis); ContentAnalysis::ContentAnalysis() @@ -942,6 +1068,7 @@ nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken, // May be shutting down return; } + owner->SetLastResult(aResult); nsCOMPtr<nsIObserverService> obsServ = mozilla::services::GetObserverService(); bool allow = Preferences::GetBool(kDefaultAllowPref); @@ -951,6 +1078,20 @@ nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken, : nsIContentAnalysisResponse::Action::eCanceled, aRequestToken); response->SetOwner(owner); + nsIContentAnalysisResponse::CancelError cancelError; + switch (aResult) { + case NS_ERROR_NOT_AVAILABLE: + cancelError = nsIContentAnalysisResponse::CancelError::eNoAgent; + break; + case NS_ERROR_INVALID_SIGNATURE: + cancelError = + nsIContentAnalysisResponse::CancelError::eInvalidAgentSignature; + break; + default: + cancelError = nsIContentAnalysisResponse::CancelError::eErrorOther; + break; + } + response->SetCancelError(cancelError); obsServ->NotifyObservers(response, "dlp-response", nullptr); nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder; { @@ -1156,6 +1297,8 @@ void ContentAnalysis::IssueResponse(RefPtr<ContentAnalysisResponse>& response) { LOGE("Content analysis couldn't get request token from response!"); return; } + // Successfully made a request to the agent, so mark that we succeeded + mLastResult = NS_OK; Maybe<CallbackData> maybeCallbackData; { @@ -1187,7 +1330,9 @@ void ContentAnalysis::IssueResponse(RefPtr<ContentAnalysisResponse>& response) { LOGD("Content analysis resolving response promise for token %s", responseRequestToken.get()); - nsIContentAnalysisResponse::Action action = response->GetAction(); + nsIContentAnalysisResponse::Action action; + DebugOnly<nsresult> rv = response->GetAction(&action); + MOZ_ASSERT(NS_SUCCEEDED(rv)); nsCOMPtr<nsIObserverService> obsServ = mozilla::services::GetObserverService(); if (action == nsIContentAnalysisResponse::Action::eWarn) { @@ -1232,8 +1377,28 @@ NS_IMETHODIMP ContentAnalysis::AnalyzeContentRequestCallback( nsIContentAnalysisRequest* aRequest, bool aAutoAcknowledge, nsIContentAnalysisCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); NS_ENSURE_ARG(aRequest); NS_ENSURE_ARG(aCallback); + nsresult rv = AnalyzeContentRequestCallbackPrivate(aRequest, aAutoAcknowledge, + aCallback); + if (NS_FAILED(rv)) { + nsCString requestToken; + nsresult requestTokenRv = aRequest->GetRequestToken(requestToken); + NS_ENSURE_SUCCESS(requestTokenRv, requestTokenRv); + CancelWithError(requestToken, rv); + } + return rv; +} + +nsresult ContentAnalysis::AnalyzeContentRequestCallbackPrivate( + nsIContentAnalysisRequest* aRequest, bool aAutoAcknowledge, + nsIContentAnalysisCallback* aCallback) { + // Make sure we send the notification first, so if we later return + // an error the JS will handle it correctly. + nsCOMPtr<nsIObserverService> obsServ = + mozilla::services::GetObserverService(); + obsServ->NotifyObservers(aRequest, "dlp-request-made", nullptr); bool isActive; nsresult rv = GetIsActive(&isActive); @@ -1242,10 +1407,6 @@ ContentAnalysis::AnalyzeContentRequestCallback( return NS_ERROR_NOT_AVAILABLE; } - nsCOMPtr<nsIObserverService> obsServ = - mozilla::services::GetObserverService(); - obsServ->NotifyObservers(aRequest, "dlp-request-made", nullptr); - MOZ_ASSERT(NS_IsMainThread()); // since we're on the main thread, don't need to synchronize this int64_t requestCount = ++mRequestCount; @@ -1333,7 +1494,9 @@ ContentAnalysis::RespondToWarnDialog(const nsACString& aRequestToken, return; } entry->mResponse->ResolveWarnAction(aAllowContent); - auto action = entry->mResponse->GetAction(); + nsIContentAnalysisResponse::Action action; + DebugOnly<nsresult> rv = entry->mResponse->GetAction(&action); + MOZ_ASSERT(NS_SUCCEEDED(rv)); if (entry->mCallbackData.AutoAcknowledge()) { RefPtr<ContentAnalysisAcknowledgement> acknowledgement = new ContentAnalysisAcknowledgement( @@ -1358,6 +1521,181 @@ ContentAnalysis::RespondToWarnDialog(const nsACString& aRequestToken, return NS_OK; } +#if defined(XP_WIN) +RefPtr<ContentAnalysis::PrintAllowedPromise> +ContentAnalysis::PrintToPDFToDetermineIfPrintAllowed( + dom::CanonicalBrowsingContext* aBrowsingContext, + nsIPrintSettings* aPrintSettings) { + // Note that the IsChrome() check here excludes a few + // common about pages like about:config, about:preferences, + // and about:support, but other about: pages may still + // go through content analysis. + if (aBrowsingContext->IsChrome()) { + return PrintAllowedPromise::CreateAndResolve(PrintAllowedResult(true), + __func__); + } + nsCOMPtr<nsIPrintSettings> contentAnalysisPrintSettings; + if (NS_WARN_IF(NS_FAILED(aPrintSettings->Clone( + getter_AddRefs(contentAnalysisPrintSettings)))) || + NS_WARN_IF(!aBrowsingContext->GetCurrentWindowGlobal())) { + return PrintAllowedPromise::CreateAndReject( + PrintAllowedError(NS_ERROR_FAILURE), __func__); + } + contentAnalysisPrintSettings->SetOutputDestination( + nsIPrintSettings::OutputDestinationType::kOutputDestinationStream); + contentAnalysisPrintSettings->SetOutputFormat( + nsIPrintSettings::kOutputFormatPDF); + nsCOMPtr<nsIStorageStream> storageStream = + do_CreateInstance("@mozilla.org/storagestream;1"); + if (!storageStream) { + return PrintAllowedPromise::CreateAndReject( + PrintAllowedError(NS_ERROR_FAILURE), __func__); + } + // Use segment size of 512K + nsresult rv = storageStream->Init(0x80000, UINT32_MAX); + if (NS_WARN_IF(NS_FAILED(rv))) { + return PrintAllowedPromise::CreateAndReject(PrintAllowedError(rv), + __func__); + } + + nsCOMPtr<nsIOutputStream> outputStream; + storageStream->QueryInterface(NS_GET_IID(nsIOutputStream), + getter_AddRefs(outputStream)); + MOZ_ASSERT(outputStream); + + contentAnalysisPrintSettings->SetOutputStream(outputStream.get()); + RefPtr<dom::CanonicalBrowsingContext> browsingContext = aBrowsingContext; + auto promise = MakeRefPtr<PrintAllowedPromise::Private>(__func__); + nsCOMPtr<nsIPrintSettings> finalPrintSettings(aPrintSettings); + aBrowsingContext + ->PrintWithNoContentAnalysis(contentAnalysisPrintSettings, true, nullptr) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [browsingContext, contentAnalysisPrintSettings, finalPrintSettings, + promise]( + dom::MaybeDiscardedBrowsingContext cachedStaticBrowsingContext) + MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA mutable { + nsCOMPtr<nsIOutputStream> outputStream; + contentAnalysisPrintSettings->GetOutputStream( + getter_AddRefs(outputStream)); + nsCOMPtr<nsIStorageStream> storageStream = + do_QueryInterface(outputStream); + MOZ_ASSERT(storageStream); + nsTArray<uint8_t> printData; + uint32_t length = 0; + storageStream->GetLength(&length); + if (!printData.SetLength(length, fallible)) { + promise->Reject( + PrintAllowedError(NS_ERROR_OUT_OF_MEMORY, + cachedStaticBrowsingContext), + __func__); + return; + } + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = storageStream->NewInputStream( + 0, getter_AddRefs(inputStream)); + if (NS_FAILED(rv)) { + promise->Reject( + PrintAllowedError(rv, cachedStaticBrowsingContext), + __func__); + return; + } + uint32_t currentPosition = 0; + while (currentPosition < length) { + uint32_t elementsRead = 0; + // Make sure the reinterpret_cast<> below is safe + static_assert(std::is_trivially_assignable_v< + decltype(*printData.Elements()), char>); + rv = inputStream->Read( + reinterpret_cast<char*>(printData.Elements()) + + currentPosition, + length - currentPosition, &elementsRead); + if (NS_WARN_IF(NS_FAILED(rv) || !elementsRead)) { + promise->Reject( + PrintAllowedError(NS_FAILED(rv) ? rv : NS_ERROR_FAILURE, + cachedStaticBrowsingContext), + __func__); + return; + } + currentPosition += elementsRead; + } + + nsString printerName; + rv = contentAnalysisPrintSettings->GetPrinterName(printerName); + if (NS_WARN_IF(NS_FAILED(rv))) { + promise->Reject( + PrintAllowedError(rv, cachedStaticBrowsingContext), + __func__); + return; + } + + auto* windowParent = browsingContext->GetCurrentWindowGlobal(); + if (!windowParent) { + // The print window may have been closed by the user by now. + // Cancel the print. + promise->Reject( + PrintAllowedError(NS_ERROR_ABORT, + cachedStaticBrowsingContext), + __func__); + return; + } + nsCOMPtr<nsIURI> uri = windowParent->GetDocumentURI(); + nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest = + new contentanalysis::ContentAnalysisRequest( + std::move(printData), std::move(uri), + std::move(printerName), windowParent); + auto callback = + MakeRefPtr<contentanalysis::ContentAnalysisCallback>( + [browsingContext, cachedStaticBrowsingContext, promise, + finalPrintSettings = std::move(finalPrintSettings)]( + nsIContentAnalysisResponse* aResponse) + MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA mutable { + bool shouldAllow = false; + DebugOnly<nsresult> rv = + aResponse->GetShouldAllowContent( + &shouldAllow); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + promise->Resolve( + PrintAllowedResult( + shouldAllow, cachedStaticBrowsingContext), + __func__); + }, + [promise, + cachedStaticBrowsingContext](nsresult aError) { + promise->Reject( + PrintAllowedError(aError, + cachedStaticBrowsingContext), + __func__); + }); + nsCOMPtr<nsIContentAnalysis> contentAnalysis = + mozilla::components::nsIContentAnalysis::Service(); + if (NS_WARN_IF(!contentAnalysis)) { + promise->Reject( + PrintAllowedError(rv, cachedStaticBrowsingContext), + __func__); + } else { + bool isActive = false; + nsresult rv = contentAnalysis->GetIsActive(&isActive); + // Should not be called if content analysis is not active + MOZ_ASSERT(isActive); + Unused << NS_WARN_IF(NS_FAILED(rv)); + rv = contentAnalysis->AnalyzeContentRequestCallback( + contentAnalysisRequest, /* aAutoAcknowledge */ true, + callback); + if (NS_WARN_IF(NS_FAILED(rv))) { + promise->Reject( + PrintAllowedError(rv, cachedStaticBrowsingContext), + __func__); + } + } + }, + [promise](nsresult aError) { + promise->Reject(PrintAllowedError(aError), __func__); + }); + return promise; +} +#endif + NS_IMETHODIMP ContentAnalysisResponse::Acknowledge( nsIContentAnalysisAcknowledgement* aAcknowledgement) { @@ -1425,6 +1763,46 @@ nsresult ContentAnalysis::RunAcknowledgeTask( return rv; } +bool ContentAnalysis::LastRequestSucceeded() { + return mLastResult != NS_ERROR_NOT_AVAILABLE && + mLastResult != NS_ERROR_INVALID_SIGNATURE && + mLastResult != NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +ContentAnalysis::GetDiagnosticInfo(JSContext* aCx, + mozilla::dom::Promise** aPromise) { + RefPtr<mozilla::dom::Promise> promise; + nsresult rv = MakePromise(aCx, &promise); + NS_ENSURE_SUCCESS(rv, rv); + mCaClientPromise->Then( + GetCurrentSerialEventTarget(), __func__, + [promise](std::shared_ptr<content_analysis::sdk::Client> client) mutable { + if (!client) { + auto info = MakeRefPtr<ContentAnalysisDiagnosticInfo>( + false, EmptyString(), false, 0); + promise->MaybeResolve(info); + return; + } + RefPtr<ContentAnalysis> self = GetContentAnalysisFromService(); + std::string agentPath = client->GetAgentInfo().binary_path; + nsString agentWidePath = NS_ConvertUTF8toUTF16(agentPath); + auto info = MakeRefPtr<ContentAnalysisDiagnosticInfo>( + self->LastRequestSucceeded(), std::move(agentWidePath), false, + self ? self->mRequestCount : 0); + promise->MaybeResolve(info); + }, + [promise](nsresult rv) { + RefPtr<ContentAnalysis> self = GetContentAnalysisFromService(); + auto info = MakeRefPtr<ContentAnalysisDiagnosticInfo>( + false, EmptyString(), rv == NS_ERROR_INVALID_SIGNATURE, + self ? self->mRequestCount : 0); + promise->MaybeResolve(info); + }); + promise.forget(aPromise); + return NS_OK; +} + NS_IMETHODIMP ContentAnalysisCallback::ContentResult( nsIContentAnalysisResponse* aResponse) { if (mPromise.isSome()) { @@ -1448,6 +1826,28 @@ ContentAnalysisCallback::ContentAnalysisCallback(RefPtr<dom::Promise> aPromise) : mPromise(Some(new nsMainThreadPtrHolder<dom::Promise>( "content analysis promise", aPromise))) {} +NS_IMETHODIMP ContentAnalysisDiagnosticInfo::GetConnectedToAgent( + bool* aConnectedToAgent) { + *aConnectedToAgent = mConnectedToAgent; + return NS_OK; +} +NS_IMETHODIMP ContentAnalysisDiagnosticInfo::GetAgentPath( + nsAString& aAgentPath) { + aAgentPath = mAgentPath; + return NS_OK; +} +NS_IMETHODIMP ContentAnalysisDiagnosticInfo::GetFailedSignatureVerification( + bool* aFailedSignatureVerification) { + *aFailedSignatureVerification = mFailedSignatureVerification; + return NS_OK; +} + +NS_IMETHODIMP ContentAnalysisDiagnosticInfo::GetRequestCount( + int64_t* aRequestCount) { + *aRequestCount = mRequestCount; + return NS_OK; +} + #undef LOGD #undef LOGE } // namespace mozilla::contentanalysis diff --git a/toolkit/components/contentanalysis/ContentAnalysis.h b/toolkit/components/contentanalysis/ContentAnalysis.h index 17b6e3fc1b..f2545624fd 100644 --- a/toolkit/components/contentanalysis/ContentAnalysis.h +++ b/toolkit/components/contentanalysis/ContentAnalysis.h @@ -8,6 +8,8 @@ #include "mozilla/DataMutex.h" #include "mozilla/MozPromise.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/MaybeDiscarded.h" #include "mozilla/dom/Promise.h" #include "nsIContentAnalysis.h" #include "nsProxyRelease.h" @@ -18,10 +20,16 @@ #include <regex> #include <string> +#ifdef XP_WIN +# include <windows.h> +#endif // XP_WIN + class nsIPrincipal; +class nsIPrintSettings; class ContentAnalysisTest; namespace mozilla::dom { +class CanonicalBrowsingContext; class DataTransfer; class WindowGlobalParent; } // namespace mozilla::dom @@ -34,6 +42,27 @@ class ContentAnalysisResponse; namespace mozilla::contentanalysis { +class ContentAnalysisDiagnosticInfo final + : public nsIContentAnalysisDiagnosticInfo { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICONTENTANALYSISDIAGNOSTICINFO + ContentAnalysisDiagnosticInfo(bool aConnectedToAgent, nsString aAgentPath, + bool aFailedSignatureVerification, + int64_t aRequestCount) + : mConnectedToAgent(aConnectedToAgent), + mAgentPath(std::move(aAgentPath)), + mFailedSignatureVerification(aFailedSignatureVerification), + mRequestCount(aRequestCount) {} + + private: + ~ContentAnalysisDiagnosticInfo() = default; + bool mConnectedToAgent; + nsString mAgentPath; + bool mFailedSignatureVerification; + int64_t mRequestCount; +}; + class ContentAnalysisRequest final : public nsIContentAnalysisRequest { public: NS_DECL_ISUPPORTS @@ -43,11 +72,15 @@ class ContentAnalysisRequest final : public nsIContentAnalysisRequest { bool aStringIsFilePath, nsCString aSha256Digest, nsCOMPtr<nsIURI> aUrl, OperationType aOperationType, dom::WindowGlobalParent* aWindowGlobalParent); + ContentAnalysisRequest(const nsTArray<uint8_t> aPrintData, + nsCOMPtr<nsIURI> aUrl, nsString aPrinterName, + dom::WindowGlobalParent* aWindowGlobalParent); static nsresult GetFileDigest(const nsAString& aFilePath, nsCString& aDigestString); private: - ~ContentAnalysisRequest() = default; + ~ContentAnalysisRequest(); + // Remove unneeded copy constructor/assignment ContentAnalysisRequest(const ContentAnalysisRequest&) = delete; ContentAnalysisRequest& operator=(ContentAnalysisRequest&) = delete; @@ -84,7 +117,16 @@ class ContentAnalysisRequest final : public nsIContentAnalysisRequest { // OPERATION_CUSTOMDISPLAYSTRING nsString mOperationDisplayString; + // The name of the printer being printed to + nsString mPrinterName; + RefPtr<dom::WindowGlobalParent> mWindowGlobalParent; +#ifdef XP_WIN + // The printed data to analyze, in PDF format + HANDLE mPrintDataHandle = 0; + // The size of the printed data in mPrintDataHandle + uint64_t mPrintDataSize = 0; +#endif friend class ::ContentAnalysisTest; }; @@ -105,6 +147,40 @@ class ContentAnalysis final : public nsIContentAnalysis { ContentAnalysis(); nsCString GetUserActionId(); + void SetLastResult(nsresult aLastResult) { mLastResult = aLastResult; } + + struct PrintAllowedResult final { + bool mAllowed; + dom::MaybeDiscarded<dom::BrowsingContext> + mCachedStaticDocumentBrowsingContext; + PrintAllowedResult(bool aAllowed, dom::MaybeDiscarded<dom::BrowsingContext> + aCachedStaticDocumentBrowsingContext) + : mAllowed(aAllowed), + mCachedStaticDocumentBrowsingContext( + aCachedStaticDocumentBrowsingContext) {} + explicit PrintAllowedResult(bool aAllowed) + : PrintAllowedResult(aAllowed, dom::MaybeDiscardedBrowsingContext()) {} + }; + struct PrintAllowedError final { + nsresult mError; + dom::MaybeDiscarded<dom::BrowsingContext> + mCachedStaticDocumentBrowsingContext; + PrintAllowedError(nsresult aError, dom::MaybeDiscarded<dom::BrowsingContext> + aCachedStaticDocumentBrowsingContext) + : mError(aError), + mCachedStaticDocumentBrowsingContext( + aCachedStaticDocumentBrowsingContext) {} + explicit PrintAllowedError(nsresult aError) + : PrintAllowedError(aError, dom::MaybeDiscardedBrowsingContext()) {} + }; + using PrintAllowedPromise = + MozPromise<PrintAllowedResult, PrintAllowedError, true>; +#if defined(XP_WIN) + MOZ_CAN_RUN_SCRIPT static RefPtr<PrintAllowedPromise> + PrintToPDFToDetermineIfPrintAllowed( + dom::CanonicalBrowsingContext* aBrowsingContext, + nsIPrintSettings* aPrintSettings); +#endif // defined(XP_WIN) private: ~ContentAnalysis(); @@ -114,6 +190,10 @@ class ContentAnalysis final : public nsIContentAnalysis { nsresult CreateContentAnalysisClient(nsCString&& aPipePathName, nsString&& aClientSignatureSetting, bool aIsPerUser); + nsresult AnalyzeContentRequestCallbackPrivate( + nsIContentAnalysisRequest* aRequest, bool aAutoAcknowledge, + nsIContentAnalysisCallback* aCallback); + nsresult RunAnalyzeRequestTask( const RefPtr<nsIContentAnalysisRequest>& aRequest, bool aAutoAcknowledge, int64_t aRequestCount, @@ -129,6 +209,7 @@ class ContentAnalysis final : public nsIContentAnalysis { content_analysis::sdk::ContentAnalysisRequest&& aRequest, const std::shared_ptr<content_analysis::sdk::Client>& aClient); void IssueResponse(RefPtr<ContentAnalysisResponse>& response); + bool LastRequestSucceeded(); // Did the URL filter completely handle the request or do we need to check // with the agent. @@ -147,6 +228,7 @@ class ContentAnalysis final : public nsIContentAnalysis { bool mClientCreationAttempted; bool mSetByEnterprise; + nsresult mLastResult = NS_OK; class CallbackData final { public: @@ -180,7 +262,7 @@ class ContentAnalysis final : public nsIContentAnalysis { std::vector<std::regex> mAllowUrlList; std::vector<std::regex> mDenyUrlList; - bool mParsedUrlLists; + bool mParsedUrlLists = false; friend class ContentAnalysisResponse; friend class ::ContentAnalysisTest; @@ -198,6 +280,7 @@ class ContentAnalysisResponse final : public nsIContentAnalysisResponse { void SetOwner(RefPtr<ContentAnalysis> aOwner); void DoNotAcknowledge() { mDoNotAcknowledge = true; } + void SetCancelError(CancelError aCancelError); private: ~ContentAnalysisResponse() = default; @@ -218,7 +301,11 @@ class ContentAnalysisResponse final : public nsIContentAnalysisResponse { // Identifier for the corresponding nsIContentAnalysisRequest nsCString mRequestToken; - // ContentAnalysis (or, more precisely, it's Client object) must outlive + // If mAction is eCanceled, this is the error explaining why the request was + // canceled, or eUserInitiated if the user canceled it. + CancelError mCancelError = CancelError::eUserInitiated; + + // ContentAnalysis (or, more precisely, its Client object) must outlive // the transaction. RefPtr<ContentAnalysis> mOwner; diff --git a/toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h b/toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h index a7cf812d0b..a554036257 100644 --- a/toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h +++ b/toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h @@ -17,12 +17,12 @@ namespace mozilla { namespace contentanalysis { enum class NoContentAnalysisResult : uint8_t { - CONTENT_ANALYSIS_NOT_ACTIVE, - CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS, - CANCELED, - ERROR_INVALID_JSON_RESPONSE, - ERROR_COULD_NOT_GET_DATA, - ERROR_OTHER, + ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE, + ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS, + ALLOW_DUE_TO_COULD_NOT_GET_DATA, + DENY_DUE_TO_CANCELED, + DENY_DUE_TO_INVALID_JSON_RESPONSE, + DENY_DUE_TO_OTHER_ERROR, LAST_VALUE }; @@ -59,7 +59,8 @@ class ContentAnalysisResult : public nsIContentAnalysisResult { } } } - return FromNoResult(NoContentAnalysisResult::ERROR_INVALID_JSON_RESPONSE); + return FromNoResult( + NoContentAnalysisResult::DENY_DUE_TO_INVALID_JSON_RESPONSE); } static RefPtr<ContentAnalysisResult> FromJSONContentAnalysisResponse( @@ -76,16 +77,21 @@ class ContentAnalysisResult : public nsIContentAnalysisResult { } else if (shouldAllowValue.isFalse()) { return FromAction(nsIContentAnalysisResponse::Action::eBlock); } else { - return FromNoResult(NoContentAnalysisResult::ERROR_OTHER); + return FromNoResult(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR); } } } - return FromNoResult(NoContentAnalysisResult::ERROR_INVALID_JSON_RESPONSE); + return FromNoResult( + NoContentAnalysisResult::DENY_DUE_TO_INVALID_JSON_RESPONSE); } static RefPtr<ContentAnalysisResult> FromContentAnalysisResponse( nsIContentAnalysisResponse* aResponse) { - if (aResponse->GetShouldAllowContent()) { + bool shouldAllowContent = false; + DebugOnly<nsresult> rv = + aResponse->GetShouldAllowContent(&shouldAllowContent); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (shouldAllowContent) { return FromAction(nsIContentAnalysisResponse::Action::eAllow); } else { return FromAction(nsIContentAnalysisResponse::Action::eBlock); @@ -159,7 +165,8 @@ struct ParamTraits<mozilla::contentanalysis::ContentAnalysisResult*> { return true; } *aResult = mozilla::contentanalysis::ContentAnalysisResult::FromNoResult( - mozilla::contentanalysis::NoContentAnalysisResult::ERROR_OTHER); + mozilla::contentanalysis::NoContentAnalysisResult:: + DENY_DUE_TO_OTHER_ERROR); return ReadParam(aReader, &((*aResult)->mValue)); } }; diff --git a/toolkit/components/contentanalysis/components.conf b/toolkit/components/contentanalysis/components.conf index 82236cb1b9..1683ef99d7 100644 --- a/toolkit/components/contentanalysis/components.conf +++ b/toolkit/components/contentanalysis/components.conf @@ -11,5 +11,6 @@ Classes = [ 'contract_ids': ['@mozilla.org/contentanalysis;1'], 'type': 'mozilla::contentanalysis::ContentAnalysis', 'headers': ['/toolkit/components/contentanalysis/ContentAnalysis.h'], + 'overridable': True, }, ] diff --git a/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.cc b/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.cc index 6a2e1ccf46..b7a7434685 100644 --- a/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.cc +++ b/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.cc @@ -1,7 +1,7 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! -// source: analysis.proto +// source: content_analysis/sdk/analysis.proto -#include "analysis.pb.h" +#include "content_analysis/sdk/analysis.pb.h" #include <algorithm> @@ -134,9 +134,10 @@ PROTOBUF_CONSTEXPR ContentAnalysisRequest::ContentAnalysisRequest( , /*decltype(_impl_.user_action_id_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} , /*decltype(_impl_.request_data_)*/nullptr , /*decltype(_impl_.client_metadata_)*/nullptr + , /*decltype(_impl_.analysis_connector_)*/0 + , /*decltype(_impl_.reason_)*/0 , /*decltype(_impl_.expires_at_)*/int64_t{0} , /*decltype(_impl_.user_action_requests_count_)*/int64_t{0} - , /*decltype(_impl_.analysis_connector_)*/0 , /*decltype(_impl_.content_data_)*/{} , /*decltype(_impl_._oneof_case_)*/{}} {} struct ContentAnalysisRequestDefaultTypeInternal { @@ -400,6 +401,94 @@ constexpr ClientDownloadRequest_ResourceType ClientDownloadRequest::ResourceType constexpr ClientDownloadRequest_ResourceType ClientDownloadRequest::ResourceType_MAX; constexpr int ClientDownloadRequest::ResourceType_ARRAYSIZE; #endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool ContentAnalysisRequest_Reason_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed<std::string> ContentAnalysisRequest_Reason_strings[8] = {}; + +static const char ContentAnalysisRequest_Reason_names[] = + "CLIPBOARD_PASTE" + "DRAG_AND_DROP" + "FILE_PICKER_DIALOG" + "NORMAL_DOWNLOAD" + "PRINT_PREVIEW_PRINT" + "SAVE_AS_DOWNLOAD" + "SYSTEM_DIALOG_PRINT" + "UNKNOWN"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry ContentAnalysisRequest_Reason_entries[] = { + { {ContentAnalysisRequest_Reason_names + 0, 15}, 1 }, + { {ContentAnalysisRequest_Reason_names + 15, 13}, 2 }, + { {ContentAnalysisRequest_Reason_names + 28, 18}, 3 }, + { {ContentAnalysisRequest_Reason_names + 46, 15}, 6 }, + { {ContentAnalysisRequest_Reason_names + 61, 19}, 4 }, + { {ContentAnalysisRequest_Reason_names + 80, 16}, 7 }, + { {ContentAnalysisRequest_Reason_names + 96, 19}, 5 }, + { {ContentAnalysisRequest_Reason_names + 115, 7}, 0 }, +}; + +static const int ContentAnalysisRequest_Reason_entries_by_number[] = { + 7, // 0 -> UNKNOWN + 0, // 1 -> CLIPBOARD_PASTE + 1, // 2 -> DRAG_AND_DROP + 2, // 3 -> FILE_PICKER_DIALOG + 4, // 4 -> PRINT_PREVIEW_PRINT + 6, // 5 -> SYSTEM_DIALOG_PRINT + 3, // 6 -> NORMAL_DOWNLOAD + 5, // 7 -> SAVE_AS_DOWNLOAD +}; + +const std::string& ContentAnalysisRequest_Reason_Name( + ContentAnalysisRequest_Reason value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + ContentAnalysisRequest_Reason_entries, + ContentAnalysisRequest_Reason_entries_by_number, + 8, ContentAnalysisRequest_Reason_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + ContentAnalysisRequest_Reason_entries, + ContentAnalysisRequest_Reason_entries_by_number, + 8, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + ContentAnalysisRequest_Reason_strings[idx].get(); +} +bool ContentAnalysisRequest_Reason_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ContentAnalysisRequest_Reason* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + ContentAnalysisRequest_Reason_entries, 8, name, &int_value); + if (success) { + *value = static_cast<ContentAnalysisRequest_Reason>(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::UNKNOWN; +constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::CLIPBOARD_PASTE; +constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::DRAG_AND_DROP; +constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::FILE_PICKER_DIALOG; +constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::PRINT_PREVIEW_PRINT; +constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::SYSTEM_DIALOG_PRINT; +constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::NORMAL_DOWNLOAD; +constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::SAVE_AS_DOWNLOAD; +constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::Reason_MIN; +constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::Reason_MAX; +constexpr int ContentAnalysisRequest::Reason_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) bool ContentAnalysisResponse_Result_TriggeredRule_Action_IsValid(int value) { switch (value) { case 0: @@ -2650,7 +2739,7 @@ class ContentAnalysisRequest::_Internal { (*has_bits)[0] |= 1u; } static void set_has_analysis_connector(HasBits* has_bits) { - (*has_bits)[0] |= 64u; + (*has_bits)[0] |= 16u; } static const ::content_analysis::sdk::ContentMetaData& request_data(const ContentAnalysisRequest* msg); static void set_has_request_data(HasBits* has_bits) { @@ -2662,12 +2751,15 @@ class ContentAnalysisRequest::_Internal { } static const ::content_analysis::sdk::ContentAnalysisRequest_PrintData& print_data(const ContentAnalysisRequest* msg); static void set_has_expires_at(HasBits* has_bits) { - (*has_bits)[0] |= 16u; + (*has_bits)[0] |= 64u; } static void set_has_user_action_id(HasBits* has_bits) { (*has_bits)[0] |= 2u; } static void set_has_user_action_requests_count(HasBits* has_bits) { + (*has_bits)[0] |= 128u; + } + static void set_has_reason(HasBits* has_bits) { (*has_bits)[0] |= 32u; } }; @@ -2716,9 +2808,10 @@ ContentAnalysisRequest::ContentAnalysisRequest(const ContentAnalysisRequest& fro , decltype(_impl_.user_action_id_){} , decltype(_impl_.request_data_){nullptr} , decltype(_impl_.client_metadata_){nullptr} + , decltype(_impl_.analysis_connector_){} + , decltype(_impl_.reason_){} , decltype(_impl_.expires_at_){} , decltype(_impl_.user_action_requests_count_){} - , decltype(_impl_.analysis_connector_){} , decltype(_impl_.content_data_){} , /*decltype(_impl_._oneof_case_)*/{}}; @@ -2745,9 +2838,9 @@ ContentAnalysisRequest::ContentAnalysisRequest(const ContentAnalysisRequest& fro if (from._internal_has_client_metadata()) { _this->_impl_.client_metadata_ = new ::content_analysis::sdk::ClientMetadata(*from._impl_.client_metadata_); } - ::memcpy(&_impl_.expires_at_, &from._impl_.expires_at_, - static_cast<size_t>(reinterpret_cast<char*>(&_impl_.analysis_connector_) - - reinterpret_cast<char*>(&_impl_.expires_at_)) + sizeof(_impl_.analysis_connector_)); + ::memcpy(&_impl_.analysis_connector_, &from._impl_.analysis_connector_, + static_cast<size_t>(reinterpret_cast<char*>(&_impl_.user_action_requests_count_) - + reinterpret_cast<char*>(&_impl_.analysis_connector_)) + sizeof(_impl_.user_action_requests_count_)); clear_has_content_data(); switch (from.content_data_case()) { case kTextContent: { @@ -2782,9 +2875,10 @@ inline void ContentAnalysisRequest::SharedCtor( , decltype(_impl_.user_action_id_){} , decltype(_impl_.request_data_){nullptr} , decltype(_impl_.client_metadata_){nullptr} + , decltype(_impl_.analysis_connector_){0} + , decltype(_impl_.reason_){0} , decltype(_impl_.expires_at_){int64_t{0}} , decltype(_impl_.user_action_requests_count_){int64_t{0}} - , decltype(_impl_.analysis_connector_){0} , decltype(_impl_.content_data_){} , /*decltype(_impl_._oneof_case_)*/{} }; @@ -2873,10 +2967,10 @@ void ContentAnalysisRequest::Clear() { _impl_.client_metadata_->Clear(); } } - if (cached_has_bits & 0x00000070u) { - ::memset(&_impl_.expires_at_, 0, static_cast<size_t>( - reinterpret_cast<char*>(&_impl_.analysis_connector_) - - reinterpret_cast<char*>(&_impl_.expires_at_)) + sizeof(_impl_.analysis_connector_)); + if (cached_has_bits & 0x000000f0u) { + ::memset(&_impl_.analysis_connector_, 0, static_cast<size_t>( + reinterpret_cast<char*>(&_impl_.user_action_requests_count_) - + reinterpret_cast<char*>(&_impl_.analysis_connector_)) + sizeof(_impl_.user_action_requests_count_)); } clear_content_data(); _impl_._has_bits_.Clear(); @@ -2995,6 +3089,19 @@ const char* ContentAnalysisRequest::_InternalParse(const char* ptr, ::_pbi::Pars } else goto handle_unusual; continue; + // optional .content_analysis.sdk.ContentAnalysisRequest.Reason reason = 19; + case 19: + if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 152)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::content_analysis::sdk::ContentAnalysisRequest_Reason_IsValid(val))) { + _internal_set_reason(static_cast<::content_analysis::sdk::ContentAnalysisRequest_Reason>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(19, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; default: goto handle_unusual; } // switch @@ -3033,7 +3140,7 @@ uint8_t* ContentAnalysisRequest::_InternalSerialize( } // optional .content_analysis.sdk.AnalysisConnector analysis_connector = 9; - if (cached_has_bits & 0x00000040u) { + if (cached_has_bits & 0x00000010u) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteEnumToArray( 9, this->_internal_analysis_connector(), target); @@ -3073,7 +3180,7 @@ uint8_t* ContentAnalysisRequest::_InternalSerialize( default: ; } // optional int64 expires_at = 15; - if (cached_has_bits & 0x00000010u) { + if (cached_has_bits & 0x00000040u) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteInt64ToArray(15, this->_internal_expires_at(), target); } @@ -3085,7 +3192,7 @@ uint8_t* ContentAnalysisRequest::_InternalSerialize( } // optional int64 user_action_requests_count = 17; - if (cached_has_bits & 0x00000020u) { + if (cached_has_bits & 0x00000080u) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteInt64ToArray(17, this->_internal_user_action_requests_count(), target); } @@ -3097,6 +3204,13 @@ uint8_t* ContentAnalysisRequest::_InternalSerialize( _Internal::print_data(this).GetCachedSize(), target, stream); } + // optional .content_analysis.sdk.ContentAnalysisRequest.Reason reason = 19; + if (cached_has_bits & 0x00000020u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 19, this->_internal_reason(), target); + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); @@ -3122,7 +3236,7 @@ size_t ContentAnalysisRequest::ByteSizeLong() const { } cached_has_bits = _impl_._has_bits_[0]; - if (cached_has_bits & 0x0000007fu) { + if (cached_has_bits & 0x000000ffu) { // optional string request_token = 5; if (cached_has_bits & 0x00000001u) { total_size += 1 + @@ -3151,22 +3265,28 @@ size_t ContentAnalysisRequest::ByteSizeLong() const { *_impl_.client_metadata_); } - // optional int64 expires_at = 15; + // optional .content_analysis.sdk.AnalysisConnector analysis_connector = 9; if (cached_has_bits & 0x00000010u) { - total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_expires_at()); + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_analysis_connector()); } - // optional int64 user_action_requests_count = 17; + // optional .content_analysis.sdk.ContentAnalysisRequest.Reason reason = 19; if (cached_has_bits & 0x00000020u) { total_size += 2 + - ::_pbi::WireFormatLite::Int64Size( - this->_internal_user_action_requests_count()); + ::_pbi::WireFormatLite::EnumSize(this->_internal_reason()); } - // optional .content_analysis.sdk.AnalysisConnector analysis_connector = 9; + // optional int64 expires_at = 15; if (cached_has_bits & 0x00000040u) { - total_size += 1 + - ::_pbi::WireFormatLite::EnumSize(this->_internal_analysis_connector()); + total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_expires_at()); + } + + // optional int64 user_action_requests_count = 17; + if (cached_has_bits & 0x00000080u) { + total_size += 2 + + ::_pbi::WireFormatLite::Int64Size( + this->_internal_user_action_requests_count()); } } @@ -3219,7 +3339,7 @@ void ContentAnalysisRequest::MergeFrom(const ContentAnalysisRequest& from) { _this->_impl_.tags_.MergeFrom(from._impl_.tags_); cached_has_bits = from._impl_._has_bits_[0]; - if (cached_has_bits & 0x0000007fu) { + if (cached_has_bits & 0x000000ffu) { if (cached_has_bits & 0x00000001u) { _this->_internal_set_request_token(from._internal_request_token()); } @@ -3235,13 +3355,16 @@ void ContentAnalysisRequest::MergeFrom(const ContentAnalysisRequest& from) { from._internal_client_metadata()); } if (cached_has_bits & 0x00000010u) { - _this->_impl_.expires_at_ = from._impl_.expires_at_; + _this->_impl_.analysis_connector_ = from._impl_.analysis_connector_; } if (cached_has_bits & 0x00000020u) { - _this->_impl_.user_action_requests_count_ = from._impl_.user_action_requests_count_; + _this->_impl_.reason_ = from._impl_.reason_; } if (cached_has_bits & 0x00000040u) { - _this->_impl_.analysis_connector_ = from._impl_.analysis_connector_; + _this->_impl_.expires_at_ = from._impl_.expires_at_; + } + if (cached_has_bits & 0x00000080u) { + _this->_impl_.user_action_requests_count_ = from._impl_.user_action_requests_count_; } _this->_impl_._has_bits_[0] |= cached_has_bits; } @@ -3296,8 +3419,8 @@ void ContentAnalysisRequest::InternalSwap(ContentAnalysisRequest* other) { &other->_impl_.user_action_id_, rhs_arena ); ::PROTOBUF_NAMESPACE_ID::internal::memswap< - PROTOBUF_FIELD_OFFSET(ContentAnalysisRequest, _impl_.analysis_connector_) - + sizeof(ContentAnalysisRequest::_impl_.analysis_connector_) + PROTOBUF_FIELD_OFFSET(ContentAnalysisRequest, _impl_.user_action_requests_count_) + + sizeof(ContentAnalysisRequest::_impl_.user_action_requests_count_) - PROTOBUF_FIELD_OFFSET(ContentAnalysisRequest, _impl_.request_data_)>( reinterpret_cast<char*>(&_impl_.request_data_), reinterpret_cast<char*>(&other->_impl_.request_data_)); diff --git a/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.h b/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.h index 186cc97649..875c632ebf 100644 --- a/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.h +++ b/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.h @@ -1,8 +1,8 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! -// source: analysis.proto +// source: content_analysis/sdk/analysis.proto -#ifndef GOOGLE_PROTOBUF_INCLUDED_analysis_2eproto -#define GOOGLE_PROTOBUF_INCLUDED_analysis_2eproto +#ifndef GOOGLE_PROTOBUF_INCLUDED_content_5fanalysis_2fsdk_2fanalysis_2eproto +#define GOOGLE_PROTOBUF_INCLUDED_content_5fanalysis_2fsdk_2fanalysis_2eproto #include <limits> #include <string> @@ -13,7 +13,7 @@ #error incompatible with your Protocol Buffer headers. Please update #error your headers. #endif -#if 3021006 < PROTOBUF_MIN_PROTOC_VERSION +#if 3021012 < PROTOBUF_MIN_PROTOC_VERSION #error This file was generated by an older version of protoc which is #error incompatible with your Protocol Buffer headers. Please #error regenerate this file with a newer version of protoc. @@ -31,7 +31,7 @@ #include <google/protobuf/generated_enum_util.h> // @@protoc_insertion_point(includes) #include <google/protobuf/port_def.inc> -#define PROTOBUF_INTERNAL_EXPORT_analysis_2eproto +#define PROTOBUF_INTERNAL_EXPORT_content_5fanalysis_2fsdk_2fanalysis_2eproto PROTOBUF_NAMESPACE_OPEN namespace internal { class AnyMetadata; @@ -39,7 +39,7 @@ class AnyMetadata; PROTOBUF_NAMESPACE_CLOSE // Internal implementation detail -- do not use these members. -struct TableStruct_analysis_2eproto { +struct TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto { static const uint32_t offsets[]; }; namespace content_analysis { @@ -154,6 +154,31 @@ inline const std::string& ClientDownloadRequest_ResourceType_Name(T enum_t_value } bool ClientDownloadRequest_ResourceType_Parse( ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ClientDownloadRequest_ResourceType* value); +enum ContentAnalysisRequest_Reason : int { + ContentAnalysisRequest_Reason_UNKNOWN = 0, + ContentAnalysisRequest_Reason_CLIPBOARD_PASTE = 1, + ContentAnalysisRequest_Reason_DRAG_AND_DROP = 2, + ContentAnalysisRequest_Reason_FILE_PICKER_DIALOG = 3, + ContentAnalysisRequest_Reason_PRINT_PREVIEW_PRINT = 4, + ContentAnalysisRequest_Reason_SYSTEM_DIALOG_PRINT = 5, + ContentAnalysisRequest_Reason_NORMAL_DOWNLOAD = 6, + ContentAnalysisRequest_Reason_SAVE_AS_DOWNLOAD = 7 +}; +bool ContentAnalysisRequest_Reason_IsValid(int value); +constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest_Reason_Reason_MIN = ContentAnalysisRequest_Reason_UNKNOWN; +constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest_Reason_Reason_MAX = ContentAnalysisRequest_Reason_SAVE_AS_DOWNLOAD; +constexpr int ContentAnalysisRequest_Reason_Reason_ARRAYSIZE = ContentAnalysisRequest_Reason_Reason_MAX + 1; + +const std::string& ContentAnalysisRequest_Reason_Name(ContentAnalysisRequest_Reason value); +template<typename T> +inline const std::string& ContentAnalysisRequest_Reason_Name(T enum_t_value) { + static_assert(::std::is_same<T, ContentAnalysisRequest_Reason>::value || + ::std::is_integral<T>::value, + "Incorrect type passed to function ContentAnalysisRequest_Reason_Name."); + return ContentAnalysisRequest_Reason_Name(static_cast<ContentAnalysisRequest_Reason>(enum_t_value)); +} +bool ContentAnalysisRequest_Reason_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ContentAnalysisRequest_Reason* value); enum ContentAnalysisResponse_Result_TriggeredRule_Action : int { ContentAnalysisResponse_Result_TriggeredRule_Action_ACTION_UNSPECIFIED = 0, ContentAnalysisResponse_Result_TriggeredRule_Action_REPORT_ONLY = 1, @@ -448,7 +473,7 @@ class ContentMetaData_PrintMetadata final : int printer_type_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -717,7 +742,7 @@ class ContentMetaData final : ::content_analysis::sdk::ContentMetaData_PrintMetadata* print_metadata_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -864,7 +889,7 @@ class ClientMetadata_Browser final : ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr machine_user_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -1013,7 +1038,7 @@ class ClientMetadata final : ::content_analysis::sdk::ClientMetadata_Browser* browser_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -1178,7 +1203,7 @@ class ClientDownloadRequest_Resource final : int type_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -1360,7 +1385,7 @@ class ClientDownloadRequest final : mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -1517,7 +1542,7 @@ class ContentAnalysisRequest_PrintData final : int64_t size_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -1637,6 +1662,44 @@ class ContentAnalysisRequest final : typedef ContentAnalysisRequest_PrintData PrintData; + typedef ContentAnalysisRequest_Reason Reason; + static constexpr Reason UNKNOWN = + ContentAnalysisRequest_Reason_UNKNOWN; + static constexpr Reason CLIPBOARD_PASTE = + ContentAnalysisRequest_Reason_CLIPBOARD_PASTE; + static constexpr Reason DRAG_AND_DROP = + ContentAnalysisRequest_Reason_DRAG_AND_DROP; + static constexpr Reason FILE_PICKER_DIALOG = + ContentAnalysisRequest_Reason_FILE_PICKER_DIALOG; + static constexpr Reason PRINT_PREVIEW_PRINT = + ContentAnalysisRequest_Reason_PRINT_PREVIEW_PRINT; + static constexpr Reason SYSTEM_DIALOG_PRINT = + ContentAnalysisRequest_Reason_SYSTEM_DIALOG_PRINT; + static constexpr Reason NORMAL_DOWNLOAD = + ContentAnalysisRequest_Reason_NORMAL_DOWNLOAD; + static constexpr Reason SAVE_AS_DOWNLOAD = + ContentAnalysisRequest_Reason_SAVE_AS_DOWNLOAD; + static inline bool Reason_IsValid(int value) { + return ContentAnalysisRequest_Reason_IsValid(value); + } + static constexpr Reason Reason_MIN = + ContentAnalysisRequest_Reason_Reason_MIN; + static constexpr Reason Reason_MAX = + ContentAnalysisRequest_Reason_Reason_MAX; + static constexpr int Reason_ARRAYSIZE = + ContentAnalysisRequest_Reason_Reason_ARRAYSIZE; + template<typename T> + static inline const std::string& Reason_Name(T enum_t_value) { + static_assert(::std::is_same<T, Reason>::value || + ::std::is_integral<T>::value, + "Incorrect type passed to function Reason_Name."); + return ContentAnalysisRequest_Reason_Name(enum_t_value); + } + static inline bool Reason_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + Reason* value) { + return ContentAnalysisRequest_Reason_Parse(name, value); + } + // accessors ------------------------------------------------------- enum : int { @@ -1645,9 +1708,10 @@ class ContentAnalysisRequest final : kUserActionIdFieldNumber = 16, kRequestDataFieldNumber = 10, kClientMetadataFieldNumber = 12, + kAnalysisConnectorFieldNumber = 9, + kReasonFieldNumber = 19, kExpiresAtFieldNumber = 15, kUserActionRequestsCountFieldNumber = 17, - kAnalysisConnectorFieldNumber = 9, kTextContentFieldNumber = 13, kFilePathFieldNumber = 14, kPrintDataFieldNumber = 18, @@ -1748,6 +1812,32 @@ class ContentAnalysisRequest final : ::content_analysis::sdk::ClientMetadata* client_metadata); ::content_analysis::sdk::ClientMetadata* unsafe_arena_release_client_metadata(); + // optional .content_analysis.sdk.AnalysisConnector analysis_connector = 9; + bool has_analysis_connector() const; + private: + bool _internal_has_analysis_connector() const; + public: + void clear_analysis_connector(); + ::content_analysis::sdk::AnalysisConnector analysis_connector() const; + void set_analysis_connector(::content_analysis::sdk::AnalysisConnector value); + private: + ::content_analysis::sdk::AnalysisConnector _internal_analysis_connector() const; + void _internal_set_analysis_connector(::content_analysis::sdk::AnalysisConnector value); + public: + + // optional .content_analysis.sdk.ContentAnalysisRequest.Reason reason = 19; + bool has_reason() const; + private: + bool _internal_has_reason() const; + public: + void clear_reason(); + ::content_analysis::sdk::ContentAnalysisRequest_Reason reason() const; + void set_reason(::content_analysis::sdk::ContentAnalysisRequest_Reason value); + private: + ::content_analysis::sdk::ContentAnalysisRequest_Reason _internal_reason() const; + void _internal_set_reason(::content_analysis::sdk::ContentAnalysisRequest_Reason value); + public: + // optional int64 expires_at = 15; bool has_expires_at() const; private: @@ -1774,19 +1864,6 @@ class ContentAnalysisRequest final : void _internal_set_user_action_requests_count(int64_t value); public: - // optional .content_analysis.sdk.AnalysisConnector analysis_connector = 9; - bool has_analysis_connector() const; - private: - bool _internal_has_analysis_connector() const; - public: - void clear_analysis_connector(); - ::content_analysis::sdk::AnalysisConnector analysis_connector() const; - void set_analysis_connector(::content_analysis::sdk::AnalysisConnector value); - private: - ::content_analysis::sdk::AnalysisConnector _internal_analysis_connector() const; - void _internal_set_analysis_connector(::content_analysis::sdk::AnalysisConnector value); - public: - // string text_content = 13; bool has_text_content() const; private: @@ -1864,9 +1941,10 @@ class ContentAnalysisRequest final : ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr user_action_id_; ::content_analysis::sdk::ContentMetaData* request_data_; ::content_analysis::sdk::ClientMetadata* client_metadata_; + int analysis_connector_; + int reason_; int64_t expires_at_; int64_t user_action_requests_count_; - int analysis_connector_; union ContentDataUnion { constexpr ContentDataUnion() : _constinit_{} {} ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_; @@ -1878,7 +1956,7 @@ class ContentAnalysisRequest final : }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -2090,7 +2168,7 @@ class ContentAnalysisResponse_Result_TriggeredRule final : int action_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -2302,7 +2380,7 @@ class ContentAnalysisResponse_Result final : int status_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -2471,7 +2549,7 @@ class ContentAnalysisResponse final : ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr request_token_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -2708,7 +2786,7 @@ class ContentAnalysisAcknowledgement final : int status_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -2855,7 +2933,7 @@ class ContentAnalysisCancelRequests final : ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr user_action_id_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -3042,7 +3120,7 @@ class ChromeToAgent final : ::content_analysis::sdk::ContentAnalysisCancelRequests* cancel_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // ------------------------------------------------------------------- @@ -3189,7 +3267,7 @@ class AgentToChrome final : ::content_analysis::sdk::ContentAnalysisResponse* response_; }; union { Impl_ _impl_; }; - friend struct ::TableStruct_analysis_2eproto; + friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto; }; // =================================================================== @@ -4268,7 +4346,7 @@ inline void ContentAnalysisRequest::set_allocated_request_token(std::string* req // optional .content_analysis.sdk.AnalysisConnector analysis_connector = 9; inline bool ContentAnalysisRequest::_internal_has_analysis_connector() const { - bool value = (_impl_._has_bits_[0] & 0x00000040u) != 0; + bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0; return value; } inline bool ContentAnalysisRequest::has_analysis_connector() const { @@ -4276,7 +4354,7 @@ inline bool ContentAnalysisRequest::has_analysis_connector() const { } inline void ContentAnalysisRequest::clear_analysis_connector() { _impl_.analysis_connector_ = 0; - _impl_._has_bits_[0] &= ~0x00000040u; + _impl_._has_bits_[0] &= ~0x00000010u; } inline ::content_analysis::sdk::AnalysisConnector ContentAnalysisRequest::_internal_analysis_connector() const { return static_cast< ::content_analysis::sdk::AnalysisConnector >(_impl_.analysis_connector_); @@ -4287,7 +4365,7 @@ inline ::content_analysis::sdk::AnalysisConnector ContentAnalysisRequest::analys } inline void ContentAnalysisRequest::_internal_set_analysis_connector(::content_analysis::sdk::AnalysisConnector value) { assert(::content_analysis::sdk::AnalysisConnector_IsValid(value)); - _impl_._has_bits_[0] |= 0x00000040u; + _impl_._has_bits_[0] |= 0x00000010u; _impl_.analysis_connector_ = value; } inline void ContentAnalysisRequest::set_analysis_connector(::content_analysis::sdk::AnalysisConnector value) { @@ -4780,7 +4858,7 @@ inline ::content_analysis::sdk::ContentAnalysisRequest_PrintData* ContentAnalysi // optional int64 expires_at = 15; inline bool ContentAnalysisRequest::_internal_has_expires_at() const { - bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0; + bool value = (_impl_._has_bits_[0] & 0x00000040u) != 0; return value; } inline bool ContentAnalysisRequest::has_expires_at() const { @@ -4788,7 +4866,7 @@ inline bool ContentAnalysisRequest::has_expires_at() const { } inline void ContentAnalysisRequest::clear_expires_at() { _impl_.expires_at_ = int64_t{0}; - _impl_._has_bits_[0] &= ~0x00000010u; + _impl_._has_bits_[0] &= ~0x00000040u; } inline int64_t ContentAnalysisRequest::_internal_expires_at() const { return _impl_.expires_at_; @@ -4798,7 +4876,7 @@ inline int64_t ContentAnalysisRequest::expires_at() const { return _internal_expires_at(); } inline void ContentAnalysisRequest::_internal_set_expires_at(int64_t value) { - _impl_._has_bits_[0] |= 0x00000010u; + _impl_._has_bits_[0] |= 0x00000040u; _impl_.expires_at_ = value; } inline void ContentAnalysisRequest::set_expires_at(int64_t value) { @@ -4876,7 +4954,7 @@ inline void ContentAnalysisRequest::set_allocated_user_action_id(std::string* us // optional int64 user_action_requests_count = 17; inline bool ContentAnalysisRequest::_internal_has_user_action_requests_count() const { - bool value = (_impl_._has_bits_[0] & 0x00000020u) != 0; + bool value = (_impl_._has_bits_[0] & 0x00000080u) != 0; return value; } inline bool ContentAnalysisRequest::has_user_action_requests_count() const { @@ -4884,7 +4962,7 @@ inline bool ContentAnalysisRequest::has_user_action_requests_count() const { } inline void ContentAnalysisRequest::clear_user_action_requests_count() { _impl_.user_action_requests_count_ = int64_t{0}; - _impl_._has_bits_[0] &= ~0x00000020u; + _impl_._has_bits_[0] &= ~0x00000080u; } inline int64_t ContentAnalysisRequest::_internal_user_action_requests_count() const { return _impl_.user_action_requests_count_; @@ -4894,7 +4972,7 @@ inline int64_t ContentAnalysisRequest::user_action_requests_count() const { return _internal_user_action_requests_count(); } inline void ContentAnalysisRequest::_internal_set_user_action_requests_count(int64_t value) { - _impl_._has_bits_[0] |= 0x00000020u; + _impl_._has_bits_[0] |= 0x00000080u; _impl_.user_action_requests_count_ = value; } inline void ContentAnalysisRequest::set_user_action_requests_count(int64_t value) { @@ -4902,6 +4980,35 @@ inline void ContentAnalysisRequest::set_user_action_requests_count(int64_t value // @@protoc_insertion_point(field_set:content_analysis.sdk.ContentAnalysisRequest.user_action_requests_count) } +// optional .content_analysis.sdk.ContentAnalysisRequest.Reason reason = 19; +inline bool ContentAnalysisRequest::_internal_has_reason() const { + bool value = (_impl_._has_bits_[0] & 0x00000020u) != 0; + return value; +} +inline bool ContentAnalysisRequest::has_reason() const { + return _internal_has_reason(); +} +inline void ContentAnalysisRequest::clear_reason() { + _impl_.reason_ = 0; + _impl_._has_bits_[0] &= ~0x00000020u; +} +inline ::content_analysis::sdk::ContentAnalysisRequest_Reason ContentAnalysisRequest::_internal_reason() const { + return static_cast< ::content_analysis::sdk::ContentAnalysisRequest_Reason >(_impl_.reason_); +} +inline ::content_analysis::sdk::ContentAnalysisRequest_Reason ContentAnalysisRequest::reason() const { + // @@protoc_insertion_point(field_get:content_analysis.sdk.ContentAnalysisRequest.reason) + return _internal_reason(); +} +inline void ContentAnalysisRequest::_internal_set_reason(::content_analysis::sdk::ContentAnalysisRequest_Reason value) { + assert(::content_analysis::sdk::ContentAnalysisRequest_Reason_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000020u; + _impl_.reason_ = value; +} +inline void ContentAnalysisRequest::set_reason(::content_analysis::sdk::ContentAnalysisRequest_Reason value) { + _internal_set_reason(value); + // @@protoc_insertion_point(field_set:content_analysis.sdk.ContentAnalysisRequest.reason) +} + inline bool ContentAnalysisRequest::has_content_data() const { return content_data_case() != CONTENT_DATA_NOT_SET; } @@ -5944,6 +6051,7 @@ PROTOBUF_NAMESPACE_OPEN template <> struct is_proto_enum< ::content_analysis::sdk::ContentMetaData_PrintMetadata_PrinterType> : ::std::true_type {}; template <> struct is_proto_enum< ::content_analysis::sdk::ClientDownloadRequest_ResourceType> : ::std::true_type {}; +template <> struct is_proto_enum< ::content_analysis::sdk::ContentAnalysisRequest_Reason> : ::std::true_type {}; template <> struct is_proto_enum< ::content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action> : ::std::true_type {}; template <> struct is_proto_enum< ::content_analysis::sdk::ContentAnalysisResponse_Result_Status> : ::std::true_type {}; template <> struct is_proto_enum< ::content_analysis::sdk::ContentAnalysisAcknowledgement_Status> : ::std::true_type {}; @@ -5955,4 +6063,4 @@ PROTOBUF_NAMESPACE_CLOSE // @@protoc_insertion_point(global_scope) #include <google/protobuf/port_undef.inc> -#endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_analysis_2eproto +#endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_content_5fanalysis_2fsdk_2fanalysis_2eproto diff --git a/toolkit/components/contentanalysis/nsIContentAnalysis.idl b/toolkit/components/contentanalysis/nsIContentAnalysis.idl index 21f98b88e6..63ac8d12fb 100644 --- a/toolkit/components/contentanalysis/nsIContentAnalysis.idl +++ b/toolkit/components/contentanalysis/nsIContentAnalysis.idl @@ -36,7 +36,7 @@ interface nsIContentAnalysisAcknowledgement : nsISupports readonly attribute nsIContentAnalysisAcknowledgement_FinalAction finalAction; }; -[scriptable, builtinclass, uuid(89088c61-15f6-4ace-a880-a1b5ea47ca66)] +[scriptable, uuid(89088c61-15f6-4ace-a880-a1b5ea47ca66)] interface nsIContentAnalysisResponse : nsISupports { // These values must stay synchronized with ContentAnalysisResponse @@ -52,8 +52,18 @@ interface nsIContentAnalysisResponse : nsISupports eCanceled = 1001, }; - [infallible] readonly attribute nsIContentAnalysisResponse_Action action; - [infallible] readonly attribute boolean shouldAllowContent; + cenum CancelError : 32 { + eUserInitiated = 0, + eNoAgent = 1, + eInvalidAgentSignature = 2, + eErrorOther = 3, + }; + + readonly attribute nsIContentAnalysisResponse_Action action; + readonly attribute boolean shouldAllowContent; + // If action is eCanceled, this is the error explaining why the request was canceled, + // or eUserInitiated if the user canceled it. + readonly attribute nsIContentAnalysisResponse_CancelError cancelError; // Identifier for the corresponding nsIContentAnalysisRequest readonly attribute ACString requestToken; @@ -121,6 +131,7 @@ interface nsIContentAnalysisRequest : nsISupports eCustomDisplayString = 0, eClipboard = 1, eDroppedText = 2, + eOperationPrint = 3, }; readonly attribute nsIContentAnalysisRequest_OperationType operationTypeForDisplay; readonly attribute AString operationDisplayString; @@ -131,6 +142,15 @@ interface nsIContentAnalysisRequest : nsISupports // Name of file to analyze. Only one of textContent or filePath is defined. readonly attribute AString filePath; + // HANDLE to the printed data in PDF format. + readonly attribute unsigned long long printDataHandle; + + // Size of the data stored in printDataHandle. + readonly attribute unsigned long long printDataSize; + + // Name of the printer being printed to. + readonly attribute AString printerName; + // The URL containing the file download/upload or to which web content is // being uploaded. readonly attribute nsIURI url; @@ -166,7 +186,16 @@ interface nsIContentAnalysisCallback : nsISupports void error(in nsresult aResult); }; -[scriptable, builtinclass, uuid(61497587-2bba-4a88-acd3-3fbb2cedf163)] +[scriptable, builtinclass, uuid(a430f6ef-a526-4055-8a82-7741ea757367)] +interface nsIContentAnalysisDiagnosticInfo : nsISupports +{ + [infallible] readonly attribute boolean connectedToAgent; + readonly attribute AString agentPath; + [infallible] readonly attribute boolean failedSignatureVerification; + [infallible] readonly attribute long long requestCount; +}; + +[scriptable, uuid(61497587-2bba-4a88-acd3-3fbb2cedf163)] interface nsIContentAnalysis : nsISupports { /** @@ -182,14 +211,14 @@ interface nsIContentAnalysis : nsISupports * into the parent process to determine whether content analysis is actually * active. */ - readonly attribute bool mightBeActive; + readonly attribute boolean mightBeActive; /** * True if content-analysis activation was determined by enterprise policy, * as opposed to enabled with the `allow-content-analysis` command-line * parameter. */ - attribute bool isSetByEnterprisePolicy; + attribute boolean isSetByEnterprisePolicy; /** * Consults content analysis server, if any, to request a permission @@ -210,7 +239,7 @@ interface nsIContentAnalysis : nsISupports * calling nsIContentAnalysisResponse::acknowledge() if the Promise is resolved. */ [implicit_jscontext] - Promise analyzeContentRequest(in nsIContentAnalysisRequest aCar, in bool aAutoAcknowledge); + Promise analyzeContentRequest(in nsIContentAnalysisRequest aCar, in boolean aAutoAcknowledge); /** * Same functionality as AnalyzeContentRequest(), but more convenient to call @@ -226,7 +255,7 @@ interface nsIContentAnalysis : nsISupports * @param callback * Callbacks to be called when the agent sends a response message (or when there is an error). */ - void analyzeContentRequestCallback(in nsIContentAnalysisRequest aCar, in bool aAutoAcknowledge, in nsIContentAnalysisCallback callback); + void analyzeContentRequestCallback(in nsIContentAnalysisRequest aCar, in boolean aAutoAcknowledge, in nsIContentAnalysisCallback callback); /** * Cancels the request that is in progress. This may not actually cancel the request @@ -242,7 +271,7 @@ interface nsIContentAnalysis : nsISupports * Indicates that the user has responded to a WARN dialog. aAllowContent represents * whether the user wants to allow the request to go through. */ - void respondToWarnDialog(in ACString aRequestToken, in bool aAllowContent); + void respondToWarnDialog(in ACString aRequestToken, in boolean aAllowContent); /** * Cancels all outstanding DLP requests. Used on shutdown. @@ -254,4 +283,11 @@ interface nsIContentAnalysis : nsISupports * given to Gecko on the command line. */ void testOnlySetCACmdLineArg(in boolean aVal); + + /** + * Gets diagnostic information about content analysis. Returns a + * nsIContentAnalysisDiagnosticInfo via the returned promise. + */ + [implicit_jscontext] + Promise getDiagnosticInfo(); }; diff --git a/toolkit/components/contentanalysis/tests/browser/browser.toml b/toolkit/components/contentanalysis/tests/browser/browser.toml index 0e21090299..bdbf350593 100644 --- a/toolkit/components/contentanalysis/tests/browser/browser.toml +++ b/toolkit/components/contentanalysis/tests/browser/browser.toml @@ -1,3 +1,20 @@ [DEFAULT] +run-if = ["os == 'win'"] +support-files = [ + "head.js", +] ["browser_content_analysis_policies.js"] + +["browser_print_changing_page_content_analysis.js"] +support-files = [ + "!/toolkit/components/printing/tests/head.js", + "changing_page_for_print.html", +] + +["browser_print_content_analysis.js"] +support-files = [ + "!/toolkit/components/printing/tests/head.js", + "!/toolkit/components/printing/tests/longerArticle.html", + "!/toolkit/components/printing/tests/simplifyArticleSample.html", +] diff --git a/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js b/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js index e2e001e9d1..b226c0a37a 100644 --- a/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js +++ b/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js @@ -11,15 +11,18 @@ "use strict"; -const { EnterprisePolicyTesting } = ChromeUtils.importESModule( - "resource://testing-common/EnterprisePolicyTesting.sys.mjs" -); +const { EnterprisePolicyTesting, PoliciesPrefTracker } = + ChromeUtils.importESModule( + "resource://testing-common/EnterprisePolicyTesting.sys.mjs" + ); const kEnabledPref = "enabled"; const kPipeNamePref = "pipe_path_name"; const kTimeoutPref = "agent_timeout"; const kAllowUrlPref = "allow_url_regex_list"; const kDenyUrlPref = "deny_url_regex_list"; +const kAgentNamePref = "agent_name"; +const kClientSignaturePref = "client_signature"; const kPerUserPref = "is_per_user"; const kShowBlockedPref = "show_blocked_result"; const kDefaultAllowPref = "default_allow"; @@ -29,6 +32,7 @@ const ca = Cc["@mozilla.org/contentanalysis;1"].getService( ); add_task(async function test_ca_active() { + PoliciesPrefTracker.start(); ok(!ca.isActive, "CA is inactive when pref and cmd line arg are missing"); // Set the pref without enterprise policy. CA should not be active. @@ -62,11 +66,15 @@ add_task(async function test_ca_active() { }, }); ok(ca.isActive, "CA is active when enabled by enterprise policy pref"); + PoliciesPrefTracker.stop(); }); add_task(async function test_ca_enterprise_config() { + PoliciesPrefTracker.start(); const string1 = "this is a string"; const string2 = "this is another string"; + const string3 = "an agent name"; + const string4 = "a client signature"; await EnterprisePolicyTesting.setupPolicyEngineWithJson({ policies: { @@ -75,6 +83,8 @@ add_task(async function test_ca_enterprise_config() { AgentTimeout: 99, AllowUrlRegexList: string1, DenyUrlRegexList: string2, + AgentName: string3, + ClientSignature: string4, IsPerUser: true, ShowBlockedResult: false, DefaultAllow: true, @@ -103,6 +113,18 @@ add_task(async function test_ca_enterprise_config() { "deny urls match" ); is( + Services.prefs.getStringPref("browser.contentanalysis." + kAgentNamePref), + string3, + "agent names match" + ); + is( + Services.prefs.getStringPref( + "browser.contentanalysis." + kClientSignaturePref + ), + string4, + "client signatures match" + ); + is( Services.prefs.getBoolPref("browser.contentanalysis." + kPerUserPref), true, "per user match" @@ -117,6 +139,7 @@ add_task(async function test_ca_enterprise_config() { true, "default allow match" ); + PoliciesPrefTracker.stop(); }); add_task(async function test_cleanup() { @@ -124,4 +147,9 @@ add_task(async function test_cleanup() { await EnterprisePolicyTesting.setupPolicyEngineWithJson({ policies: {}, }); + // These may have gotten set when ContentAnalysis was enabled through + // the policy and do not get cleared if there is no ContentAnalysis + // element - reset them manually here. + ca.isSetByEnterprisePolicy = false; + Services.prefs.setBoolPref("browser.contentanalysis." + kEnabledPref, false); }); diff --git a/toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js new file mode 100644 index 0000000000..72a7dcbb91 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js @@ -0,0 +1,339 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/components/printing/tests/head.js", + this +); + +const PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService( + Ci.nsIPrintSettingsService +); + +let mockCA = { + isActive: true, + mightBeActive: true, + errorValue: undefined, + + setupForTest(shouldAllowRequest) { + this.shouldAllowRequest = shouldAllowRequest; + this.errorValue = undefined; + this.calls = []; + }, + + setupForTestWithError(errorValue) { + this.errorValue = errorValue; + this.calls = []; + }, + + getAction() { + if (this.shouldAllowRequest === undefined) { + this.shouldAllowRequest = true; + } + return this.shouldAllowRequest + ? Ci.nsIContentAnalysisResponse.eAllow + : Ci.nsIContentAnalysisResponse.eBlock; + }, + + // nsIContentAnalysis methods + async analyzeContentRequest(request, _autoAcknowledge) { + info( + "Mock ContentAnalysis service: analyzeContentRequest, this.shouldAllowRequest=" + + this.shouldAllowRequest + + ", this.errorValue=" + + this.errorValue + ); + this.calls.push(request); + if (this.errorValue) { + throw this.errorValue; + } + // Use setTimeout to simulate an async activity + await new Promise(res => setTimeout(res, 0)); + return makeContentAnalysisResponse(this.getAction(), request.requestToken); + }, + + analyzeContentRequestCallback(request, autoAcknowledge, callback) { + info( + "Mock ContentAnalysis service: analyzeContentRequestCallback, this.shouldAllowRequest=" + + this.shouldAllowRequest + + ", this.errorValue=" + + this.errorValue + ); + this.calls.push(request); + if (this.errorValue) { + throw this.errorValue; + } + let response = makeContentAnalysisResponse( + this.getAction(), + request.requestToken + ); + // Use setTimeout to simulate an async activity + setTimeout(() => { + callback.contentResult(response); + }, 0); + }, +}; + +add_setup(async function test_setup() { + mockCA = mockContentAnalysisService(mockCA); +}); + +const TEST_PAGE_URL = PrintHelper.getTestPageUrlHTTPS( + "changing_page_for_print.html" +); + +function addUniqueSuffix(prefix) { + return `${prefix}-${Services.uuid + .generateUUID() + .toString() + .slice(1, -1)}.pdf`; +} + +async function printToDestination(aBrowser, aDestination) { + let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile); + let fileName = addUniqueSuffix(`printDestinationTest-${aDestination}`); + let filePath = PathUtils.join(tmpDir.path, fileName); + + info(`Printing to ${filePath}`); + + let settings = PSSVC.createNewPrintSettings(); + settings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF; + settings.outputDestination = aDestination; + + settings.headerStrCenter = ""; + settings.headerStrLeft = ""; + settings.headerStrRight = ""; + settings.footerStrCenter = ""; + settings.footerStrLeft = ""; + settings.footerStrRight = ""; + + settings.unwriteableMarginTop = 1; /* Just to ensure settings are respected on both */ + let outStream = null; + if (aDestination == Ci.nsIPrintSettings.kOutputDestinationFile) { + settings.toFileName = PathUtils.join(tmpDir.path, fileName); + } else { + is(aDestination, Ci.nsIPrintSettings.kOutputDestinationStream); + outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance( + Ci.nsIFileOutputStream + ); + let tmpFile = tmpDir.clone(); + tmpFile.append(fileName); + outStream.init(tmpFile, -1, 0o666, 0); + settings.outputStream = outStream; + } + + await aBrowser.browsingContext.print(settings); + + return filePath; +} + +function assertContentAnalysisRequest(request) { + is(request.url.spec, TEST_PAGE_URL, "request has correct URL"); + is( + request.analysisType, + Ci.nsIContentAnalysisRequest.ePrint, + "request has print analysisType" + ); + is( + request.operationTypeForDisplay, + Ci.nsIContentAnalysisRequest.eOperationPrint, + "request has print operationTypeForDisplay" + ); + is(request.textContent, "", "request textContent should be empty"); + is(request.filePath, "", "request filePath should be empty"); + isnot(request.printDataHandle, 0, "request printDataHandle should not be 0"); + isnot(request.printDataSize, 0, "request printDataSize should not be 0"); + ok(!!request.requestToken.length, "request requestToken should not be empty"); +} + +add_task( + async function testPrintToStreamWithContentAnalysisActiveAndAllowing() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(true); + + let filePath = await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + + // This effectively tests that the PDF content sent to Content Analysis + // and the content that is actually printed matches. This is necessary + // because a previous iteration of the Content Analysis code didn't use + // a static Document clone for this and so the content would differ. (since + // the .html file in question adds content to the page when print events + // happen) + await waitForFileToAlmostMatchSize( + filePath, + mockCA.calls[0].printDataSize + ); + + await IOUtils.remove(filePath); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task( + async function testPrintToStreamWithContentAnalysisActiveAndBlocking() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(false); + + try { + await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + ok(false, "Content analysis should make this fail to print"); + } catch (e) { + ok( + /NS_ERROR_CONTENT_BLOCKED/.test(e.toString()), + "Got content blocked error" + ); + } + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task(async function testPrintToStreamWithContentAnalysisReturningError() { + await PrintHelper.withTestPage( + async helper => { + expectUncaughtException(); + mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE); + + try { + await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + ok(false, "Content analysis should make this fail to print"); + } catch (e) { + ok( + /NS_ERROR_NOT_AVAILABLE/.test(e.toString()), + "Error in mock CA was propagated out" + ); + } + is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); +}); + +add_task(async function testPrintThroughDialogWithContentAnalysisActive() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(true); + + let fileName = addUniqueSuffix(`printDialogTest`); + let file = helper.mockFilePicker(fileName); + info(`Printing to ${file.path}`); + await helper.startPrint(); + await helper.assertPrintToFile(file, () => { + EventUtils.sendKey("return", helper.win); + }); + + is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest(mockCA.calls[0]); + + await waitForFileToAlmostMatchSize( + file.path, + mockCA.calls[0].printDataSize + ); + }, + TEST_PAGE_URL, + true + ); +}); + +add_task( + async function testPrintThroughDialogWithContentAnalysisActiveAndBlocking() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(false); + + await helper.startPrint(); + let fileName = addUniqueSuffix(`printDialogTest`); + let file = helper.mockFilePicker(fileName); + info(`Printing to ${file.path}`); + try { + await helper.assertPrintToFile(file, () => { + EventUtils.sendKey("return", helper.win); + }); + } catch (e) { + ok( + /Wait for target file to get created/.test(e.toString()), + "Target file should not get created" + ); + } + ok(!file.exists(), "File should not exist"); + + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task( + async function testPrintThroughDialogWithContentAnalysisReturningError() { + await PrintHelper.withTestPage( + async helper => { + expectUncaughtException(); + mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE); + + await helper.startPrint(); + let fileName = addUniqueSuffix(`printDialogTest`); + let file = helper.mockFilePicker(fileName); + info(`Printing to ${file.path}`); + try { + await helper.assertPrintToFile(file, () => { + EventUtils.sendKey("return", helper.win); + }); + } catch (e) { + ok( + /Wait for target file to get created/.test(e.toString()), + "Target file should not get created" + ); + } + ok(!file.exists(), "File should not exist"); + + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); + } +); diff --git a/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js new file mode 100644 index 0000000000..9b4c0ffa60 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js @@ -0,0 +1,390 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/components/printing/tests/head.js", + this +); + +const PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService( + Ci.nsIPrintSettingsService +); + +let mockCA = { + isActive: true, + mightBeActive: true, + errorValue: undefined, + + setupForTest(shouldAllowRequest) { + this.shouldAllowRequest = shouldAllowRequest; + this.errorValue = undefined; + this.calls = []; + }, + + setupForTestWithError(errorValue) { + this.errorValue = errorValue; + this.calls = []; + }, + + clearCalls() { + this.calls = []; + }, + + getAction() { + if (this.shouldAllowRequest === undefined) { + this.shouldAllowRequest = true; + } + return this.shouldAllowRequest + ? Ci.nsIContentAnalysisResponse.eAllow + : Ci.nsIContentAnalysisResponse.eBlock; + }, + + // nsIContentAnalysis methods + async analyzeContentRequest(request, _autoAcknowledge) { + info( + "Mock ContentAnalysis service: analyzeContentRequest, this.shouldAllowRequest=" + + this.shouldAllowRequest + + ", this.errorValue=" + + this.errorValue + ); + this.calls.push(request); + if (this.errorValue) { + throw this.errorValue; + } + // Use setTimeout to simulate an async activity + await new Promise(res => setTimeout(res, 0)); + return makeContentAnalysisResponse(this.getAction(), request.requestToken); + }, + + analyzeContentRequestCallback(request, autoAcknowledge, callback) { + info( + "Mock ContentAnalysis service: analyzeContentRequestCallback, this.shouldAllowRequest=" + + this.shouldAllowRequest + + ", this.errorValue=" + + this.errorValue + ); + this.calls.push(request); + if (this.errorValue) { + throw this.errorValue; + } + let response = makeContentAnalysisResponse( + this.getAction(), + request.requestToken + ); + // Use setTimeout to simulate an async activity + setTimeout(() => { + callback.contentResult(response); + }, 0); + }, +}; + +add_setup(async function test_setup() { + mockCA = mockContentAnalysisService(mockCA); +}); + +const TEST_PAGE_URL = + "https://example.com/browser/toolkit/components/printing/tests/simplifyArticleSample.html"; +const TEST_PAGE_URL_2 = + "https://example.com/browser/toolkit/components/printing/tests/longerArticle.html"; + +function addUniqueSuffix(prefix) { + return `${prefix}-${Services.uuid + .generateUUID() + .toString() + .slice(1, -1)}.pdf`; +} + +async function printToDestination(aBrowser, aDestination) { + let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile); + let fileName = addUniqueSuffix(`printDestinationTest-${aDestination}`); + let filePath = PathUtils.join(tmpDir.path, fileName); + + info(`Printing to ${filePath}`); + + let settings = PSSVC.createNewPrintSettings(); + settings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF; + settings.outputDestination = aDestination; + + settings.headerStrCenter = ""; + settings.headerStrLeft = ""; + settings.headerStrRight = ""; + settings.footerStrCenter = ""; + settings.footerStrLeft = ""; + settings.footerStrRight = ""; + + settings.unwriteableMarginTop = 1; /* Just to ensure settings are respected on both */ + let outStream = null; + if (aDestination == Ci.nsIPrintSettings.kOutputDestinationFile) { + settings.toFileName = PathUtils.join(tmpDir.path, fileName); + } else { + is(aDestination, Ci.nsIPrintSettings.kOutputDestinationStream); + outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance( + Ci.nsIFileOutputStream + ); + let tmpFile = tmpDir.clone(); + tmpFile.append(fileName); + outStream.init(tmpFile, -1, 0o666, 0); + settings.outputStream = outStream; + } + + await aBrowser.browsingContext.print(settings); + + return filePath; +} + +function assertContentAnalysisRequest(request, expectedUrl) { + is(request.url.spec, expectedUrl ?? TEST_PAGE_URL, "request has correct URL"); + is( + request.analysisType, + Ci.nsIContentAnalysisRequest.ePrint, + "request has print analysisType" + ); + is( + request.operationTypeForDisplay, + Ci.nsIContentAnalysisRequest.eOperationPrint, + "request has print operationTypeForDisplay" + ); + is(request.textContent, "", "request textContent should be empty"); + is(request.filePath, "", "request filePath should be empty"); + isnot(request.printDataHandle, 0, "request printDataHandle should not be 0"); + isnot(request.printDataSize, 0, "request printDataSize should not be 0"); + ok(!!request.requestToken.length, "request requestToken should not be empty"); +} + +// Printing to a stream is different than going through the print preview dialog because it +// doesn't make a static clone of the document before the print, which causes the +// Content Analysis code to go through a different code path. This is similar to what +// happens when various preferences are set to skip the print preview dialog, for example +// print.prefer_system_dialog. +add_task( + async function testPrintToStreamWithContentAnalysisActiveAndAllowing() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(true); + + let filePath = await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + + await waitForFileToAlmostMatchSize( + filePath, + mockCA.calls[0].printDataSize + ); + + await IOUtils.remove(filePath); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task( + async function testPrintToStreamAfterNavigationWithContentAnalysisActiveAndAllowing() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(true); + + let filePath = await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + mockCA.clearCalls(); + + await IOUtils.remove(filePath); + + BrowserTestUtils.startLoadingURIString( + helper.sourceBrowser, + TEST_PAGE_URL_2 + ); + await BrowserTestUtils.browserLoaded(helper.sourceBrowser); + + filePath = await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0], TEST_PAGE_URL_2); + await waitForFileToAlmostMatchSize( + filePath, + mockCA.calls[0].printDataSize + ); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task( + async function testPrintToStreamWithContentAnalysisActiveAndBlocking() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(false); + + try { + await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + ok(false, "Content analysis should make this fail to print"); + } catch (e) { + ok( + /NS_ERROR_CONTENT_BLOCKED/.test(e.toString()), + "Got content blocked error" + ); + } + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task(async function testPrintToStreamWithContentAnalysisReturningError() { + await PrintHelper.withTestPage( + async helper => { + expectUncaughtException(); + mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE); + + try { + await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + ok(false, "Content analysis should make this fail to print"); + } catch (e) { + ok( + /NS_ERROR_NOT_AVAILABLE/.test(e.toString()), + "Error in mock CA was propagated out" + ); + } + is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); +}); + +add_task(async function testPrintThroughDialogWithContentAnalysisActive() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(true); + + await helper.startPrint(); + let fileName = addUniqueSuffix(`printDialogTest`); + let file = helper.mockFilePicker(fileName); + info(`Printing to ${file.path}`); + await helper.assertPrintToFile(file, () => { + EventUtils.sendKey("return", helper.win); + }); + + is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest(mockCA.calls[0]); + + await waitForFileToAlmostMatchSize( + file.path, + mockCA.calls[0].printDataSize + ); + }, + TEST_PAGE_URL, + true + ); +}); + +add_task( + async function testPrintThroughDialogWithContentAnalysisActiveAndBlocking() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(false); + + await helper.startPrint(); + let fileName = addUniqueSuffix(`printDialogTest`); + let file = helper.mockFilePicker(fileName); + info(`Printing to ${file.path}`); + try { + await helper.assertPrintToFile(file, () => { + EventUtils.sendKey("return", helper.win); + }); + } catch (e) { + ok( + /Wait for target file to get created/.test(e.toString()), + "Target file should not get created" + ); + } + ok(!file.exists(), "File should not exist"); + + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task( + async function testPrintThroughDialogWithContentAnalysisReturningError() { + await PrintHelper.withTestPage( + async helper => { + expectUncaughtException(); + mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE); + + await helper.startPrint(); + let fileName = addUniqueSuffix(`printDialogTest`); + let file = helper.mockFilePicker(fileName); + info(`Printing to ${file.path}`); + try { + await helper.assertPrintToFile(file, () => { + EventUtils.sendKey("return", helper.win); + }); + } catch (e) { + ok( + /Wait for target file to get created/.test(e.toString()), + "Target file should not get created" + ); + } + ok(!file.exists(), "File should not exist"); + + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); + } +); diff --git a/toolkit/components/contentanalysis/tests/browser/changing_page_for_print.html b/toolkit/components/contentanalysis/tests/browser/changing_page_for_print.html new file mode 100644 index 0000000000..de6f9001aa --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/changing_page_for_print.html @@ -0,0 +1,12 @@ +<!doctype html> +<p>Some random text</p> +<button onclick="print()">Print the page</button> +<pre id="log"></pre> +<script> +let i = 0; +for (let t of ["beforeprint", "afterprint"]) { + addEventListener(t, () => { + document.getElementById("log").appendChild(document.createTextNode(`[${i++}] ${t}\n`)); + }); +} +</script> diff --git a/toolkit/components/contentanalysis/tests/browser/head.js b/toolkit/components/contentanalysis/tests/browser/head.js new file mode 100644 index 0000000000..e645caa2d7 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/head.js @@ -0,0 +1,114 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { MockRegistrar } = ChromeUtils.importESModule( + "resource://testing-common/MockRegistrar.sys.mjs" +); + +// Wraps the given object in an XPConnect wrapper and, if an interface +// is passed, queries the result to that interface. +function xpcWrap(obj, iface) { + let ifacePointer = Cc[ + "@mozilla.org/supports-interface-pointer;1" + ].createInstance(Ci.nsISupportsInterfacePointer); + + ifacePointer.data = obj; + if (iface) { + return ifacePointer.data.QueryInterface(iface); + } + return ifacePointer.data; +} + +/** + * Mock a (set of) service(s) as the object mockService. + * + * @param {[string]} serviceNames + * array of services names that mockService will be + * allowed to QI to. Must include the name of the + * service referenced by contractId. + * @param {string} contractId + * the component ID that will reference the mock object + * instead of the original service + * @param {object} interfaceObj + * interface object for the component + * @param {object} mockService + * object that satisfies the contract well + * enough to use as a mock of it + * @returns {object} The newly-mocked service + */ +function mockService(serviceNames, contractId, interfaceObj, mockService) { + // xpcWrap allows us to mock [implicit_jscontext] methods. + let newService = { + ...mockService, + QueryInterface: ChromeUtils.generateQI(serviceNames), + }; + let o = xpcWrap(newService, interfaceObj); + let cid = MockRegistrar.register(contractId, o); + registerCleanupFunction(() => { + MockRegistrar.unregister(cid); + }); + return newService; +} + +/** + * Mock the nsIContentAnalysis service with the object mockCAService. + * + * @param {object} mockCAService + * the service to mock for nsIContentAnalysis + * @returns {object} The newly-mocked service + */ +function mockContentAnalysisService(mockCAService) { + return mockService( + ["nsIContentAnalysis"], + "@mozilla.org/contentanalysis;1", + Ci.nsIContentAnalysis, + mockCAService + ); +} + +/** + * Make an nsIContentAnalysisResponse. + * + * @param {number} action The action to take, from the + * nsIContentAnalysisResponse.Action enum. + * @param {string} token The requestToken. + * @returns {object} An object that conforms to nsIContentAnalysisResponse. + */ +function makeContentAnalysisResponse(action, token) { + return { + action, + shouldAllowContent: action != Ci.nsIContentAnalysisResponse.eBlock, + requestToken: token, + acknowledge: _acknowledgement => {}, + }; +} + +async function waitForFileToAlmostMatchSize(filePath, expectedSize) { + // In Cocoa the CGContext adds a hash, plus there are other minor + // non-user-visible differences, so we need to be a bit more sloppy there. + // + // We see one byte difference in Windows and Linux on automation sometimes, + // though files are consistently the same locally, that needs + // investigation, but it's probably harmless. + // Note that this is copied from browser_print_stream.js. + const maxSizeDifference = AppConstants.platform == "macosx" ? 100 : 3; + + // Buffering shenanigans? Wait for sizes to match... There's no great + // IOUtils methods to force a flush without writing anything... + // Note that this means if this results in a timeout this is exactly + // the same as a test failure. + // This is taken from toolkit/components/printing/tests/browser_print_stream.js + await TestUtils.waitForCondition(async function () { + let fileStat = await IOUtils.stat(filePath); + + info("got size: " + fileStat.size + " expected: " + expectedSize); + Assert.greater( + fileStat.size, + 0, + "File should not be empty: " + fileStat.size + ); + return Math.abs(fileStat.size - expectedSize) <= maxSizeDifference; + }, "Sizes should (almost) match"); +} |