/* -*- 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 "TestContentAnalysisUtils.h" #include #include #include #include #include using namespace mozilla; MozAgentInfo LaunchAgentNormal(const wchar_t* aToBlock, const wchar_t* aToWarn /* = L"warn" */) { nsString pipeName; GeneratePipeName(L"contentanalysissdk-gtest-", pipeName); return LaunchAgentNormal(aToBlock, aToWarn, pipeName); } MozAgentInfo LaunchAgentNormal(const wchar_t* aToBlock, const wchar_t* aToWarn, const nsString& pipeName) { nsString cmdLineArguments; if (aToBlock && aToBlock[0] != 0) { cmdLineArguments.Append(L" --toblock="); cmdLineArguments.Append(aToBlock); } cmdLineArguments.Append(L" --towarn="); cmdLineArguments.Append(aToWarn); cmdLineArguments.Append(L" --user"); cmdLineArguments.Append(L" --path="); cmdLineArguments.Append(pipeName); cmdLineArguments.Append(L" --delaysMs=100"); MozAgentInfo agentInfo; LaunchAgentWithCommandLineArguments(cmdLineArguments, pipeName, agentInfo); return agentInfo; } void GeneratePipeName(const wchar_t* prefix, nsString& pipeName) { pipeName = u""_ns; pipeName.Append(prefix); UUID uuid; ASSERT_EQ(RPC_S_OK, UuidCreate(&uuid)); // 39 == length of a UUID string including braces and NUL. wchar_t guidBuf[39] = {}; ASSERT_EQ(39, StringFromGUID2(uuid, guidBuf, 39)); // omit opening and closing braces (and trailing null) pipeName.Append(&guidBuf[1], 36); } void LaunchAgentWithCommandLineArguments(const nsString& cmdLineArguments, const nsString& pipeName, MozAgentInfo& agentInfo) { wchar_t progName[MAX_PATH] = {}; // content_analysis_sdk_agent.exe is either next to icecat.exe (for local // builds), or in ../../tests/bin/ (for try/treeherder builds) DWORD nameSize = ::GetModuleFileNameW(nullptr, progName, MAX_PATH); ASSERT_NE(DWORD{0}, nameSize); ASSERT_EQ(S_OK, PathCchRemoveFileSpec(progName, nameSize)); wchar_t normalizedPath[MAX_PATH] = {}; nsString test1 = nsString(progName) + u"\\content_analysis_sdk_agent.exe"_ns; ASSERT_EQ(S_OK, PathCchCanonicalize(normalizedPath, MAX_PATH, test1.get())); nsString agentPath; if (::PathFileExistsW(normalizedPath)) { agentPath = nsString(normalizedPath); } if (agentPath.IsEmpty()) { nsString unNormalizedPath = nsString(progName) + u"\\..\\..\\tests\\bin\\content_analysis_sdk_agent.exe"_ns; ASSERT_EQ(S_OK, PathCchCanonicalize(normalizedPath, MAX_PATH, unNormalizedPath.get())); if (::PathFileExistsW(normalizedPath)) { agentPath = nsString(normalizedPath); } } ASSERT_FALSE(agentPath.IsEmpty()); nsString localCmdLine = nsString(agentPath) + u" "_ns + cmdLineArguments; STARTUPINFOW startupInfo = {sizeof(startupInfo)}; PROCESS_INFORMATION processInfo; BOOL ok = ::CreateProcessW(nullptr, localCmdLine.get(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startupInfo, &processInfo); // The documentation for CreateProcessW() says that any non-zero value is a // success if (!ok) { // Show the last error ASSERT_EQ(0UL, GetLastError()) << "Failed to launch content_analysis_sdk_agent"; } // Allow time for the agent to set up the pipe ::Sleep(2000); content_analysis::sdk::Client::Config config; config.name = NS_ConvertUTF16toUTF8(pipeName); config.user_specific = true; auto clientPtr = content_analysis::sdk::Client::Create(config); ASSERT_NE(nullptr, clientPtr.get()); agentInfo.processInfo = processInfo; agentInfo.client = std::move(clientPtr); } void SendRequestAndExpectResponse( RefPtr contentAnalysis, const nsCOMPtr& request, Maybe expectedShouldAllow, Maybe expectedAction, Maybe expectedIsCached) { SendRequestAndExpectResponseInternal(contentAnalysis, request, expectedShouldAllow, expectedAction, expectedIsCached, false); } void SendRequestAndExpectResponseInternal( RefPtr contentAnalysis, const nsCOMPtr& request, Maybe expectedShouldAllow, Maybe expectedAction, Maybe expectedIsCached, bool aIsEarlyResponse) { if (aIsEarlyResponse) { EXPECT_FALSE(expectedAction.isSome()) << "Early responses do not have an action"; EXPECT_FALSE(expectedIsCached.isSome()) << "Early responses do not have an isCached value"; } bool gotResponse = false; bool gotAcknowledgement = false; nsCString requestToken; MOZ_ALWAYS_SUCCEEDS(request->GetRequestToken(requestToken)); if (requestToken.IsEmpty()) { MOZ_ALWAYS_SUCCEEDS(request->SetRequestToken(GenerateUUID())); } // Make timedOut a RefPtr so if we get a response from content analysis // after this function has finished we can safely check that (and don't // start accessing stack values that don't exist anymore) RefPtr timedOut = MakeRefPtr>(); auto callback = MakeRefPtr( [&, timedOut](nsIContentAnalysisResult* result) { EXPECT_TRUE(NS_IsMainThread()); if (timedOut->mValue) { return; } if (expectedShouldAllow.isSome()) { EXPECT_EQ(*expectedShouldAllow, result->GetShouldAllowContent()); } if (aIsEarlyResponse) { // We will not get an acknowledgement for early responses, // so just set gotAcknowledgement to true so we don't wait for it. gotAcknowledgement = true; } else { nsCOMPtr response = do_QueryInterface(result); EXPECT_TRUE(response); if (expectedAction.isSome()) { EXPECT_EQ(*expectedAction, response->GetAction()); } if (expectedIsCached.isSome()) { bool isCached; MOZ_ALWAYS_SUCCEEDS(response->GetIsCachedResponse(&isCached)); EXPECT_EQ(*expectedIsCached, isCached); } nsCString requestToken, originalRequestToken; MOZ_ALWAYS_SUCCEEDS(response->GetRequestToken(requestToken)); MOZ_ALWAYS_SUCCEEDS(request->GetRequestToken(originalRequestToken)); EXPECT_EQ(originalRequestToken, requestToken); nsCString userActionId, originalUserActionId; MOZ_ALWAYS_SUCCEEDS(response->GetUserActionId(userActionId)); MOZ_ALWAYS_SUCCEEDS(request->GetUserActionId(originalUserActionId)); EXPECT_EQ(originalUserActionId, userActionId); } gotResponse = true; }, [&gotResponse, &gotAcknowledgement, timedOut](nsresult error) { EXPECT_TRUE(NS_IsMainThread()); if (timedOut->mValue) { return; } const char* errorName = mozilla::GetStaticErrorName(error); errorName = errorName ? errorName : ""; printf("Got error response code %s(%x)\n", errorName, error); // Errors should not have errorCode NS_OK EXPECT_NE(NS_OK, error); gotResponse = true; // An acknowledgement won't be sent, so don't wait for one gotAcknowledgement = true; FAIL() << "Got error response"; }); auto rawAcknowledgementObserver = MakeRefPtr(); nsCOMPtr obsServ = mozilla::services::GetObserverService(); if (!aIsEarlyResponse) { MOZ_ALWAYS_SUCCEEDS(obsServ->AddObserver( rawAcknowledgementObserver, "dlp-acknowledgement-sent-raw", false)); } AutoTArray, 1> requests{request.get()}; MOZ_ALWAYS_SUCCEEDS(contentAnalysis->AnalyzeContentRequestsCallback( requests, true, callback)); RefPtr timer = QueueTimeoutToMainThread(timedOut); mozilla::SpinEventLoopUntil( "Waiting for ContentAnalysis result"_ns, [&, timedOut]() { if (timedOut->mValue) { return true; } auto acknowledgements = rawAcknowledgementObserver->GetAcknowledgements(); nsCString requestToken; MOZ_ALWAYS_SUCCEEDS(request->GetRequestToken(requestToken)); for (const auto& acknowledgement : acknowledgements) { if (nsCString(acknowledgement.request_token()) == requestToken) { // Wait for the acknowledgement to happen to avoid background // activity that might interfere with other tests. gotAcknowledgement = true; break; } } return gotResponse && gotAcknowledgement; }); timer->Cancel(); EXPECT_TRUE(gotResponse); EXPECT_TRUE(gotAcknowledgement); EXPECT_FALSE(timedOut->mValue); if (!aIsEarlyResponse) { obsServ->RemoveObserver(rawAcknowledgementObserver, "dlp-acknowledgement-sent-raw"); } } NS_IMPL_ISUPPORTS(RawAcknowledgementObserver, nsIObserver); NS_IMETHODIMP RawAcknowledgementObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { content_analysis::sdk::ContentAnalysisAcknowledgement acknowledgement; ParseFromWideModifiedString(&acknowledgement, aData); mAcknowledgements.push_back(std::move(acknowledgement)); return NS_OK; }