diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
commit | 40a355a42d4a9444dc753c04c6608dade2f06a23 (patch) | |
tree | 871fc667d2de662f171103ce5ec067014ef85e61 /toolkit/components/contentanalysis | |
parent | Adding upstream version 124.0.1. (diff) | |
download | firefox-upstream/125.0.1.tar.xz firefox-upstream/125.0.1.zip |
Adding upstream version 125.0.1.upstream/125.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/contentanalysis')
14 files changed, 856 insertions, 263 deletions
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 <algorithm> #include <sstream> +#include <string> #ifdef XP_WIN # include <windows.h> # define SECURITY_WIN32 1 # include <security.h> +# 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<mozilla::dom::Promise>* 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<wchar_t[]> 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<const wchar_t*>(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> 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<RefPtr<nsIClientDownloadResource>> 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<ContentAnalysis> owner = GetContentAnalysisFromService(); if (!owner) { @@ -787,7 +983,10 @@ RefPtr<ContentAnalysis> ContentAnalysis::GetContentAnalysisFromService() { nsresult ContentAnalysis::RunAnalyzeRequestTask( const RefPtr<nsIContentAnalysisRequest>& aRequest, bool aAutoAcknowledge, + int64_t aRequestCount, const RefPtr<nsIContentAnalysisCallback>& 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<nsIContentAnalysisCallback> callbackHolderCopy( new nsMainThreadPtrHolder<nsIContentAnalysisCallback>( "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<CallbackData> 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<ContentAnalysisAcknowledgement>( - 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<nsIObserverService> 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<ContentAnalysisResponse>& 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<ContentAnalysisAcknowledgement>( - nsIContentAnalysisAcknowledgement::Result::eSuccess, - ConvertResult(action)); - response->Acknowledge(acknowledgement); - } + Maybe<CallbackData> 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<ContentAnalysisAcknowledgement>( + nsIContentAnalysisAcknowledgement::Result::eTooLate, + nsIContentAnalysisAcknowledgement::FinalAction::eBlock); + response->Acknowledge(acknowledgement); + return; + } - nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder = - maybeCallbackData->TakeCallbackHolder(); - callbackHolder->ContentResult(response); - })); + LOGD("Content analysis resolving response promise for token %s", + responseRequestToken.get()); + nsIContentAnalysisResponse::Action action = response->GetAction(); + nsCOMPtr<nsIObserverService> 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<ContentAnalysisAcknowledgement>( + nsIContentAnalysisAcknowledgement::Result::eSuccess, + ConvertResult(action)); + response->Acknowledge(acknowledgement); + } + + nsMainThreadPtrHandle<nsIContentAnalysisCallback> 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 @@ -1047,6 +1279,33 @@ ContentAnalysis::CancelContentAnalysisRequest(const nsACString& aRequestToken) { } NS_IMETHODIMP +ContentAnalysis::CancelAllRequests() { + mCaClientPromise->Then( + GetCurrentSerialEventTarget(), __func__, + [&](std::shared_ptr<content_analysis::sdk::Client> 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) { nsCString requestToken(aRequestToken); @@ -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 <atomic> +#include <regex> #include <string> +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<dom::WindowGlobalParent> 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<nsIContentAnalysisRequest>& aRequest, bool aAutoAcknowledge, + int64_t aRequestCount, const RefPtr<nsIContentAnalysisCallback>& aCallback); nsresult RunAcknowledgeTask( nsIContentAnalysisAcknowledgement* aAcknowledgement, const nsACString& aRequestToken); nsresult CancelWithError(nsCString aRequestToken, nsresult aResult); + void GenerateUserActionId(); static RefPtr<ContentAnalysis> GetContentAnalysisFromService(); static void DoAnalyzeRequest( nsCString aRequestToken, content_analysis::sdk::ContentAnalysisRequest&& aRequest, const std::shared_ptr<content_analysis::sdk::Client>& aClient); + void IssueResponse(RefPtr<ContentAnalysisResponse>& 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<std::shared_ptr<content_analysis::sdk::Client>, nsresult, false>; + nsCString mUserActionId; + int64_t mRequestCount = 0; RefPtr<ClientPromise::Private> 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<nsTHashMap<nsCString, WarnResponseData>> mWarnResponseDataMap; + std::vector<std::regex> mAllowUrlList; + std::vector<std::regex> 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<ContentAnalysis> aOwner); + void DoNotAcknowledge() { mDoNotAcknowledge = true; } private: ~ContentAnalysisResponse() = default; @@ -189,7 +223,11 @@ class ContentAnalysisResponse final : public nsIContentAnalysisResponse { RefPtr<ContentAnalysis> 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 @@ -185,6 +185,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/ * uploading/printing/etc, based on the request. @@ -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 <processenv.h> #include <synchapi.h> -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<nsIContentAnalysis> caSvc = + do_GetService("@mozilla.org/contentanalysis;1"); + MOZ_ASSERT(caSvc); + mContentAnalysis = static_cast<ContentAnalysis*>(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<nsIContentAnalysisRequest> CreateRequest(const char* aUrl) { + nsCOMPtr<nsIURI> 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<ContentAnalysis> 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<nsIContentAnalysisRequest> 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<nsIContentAnalysisRequest> 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<nsIContentAnalysisRequest> 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<nsIContentAnalysisRequest> 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<nsIContentAnalysisRequest> car = + CreateRequest("https://example.org/matchme/"); + ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eDeny); } 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 <processenv.h> +#include <synchapi.h> + +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/TestContentAnalysis.h b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.h index 9e31036262..9e31036262 100644 --- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.h +++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.h 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 <processenv.h> #include <synchapi.h> #include <windows.h> 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 <combaseapi.h> #include <pathcch.h> #include <shlwapi.h> 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"] |