forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1160 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1160 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
#include "ContentAnalysis.h"
 | 
						|
#include "ContentAnalysisIPCTypes.h"
 | 
						|
#include "content_analysis/sdk/analysis_client.h"
 | 
						|
 | 
						|
#include "base/process_util.h"
 | 
						|
#include "GMPUtils.h"  // ToHexString
 | 
						|
#include "mozilla/Components.h"
 | 
						|
#include "mozilla/dom/Promise.h"
 | 
						|
#include "mozilla/Logging.h"
 | 
						|
#include "mozilla/ScopeExit.h"
 | 
						|
#include "mozilla/Services.h"
 | 
						|
#include "mozilla/StaticPrefs_browser.h"
 | 
						|
#include "nsAppRunner.h"
 | 
						|
#include "nsComponentManagerUtils.h"
 | 
						|
#include "nsIClassInfoImpl.h"
 | 
						|
#include "nsIFile.h"
 | 
						|
#include "nsIGlobalObject.h"
 | 
						|
#include "nsIObserverService.h"
 | 
						|
#include "ScopedNSSTypes.h"
 | 
						|
#include "xpcpublic.h"
 | 
						|
 | 
						|
#include <algorithm>
 | 
						|
#include <sstream>
 | 
						|
 | 
						|
#ifdef XP_WIN
 | 
						|
#  include <windows.h>
 | 
						|
#  define SECURITY_WIN32 1
 | 
						|
#  include <security.h>
 | 
						|
#endif  // XP_WIN
 | 
						|
 | 
						|
namespace mozilla::contentanalysis {
 | 
						|
 | 
						|
LazyLogModule gContentAnalysisLog("contentanalysis");
 | 
						|
#define LOGD(...)                                        \
 | 
						|
  MOZ_LOG(mozilla::contentanalysis::gContentAnalysisLog, \
 | 
						|
          mozilla::LogLevel::Debug, (__VA_ARGS__))
 | 
						|
 | 
						|
#define LOGE(...)                                        \
 | 
						|
  MOZ_LOG(mozilla::contentanalysis::gContentAnalysisLog, \
 | 
						|
          mozilla::LogLevel::Error, (__VA_ARGS__))
 | 
						|
 | 
						|
}  // namespace mozilla::contentanalysis
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
const char* kIsDLPEnabledPref = "browser.contentanalysis.enabled";
 | 
						|
const char* kIsPerUserPref = "browser.contentanalysis.is_per_user";
 | 
						|
const char* kPipePathNamePref = "browser.contentanalysis.pipe_path_name";
 | 
						|
 | 
						|
static constexpr uint32_t kAnalysisTimeoutSecs = 30;  // 30 sec
 | 
						|
 | 
						|
nsresult MakePromise(JSContext* aCx, RefPtr<mozilla::dom::Promise>* aPromise) {
 | 
						|
  nsIGlobalObject* go = xpc::CurrentNativeGlobal(aCx);
 | 
						|
  if (NS_WARN_IF(!go)) {
 | 
						|
    return NS_ERROR_UNEXPECTED;
 | 
						|
  }
 | 
						|
  mozilla::ErrorResult result;
 | 
						|
  *aPromise = mozilla::dom::Promise::Create(go, result);
 | 
						|
  if (NS_WARN_IF(result.Failed())) {
 | 
						|
    return result.StealNSResult();
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsCString GenerateRequestToken() {
 | 
						|
  nsID id = nsID::GenerateUUID();
 | 
						|
  return nsCString(id.ToString().get());
 | 
						|
}
 | 
						|
 | 
						|
static nsresult GetFileDisplayName(const nsString& aFilePath,
 | 
						|
                                   nsString& aFileDisplayName) {
 | 
						|
  nsresult rv;
 | 
						|
  nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1", &rv);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  rv = file->InitWithPath(aFilePath);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  return file->GetDisplayName(aFileDisplayName);
 | 
						|
}
 | 
						|
 | 
						|
nsIContentAnalysisAcknowledgement::FinalAction ConvertResult(
 | 
						|
    nsIContentAnalysisResponse::Action aResponseResult) {
 | 
						|
  switch (aResponseResult) {
 | 
						|
    case nsIContentAnalysisResponse::Action::eReportOnly:
 | 
						|
      return nsIContentAnalysisAcknowledgement::FinalAction::eReportOnly;
 | 
						|
    case nsIContentAnalysisResponse::Action::eWarn:
 | 
						|
      return nsIContentAnalysisAcknowledgement::FinalAction::eWarn;
 | 
						|
    case nsIContentAnalysisResponse::Action::eBlock:
 | 
						|
      return nsIContentAnalysisAcknowledgement::FinalAction::eBlock;
 | 
						|
    case nsIContentAnalysisResponse::Action::eAllow:
 | 
						|
      return nsIContentAnalysisAcknowledgement::FinalAction::eAllow;
 | 
						|
    case nsIContentAnalysisResponse::Action::eUnspecified:
 | 
						|
      return nsIContentAnalysisAcknowledgement::FinalAction::eUnspecified;
 | 
						|
    default:
 | 
						|
      LOGE(
 | 
						|
          "ConvertResult got unexpected responseResult "
 | 
						|
          "%d",
 | 
						|
          static_cast<uint32_t>(aResponseResult));
 | 
						|
      return nsIContentAnalysisAcknowledgement::FinalAction::eUnspecified;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
}  // anonymous namespace
 | 
						|
 | 
						|
namespace mozilla::contentanalysis {
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisRequest::GetAnalysisType(AnalysisType* aAnalysisType) {
 | 
						|
  *aAnalysisType = mAnalysisType;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisRequest::GetTextContent(nsAString& aTextContent) {
 | 
						|
  aTextContent = mTextContent;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisRequest::GetFilePath(nsAString& aFilePath) {
 | 
						|
  aFilePath = mFilePath;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisRequest::GetUrl(nsAString& aUrl) {
 | 
						|
  aUrl = mUrl;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisRequest::GetEmail(nsAString& aEmail) {
 | 
						|
  aEmail = mEmail;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisRequest::GetSha256Digest(nsACString& aSha256Digest) {
 | 
						|
  aSha256Digest = mSha256Digest;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisRequest::GetResources(
 | 
						|
    nsTArray<RefPtr<nsIClientDownloadResource>>& aResources) {
 | 
						|
  aResources = mResources.Clone();
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisRequest::GetRequestToken(nsACString& aRequestToken) {
 | 
						|
  aRequestToken = mRequestToken;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisRequest::GetOperationTypeForDisplay(
 | 
						|
    OperationType* aOperationType) {
 | 
						|
  *aOperationType = mOperationTypeForDisplay;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisRequest::GetOperationDisplayString(
 | 
						|
    nsAString& aOperationDisplayString) {
 | 
						|
  aOperationDisplayString = mOperationDisplayString;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisRequest::GetWindowGlobalParent(
 | 
						|
    dom::WindowGlobalParent** aWindowGlobalParent) {
 | 
						|
  NS_IF_ADDREF(*aWindowGlobalParent = mWindowGlobalParent);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult ContentAnalysis::CreateContentAnalysisClient(nsCString&& aPipePathName,
 | 
						|
                                                      bool aIsPerUser) {
 | 
						|
  MOZ_ASSERT(!NS_IsMainThread());
 | 
						|
  // This method should only be called once
 | 
						|
  MOZ_ASSERT(!mCaClientPromise->IsResolved());
 | 
						|
 | 
						|
  std::shared_ptr<content_analysis::sdk::Client> client(
 | 
						|
      content_analysis::sdk::Client::Create({aPipePathName.Data(), aIsPerUser})
 | 
						|
          .release());
 | 
						|
  LOGD("Content analysis is %s", client ? "connected" : "not available");
 | 
						|
  mCaClientPromise->Resolve(client, __func__);
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
ContentAnalysisRequest::ContentAnalysisRequest(
 | 
						|
    AnalysisType aAnalysisType, nsString aString, bool aStringIsFilePath,
 | 
						|
    nsCString aSha256Digest, nsString aUrl, OperationType aOperationType,
 | 
						|
    dom::WindowGlobalParent* aWindowGlobalParent)
 | 
						|
    : mAnalysisType(aAnalysisType),
 | 
						|
      mUrl(std::move(aUrl)),
 | 
						|
      mSha256Digest(std::move(aSha256Digest)),
 | 
						|
      mWindowGlobalParent(aWindowGlobalParent) {
 | 
						|
  if (aStringIsFilePath) {
 | 
						|
    mFilePath = std::move(aString);
 | 
						|
  } else {
 | 
						|
    mTextContent = std::move(aString);
 | 
						|
  }
 | 
						|
  mOperationTypeForDisplay = aOperationType;
 | 
						|
  if (mOperationTypeForDisplay == OperationType::eCustomDisplayString) {
 | 
						|
    MOZ_ASSERT(aStringIsFilePath);
 | 
						|
    nsresult rv = GetFileDisplayName(mFilePath, mOperationDisplayString);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      mOperationDisplayString = u"file";
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  mRequestToken = GenerateRequestToken();
 | 
						|
}
 | 
						|
 | 
						|
nsresult ContentAnalysisRequest::GetFileDigest(const nsAString& aFilePath,
 | 
						|
                                               nsCString& aDigestString) {
 | 
						|
  MOZ_ASSERT(!NS_IsMainThread(),
 | 
						|
             "ContentAnalysisRequest::GetFileDigest does file IO and should "
 | 
						|
             "not run on the main thread");
 | 
						|
  nsresult rv;
 | 
						|
  mozilla::Digest digest;
 | 
						|
  digest.Begin(SEC_OID_SHA256);
 | 
						|
  PRFileDesc* fd = nullptr;
 | 
						|
  nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1", &rv);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  rv = file->InitWithPath(aFilePath);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  rv = file->OpenNSPRFileDesc(PR_RDONLY | nsIFile::OS_READAHEAD, 0, &fd);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  auto closeFile = MakeScopeExit([fd]() { PR_Close(fd); });
 | 
						|
  uint8_t buffer[4096];
 | 
						|
  PRInt32 bytesRead;
 | 
						|
  bytesRead = PR_Read(fd, buffer, sizeof(buffer) / sizeof(uint8_t));
 | 
						|
  while (bytesRead != 0) {
 | 
						|
    if (bytesRead == -1) {
 | 
						|
      return NS_ERROR_DOM_FILE_NOT_READABLE_ERR;
 | 
						|
    }
 | 
						|
    digest.Update(mozilla::Span<const uint8_t>(buffer, bytesRead));
 | 
						|
    bytesRead = PR_Read(fd, buffer, sizeof(buffer) / sizeof(uint8_t));
 | 
						|
  }
 | 
						|
  nsTArray<uint8_t> digestResults;
 | 
						|
  rv = digest.End(digestResults);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  aDigestString = mozilla::ToHexString(digestResults);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
static nsresult ConvertToProtobuf(
 | 
						|
    nsIClientDownloadResource* aIn,
 | 
						|
    content_analysis::sdk::ClientDownloadRequest_Resource* aOut) {
 | 
						|
  nsString url;
 | 
						|
  nsresult rv = aIn->GetUrl(url);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  aOut->set_url(NS_ConvertUTF16toUTF8(url).get());
 | 
						|
 | 
						|
  uint32_t resourceType;
 | 
						|
  rv = aIn->GetType(&resourceType);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  aOut->set_type(
 | 
						|
      static_cast<content_analysis::sdk::ClientDownloadRequest_ResourceType>(
 | 
						|
          resourceType));
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
static nsresult ConvertToProtobuf(
 | 
						|
    nsIContentAnalysisRequest* aIn,
 | 
						|
    content_analysis::sdk::ContentAnalysisRequest* aOut) {
 | 
						|
  aOut->set_expires_at(time(nullptr) + kAnalysisTimeoutSecs);  // TODO:
 | 
						|
 | 
						|
  nsIContentAnalysisRequest::AnalysisType analysisType;
 | 
						|
  nsresult rv = aIn->GetAnalysisType(&analysisType);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  auto connector =
 | 
						|
      static_cast<content_analysis::sdk::AnalysisConnector>(analysisType);
 | 
						|
  aOut->set_analysis_connector(connector);
 | 
						|
 | 
						|
  nsCString requestToken;
 | 
						|
  rv = aIn->GetRequestToken(requestToken);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  aOut->set_request_token(requestToken.get(), requestToken.Length());
 | 
						|
 | 
						|
  const std::string tag = "dlp";  // TODO:
 | 
						|
  *aOut->add_tags() = tag;
 | 
						|
 | 
						|
  auto* requestData = aOut->mutable_request_data();
 | 
						|
 | 
						|
  nsString url;
 | 
						|
  rv = aIn->GetUrl(url);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  if (!url.IsEmpty()) {
 | 
						|
    requestData->set_url(NS_ConvertUTF16toUTF8(url).get());
 | 
						|
  }
 | 
						|
 | 
						|
  nsString email;
 | 
						|
  rv = aIn->GetEmail(email);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  if (!email.IsEmpty()) {
 | 
						|
    requestData->set_email(NS_ConvertUTF16toUTF8(email).get());
 | 
						|
  }
 | 
						|
 | 
						|
  nsCString sha256Digest;
 | 
						|
  rv = aIn->GetSha256Digest(sha256Digest);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  if (!sha256Digest.IsEmpty()) {
 | 
						|
    requestData->set_digest(sha256Digest.get());
 | 
						|
  }
 | 
						|
 | 
						|
  nsString filePath;
 | 
						|
  rv = aIn->GetFilePath(filePath);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  if (!filePath.IsEmpty()) {
 | 
						|
    std::string filePathStr = NS_ConvertUTF16toUTF8(filePath).get();
 | 
						|
    aOut->set_file_path(filePathStr);
 | 
						|
    auto filename = filePathStr.substr(filePathStr.find_last_of("/\\") + 1);
 | 
						|
    if (!filename.empty()) {
 | 
						|
      requestData->set_filename(filename);
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    nsString textContent;
 | 
						|
    rv = aIn->GetTextContent(textContent);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
    MOZ_ASSERT(!textContent.IsEmpty());
 | 
						|
    aOut->set_text_content(NS_ConvertUTF16toUTF8(textContent).get());
 | 
						|
  }
 | 
						|
 | 
						|
#ifdef XP_WIN
 | 
						|
  ULONG userLen = 0;
 | 
						|
  GetUserNameExW(NameSamCompatible, nullptr, &userLen);
 | 
						|
  if (GetLastError() == ERROR_MORE_DATA && userLen > 0) {
 | 
						|
    auto user = mozilla::MakeUnique<wchar_t[]>(userLen);
 | 
						|
    if (GetUserNameExW(NameSamCompatible, user.get(), &userLen)) {
 | 
						|
      auto* clientMetadata = aOut->mutable_client_metadata();
 | 
						|
      auto* browser = clientMetadata->mutable_browser();
 | 
						|
      browser->set_machine_user(NS_ConvertUTF16toUTF8(user.get()).get());
 | 
						|
    }
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
  nsTArray<RefPtr<nsIClientDownloadResource>> resources;
 | 
						|
  rv = aIn->GetResources(resources);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  if (!resources.IsEmpty()) {
 | 
						|
    auto* pbClientDownloadRequest = requestData->mutable_csd();
 | 
						|
    for (auto& nsResource : resources) {
 | 
						|
      rv = ConvertToProtobuf(nsResource.get(),
 | 
						|
                             pbClientDownloadRequest->add_resources());
 | 
						|
      NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
static void LogRequest(
 | 
						|
    content_analysis::sdk::ContentAnalysisRequest* aPbRequest) {
 | 
						|
  // We cannot use Protocol Buffer's DebugString() because we optimize for
 | 
						|
  // lite runtime.
 | 
						|
  if (!static_cast<LogModule*>(gContentAnalysisLog)
 | 
						|
           ->ShouldLog(LogLevel::Debug)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  std::stringstream ss;
 | 
						|
  ss << "ContentAnalysisRequest:"
 | 
						|
     << "\n";
 | 
						|
 | 
						|
#define ADD_FIELD(PBUF, NAME, FUNC) \
 | 
						|
  ss << "  " << (NAME) << ": ";     \
 | 
						|
  if ((PBUF)->has_##FUNC())         \
 | 
						|
    ss << (PBUF)->FUNC() << "\n";   \
 | 
						|
  else                              \
 | 
						|
    ss << "<none>"                  \
 | 
						|
       << "\n";
 | 
						|
 | 
						|
#define ADD_EXISTS(PBUF, NAME, FUNC) \
 | 
						|
  ss << "  " << (NAME) << ": "       \
 | 
						|
     << ((PBUF)->has_##FUNC() ? "<exists>" : "<none>") << "\n";
 | 
						|
 | 
						|
  ADD_FIELD(aPbRequest, "Expires", expires_at);
 | 
						|
  ADD_FIELD(aPbRequest, "Analysis Type", analysis_connector);
 | 
						|
  ADD_FIELD(aPbRequest, "Request Token", request_token);
 | 
						|
  ADD_FIELD(aPbRequest, "File Path", file_path);
 | 
						|
  ADD_FIELD(aPbRequest, "Text Content", text_content);
 | 
						|
  // TODO: Tags
 | 
						|
  ADD_EXISTS(aPbRequest, "Request Data Struct", request_data);
 | 
						|
  const auto* requestData =
 | 
						|
      aPbRequest->has_request_data() ? &aPbRequest->request_data() : nullptr;
 | 
						|
  if (requestData) {
 | 
						|
    ADD_FIELD(requestData, "  Url", url);
 | 
						|
    ADD_FIELD(requestData, "  Email", email);
 | 
						|
    ADD_FIELD(requestData, "  SHA-256 Digest", digest);
 | 
						|
    ADD_FIELD(requestData, "  Filename", filename);
 | 
						|
    ADD_EXISTS(requestData, "  Client Download Request struct", csd);
 | 
						|
    const auto* csd = requestData->has_csd() ? &requestData->csd() : nullptr;
 | 
						|
    if (csd) {
 | 
						|
      uint32_t i = 0;
 | 
						|
      for (const auto& resource : csd->resources()) {
 | 
						|
        ss << "      Resource " << i << ":"
 | 
						|
           << "\n";
 | 
						|
        ADD_FIELD(&resource, "      Url", url);
 | 
						|
        ADD_FIELD(&resource, "      Type", type);
 | 
						|
        ++i;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  ADD_EXISTS(aPbRequest, "Client Metadata Struct", client_metadata);
 | 
						|
  const auto* clientMetadata = aPbRequest->has_client_metadata()
 | 
						|
                                   ? &aPbRequest->client_metadata()
 | 
						|
                                   : nullptr;
 | 
						|
  if (clientMetadata) {
 | 
						|
    ADD_EXISTS(clientMetadata, "  Browser Struct", browser);
 | 
						|
    const auto* browser =
 | 
						|
        clientMetadata->has_browser() ? &clientMetadata->browser() : nullptr;
 | 
						|
    if (browser) {
 | 
						|
      ADD_FIELD(browser, "    Machine User", machine_user);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
#undef ADD_EXISTS
 | 
						|
#undef ADD_FIELD
 | 
						|
 | 
						|
  LOGD("%s", ss.str().c_str());
 | 
						|
}
 | 
						|
 | 
						|
ContentAnalysisResponse::ContentAnalysisResponse(
 | 
						|
    content_analysis::sdk::ContentAnalysisResponse&& aResponse)
 | 
						|
    : mHasAcknowledged(false) {
 | 
						|
  mAction = Action::eUnspecified;
 | 
						|
  for (const auto& result : aResponse.results()) {
 | 
						|
    if (!result.has_status() ||
 | 
						|
        result.status() !=
 | 
						|
            content_analysis::sdk::ContentAnalysisResponse::Result::SUCCESS) {
 | 
						|
      mAction = Action::eUnspecified;
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // The action values increase with severity, so the max is the most severe.
 | 
						|
    for (const auto& rule : result.triggered_rules()) {
 | 
						|
      mAction =
 | 
						|
          static_cast<Action>(std::max(static_cast<uint32_t>(mAction),
 | 
						|
                                       static_cast<uint32_t>(rule.action())));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If no rules blocked then we should allow.
 | 
						|
  if (mAction == Action::eUnspecified) {
 | 
						|
    mAction = Action::eAllow;
 | 
						|
  }
 | 
						|
 | 
						|
  const auto& requestToken = aResponse.request_token();
 | 
						|
  mRequestToken.Assign(requestToken.data(), requestToken.size());
 | 
						|
}
 | 
						|
 | 
						|
ContentAnalysisResponse::ContentAnalysisResponse(
 | 
						|
    Action aAction, const nsACString& aRequestToken)
 | 
						|
    : mAction(aAction), mRequestToken(aRequestToken), mHasAcknowledged(false) {}
 | 
						|
 | 
						|
/* static */
 | 
						|
already_AddRefed<ContentAnalysisResponse> ContentAnalysisResponse::FromProtobuf(
 | 
						|
    content_analysis::sdk::ContentAnalysisResponse&& aResponse) {
 | 
						|
  auto ret = RefPtr<ContentAnalysisResponse>(
 | 
						|
      new ContentAnalysisResponse(std::move(aResponse)));
 | 
						|
 | 
						|
  if (ret->mAction == Action::eUnspecified) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  return ret.forget();
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
RefPtr<ContentAnalysisResponse> ContentAnalysisResponse::FromAction(
 | 
						|
    Action aAction, const nsACString& aRequestToken) {
 | 
						|
  if (aAction == Action::eUnspecified) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
  return RefPtr<ContentAnalysisResponse>(
 | 
						|
      new ContentAnalysisResponse(aAction, aRequestToken));
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisResponse::GetRequestToken(nsACString& aRequestToken) {
 | 
						|
  aRequestToken = mRequestToken;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
static void LogResponse(
 | 
						|
    content_analysis::sdk::ContentAnalysisResponse* aPbResponse) {
 | 
						|
  if (!static_cast<LogModule*>(gContentAnalysisLog)
 | 
						|
           ->ShouldLog(LogLevel::Debug)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  std::stringstream ss;
 | 
						|
  ss << "ContentAnalysisResponse:"
 | 
						|
     << "\n";
 | 
						|
 | 
						|
#define ADD_FIELD(PBUF, NAME, FUNC) \
 | 
						|
  ss << "  " << (NAME) << ": ";     \
 | 
						|
  if ((PBUF)->has_##FUNC())         \
 | 
						|
    ss << (PBUF)->FUNC() << "\n";   \
 | 
						|
  else                              \
 | 
						|
    ss << "<none>"                  \
 | 
						|
       << "\n";
 | 
						|
 | 
						|
  ADD_FIELD(aPbResponse, "Request Token", request_token);
 | 
						|
  uint32_t i = 0;
 | 
						|
  for (const auto& result : aPbResponse->results()) {
 | 
						|
    ss << "  Result " << i << ":"
 | 
						|
       << "\n";
 | 
						|
    ADD_FIELD(&result, "    Status", status);
 | 
						|
    uint32_t j = 0;
 | 
						|
    for (const auto& rule : result.triggered_rules()) {
 | 
						|
      ss << "    Rule " << j << ":"
 | 
						|
         << "\n";
 | 
						|
      ADD_FIELD(&rule, "    action", action);
 | 
						|
      ++j;
 | 
						|
    }
 | 
						|
    ++i;
 | 
						|
  }
 | 
						|
 | 
						|
#undef ADD_FIELD
 | 
						|
 | 
						|
  LOGD("%s", ss.str().c_str());
 | 
						|
}
 | 
						|
 | 
						|
static nsresult ConvertToProtobuf(
 | 
						|
    nsIContentAnalysisAcknowledgement* aIn, const nsACString& aRequestToken,
 | 
						|
    content_analysis::sdk::ContentAnalysisAcknowledgement* aOut) {
 | 
						|
  aOut->set_request_token(aRequestToken.Data(), aRequestToken.Length());
 | 
						|
 | 
						|
  nsIContentAnalysisAcknowledgement::Result result;
 | 
						|
  nsresult rv = aIn->GetResult(&result);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  aOut->set_status(
 | 
						|
      static_cast<content_analysis::sdk::ContentAnalysisAcknowledgement_Status>(
 | 
						|
          result));
 | 
						|
 | 
						|
  nsIContentAnalysisAcknowledgement::FinalAction finalAction;
 | 
						|
  rv = aIn->GetFinalAction(&finalAction);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  aOut->set_final_action(
 | 
						|
      static_cast<
 | 
						|
          content_analysis::sdk::ContentAnalysisAcknowledgement_FinalAction>(
 | 
						|
          finalAction));
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisResponse::GetAction(Action* aAction) {
 | 
						|
  *aAction = mAction;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
static void LogAcknowledgement(
 | 
						|
    content_analysis::sdk::ContentAnalysisAcknowledgement* aPbAck) {
 | 
						|
  if (!static_cast<LogModule*>(gContentAnalysisLog)
 | 
						|
           ->ShouldLog(LogLevel::Debug)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  std::stringstream ss;
 | 
						|
  ss << "ContentAnalysisAcknowledgement:"
 | 
						|
     << "\n";
 | 
						|
 | 
						|
#define ADD_FIELD(PBUF, NAME, FUNC) \
 | 
						|
  ss << "  " << (NAME) << ": ";     \
 | 
						|
  if ((PBUF)->has_##FUNC())         \
 | 
						|
    ss << (PBUF)->FUNC() << "\n";   \
 | 
						|
  else                              \
 | 
						|
    ss << "<none>"                  \
 | 
						|
       << "\n";
 | 
						|
 | 
						|
  ADD_FIELD(aPbAck, "Status", status);
 | 
						|
  ADD_FIELD(aPbAck, "Final Action", final_action);
 | 
						|
 | 
						|
#undef ADD_FIELD
 | 
						|
 | 
						|
  LOGD("%s", ss.str().c_str());
 | 
						|
}
 | 
						|
 | 
						|
void ContentAnalysisResponse::SetOwner(RefPtr<ContentAnalysis> aOwner) {
 | 
						|
  mOwner = std::move(aOwner);
 | 
						|
}
 | 
						|
 | 
						|
void ContentAnalysisResponse::ResolveWarnAction(bool aAllowContent) {
 | 
						|
  MOZ_ASSERT(mAction == Action::eWarn);
 | 
						|
  mAction = aAllowContent ? Action::eAllow : Action::eBlock;
 | 
						|
}
 | 
						|
 | 
						|
ContentAnalysisAcknowledgement::ContentAnalysisAcknowledgement(
 | 
						|
    Result aResult, FinalAction aFinalAction)
 | 
						|
    : mResult(aResult), mFinalAction(aFinalAction) {}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisAcknowledgement::GetResult(Result* aResult) {
 | 
						|
  *aResult = mResult;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisAcknowledgement::GetFinalAction(FinalAction* aFinalAction) {
 | 
						|
  *aFinalAction = mFinalAction;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
namespace {
 | 
						|
static bool ShouldAllowAction(
 | 
						|
    nsIContentAnalysisResponse::Action aResponseCode) {
 | 
						|
  return aResponseCode == nsIContentAnalysisResponse::Action::eAllow ||
 | 
						|
         aResponseCode == nsIContentAnalysisResponse::Action::eReportOnly ||
 | 
						|
         aResponseCode == nsIContentAnalysisResponse::Action::eWarn;
 | 
						|
}
 | 
						|
}  // namespace
 | 
						|
 | 
						|
NS_IMETHODIMP ContentAnalysisResponse::GetShouldAllowContent(
 | 
						|
    bool* aShouldAllowContent) {
 | 
						|
  *aShouldAllowContent = ShouldAllowAction(mAction);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP ContentAnalysisResult::GetShouldAllowContent(
 | 
						|
    bool* aShouldAllowContent) {
 | 
						|
  if (mValue.is<NoContentAnalysisResult>()) {
 | 
						|
    NoContentAnalysisResult result = mValue.as<NoContentAnalysisResult>();
 | 
						|
    *aShouldAllowContent =
 | 
						|
        result == NoContentAnalysisResult::AGENT_NOT_PRESENT ||
 | 
						|
        result == NoContentAnalysisResult::NO_PARENT_BROWSER;
 | 
						|
  } else {
 | 
						|
    *aShouldAllowContent =
 | 
						|
        ShouldAllowAction(mValue.as<nsIContentAnalysisResponse::Action>());
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMPL_CLASSINFO(ContentAnalysisRequest, nullptr, 0, {0});
 | 
						|
NS_IMPL_ISUPPORTS_CI(ContentAnalysisRequest, nsIContentAnalysisRequest);
 | 
						|
NS_IMPL_CLASSINFO(ContentAnalysisResponse, nullptr, 0, {0});
 | 
						|
NS_IMPL_ISUPPORTS_CI(ContentAnalysisResponse, nsIContentAnalysisResponse);
 | 
						|
NS_IMPL_ISUPPORTS(ContentAnalysisAcknowledgement,
 | 
						|
                  nsIContentAnalysisAcknowledgement);
 | 
						|
NS_IMPL_ISUPPORTS(ContentAnalysisCallback, nsIContentAnalysisCallback);
 | 
						|
NS_IMPL_ISUPPORTS(ContentAnalysisResult, nsIContentAnalysisResult);
 | 
						|
NS_IMPL_ISUPPORTS(ContentAnalysis, nsIContentAnalysis, ContentAnalysis);
 | 
						|
 | 
						|
ContentAnalysis::ContentAnalysis()
 | 
						|
    : mCaClientPromise(
 | 
						|
          new ClientPromise::Private("ContentAnalysis::ContentAnalysis")),
 | 
						|
      mClientCreationAttempted(false),
 | 
						|
      mCallbackMap("ContentAnalysis::mCallbackMap"),
 | 
						|
      mWarnResponseDataMap("ContentAnalysis::mWarnResponseDataMap") {}
 | 
						|
 | 
						|
ContentAnalysis::~ContentAnalysis() {
 | 
						|
  // Accessing mClientCreationAttempted so need to be on the main thread
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  if (!mClientCreationAttempted) {
 | 
						|
    // Reject the promise to avoid assertions when it gets destroyed
 | 
						|
    mCaClientPromise->Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
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
 | 
						|
  MOZ_ASSERT(XRE_IsParentProcess());
 | 
						|
  if (!gAllowContentAnalysis || !Preferences::GetBool(kIsDLPEnabledPref)) {
 | 
						|
    LOGD("Local DLP Content Analysis is not active");
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
  *aIsActive = true;
 | 
						|
  LOGD("Local DLP Content Analysis is active");
 | 
						|
  // mClientCreationAttempted is only accessed on the main thread,
 | 
						|
  // so no need for synchronization here.
 | 
						|
  if (!mClientCreationAttempted) {
 | 
						|
    mClientCreationAttempted = true;
 | 
						|
    LOGD("Dispatching background task to create Content Analysis client");
 | 
						|
 | 
						|
    nsCString pipePathName;
 | 
						|
    nsresult rv = Preferences::GetCString(kPipePathNamePref, pipePathName);
 | 
						|
    if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
      mCaClientPromise->Reject(rv, __func__);
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
    bool isPerUser = Preferences::GetBool(kIsPerUserPref);
 | 
						|
    rv = NS_DispatchBackgroundTask(NS_NewCancelableRunnableFunction(
 | 
						|
        "ContentAnalysis::CreateContentAnalysisClient",
 | 
						|
        [owner = RefPtr{this}, pipePathName = std::move(pipePathName),
 | 
						|
         isPerUser]() mutable {
 | 
						|
          owner->CreateContentAnalysisClient(std::move(pipePathName),
 | 
						|
                                             isPerUser);
 | 
						|
        }));
 | 
						|
    if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
      mCaClientPromise->Reject(rv, __func__);
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysis::GetMightBeActive(bool* aMightBeActive) {
 | 
						|
  // A DLP connection is not permitted to be added/removed while the
 | 
						|
  // browser is running, so we can cache this.
 | 
						|
  static bool sIsEnabled = Preferences::GetBool(kIsDLPEnabledPref);
 | 
						|
  // Note that we can't check gAllowContentAnalysis here because it
 | 
						|
  // only gets set in the parent process.
 | 
						|
  *aMightBeActive = sIsEnabled;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken,
 | 
						|
                                          nsresult aResult) {
 | 
						|
  return NS_DispatchToMainThread(NS_NewCancelableRunnableFunction(
 | 
						|
      "ContentAnalysis::RunAnalyzeRequestTask::HandleResponse",
 | 
						|
      [aResult, aRequestToken = std::move(aRequestToken)] {
 | 
						|
        RefPtr<ContentAnalysis> owner = GetContentAnalysisFromService();
 | 
						|
        if (!owner) {
 | 
						|
          // May be shutting down
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder;
 | 
						|
        {
 | 
						|
          auto lock = owner->mCallbackMap.Lock();
 | 
						|
          auto callbackData = lock->Extract(aRequestToken);
 | 
						|
          if (callbackData.isSome()) {
 | 
						|
            callbackHolder = callbackData->TakeCallbackHolder();
 | 
						|
          }
 | 
						|
        }
 | 
						|
        if (callbackHolder) {
 | 
						|
          callbackHolder->Error(aResult);
 | 
						|
        }
 | 
						|
      }));
 | 
						|
}
 | 
						|
 | 
						|
RefPtr<ContentAnalysis> ContentAnalysis::GetContentAnalysisFromService() {
 | 
						|
  RefPtr<ContentAnalysis> contentAnalysisService =
 | 
						|
      mozilla::components::nsIContentAnalysis::Service();
 | 
						|
  if (!contentAnalysisService) {
 | 
						|
    // May be shutting down
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  return contentAnalysisService;
 | 
						|
}
 | 
						|
 | 
						|
nsresult ContentAnalysis::RunAnalyzeRequestTask(
 | 
						|
    const RefPtr<nsIContentAnalysisRequest>& aRequest, bool aAutoAcknowledge,
 | 
						|
    const RefPtr<nsIContentAnalysisCallback>& aCallback) {
 | 
						|
  nsresult rv = NS_ERROR_FAILURE;
 | 
						|
  auto callbackCopy = aCallback;
 | 
						|
  auto se = MakeScopeExit([&] {
 | 
						|
    if (!NS_SUCCEEDED(rv)) {
 | 
						|
      LOGE("RunAnalyzeRequestTask failed");
 | 
						|
      callbackCopy->Error(rv);
 | 
						|
    }
 | 
						|
  });
 | 
						|
 | 
						|
  content_analysis::sdk::ContentAnalysisRequest pbRequest;
 | 
						|
  rv = ConvertToProtobuf(aRequest, &pbRequest);
 | 
						|
  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));
 | 
						|
  }
 | 
						|
 | 
						|
  LOGD("Issuing ContentAnalysisRequest for token %s", requestToken.get());
 | 
						|
  LogRequest(&pbRequest);
 | 
						|
 | 
						|
  mCaClientPromise->Then(
 | 
						|
      GetCurrentSerialEventTarget(), __func__,
 | 
						|
      [requestToken, pbRequest = std::move(pbRequest)](
 | 
						|
          std::shared_ptr<content_analysis::sdk::Client> client) mutable {
 | 
						|
        // The content analysis call is synchronous so run in the background.
 | 
						|
        NS_DispatchBackgroundTask(
 | 
						|
            NS_NewCancelableRunnableFunction(
 | 
						|
                __func__,
 | 
						|
                [requestToken, pbRequest = std::move(pbRequest),
 | 
						|
                 client = std::move(client)]() mutable {
 | 
						|
                  DoAnalyzeRequest(requestToken, std::move(pbRequest), client);
 | 
						|
                }),
 | 
						|
            NS_DISPATCH_EVENT_MAY_BLOCK);
 | 
						|
      },
 | 
						|
      [requestToken](nsresult rv) mutable {
 | 
						|
        LOGD("RunAnalyzeRequestTask failed to get client");
 | 
						|
        RefPtr<ContentAnalysis> owner = GetContentAnalysisFromService();
 | 
						|
        if (!owner) {
 | 
						|
          // May be shutting down
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        owner->CancelWithError(std::move(requestToken), rv);
 | 
						|
      });
 | 
						|
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
void ContentAnalysis::DoAnalyzeRequest(
 | 
						|
    nsCString aRequestToken,
 | 
						|
    content_analysis::sdk::ContentAnalysisRequest&& aRequest,
 | 
						|
    const std::shared_ptr<content_analysis::sdk::Client>& aClient) {
 | 
						|
  MOZ_ASSERT(!NS_IsMainThread());
 | 
						|
  RefPtr<ContentAnalysis> owner =
 | 
						|
      ContentAnalysis::GetContentAnalysisFromService();
 | 
						|
  if (!owner) {
 | 
						|
    // May be shutting down
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!aClient) {
 | 
						|
    owner->CancelWithError(std::move(aRequestToken), NS_ERROR_NOT_AVAILABLE);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (aRequest.has_file_path() && !aRequest.file_path().empty() &&
 | 
						|
      (!aRequest.request_data().has_digest() ||
 | 
						|
       aRequest.request_data().digest().empty())) {
 | 
						|
    // Calculate the digest
 | 
						|
    nsCString digest;
 | 
						|
    nsCString fileCPath(aRequest.file_path().data(),
 | 
						|
                        aRequest.file_path().length());
 | 
						|
    nsString filePath = NS_ConvertUTF8toUTF16(fileCPath);
 | 
						|
    nsresult rv = ContentAnalysisRequest::GetFileDigest(filePath, digest);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      owner->CancelWithError(std::move(aRequestToken), rv);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    if (!digest.IsEmpty()) {
 | 
						|
      aRequest.mutable_request_data()->set_digest(digest.get());
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    auto callbackMap = owner->mCallbackMap.Lock();
 | 
						|
    if (!callbackMap->Contains(aRequestToken)) {
 | 
						|
      LOGD(
 | 
						|
          "RunAnalyzeRequestTask token %s has already been "
 | 
						|
          "cancelled - not issuing request",
 | 
						|
          aRequestToken.get());
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Run request, then dispatch back to main thread to resolve
 | 
						|
  // aCallback
 | 
						|
  content_analysis::sdk::ContentAnalysisResponse pbResponse;
 | 
						|
  int err = aClient->Send(aRequest, &pbResponse);
 | 
						|
  if (err != 0) {
 | 
						|
    LOGE("RunAnalyzeRequestTask client transaction failed");
 | 
						|
    owner->CancelWithError(std::move(aRequestToken), NS_ERROR_FAILURE);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  LOGD("Content analysis client transaction succeeded");
 | 
						|
  LogResponse(&pbResponse);
 | 
						|
  NS_DispatchToMainThread(NS_NewCancelableRunnableFunction(
 | 
						|
      "ContentAnalysis::RunAnalyzeRequestTask::HandleResponse",
 | 
						|
      [pbResponse = std::move(pbResponse)]() mutable {
 | 
						|
        RefPtr<ContentAnalysis> owner = GetContentAnalysisFromService();
 | 
						|
        if (!owner) {
 | 
						|
          // May be shutting down
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
        RefPtr<ContentAnalysisResponse> response =
 | 
						|
            ContentAnalysisResponse::FromProtobuf(std::move(pbResponse));
 | 
						|
        if (!response) {
 | 
						|
          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;
 | 
						|
        }
 | 
						|
 | 
						|
        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;
 | 
						|
        }
 | 
						|
 | 
						|
        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
 | 
						|
ContentAnalysis::AnalyzeContentRequest(nsIContentAnalysisRequest* aRequest,
 | 
						|
                                       bool aAutoAcknowledge, JSContext* aCx,
 | 
						|
                                       mozilla::dom::Promise** aPromise) {
 | 
						|
  RefPtr<mozilla::dom::Promise> promise;
 | 
						|
  nsresult rv = MakePromise(aCx, &promise);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  RefPtr<ContentAnalysisCallback> callbackPtr =
 | 
						|
      new ContentAnalysisCallback(promise);
 | 
						|
  promise.forget(aPromise);
 | 
						|
  return AnalyzeContentRequestCallback(aRequest, aAutoAcknowledge,
 | 
						|
                                       callbackPtr.get());
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysis::AnalyzeContentRequestCallback(
 | 
						|
    nsIContentAnalysisRequest* aRequest, bool aAutoAcknowledge,
 | 
						|
    nsIContentAnalysisCallback* aCallback) {
 | 
						|
  NS_ENSURE_ARG(aRequest);
 | 
						|
  NS_ENSURE_ARG(aCallback);
 | 
						|
 | 
						|
  bool isActive;
 | 
						|
  nsresult rv = GetIsActive(&isActive);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  if (!isActive) {
 | 
						|
    return NS_ERROR_NOT_AVAILABLE;
 | 
						|
  }
 | 
						|
 | 
						|
  nsCOMPtr<nsIObserverService> obsServ =
 | 
						|
      mozilla::services::GetObserverService();
 | 
						|
  obsServ->NotifyObservers(aRequest, "dlp-request-made", nullptr);
 | 
						|
 | 
						|
  rv = RunAnalyzeRequestTask(aRequest, aAutoAcknowledge, aCallback);
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysis::CancelContentAnalysisRequest(const nsACString& aRequestToken) {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  nsCString requestToken(aRequestToken);
 | 
						|
 | 
						|
  auto callbackMap = mCallbackMap.Lock();
 | 
						|
  auto entry = callbackMap->Lookup(requestToken);
 | 
						|
  LOGD("Content analysis cancelling request %s", requestToken.get());
 | 
						|
  // Make sure the entry hasn't been cancelled already
 | 
						|
  if (entry && !entry->Canceled()) {
 | 
						|
    nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder =
 | 
						|
        entry->TakeCallbackHolder();
 | 
						|
    entry->SetCanceled();
 | 
						|
    // Should only be called once
 | 
						|
    MOZ_ASSERT(callbackHolder);
 | 
						|
    if (callbackHolder) {
 | 
						|
      callbackHolder->Error(NS_ERROR_ABORT);
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    LOGD("Content analysis request not found when trying to cancel %s",
 | 
						|
         requestToken.get());
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysis::RespondToWarnDialog(const nsACString& aRequestToken,
 | 
						|
                                     bool aAllowContent) {
 | 
						|
  nsCString requestToken(aRequestToken);
 | 
						|
  NS_DispatchToMainThread(NS_NewCancelableRunnableFunction(
 | 
						|
      "RespondToWarnDialog",
 | 
						|
      [aAllowContent, requestToken = std::move(requestToken)]() {
 | 
						|
        RefPtr<ContentAnalysis> self = GetContentAnalysisFromService();
 | 
						|
        if (!self) {
 | 
						|
          // May be shutting down
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
        LOGD("Content analysis getting warn response %d for request %s",
 | 
						|
             aAllowContent ? 1 : 0, requestToken.get());
 | 
						|
        Maybe<WarnResponseData> entry;
 | 
						|
        {
 | 
						|
          auto warnResponseDataMap = self->mWarnResponseDataMap.Lock();
 | 
						|
          entry = warnResponseDataMap->Extract(requestToken);
 | 
						|
        }
 | 
						|
        if (!entry) {
 | 
						|
          LOGD(
 | 
						|
              "Content analysis request not found when trying to send warn "
 | 
						|
              "response for request %s",
 | 
						|
              requestToken.get());
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        entry->mResponse->ResolveWarnAction(aAllowContent);
 | 
						|
        auto action = entry->mResponse->GetAction();
 | 
						|
        if (entry->mCallbackData.AutoAcknowledge()) {
 | 
						|
          RefPtr<ContentAnalysisAcknowledgement> acknowledgement =
 | 
						|
              new ContentAnalysisAcknowledgement(
 | 
						|
                  nsIContentAnalysisAcknowledgement::Result::eSuccess,
 | 
						|
                  ConvertResult(action));
 | 
						|
          entry->mResponse->Acknowledge(acknowledgement);
 | 
						|
        }
 | 
						|
        nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder =
 | 
						|
            entry->mCallbackData.TakeCallbackHolder();
 | 
						|
        if (callbackHolder) {
 | 
						|
          RefPtr<ContentAnalysisResponse> response =
 | 
						|
              ContentAnalysisResponse::FromAction(action, requestToken);
 | 
						|
          response->SetOwner(self);
 | 
						|
          callbackHolder.get()->ContentResult(response.get());
 | 
						|
        } else {
 | 
						|
          LOGD(
 | 
						|
              "Content analysis had no callback to send warn final response "
 | 
						|
              "to for request %s",
 | 
						|
              requestToken.get());
 | 
						|
        }
 | 
						|
      }));
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
ContentAnalysisResponse::Acknowledge(
 | 
						|
    nsIContentAnalysisAcknowledgement* aAcknowledgement) {
 | 
						|
  MOZ_ASSERT(mOwner);
 | 
						|
  if (mHasAcknowledged) {
 | 
						|
    MOZ_ASSERT(false, "Already acknowledged this ContentAnalysisResponse!");
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  mHasAcknowledged = true;
 | 
						|
  return mOwner->RunAcknowledgeTask(aAcknowledgement, mRequestToken);
 | 
						|
};
 | 
						|
 | 
						|
nsresult ContentAnalysis::RunAcknowledgeTask(
 | 
						|
    nsIContentAnalysisAcknowledgement* aAcknowledgement,
 | 
						|
    const nsACString& aRequestToken) {
 | 
						|
  bool isActive;
 | 
						|
  nsresult rv = GetIsActive(&isActive);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  if (!isActive) {
 | 
						|
    return NS_ERROR_NOT_AVAILABLE;
 | 
						|
  }
 | 
						|
 | 
						|
  content_analysis::sdk::ContentAnalysisAcknowledgement pbAck;
 | 
						|
  rv = ConvertToProtobuf(aAcknowledgement, aRequestToken, &pbAck);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  LOGD("Issuing ContentAnalysisAcknowledgement");
 | 
						|
  LogAcknowledgement(&pbAck);
 | 
						|
 | 
						|
  // The content analysis connection is synchronous so run in the background.
 | 
						|
  LOGD("RunAcknowledgeTask dispatching acknowledge task");
 | 
						|
  mCaClientPromise->Then(
 | 
						|
      GetCurrentSerialEventTarget(), __func__,
 | 
						|
      [pbAck = std::move(pbAck)](
 | 
						|
          std::shared_ptr<content_analysis::sdk::Client> client) mutable {
 | 
						|
        NS_DispatchBackgroundTask(
 | 
						|
            NS_NewCancelableRunnableFunction(
 | 
						|
                __func__,
 | 
						|
                [pbAck = std::move(pbAck),
 | 
						|
                 client = std::move(client)]() mutable {
 | 
						|
                  RefPtr<ContentAnalysis> owner =
 | 
						|
                      GetContentAnalysisFromService();
 | 
						|
                  if (!owner) {
 | 
						|
                    // May be shutting down
 | 
						|
                    return;
 | 
						|
                  }
 | 
						|
                  if (!client) {
 | 
						|
                    return;
 | 
						|
                  }
 | 
						|
 | 
						|
                  int err = client->Acknowledge(pbAck);
 | 
						|
                  MOZ_ASSERT(err == 0);
 | 
						|
                  LOGD(
 | 
						|
                      "RunAcknowledgeTask sent transaction acknowledgement, "
 | 
						|
                      "err=%d",
 | 
						|
                      err);
 | 
						|
                }),
 | 
						|
            NS_DISPATCH_EVENT_MAY_BLOCK);
 | 
						|
      },
 | 
						|
      [](nsresult rv) { LOGD("RunAcknowledgeTask failed to get the client"); });
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP ContentAnalysisCallback::ContentResult(
 | 
						|
    nsIContentAnalysisResponse* aResponse) {
 | 
						|
  if (mPromise.isSome()) {
 | 
						|
    mPromise->get()->MaybeResolve(aResponse);
 | 
						|
  } else {
 | 
						|
    mContentResponseCallback(aResponse);
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP ContentAnalysisCallback::Error(nsresult aError) {
 | 
						|
  if (mPromise.isSome()) {
 | 
						|
    mPromise->get()->MaybeReject(aError);
 | 
						|
  } else {
 | 
						|
    mErrorCallback(aError);
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
ContentAnalysisCallback::ContentAnalysisCallback(RefPtr<dom::Promise> aPromise)
 | 
						|
    : mPromise(Some(new nsMainThreadPtrHolder<dom::Promise>(
 | 
						|
          "content analysis promise", aPromise))) {}
 | 
						|
 | 
						|
#undef LOGD
 | 
						|
#undef LOGE
 | 
						|
}  // namespace mozilla::contentanalysis
 |