From 086c044dc34dfc0f74fbe41f4ecb402b2cd34884 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:13:33 +0200 Subject: Merging upstream version 125.0.1. Signed-off-by: Daniel Baumann --- .../components/contentanalysis/ContentAnalysis.cpp | 437 +++++++++++++++++---- .../components/contentanalysis/ContentAnalysis.h | 42 +- toolkit/components/contentanalysis/moz.build | 124 +++--- .../contentanalysis/nsIContentAnalysis.idl | 18 + .../contentanalysis/tests/browser/browser.toml | 3 + .../browser/browser_content_analysis_policies.js | 127 ++++++ .../contentanalysis/tests/browser/moz.build | 7 + .../tests/gtest/TestContentAnalysis.cpp | 214 +++++----- .../tests/gtest/TestContentAnalysis.h | 24 -- .../tests/gtest/TestContentAnalysisAgent.cpp | 132 +++++++ .../tests/gtest/TestContentAnalysisAgent.h | 24 ++ .../tests/gtest/TestContentAnalysisMisbehaving.cpp | 2 +- .../tests/gtest/TestContentAnalysisUtils.cpp | 2 +- .../contentanalysis/tests/gtest/moz.build | 4 + toolkit/components/contentanalysis/tests/moz.build | 7 + 15 files changed, 880 insertions(+), 287 deletions(-) create mode 100644 toolkit/components/contentanalysis/tests/browser/browser.toml create mode 100644 toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js create mode 100644 toolkit/components/contentanalysis/tests/browser/moz.build delete mode 100644 toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.h create mode 100644 toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp create mode 100644 toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.h create mode 100644 toolkit/components/contentanalysis/tests/moz.build (limited to 'toolkit/components/contentanalysis') diff --git a/toolkit/components/contentanalysis/ContentAnalysis.cpp b/toolkit/components/contentanalysis/ContentAnalysis.cpp index e749fd0acd..1c71f5d986 100644 --- a/toolkit/components/contentanalysis/ContentAnalysis.cpp +++ b/toolkit/components/contentanalysis/ContentAnalysis.cpp @@ -12,9 +12,11 @@ #include "GMPUtils.h" // ToHexString #include "mozilla/Components.h" #include "mozilla/dom/Promise.h" +#include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/Logging.h" #include "mozilla/ScopeExit.h" #include "mozilla/Services.h" +#include "mozilla/StaticMutex.h" #include "mozilla/StaticPrefs_browser.h" #include "nsAppRunner.h" #include "nsComponentManagerUtils.h" @@ -27,11 +29,13 @@ #include #include +#include #ifdef XP_WIN # include # define SECURITY_WIN32 1 # include +# include "mozilla/WinDllServices.h" #endif // XP_WIN namespace mozilla::contentanalysis { @@ -53,8 +57,9 @@ const char* kIsDLPEnabledPref = "browser.contentanalysis.enabled"; const char* kIsPerUserPref = "browser.contentanalysis.is_per_user"; const char* kPipePathNamePref = "browser.contentanalysis.pipe_path_name"; const char* kDefaultAllowPref = "browser.contentanalysis.default_allow"; - -static constexpr uint32_t kAnalysisTimeoutSecs = 30; // 30 sec +const char* kClientSignature = "browser.contentanalysis.client_signature"; +const char* kAllowUrlPref = "browser.contentanalysis.allow_url_regex_list"; +const char* kDenyUrlPref = "browser.contentanalysis.deny_url_regex_list"; nsresult MakePromise(JSContext* aCx, RefPtr* aPromise) { nsIGlobalObject* go = xpc::CurrentNativeGlobal(aCx); @@ -181,8 +186,9 @@ ContentAnalysisRequest::GetWindowGlobalParent( return NS_OK; } -nsresult ContentAnalysis::CreateContentAnalysisClient(nsCString&& aPipePathName, - bool aIsPerUser) { +nsresult ContentAnalysis::CreateContentAnalysisClient( + nsCString&& aPipePathName, nsString&& aClientSignatureSetting, + bool aIsPerUser) { MOZ_ASSERT(!NS_IsMainThread()); // This method should only be called once MOZ_ASSERT(!mCaClientPromise->IsResolved()); @@ -191,6 +197,30 @@ nsresult ContentAnalysis::CreateContentAnalysisClient(nsCString&& aPipePathName, content_analysis::sdk::Client::Create({aPipePathName.Data(), aIsPerUser}) .release()); LOGD("Content analysis is %s", client ? "connected" : "not available"); +#ifdef XP_WIN + if (client && !aClientSignatureSetting.IsEmpty()) { + std::string agentPath = client->GetAgentInfo().binary_path; + nsString agentWidePath = NS_ConvertUTF8toUTF16(agentPath); + UniquePtr orgName = + mozilla::DllServices::Get()->GetBinaryOrgName(agentWidePath.Data()); + bool signatureMatches = false; + if (orgName) { + auto dependentOrgName = nsDependentString(orgName.get()); + LOGD("Content analysis client signed with organization name \"%S\"", + static_cast(dependentOrgName.get())); + signatureMatches = aClientSignatureSetting.Equals(dependentOrgName); + } else { + LOGD("Content analysis client has no signature"); + } + if (!signatureMatches) { + LOGE( + "Got mismatched content analysis client signature! All content " + "analysis operations will fail."); + mCaClientPromise->Reject(NS_ERROR_INVALID_SIGNATURE, __func__); + return NS_OK; + } + } +#endif // XP_WIN mCaClientPromise->Resolve(client, __func__); return NS_OK; @@ -258,6 +288,15 @@ nsresult ContentAnalysisRequest::GetFileDigest(const nsAString& aFilePath, return NS_OK; } +// Generate an ID that will be shared by all DLP requests. +// Used to cancel all requests on Firefox shutdown. +void ContentAnalysis::GenerateUserActionId() { + nsID id = nsID::GenerateUUID(); + mUserActionId = nsPrintfCString("Firefox %s", id.ToString().get()); +} + +nsCString ContentAnalysis::GetUserActionId() { return mUserActionId; } + static nsresult ConvertToProtobuf( nsIClientDownloadResource* aIn, content_analysis::sdk::ClientDownloadRequest_Resource* aOut) { @@ -277,9 +316,11 @@ static nsresult ConvertToProtobuf( } static nsresult ConvertToProtobuf( - nsIContentAnalysisRequest* aIn, + nsIContentAnalysisRequest* aIn, nsCString&& aUserActionId, + int64_t aRequestCount, content_analysis::sdk::ContentAnalysisRequest* aOut) { - aOut->set_expires_at(time(nullptr) + kAnalysisTimeoutSecs); // TODO: + uint32_t timeout = StaticPrefs::browser_contentanalysis_agent_timeout(); + aOut->set_expires_at(time(nullptr) + timeout); nsIContentAnalysisRequest::AnalysisType analysisType; nsresult rv = aIn->GetAnalysisType(&analysisType); @@ -293,6 +334,9 @@ static nsresult ConvertToProtobuf( NS_ENSURE_SUCCESS(rv, rv); aOut->set_request_token(requestToken.get(), requestToken.Length()); + aOut->set_user_action_id(aUserActionId.get()); + aOut->set_user_action_requests_count(aRequestCount); + const std::string tag = "dlp"; // TODO: *aOut->add_tags() = tag; @@ -440,8 +484,7 @@ static void LogRequest( } ContentAnalysisResponse::ContentAnalysisResponse( - content_analysis::sdk::ContentAnalysisResponse&& aResponse) - : mHasAcknowledged(false) { + content_analysis::sdk::ContentAnalysisResponse&& aResponse) { mAction = Action::eUnspecified; for (const auto& result : aResponse.results()) { if (!result.has_status() || @@ -469,7 +512,7 @@ ContentAnalysisResponse::ContentAnalysisResponse( ContentAnalysisResponse::ContentAnalysisResponse( Action aAction, const nsACString& aRequestToken) - : mAction(aAction), mRequestToken(aRequestToken), mHasAcknowledged(false) {} + : mAction(aAction), mRequestToken(aRequestToken) {} /* static */ already_AddRefed ContentAnalysisResponse::FromProtobuf( @@ -658,6 +701,122 @@ NS_IMETHODIMP ContentAnalysisResult::GetShouldAllowContent( return NS_OK; } +void ContentAnalysis::EnsureParsedUrlFilters() { + MOZ_ASSERT(NS_IsMainThread()); + if (mParsedUrlLists) { + return; + } + + mParsedUrlLists = true; + nsAutoCString allowList; + MOZ_ALWAYS_SUCCEEDS(Preferences::GetCString(kAllowUrlPref, allowList)); + for (const nsACString& regexSubstr : allowList.Split(u' ')) { + if (!regexSubstr.IsEmpty()) { + auto flatStr = PromiseFlatCString(regexSubstr); + const char* regex = flatStr.get(); + LOGD("CA will allow URLs that match %s", regex); + mAllowUrlList.push_back(std::regex(regex)); + } + } + + nsAutoCString denyList; + MOZ_ALWAYS_SUCCEEDS(Preferences::GetCString(kDenyUrlPref, denyList)); + for (const nsACString& regexSubstr : denyList.Split(u' ')) { + if (!regexSubstr.IsEmpty()) { + auto flatStr = PromiseFlatCString(regexSubstr); + const char* regex = flatStr.get(); + LOGD("CA will block URLs that match %s", regex); + mDenyUrlList.push_back(std::regex(regex)); + } + } +} + +ContentAnalysis::UrlFilterResult ContentAnalysis::FilterByUrlLists( + nsIContentAnalysisRequest* aRequest) { + EnsureParsedUrlFilters(); + + nsIURI* nsiUrl = nullptr; + MOZ_ALWAYS_SUCCEEDS(aRequest->GetUrl(&nsiUrl)); + nsCString urlString; + nsresult rv = nsiUrl->GetSpec(urlString); + NS_ENSURE_SUCCESS(rv, UrlFilterResult::eDeny); + MOZ_ASSERT(!urlString.IsEmpty()); + std::string url = urlString.BeginReading(); + size_t count = 0; + for (const auto& denyFilter : mDenyUrlList) { + if (std::regex_search(url, denyFilter)) { + LOGD("Denying CA request : Deny filter %zu matched url %s", count, + url.c_str()); + return UrlFilterResult::eDeny; + } + ++count; + } + + count = 0; + UrlFilterResult result = UrlFilterResult::eCheck; + for (const auto& allowFilter : mAllowUrlList) { + if (std::regex_match(url, allowFilter)) { + LOGD("CA request : Allow filter %zu matched %s", count, url.c_str()); + result = UrlFilterResult::eAllow; + break; + } + ++count; + } + + // The rest only applies to download resources. + nsIContentAnalysisRequest::AnalysisType analysisType; + MOZ_ALWAYS_SUCCEEDS(aRequest->GetAnalysisType(&analysisType)); + if (analysisType != ContentAnalysisRequest::AnalysisType::eFileDownloaded) { + MOZ_ASSERT(result == UrlFilterResult::eCheck || + result == UrlFilterResult::eAllow); + LOGD("CA request filter result: %s", + result == UrlFilterResult::eCheck ? "check" : "allow"); + return result; + } + + nsTArray> resources; + MOZ_ALWAYS_SUCCEEDS(aRequest->GetResources(resources)); + for (size_t resourceIdx = 0; resourceIdx < resources.Length(); + /* noop */) { + auto& resource = resources[resourceIdx]; + nsAutoString nsUrl; + MOZ_ALWAYS_SUCCEEDS(resource->GetUrl(nsUrl)); + std::string url = NS_ConvertUTF16toUTF8(nsUrl).get(); + count = 0; + for (auto& denyFilter : mDenyUrlList) { + if (std::regex_search(url, denyFilter)) { + LOGD( + "Denying CA request : Deny filter %zu matched download resource " + "at url %s", + count, url.c_str()); + return UrlFilterResult::eDeny; + } + ++count; + } + + count = 0; + bool removed = false; + for (auto& allowFilter : mAllowUrlList) { + if (std::regex_search(url, allowFilter)) { + LOGD( + "CA request : Allow filter %zu matched download resource " + "at url %s", + count, url.c_str()); + resources.RemoveElementAt(resourceIdx); + removed = true; + break; + } + ++count; + } + if (!removed) { + ++resourceIdx; + } + } + + // Check unless all were allowed. + return resources.Length() ? UrlFilterResult::eCheck : UrlFilterResult::eAllow; +} + NS_IMPL_CLASSINFO(ContentAnalysisRequest, nullptr, 0, {0}); NS_IMPL_ISUPPORTS_CI(ContentAnalysisRequest, nsIContentAnalysisRequest); NS_IMPL_CLASSINFO(ContentAnalysisResponse, nullptr, 0, {0}); @@ -672,8 +831,11 @@ ContentAnalysis::ContentAnalysis() : mCaClientPromise( new ClientPromise::Private("ContentAnalysis::ContentAnalysis")), mClientCreationAttempted(false), + mSetByEnterprise(false), mCallbackMap("ContentAnalysis::mCallbackMap"), - mWarnResponseDataMap("ContentAnalysis::mWarnResponseDataMap") {} + mWarnResponseDataMap("ContentAnalysis::mWarnResponseDataMap") { + GenerateUserActionId(); +} ContentAnalysis::~ContentAnalysis() { // Accessing mClientCreationAttempted so need to be on the main thread @@ -689,12 +851,20 @@ ContentAnalysis::GetIsActive(bool* aIsActive) { *aIsActive = false; // Need to be on the main thread to read prefs MOZ_ASSERT(NS_IsMainThread()); - // gAllowContentAnalysis is only set in the parent process + // gAllowContentAnalysisArgPresent is only set in the parent process MOZ_ASSERT(XRE_IsParentProcess()); - if (!gAllowContentAnalysis || !Preferences::GetBool(kIsDLPEnabledPref)) { + if (!Preferences::GetBool(kIsDLPEnabledPref)) { LOGD("Local DLP Content Analysis is not active"); return NS_OK; } + if (!gAllowContentAnalysisArgPresent && !mSetByEnterprise) { + LOGE( + "The content analysis pref is enabled but not by an enterprise " + "policy and -allow-content-analysis was not present on the " + "command-line. Content Analysis will not be active."); + return NS_OK; + } + *aIsActive = true; LOGD("Local DLP Content Analysis is active"); // mClientCreationAttempted is only accessed on the main thread, @@ -710,12 +880,15 @@ ContentAnalysis::GetIsActive(bool* aIsActive) { return rv; } bool isPerUser = Preferences::GetBool(kIsPerUserPref); + nsString clientSignature; + // It's OK if this fails, we will default to the empty string + Preferences::GetString(kClientSignature, clientSignature); rv = NS_DispatchBackgroundTask(NS_NewCancelableRunnableFunction( "ContentAnalysis::CreateContentAnalysisClient", [owner = RefPtr{this}, pipePathName = std::move(pipePathName), - isPerUser]() mutable { - owner->CreateContentAnalysisClient(std::move(pipePathName), - isPerUser); + clientSignature = std::move(clientSignature), isPerUser]() mutable { + owner->CreateContentAnalysisClient( + std::move(pipePathName), std::move(clientSignature), isPerUser); })); if (NS_WARN_IF(NS_FAILED(rv))) { mCaClientPromise->Reject(rv, __func__); @@ -736,10 +909,33 @@ ContentAnalysis::GetMightBeActive(bool* aMightBeActive) { return NS_OK; } +NS_IMETHODIMP +ContentAnalysis::GetIsSetByEnterprisePolicy(bool* aSetByEnterprise) { + *aSetByEnterprise = mSetByEnterprise; + return NS_OK; +} + +NS_IMETHODIMP +ContentAnalysis::SetIsSetByEnterprisePolicy(bool aSetByEnterprise) { + mSetByEnterprise = aSetByEnterprise; + return NS_OK; +} + +NS_IMETHODIMP +ContentAnalysis::TestOnlySetCACmdLineArg(bool aVal) { +#ifdef ENABLE_TESTS + gAllowContentAnalysisArgPresent = aVal; + return NS_OK; +#else + LOGE("ContentAnalysis::TestOnlySetCACmdLineArg is test-only"); + return NS_ERROR_UNEXPECTED; +#endif +} + nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken, nsresult aResult) { return NS_DispatchToMainThread(NS_NewCancelableRunnableFunction( - "ContentAnalysis::RunAnalyzeRequestTask::HandleResponse", + "ContentAnalysis::CancelWithError", [aResult, aRequestToken = std::move(aRequestToken)] { RefPtr owner = GetContentAnalysisFromService(); if (!owner) { @@ -787,7 +983,10 @@ RefPtr ContentAnalysis::GetContentAnalysisFromService() { nsresult ContentAnalysis::RunAnalyzeRequestTask( const RefPtr& aRequest, bool aAutoAcknowledge, + int64_t aRequestCount, const RefPtr& aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv = NS_ERROR_FAILURE; auto callbackCopy = aCallback; auto se = MakeScopeExit([&] { @@ -797,23 +996,52 @@ nsresult ContentAnalysis::RunAnalyzeRequestTask( } }); - content_analysis::sdk::ContentAnalysisRequest pbRequest; - rv = ConvertToProtobuf(aRequest, &pbRequest); + nsCString requestToken; + rv = aRequest->GetRequestToken(requestToken); NS_ENSURE_SUCCESS(rv, rv); - nsCString requestToken; nsMainThreadPtrHandle callbackHolderCopy( new nsMainThreadPtrHolder( "content analysis callback", aCallback)); CallbackData callbackData(std::move(callbackHolderCopy), aAutoAcknowledge); - rv = aRequest->GetRequestToken(requestToken); - NS_ENSURE_SUCCESS(rv, rv); { auto lock = mCallbackMap.Lock(); lock->InsertOrUpdate(requestToken, std::move(callbackData)); } + // Check URLs of requested info against + // browser.contentanalysis.allow_url_regex_list/deny_url_regex_list. + // Build the list once since creating regexs is slow. + // URLs that match the allow list are removed from the check. There is + // only one URL in all cases except downloads. If all contents are removed + // or the page URL is allowed (for downloads) then the operation is allowed. + // URLs that match the deny list block the entire operation. + // If the request is completely covered by this filter then flag it as + // not needing to send an Acknowledge. + auto filterResult = FilterByUrlLists(aRequest); + if (filterResult == ContentAnalysis::UrlFilterResult::eDeny) { + LOGD("Blocking request due to deny URL filter."); + auto response = ContentAnalysisResponse::FromAction( + nsIContentAnalysisResponse::Action::eBlock, requestToken); + response->DoNotAcknowledge(); + IssueResponse(response); + return NS_OK; + } + if (filterResult == ContentAnalysis::UrlFilterResult::eAllow) { + LOGD("Allowing request -- all operations match allow URL filter."); + auto response = ContentAnalysisResponse::FromAction( + nsIContentAnalysisResponse::Action::eAllow, requestToken); + response->DoNotAcknowledge(); + IssueResponse(response); + return NS_OK; + } + LOGD("Issuing ContentAnalysisRequest for token %s", requestToken.get()); + + content_analysis::sdk::ContentAnalysisRequest pbRequest; + rv = + ConvertToProtobuf(aRequest, GetUserActionId(), aRequestCount, &pbRequest); + NS_ENSURE_SUCCESS(rv, rv); LogRequest(&pbRequest); mCaClientPromise->Then( @@ -915,75 +1143,75 @@ void ContentAnalysis::DoAnalyzeRequest( LOGE("Content analysis got invalid response!"); return; } - nsCString responseRequestToken; - nsresult requestRv = response->GetRequestToken(responseRequestToken); - if (NS_FAILED(requestRv)) { - LOGE( - "Content analysis couldn't get request token " - "from response!"); - return; - } - Maybe maybeCallbackData; - { - auto callbackMap = owner->mCallbackMap.Lock(); - maybeCallbackData = callbackMap->Extract(responseRequestToken); - } - if (maybeCallbackData.isNothing()) { - LOGD( - "Content analysis did not find callback for " - "token %s", - responseRequestToken.get()); - return; - } - response->SetOwner(owner); - if (maybeCallbackData->Canceled()) { - // request has already been cancelled, so there's - // nothing to do - LOGD( - "Content analysis got response but ignoring " - "because it was already cancelled for token %s", - responseRequestToken.get()); - // Note that we always acknowledge here, even if - // autoAcknowledge isn't set, since we raise an exception - // at the caller on cancellation. - auto acknowledgement = MakeRefPtr( - nsIContentAnalysisAcknowledgement::Result::eTooLate, - nsIContentAnalysisAcknowledgement::FinalAction::eBlock); - response->Acknowledge(acknowledgement); - return; - } + owner->IssueResponse(response); + })); +} - LOGD( - "Content analysis resolving response promise for " - "token %s", - responseRequestToken.get()); - nsIContentAnalysisResponse::Action action = response->GetAction(); - nsCOMPtr obsServ = - mozilla::services::GetObserverService(); - if (action == nsIContentAnalysisResponse::Action::eWarn) { - { - auto warnResponseDataMap = owner->mWarnResponseDataMap.Lock(); - warnResponseDataMap->InsertOrUpdate( - responseRequestToken, - WarnResponseData(std::move(*maybeCallbackData), response)); - } - obsServ->NotifyObservers(response, "dlp-response", nullptr); - return; - } +void ContentAnalysis::IssueResponse(RefPtr& response) { + MOZ_ASSERT(NS_IsMainThread()); + nsCString responseRequestToken; + nsresult requestRv = response->GetRequestToken(responseRequestToken); + if (NS_FAILED(requestRv)) { + LOGE("Content analysis couldn't get request token from response!"); + return; + } - obsServ->NotifyObservers(response, "dlp-response", nullptr); - if (maybeCallbackData->AutoAcknowledge()) { - auto acknowledgement = MakeRefPtr( - nsIContentAnalysisAcknowledgement::Result::eSuccess, - ConvertResult(action)); - response->Acknowledge(acknowledgement); - } + Maybe maybeCallbackData; + { + auto callbackMap = mCallbackMap.Lock(); + maybeCallbackData = callbackMap->Extract(responseRequestToken); + } + if (maybeCallbackData.isNothing()) { + LOGD("Content analysis did not find callback for token %s", + responseRequestToken.get()); + return; + } + response->SetOwner(this); + if (maybeCallbackData->Canceled()) { + // request has already been cancelled, so there's + // nothing to do + LOGD( + "Content analysis got response but ignoring " + "because it was already cancelled for token %s", + responseRequestToken.get()); + // Note that we always acknowledge here, even if + // autoAcknowledge isn't set, since we raise an exception + // at the caller on cancellation. + auto acknowledgement = MakeRefPtr( + nsIContentAnalysisAcknowledgement::Result::eTooLate, + nsIContentAnalysisAcknowledgement::FinalAction::eBlock); + response->Acknowledge(acknowledgement); + return; + } - nsMainThreadPtrHandle callbackHolder = - maybeCallbackData->TakeCallbackHolder(); - callbackHolder->ContentResult(response); - })); + LOGD("Content analysis resolving response promise for token %s", + responseRequestToken.get()); + nsIContentAnalysisResponse::Action action = response->GetAction(); + nsCOMPtr obsServ = + mozilla::services::GetObserverService(); + if (action == nsIContentAnalysisResponse::Action::eWarn) { + { + auto warnResponseDataMap = mWarnResponseDataMap.Lock(); + warnResponseDataMap->InsertOrUpdate( + responseRequestToken, + WarnResponseData(std::move(*maybeCallbackData), response)); + } + obsServ->NotifyObservers(response, "dlp-response", nullptr); + return; + } + + obsServ->NotifyObservers(response, "dlp-response", nullptr); + if (maybeCallbackData->AutoAcknowledge()) { + auto acknowledgement = MakeRefPtr( + nsIContentAnalysisAcknowledgement::Result::eSuccess, + ConvertResult(action)); + response->Acknowledge(acknowledgement); + } + + nsMainThreadPtrHandle callbackHolder = + maybeCallbackData->TakeCallbackHolder(); + callbackHolder->ContentResult(response); } NS_IMETHODIMP @@ -1018,7 +1246,11 @@ ContentAnalysis::AnalyzeContentRequestCallback( mozilla::services::GetObserverService(); obsServ->NotifyObservers(aRequest, "dlp-request-made", nullptr); - return RunAnalyzeRequestTask(aRequest, aAutoAcknowledge, aCallback); + MOZ_ASSERT(NS_IsMainThread()); + // since we're on the main thread, don't need to synchronize this + int64_t requestCount = ++mRequestCount; + return RunAnalyzeRequestTask(aRequest, aAutoAcknowledge, requestCount, + aCallback); } NS_IMETHODIMP @@ -1046,6 +1278,33 @@ ContentAnalysis::CancelContentAnalysisRequest(const nsACString& aRequestToken) { return NS_OK; } +NS_IMETHODIMP +ContentAnalysis::CancelAllRequests() { + mCaClientPromise->Then( + GetCurrentSerialEventTarget(), __func__, + [&](std::shared_ptr client) { + auto owner = GetContentAnalysisFromService(); + if (!owner) { + // May be shutting down + return; + } + if (!client) { + LOGE("CancelAllRequests got a null client"); + return; + } + content_analysis::sdk::ContentAnalysisCancelRequests requests; + requests.set_user_action_id(owner->GetUserActionId().get()); + int err = client->CancelRequests(requests); + if (err != 0) { + LOGE("CancelAllRequests got error %d", err); + } else { + LOGD("CancelAllRequests did cancelling of requests"); + } + }, + [&](nsresult rv) { LOGE("CancelAllRequests failed to get the client"); }); + return NS_OK; +} + NS_IMETHODIMP ContentAnalysis::RespondToWarnDialog(const nsACString& aRequestToken, bool aAllowContent) { @@ -1108,6 +1367,10 @@ ContentAnalysisResponse::Acknowledge( return NS_ERROR_FAILURE; } mHasAcknowledged = true; + + if (mDoNotAcknowledge) { + return NS_OK; + } return mOwner->RunAcknowledgeTask(aAcknowledgement, mRequestToken); }; diff --git a/toolkit/components/contentanalysis/ContentAnalysis.h b/toolkit/components/contentanalysis/ContentAnalysis.h index 4579a7113d..17b6e3fc1b 100644 --- a/toolkit/components/contentanalysis/ContentAnalysis.h +++ b/toolkit/components/contentanalysis/ContentAnalysis.h @@ -7,15 +7,25 @@ #define mozilla_contentanalysis_h #include "mozilla/DataMutex.h" -#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/MozPromise.h" +#include "mozilla/dom/Promise.h" #include "nsIContentAnalysis.h" #include "nsProxyRelease.h" #include "nsString.h" #include "nsTHashMap.h" #include +#include #include +class nsIPrincipal; +class ContentAnalysisTest; + +namespace mozilla::dom { +class DataTransfer; +class WindowGlobalParent; +} // namespace mozilla::dom + namespace content_analysis::sdk { class Client; class ContentAnalysisRequest; @@ -75,6 +85,8 @@ class ContentAnalysisRequest final : public nsIContentAnalysisRequest { nsString mOperationDisplayString; RefPtr mWindowGlobalParent; + + friend class ::ContentAnalysisTest; }; #define CONTENTANALYSIS_IID \ @@ -92,6 +104,7 @@ class ContentAnalysis final : public nsIContentAnalysis { NS_DECL_NSICONTENTANALYSIS ContentAnalysis(); + nsCString GetUserActionId(); private: ~ContentAnalysis(); @@ -99,27 +112,42 @@ class ContentAnalysis final : public nsIContentAnalysis { ContentAnalysis(const ContentAnalysis&) = delete; ContentAnalysis& operator=(ContentAnalysis&) = delete; nsresult CreateContentAnalysisClient(nsCString&& aPipePathName, + nsString&& aClientSignatureSetting, bool aIsPerUser); nsresult RunAnalyzeRequestTask( const RefPtr& aRequest, bool aAutoAcknowledge, + int64_t aRequestCount, const RefPtr& aCallback); nsresult RunAcknowledgeTask( nsIContentAnalysisAcknowledgement* aAcknowledgement, const nsACString& aRequestToken); nsresult CancelWithError(nsCString aRequestToken, nsresult aResult); + void GenerateUserActionId(); static RefPtr GetContentAnalysisFromService(); static void DoAnalyzeRequest( nsCString aRequestToken, content_analysis::sdk::ContentAnalysisRequest&& aRequest, const std::shared_ptr& aClient); + void IssueResponse(RefPtr& response); + + // Did the URL filter completely handle the request or do we need to check + // with the agent. + enum UrlFilterResult { eCheck, eDeny, eAllow }; + + UrlFilterResult FilterByUrlLists(nsIContentAnalysisRequest* aRequest); + void EnsureParsedUrlFilters(); using ClientPromise = MozPromise, nsresult, false>; + nsCString mUserActionId; + int64_t mRequestCount = 0; RefPtr mCaClientPromise; // Only accessed from the main thread bool mClientCreationAttempted; + bool mSetByEnterprise; + class CallbackData final { public: CallbackData( @@ -150,7 +178,12 @@ class ContentAnalysis final : public nsIContentAnalysis { }; DataMutex> mWarnResponseDataMap; + std::vector mAllowUrlList; + std::vector mDenyUrlList; + bool mParsedUrlLists; + friend class ContentAnalysisResponse; + friend class ::ContentAnalysisTest; }; NS_DEFINE_STATIC_IID_ACCESSOR(ContentAnalysis, CONTENTANALYSIS_IID) @@ -164,6 +197,7 @@ class ContentAnalysisResponse final : public nsIContentAnalysisResponse { Action aAction, const nsACString& aRequestToken); void SetOwner(RefPtr aOwner); + void DoNotAcknowledge() { mDoNotAcknowledge = true; } private: ~ContentAnalysisResponse() = default; @@ -189,7 +223,11 @@ class ContentAnalysisResponse final : public nsIContentAnalysisResponse { RefPtr mOwner; // Whether the response has been acknowledged - bool mHasAcknowledged; + bool mHasAcknowledged = false; + + // If true, the request was completely handled by URL filter lists, so it + // was not sent to the agent and should not send an Acknowledge. + bool mDoNotAcknowledge = false; friend class ContentAnalysis; }; diff --git a/toolkit/components/contentanalysis/moz.build b/toolkit/components/contentanalysis/moz.build index e602d30302..99b57711c6 100644 --- a/toolkit/components/contentanalysis/moz.build +++ b/toolkit/components/contentanalysis/moz.build @@ -1,62 +1,62 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -with Files("**"): - BUG_COMPONENT = ("Toolkit", "General") - -UNIFIED_SOURCES += [ - "content_analysis/sdk/analysis.pb.cc", - "ContentAnalysis.cpp", -] - -UNIFIED_SOURCES += [ - "../../../third_party/content_analysis_sdk/browser/src/client_base.cc", -] - -EXPORTS += ["ContentAnalysis.h"] - -EXPORTS.mozilla.contentanalysis += [ - "ContentAnalysisIPCTypes.h", -] - -if CONFIG["OS_ARCH"] == "WINNT": - UNIFIED_SOURCES += [ - "../../../third_party/content_analysis_sdk/browser/src/client_win.cc", - "../../../third_party/content_analysis_sdk/common/utils_win.cc", - ] -elif CONFIG["OS_ARCH"] == "Darwin": - UNIFIED_SOURCES += [ - "../../../third_party/content_analysis_sdk/browser/src/client_mac.cc", - ] -else: - UNIFIED_SOURCES += [ - "../../../third_party/content_analysis_sdk/browser/src/client_posix.cc", - ] - -LOCAL_INCLUDES += [ - "../../../third_party/content_analysis_sdk", - "../../../third_party/content_analysis_sdk/browser/include", - "content_analysis/sdk/", -] - -XPIDL_SOURCES += [ - "nsIContentAnalysis.idl", -] - -XPIDL_MODULE = "toolkit_contentanalysis" - -XPCOM_MANIFESTS += [ - "components.conf", -] - -include("/ipc/chromium/chromium-config.mozbuild") - -DEFINES["GOOGLE_PROTOBUF_NO_RTTI"] = True -DEFINES["GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER"] = True - -FINAL_LIBRARY = "xul" - -TEST_DIRS += ["tests/gtest"] +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Toolkit", "General") + +UNIFIED_SOURCES += [ + "content_analysis/sdk/analysis.pb.cc", + "ContentAnalysis.cpp", +] + +UNIFIED_SOURCES += [ + "../../../third_party/content_analysis_sdk/browser/src/client_base.cc", +] + +EXPORTS += ["ContentAnalysis.h"] + +EXPORTS.mozilla.contentanalysis += [ + "ContentAnalysisIPCTypes.h", +] + +if CONFIG["OS_ARCH"] == "WINNT": + UNIFIED_SOURCES += [ + "../../../third_party/content_analysis_sdk/browser/src/client_win.cc", + "../../../third_party/content_analysis_sdk/common/utils_win.cc", + ] +elif CONFIG["OS_ARCH"] == "Darwin": + UNIFIED_SOURCES += [ + "../../../third_party/content_analysis_sdk/browser/src/client_mac.cc", + ] +else: + UNIFIED_SOURCES += [ + "../../../third_party/content_analysis_sdk/browser/src/client_posix.cc", + ] + +LOCAL_INCLUDES += [ + "../../../third_party/content_analysis_sdk", + "../../../third_party/content_analysis_sdk/browser/include", + "content_analysis/sdk/", +] + +XPIDL_SOURCES += [ + "nsIContentAnalysis.idl", +] + +XPIDL_MODULE = "toolkit_contentanalysis" + +XPCOM_MANIFESTS += [ + "components.conf", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +DEFINES["GOOGLE_PROTOBUF_NO_RTTI"] = True +DEFINES["GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER"] = True + +FINAL_LIBRARY = "xul" + +TEST_DIRS += ["tests"] diff --git a/toolkit/components/contentanalysis/nsIContentAnalysis.idl b/toolkit/components/contentanalysis/nsIContentAnalysis.idl index 142c4c3cd3..21f98b88e6 100644 --- a/toolkit/components/contentanalysis/nsIContentAnalysis.idl +++ b/toolkit/components/contentanalysis/nsIContentAnalysis.idl @@ -184,6 +184,13 @@ interface nsIContentAnalysis : nsISupports */ readonly attribute bool 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; + /** * Consults content analysis server, if any, to request a permission * decision for a network operation. Allows blocking downloading/ @@ -236,4 +243,15 @@ interface nsIContentAnalysis : nsISupports * whether the user wants to allow the request to go through. */ void respondToWarnDialog(in ACString aRequestToken, in bool aAllowContent); + + /** + * Cancels all outstanding DLP requests. Used on shutdown. + */ + void cancelAllRequests(); + + /** + * Test-only function that pretends that "-allow-content-analysis" was + * given to Gecko on the command line. + */ + void testOnlySetCACmdLineArg(in boolean aVal); }; diff --git a/toolkit/components/contentanalysis/tests/browser/browser.toml b/toolkit/components/contentanalysis/tests/browser/browser.toml new file mode 100644 index 0000000000..0e21090299 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/browser.toml @@ -0,0 +1,3 @@ +[DEFAULT] + +["browser_content_analysis_policies.js"] diff --git a/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js b/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js new file mode 100644 index 0000000000..e2e001e9d1 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js @@ -0,0 +1,127 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Check that CA is active if and only if: +// 1. browser.contentanalysis.enabled is true and +// 2. Either browser.contentanalysis.enabled was set by an enteprise +// policy or the "-allow-content-analysis" command line arg was present +// We can't really test command line arguments so we instead use a test-only +// method to set the value the command-line is supposed to update. + +"use strict"; + +const { EnterprisePolicyTesting } = 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 kPerUserPref = "is_per_user"; +const kShowBlockedPref = "show_blocked_result"; +const kDefaultAllowPref = "default_allow"; + +const ca = Cc["@mozilla.org/contentanalysis;1"].getService( + Ci.nsIContentAnalysis +); + +add_task(async function test_ca_active() { + 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. + Services.prefs.setBoolPref("browser.contentanalysis." + kEnabledPref, true); + ok( + !ca.isActive, + "CA is inactive when pref is set but cmd line arg is missing" + ); + + // Set the pref without enterprise policy but also set command line arg + // property. CA should be active. + ca.testOnlySetCACmdLineArg(true); + ok(ca.isActive, "CA is active when pref is set and cmd line arg is present"); + + // Undo test-only value before later tests. + ca.testOnlySetCACmdLineArg(false); + ok(!ca.isActive, "properly unset cmd line arg value"); + + // Disabled the pref with enterprise policy. CA should not be active. + await EnterprisePolicyTesting.setupPolicyEngineWithJson({ + policies: { + ContentAnalysis: { Enabled: false }, + }, + }); + ok(!ca.isActive, "CA is inactive when disabled by enterprise policy pref"); + + // Enabled the pref with enterprise policy. CA should be active. + await EnterprisePolicyTesting.setupPolicyEngineWithJson({ + policies: { + ContentAnalysis: { Enabled: true }, + }, + }); + ok(ca.isActive, "CA is active when enabled by enterprise policy pref"); +}); + +add_task(async function test_ca_enterprise_config() { + const string1 = "this is a string"; + const string2 = "this is another string"; + + await EnterprisePolicyTesting.setupPolicyEngineWithJson({ + policies: { + ContentAnalysis: { + PipePathName: "abc", + AgentTimeout: 99, + AllowUrlRegexList: string1, + DenyUrlRegexList: string2, + IsPerUser: true, + ShowBlockedResult: false, + DefaultAllow: true, + }, + }, + }); + + is( + Services.prefs.getStringPref("browser.contentanalysis." + kPipeNamePref), + "abc", + "pipe name match" + ); + is( + Services.prefs.getIntPref("browser.contentanalysis." + kTimeoutPref), + 99, + "timeout match" + ); + is( + Services.prefs.getStringPref("browser.contentanalysis." + kAllowUrlPref), + string1, + "allow urls match" + ); + is( + Services.prefs.getStringPref("browser.contentanalysis." + kDenyUrlPref), + string2, + "deny urls match" + ); + is( + Services.prefs.getBoolPref("browser.contentanalysis." + kPerUserPref), + true, + "per user match" + ); + is( + Services.prefs.getBoolPref("browser.contentanalysis." + kShowBlockedPref), + false, + "show blocked match" + ); + is( + Services.prefs.getBoolPref("browser.contentanalysis." + kDefaultAllowPref), + true, + "default allow match" + ); +}); + +add_task(async function test_cleanup() { + ca.testOnlySetCACmdLineArg(false); + await EnterprisePolicyTesting.setupPolicyEngineWithJson({ + policies: {}, + }); +}); diff --git a/toolkit/components/contentanalysis/tests/browser/moz.build b/toolkit/components/contentanalysis/tests/browser/moz.build new file mode 100644 index 0000000000..cfd5452a0e --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/moz.build @@ -0,0 +1,7 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +BROWSER_CHROME_MANIFESTS += ["browser.toml"] diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp index 1cf6d8fc22..cd083a7779 100644 --- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp +++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp @@ -6,127 +6,121 @@ #include "gtest/gtest.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Assertions.h" -#include "mozilla/CmdLineAndEnvUtils.h" -#include "content_analysis/sdk/analysis_client.h" -#include "TestContentAnalysis.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "nsNetUtil.h" +#include "ContentAnalysis.h" #include #include -using namespace content_analysis::sdk; +const char* kAllowUrlPref = "browser.contentanalysis.allow_url_regex_list"; +const char* kDenyUrlPref = "browser.contentanalysis.deny_url_regex_list"; -MozAgentInfo LaunchAgentNormal(const wchar_t* aToBlock) { - nsString cmdLineArguments; - if (aToBlock && aToBlock[0] != 0) { - cmdLineArguments.Append(L" --toblock=.*"); - cmdLineArguments.Append(aToBlock); - cmdLineArguments.Append(L".*"); +using namespace mozilla; +using namespace mozilla::contentanalysis; + +class ContentAnalysisTest : public testing::Test { + protected: + ContentAnalysisTest() { + auto* logmodule = LogModule::Get("contentanalysis"); + logmodule->SetLevel(LogLevel::Verbose); + + nsCOMPtr caSvc = + do_GetService("@mozilla.org/contentanalysis;1"); + MOZ_ASSERT(caSvc); + mContentAnalysis = static_cast(caSvc.get()); + + // Tests run earlier could have altered these values + mContentAnalysis->mParsedUrlLists = false; + mContentAnalysis->mAllowUrlList = {}; + mContentAnalysis->mDenyUrlList = {}; + + MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kAllowUrlPref, "")); + MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kDenyUrlPref, "")); + } + + void TearDown() override { + mContentAnalysis->mParsedUrlLists = false; + mContentAnalysis->mAllowUrlList = {}; + mContentAnalysis->mDenyUrlList = {}; + + MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kAllowUrlPref, "")); + MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kDenyUrlPref, "")); } - cmdLineArguments.Append(L" --user"); - cmdLineArguments.Append(L" --path="); - nsString pipeName; - GeneratePipeName(L"contentanalysissdk-gtest-", pipeName); - cmdLineArguments.Append(pipeName); - MozAgentInfo agentInfo; - LaunchAgentWithCommandLineArguments(cmdLineArguments, pipeName, agentInfo); - return agentInfo; + + already_AddRefed CreateRequest(const char* aUrl) { + nsCOMPtr uri; + MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), aUrl)); + // We will only use the URL and, implicitly, the analysisType + // (behavior differs for download vs other types). + return RefPtr(new ContentAnalysisRequest( + nsIContentAnalysisRequest::AnalysisType::eFileTransfer, + EmptyString(), false, EmptyCString(), uri, + nsIContentAnalysisRequest::OperationType::eDroppedText, + nullptr)) + .forget(); + } + + RefPtr mContentAnalysis; + + // Proxies for private members of ContentAnalysis. TEST_F + // creates new subclasses -- they do not inherit `friend`s. + // (FRIEND_TEST is another more verbose solution.) + using UrlFilterResult = ContentAnalysis::UrlFilterResult; + UrlFilterResult FilterByUrlLists(nsIContentAnalysisRequest* aReq) { + return mContentAnalysis->FilterByUrlLists(aReq); + } +}; + +TEST_F(ContentAnalysisTest, AllowUrlList) { + MOZ_ALWAYS_SUCCEEDS( + Preferences::SetCString(kAllowUrlPref, ".*\\.org/match.*")); + RefPtr car = + CreateRequest("https://example.org/matchme/"); + ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eAllow); + car = CreateRequest("https://example.com/matchme/"); + ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eCheck); } -TEST(ContentAnalysis, TextShouldNotBeBlocked) -{ - auto MozAgentInfo = LaunchAgentNormal(L"block"); - // Exit the test early if the process failed to launch - ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL); - ASSERT_NE(nullptr, MozAgentInfo.client.get()); - - ContentAnalysisRequest request; - request.set_request_token("request token"); - request.set_text_content("should succeed"); - ContentAnalysisResponse response; - ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response)); - ASSERT_STREQ("request token", response.request_token().c_str()); - ASSERT_EQ(1, response.results().size()); - ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS, - response.results().Get(0).status()); - ASSERT_EQ(0, response.results().Get(0).triggered_rules_size()); - - BOOL terminateResult = - ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0); - ASSERT_NE(FALSE, terminateResult) - << "Failed to terminate content_analysis_sdk_agent process"; +TEST_F(ContentAnalysisTest, MultipleAllowUrlList) { + MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString( + kAllowUrlPref, ".*\\.org/match.* .*\\.net/match.*")); + RefPtr car = + CreateRequest("https://example.org/matchme/"); + ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eAllow); + car = CreateRequest("https://example.net/matchme/"); + ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eAllow); + car = CreateRequest("https://example.com/matchme/"); + ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eCheck); } -TEST(ContentAnalysis, TextShouldBeBlocked) -{ - auto MozAgentInfo = LaunchAgentNormal(L"block"); - // Exit the test early if the process failed to launch - ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL); - ASSERT_NE(nullptr, MozAgentInfo.client.get()); - - ContentAnalysisRequest request; - request.set_request_token("request token"); - request.set_text_content("should be blocked"); - ContentAnalysisResponse response; - ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response)); - ASSERT_STREQ("request token", response.request_token().c_str()); - ASSERT_EQ(1, response.results().size()); - ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS, - response.results().Get(0).status()); - ASSERT_EQ(1, response.results().Get(0).triggered_rules_size()); - ASSERT_EQ(ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK, - response.results().Get(0).triggered_rules(0).action()); - - BOOL terminateResult = - ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0); - ASSERT_NE(FALSE, terminateResult) - << "Failed to terminate content_analysis_sdk_agent process"; +TEST_F(ContentAnalysisTest, DenyUrlList) { + MOZ_ALWAYS_SUCCEEDS( + Preferences::SetCString(kDenyUrlPref, ".*\\.com/match.*")); + RefPtr car = + CreateRequest("https://example.org/matchme/"); + ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eCheck); + car = CreateRequest("https://example.com/matchme/"); + ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eDeny); } -TEST(ContentAnalysis, FileShouldNotBeBlocked) -{ - auto MozAgentInfo = LaunchAgentNormal(L"block"); - // Exit the test early if the process failed to launch - ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL); - ASSERT_NE(nullptr, MozAgentInfo.client.get()); - - ContentAnalysisRequest request; - request.set_request_token("request token"); - request.set_file_path("..\\..\\_tests\\gtest\\allowedFile.txt"); - ContentAnalysisResponse response; - ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response)); - ASSERT_STREQ("request token", response.request_token().c_str()); - ASSERT_EQ(1, response.results().size()); - ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS, - response.results().Get(0).status()); - ASSERT_EQ(0, response.results().Get(0).triggered_rules_size()); - - BOOL terminateResult = - ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0); - ASSERT_NE(FALSE, terminateResult) - << "Failed to terminate content_analysis_sdk_agent process"; +TEST_F(ContentAnalysisTest, MultipleDenyUrlList) { + MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString( + kDenyUrlPref, ".*\\.com/match.* .*\\.biz/match.*")); + RefPtr car = + CreateRequest("https://example.org/matchme/"); + ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eCheck); + car = CreateRequest("https://example.com/matchme/"); + ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eDeny); + car = CreateRequest("https://example.biz/matchme/"); + ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eDeny); } -TEST(ContentAnalysis, FileShouldBeBlocked) -{ - auto MozAgentInfo = LaunchAgentNormal(L"block"); - // Exit the test early if the process failed to launch - ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL); - ASSERT_NE(nullptr, MozAgentInfo.client.get()); - - ContentAnalysisRequest request; - request.set_request_token("request token"); - request.set_file_path("..\\..\\_tests\\gtest\\blockedFile.txt"); - ContentAnalysisResponse response; - ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response)); - ASSERT_STREQ("request token", response.request_token().c_str()); - ASSERT_EQ(1, response.results().size()); - ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS, - response.results().Get(0).status()); - ASSERT_EQ(1, response.results().Get(0).triggered_rules_size()); - ASSERT_EQ(ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK, - response.results().Get(0).triggered_rules(0).action()); - - BOOL terminateResult = - ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0); - ASSERT_NE(FALSE, terminateResult) - << "Failed to terminate content_analysis_sdk_agent process"; +TEST_F(ContentAnalysisTest, DenyOverridesAllowUrlList) { + MOZ_ALWAYS_SUCCEEDS( + Preferences::SetCString(kAllowUrlPref, ".*\\.org/match.*")); + MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kDenyUrlPref, ".*.org/match.*")); + RefPtr car = + CreateRequest("https://example.org/matchme/"); + ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eDeny); } diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.h b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.h deleted file mode 100644 index 9e31036262..0000000000 --- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.h +++ /dev/null @@ -1,24 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef mozilla_testcontentanalysis_h -#define mozilla_testcontentanalysis_h - -#include - -#include "content_analysis/sdk/analysis_client.h" -#include "gtest/gtest.h" -#include "nsString.h" - -struct MozAgentInfo { - PROCESS_INFORMATION processInfo; - std::unique_ptr client; -}; - -void GeneratePipeName(const wchar_t* prefix, nsString& pipeName); -void LaunchAgentWithCommandLineArguments(const nsString& cmdLineArguments, - const nsString& pipeName, - MozAgentInfo& agentInfo); -#endif diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp new file mode 100644 index 0000000000..5b2b76b963 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/CmdLineAndEnvUtils.h" +#include "content_analysis/sdk/analysis_client.h" +#include "TestContentAnalysisAgent.h" +#include +#include + +using namespace content_analysis::sdk; + +MozAgentInfo LaunchAgentNormal(const wchar_t* aToBlock) { + nsString cmdLineArguments; + if (aToBlock && aToBlock[0] != 0) { + cmdLineArguments.Append(L" --toblock=.*"); + cmdLineArguments.Append(aToBlock); + cmdLineArguments.Append(L".*"); + } + cmdLineArguments.Append(L" --user"); + cmdLineArguments.Append(L" --path="); + nsString pipeName; + GeneratePipeName(L"contentanalysissdk-gtest-", pipeName); + cmdLineArguments.Append(pipeName); + MozAgentInfo agentInfo; + LaunchAgentWithCommandLineArguments(cmdLineArguments, pipeName, agentInfo); + return agentInfo; +} + +TEST(ContentAnalysisAgent, TextShouldNotBeBlocked) +{ + auto MozAgentInfo = LaunchAgentNormal(L"block"); + // Exit the test early if the process failed to launch + ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL); + ASSERT_NE(nullptr, MozAgentInfo.client.get()); + + ContentAnalysisRequest request; + request.set_request_token("request token"); + request.set_text_content("should succeed"); + ContentAnalysisResponse response; + ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response)); + ASSERT_STREQ("request token", response.request_token().c_str()); + ASSERT_EQ(1, response.results().size()); + ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS, + response.results().Get(0).status()); + ASSERT_EQ(0, response.results().Get(0).triggered_rules_size()); + + BOOL terminateResult = + ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0); + ASSERT_NE(FALSE, terminateResult) + << "Failed to terminate content_analysis_sdk_agent process"; +} + +TEST(ContentAnalysisAgent, TextShouldBeBlocked) +{ + auto MozAgentInfo = LaunchAgentNormal(L"block"); + // Exit the test early if the process failed to launch + ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL); + ASSERT_NE(nullptr, MozAgentInfo.client.get()); + + ContentAnalysisRequest request; + request.set_request_token("request token"); + request.set_text_content("should be blocked"); + ContentAnalysisResponse response; + ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response)); + ASSERT_STREQ("request token", response.request_token().c_str()); + ASSERT_EQ(1, response.results().size()); + ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS, + response.results().Get(0).status()); + ASSERT_EQ(1, response.results().Get(0).triggered_rules_size()); + ASSERT_EQ(ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK, + response.results().Get(0).triggered_rules(0).action()); + + BOOL terminateResult = + ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0); + ASSERT_NE(FALSE, terminateResult) + << "Failed to terminate content_analysis_sdk_agent process"; +} + +TEST(ContentAnalysisAgent, FileShouldNotBeBlocked) +{ + auto MozAgentInfo = LaunchAgentNormal(L"block"); + // Exit the test early if the process failed to launch + ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL); + ASSERT_NE(nullptr, MozAgentInfo.client.get()); + + ContentAnalysisRequest request; + request.set_request_token("request token"); + request.set_file_path("..\\..\\_tests\\gtest\\allowedFile.txt"); + ContentAnalysisResponse response; + ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response)); + ASSERT_STREQ("request token", response.request_token().c_str()); + ASSERT_EQ(1, response.results().size()); + ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS, + response.results().Get(0).status()); + ASSERT_EQ(0, response.results().Get(0).triggered_rules_size()); + + BOOL terminateResult = + ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0); + ASSERT_NE(FALSE, terminateResult) + << "Failed to terminate content_analysis_sdk_agent process"; +} + +TEST(ContentAnalysisAgent, FileShouldBeBlocked) +{ + auto MozAgentInfo = LaunchAgentNormal(L"block"); + // Exit the test early if the process failed to launch + ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL); + ASSERT_NE(nullptr, MozAgentInfo.client.get()); + + ContentAnalysisRequest request; + request.set_request_token("request token"); + request.set_file_path("..\\..\\_tests\\gtest\\blockedFile.txt"); + ContentAnalysisResponse response; + ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response)); + ASSERT_STREQ("request token", response.request_token().c_str()); + ASSERT_EQ(1, response.results().size()); + ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS, + response.results().Get(0).status()); + ASSERT_EQ(1, response.results().Get(0).triggered_rules_size()); + ASSERT_EQ(ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK, + response.results().Get(0).triggered_rules(0).action()); + + BOOL terminateResult = + ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0); + ASSERT_NE(FALSE, terminateResult) + << "Failed to terminate content_analysis_sdk_agent process"; +} diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.h b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.h new file mode 100644 index 0000000000..9e31036262 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef mozilla_testcontentanalysis_h +#define mozilla_testcontentanalysis_h + +#include + +#include "content_analysis/sdk/analysis_client.h" +#include "gtest/gtest.h" +#include "nsString.h" + +struct MozAgentInfo { + PROCESS_INFORMATION processInfo; + std::unique_ptr client; +}; + +void GeneratePipeName(const wchar_t* prefix, nsString& pipeName); +void LaunchAgentWithCommandLineArguments(const nsString& cmdLineArguments, + const nsString& pipeName, + MozAgentInfo& agentInfo); +#endif diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp index 0b005e1f6c..7c944ed6e3 100644 --- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp +++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp @@ -8,7 +8,7 @@ #include "mozilla/Assertions.h" #include "mozilla/CmdLineAndEnvUtils.h" #include "content_analysis/sdk/analysis_client.h" -#include "TestContentAnalysis.h" +#include "TestContentAnalysisAgent.h" #include #include #include diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp index fc0cca5acd..0e14de6b81 100644 --- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp +++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp @@ -3,7 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -#include "TestContentAnalysis.h" +#include "TestContentAnalysisAgent.h" #include #include #include diff --git a/toolkit/components/contentanalysis/tests/gtest/moz.build b/toolkit/components/contentanalysis/tests/gtest/moz.build index ce701987a4..549e1e0e39 100644 --- a/toolkit/components/contentanalysis/tests/gtest/moz.build +++ b/toolkit/components/contentanalysis/tests/gtest/moz.build @@ -12,6 +12,10 @@ LOCAL_INCLUDES += [ if CONFIG["OS_TARGET"] == "WINNT": UNIFIED_SOURCES += [ "TestContentAnalysis.cpp", + ] + SOURCES += [ + # Agent SDK usings conflicts with Gecko usings + "TestContentAnalysisAgent.cpp", "TestContentAnalysisMisbehaving.cpp", "TestContentAnalysisUtils.cpp", ] diff --git a/toolkit/components/contentanalysis/tests/moz.build b/toolkit/components/contentanalysis/tests/moz.build new file mode 100644 index 0000000000..e8087ba5ed --- /dev/null +++ b/toolkit/components/contentanalysis/tests/moz.build @@ -0,0 +1,7 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +TEST_DIRS += ["gtest", "browser"] -- cgit v1.2.3