summaryrefslogtreecommitdiffstats
path: root/toolkit/components/contentanalysis
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/contentanalysis')
-rw-r--r--toolkit/components/contentanalysis/ContentAnalysis.cpp437
-rw-r--r--toolkit/components/contentanalysis/ContentAnalysis.h42
-rw-r--r--toolkit/components/contentanalysis/moz.build124
-rw-r--r--toolkit/components/contentanalysis/nsIContentAnalysis.idl18
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser.toml3
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js127
-rw-r--r--toolkit/components/contentanalysis/tests/browser/moz.build7
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp214
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp132
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.h (renamed from toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.h)0
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp2
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp2
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/moz.build4
-rw-r--r--toolkit/components/contentanalysis/tests/moz.build7
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"]