Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/toolkit/components/contentanalysis/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 102 kB image not shown  

Quelle  ContentAnalysis.cpp   Sprache: 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/BrowserParent.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/DataTransfer.h"
#include "mozilla/dom/Directory.h"
#include "mozilla/dom/DragEvent.h"
#include "mozilla/dom/GetFilesHelper.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/SpinEventLoopUntil.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPrefs_browser.h"
#include "nsAppRunner.h"
#include "nsBaseClipboard.h"
#include "nsComponentManagerUtils.h"
#include "nsIClassInfoImpl.h"
#include "nsIFile.h"
#include "nsIGlobalObject.h"
#include "nsIObserverService.h"
#include "nsIOutputStream.h"
#include "nsIPrintSettings.h"
#include "nsIStorageStream.h"
#include "nsISupportsPrimitives.h"
#include "nsITransferable.h"
#include "ScopedNSSTypes.h"
#include "xpcpublic.h"

#include <algorithm>
#include <sstream>
#include <string>

#ifdef XP_WIN
#  include <windows.h>
#  define SECURITY_WIN32 1
#  include <security.h>
#  include "mozilla/NativeNt.h"
#  include "mozilla/WinDllServices.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* kPipePathNamePref = "browser.contentanalysis.pipe_path_name";
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);
  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) {
  nsCOMPtr<nsIFile> file;
  MOZ_TRY(NS_NewLocalFile(aFilePath, getter_AddRefs(file)));
  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

/* static */ bool nsIContentAnalysis::MightBeActive() {
  // A DLP connection is not permitted to be added/removed while the
  // browser is running, so we can cache this.
  // Furthermore, if this is set via enterprise policy the pref will be locked
  // so users won't be able to change it.
  // Ideally we would make this a mirror: once pref, but this interacts in
  // some weird ways with the enterprise policy for testing purposes.
  static bool sIsEnabled =
      mozilla::StaticPrefs::browser_contentanalysis_enabled();
  // Note that we can't check gAllowContentAnalysis here because it
  // only gets set in the parent process.
  return sIsEnabled;
}

namespace mozilla::contentanalysis {
ContentAnalysisRequest::~ContentAnalysisRequest() {
#ifdef XP_WIN
  CloseHandle(mPrintDataHandle);
#endif
}

NS_IMETHODIMP
ContentAnalysisRequest::GetAnalysisType(AnalysisType* aAnalysisType) {
  *aAnalysisType = mAnalysisType;
  return NS_OK;
}

NS_IMETHODIMP
ContentAnalysisRequest::GetReason(Reason* aReason) {
  *aReason = mReason;
  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::GetPrintDataHandle(uint64_t* aPrintDataHandle) {
#ifdef XP_WIN
  uintptr_t printDataHandle = reinterpret_cast<uintptr_t>(mPrintDataHandle);
  uint64_t printDataValue = static_cast<uint64_t>(printDataHandle);
  *aPrintDataHandle = printDataValue;
  return NS_OK;
#else
  return NS_ERROR_NOT_IMPLEMENTED;
#endif
}

NS_IMETHODIMP
ContentAnalysisRequest::GetPrinterName(nsAString& aPrinterName) {
  aPrinterName = mPrinterName;
  return NS_OK;
}

NS_IMETHODIMP
ContentAnalysisRequest::GetPrintDataSize(uint64_t* aPrintDataSize) {
#ifdef XP_WIN
  *aPrintDataSize = mPrintDataSize;
  return NS_OK;
#else
  return NS_ERROR_NOT_IMPLEMENTED;
#endif
}

NS_IMETHODIMP
ContentAnalysisRequest::GetUrl(nsIURI** aUrl) {
  NS_ENSURE_ARG_POINTER(aUrl);
  NS_IF_ADDREF(*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, nsString&& aClientSignatureSetting,
    bool aIsPerUser) {
  MOZ_ASSERT(!NS_IsMainThread());

  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");
#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\"",
           dependentOrgName.getW());
      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;
}

ContentAnalysisRequest::ContentAnalysisRequest(
    AnalysisType aAnalysisType, Reason aReason, nsString aString,
    bool aStringIsFilePath, nsCString aSha256Digest, nsCOMPtr<nsIURI> aUrl,
    OperationType aOperationType, dom::WindowGlobalParent* aWindowGlobalParent)
    : mAnalysisType(aAnalysisType),
      mReason(aReason),
      mUrl(std::move(aUrl)),
      mSha256Digest(std::move(aSha256Digest)),
      mOperationTypeForDisplay(aOperationType),
      mWindowGlobalParent(aWindowGlobalParent) {
  MOZ_ASSERT(aAnalysisType != AnalysisType::ePrint,
             "Print should use other ContentAnalysisRequest constructor!");
  MOZ_ASSERT(aReason != nsIContentAnalysisRequest::Reason::ePrintPreviewPrint &&
             aReason != nsIContentAnalysisRequest::Reason::eSystemDialogPrint);
  if (aStringIsFilePath) {
    mFilePath = std::move(aString);
  } else {
    mTextContent = std::move(aString);
  }
  if (mOperationTypeForDisplay == OperationType::eCustomDisplayString) {
    MOZ_ASSERT(aStringIsFilePath);
    nsresult rv = GetFileDisplayName(mFilePath, mOperationDisplayString);
    if (NS_FAILED(rv)) {
      mOperationDisplayString = u"file";
    }
  }

  mRequestToken = GenerateRequestToken();
}

ContentAnalysisRequest::ContentAnalysisRequest(
    const nsTArray<uint8_t> aPrintData, nsCOMPtr<nsIURI> aUrl,
    nsString aPrinterName, Reason aReason,
    dom::WindowGlobalParent* aWindowGlobalParent)
    : mAnalysisType(AnalysisType::ePrint),
      mReason(aReason),
      mUrl(std::move(aUrl)),
      mPrinterName(std::move(aPrinterName)),
      mWindowGlobalParent(aWindowGlobalParent) {
#ifdef XP_WIN
  LARGE_INTEGER dataContentLength;
  dataContentLength.QuadPart = static_cast<LONGLONG>(aPrintData.Length());
  mPrintDataHandle = ::CreateFileMappingW(
      INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, dataContentLength.HighPart,
      dataContentLength.LowPart, nullptr);
  if (mPrintDataHandle) {
    mozilla::nt::AutoMappedView view(mPrintDataHandle, FILE_MAP_ALL_ACCESS);
    memcpy(view.as<uint8_t>(), aPrintData.Elements(), aPrintData.Length());
    mPrintDataSize = aPrintData.Length();
  }
#else
  MOZ_ASSERT_UNREACHABLE(
      "Content Analysis is not supported on non-Windows platforms");
#endif
  // We currently only use this constructor when printing.
  MOZ_ASSERT(aReason == nsIContentAnalysisRequest::Reason::ePrintPreviewPrint ||
             aReason == nsIContentAnalysisRequest::Reason::eSystemDialogPrint);
  mOperationTypeForDisplay = OperationType::eOperationPrint;
  mRequestToken = GenerateRequestToken();
}

nsresult ContentAnalysisRequest::GetFileDigest(const nsAString& aFilePath,
                                               nsCString& aDigestString) {
  MOZ_DIAGNOSTIC_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;
  MOZ_TRY(NS_NewLocalFile(aFilePath, getter_AddRefs(file)));
  rv = file->OpenNSPRFileDesc(PR_RDONLY | nsIFile::OS_READAHEAD, 0, &fd);
  NS_ENSURE_SUCCESS(rv, rv);
  auto closeFile = MakeScopeExit([fd]() { PR_Close(fd); });
  constexpr uint32_t kBufferSize = 1024 * 1024;
  auto buffer = mozilla::MakeUnique<uint8_t[]>(kBufferSize);
  if (!buffer) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  PRInt32 bytesRead = PR_Read(fd, buffer.get(), kBufferSize);
  while (bytesRead != 0) {
    if (bytesRead == -1) {
      return NS_ErrorAccordingToNSPR();
    }
    digest.Update(mozilla::Span<const uint8_t>(buffer.get(), bytesRead));
    bytesRead = PR_Read(fd, buffer.get(), kBufferSize);
  }
  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, int64_t aRequestCount,
    content_analysis::sdk::ContentAnalysisRequest* aOut) {
  uint32_t timeout = StaticPrefs::browser_contentanalysis_agent_timeout();
  aOut->set_expires_at(time(nullptr) + timeout);

  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);

  nsIContentAnalysisRequest::Reason reason;
  rv = aIn->GetReason(&reason);
  NS_ENSURE_SUCCESS(rv, rv);
  auto sdkReason =
      static_cast<content_analysis::sdk::ContentAnalysisRequest::Reason>(
          reason);
  aOut->set_reason(sdkReason);

  nsCString requestToken;
  rv = aIn->GetRequestToken(requestToken);
  NS_ENSURE_SUCCESS(rv, rv);
  aOut->set_request_token(requestToken.get(), requestToken.Length());
  // Use the request_token as the user_action_id so we can cancel
  // it by request_token if needed.
  aOut->set_user_action_id(requestToken.get(), requestToken.Length());
  aOut->set_user_action_requests_count(aRequestCount);

  const std::string tag = "dlp";  // TODO:
  *aOut->add_tags() = tag;

  auto* requestData = aOut->mutable_request_data();

  nsCOMPtr<nsIURI> url;
  rv = aIn->GetUrl(getter_AddRefs(url));
  NS_ENSURE_SUCCESS(rv, rv);
  nsCString urlString;
  rv = url->GetSpec(urlString);
  NS_ENSURE_SUCCESS(rv, rv);
  if (!urlString.IsEmpty()) {
    requestData->set_url(urlString.get());
  }
  RefPtr<dom::WindowGlobalParent> windowGlobal;
  rv = aIn->GetWindowGlobalParent(getter_AddRefs(windowGlobal));
  NS_ENSURE_SUCCESS(rv, rv);
  if (windowGlobal) {
    nsString title;
    windowGlobal->GetDocumentTitle(title);
    requestData->set_tab_title(NS_ConvertUTF16toUTF8(title).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());
  }

  if (analysisType == nsIContentAnalysisRequest::AnalysisType::ePrint) {
#if XP_WIN
    uint64_t printDataHandle;
    MOZ_TRY(aIn->GetPrintDataHandle(&printDataHandle));
    if (!printDataHandle) {
      return NS_ERROR_OUT_OF_MEMORY;
    }
    aOut->mutable_print_data()->set_handle(printDataHandle);

    uint64_t printDataSize;
    MOZ_TRY(aIn->GetPrintDataSize(&printDataSize));
    aOut->mutable_print_data()->set_size(printDataSize);

    nsString printerName;
    MOZ_TRY(aIn->GetPrinterName(printerName));
    requestData->mutable_print_metadata()->set_printer_name(
        NS_ConvertUTF16toUTF8(printerName).get());
#else
    return NS_ERROR_NOT_IMPLEMENTED;
#endif
  } else {
    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;
}

namespace {
// We don't want this overload to be called for string parameters, so
// use std::enable_if
template <typename T>
typename std::enable_if_t<!std::is_same<std::string, std::decay_t<T>>::value,
                          void>
LogWithMaxLength(std::stringstream& ss, T value, size_t maxLength) {
  ss << value;
}

// 0 indicates no max length
template <typename T>
typename std::enable_if_t<std::is_same<std::string, std::decay_t<T>>::value,
                          void>
LogWithMaxLength(std::stringstream& ss, T value, size_t maxLength) {
  if (!maxLength || value.length() < maxLength) {
    ss << value;
  } else {
    ss << value.substr(0, maxLength) << " (truncated)";
  }
}
}  // namespace

static void LogRequest(
    const 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()) {                  \
    LogWithMaxLength(ss, (PBUF)->FUNC(), 500); \
    ss << "\n";                                \
  } else                                       \
    ss << ""                             \
       << "\n";

#define ADD_EXISTS(PBUF, NAME, FUNC) \
  ss << " " << (NAME) << ": "       \
     << ((PBUF)->has_##FUNC() ? "" : "") << "\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) {
  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) {}

/* 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 << ""                  \
       << "\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;
}

NS_IMETHODIMP
ContentAnalysisResponse::GetCancelError(CancelError* aCancelError) {
  *aCancelError = mCancelError;
  return NS_OK;
}

NS_IMETHODIMP
ContentAnalysisResponse::GetIsCachedResponse(bool* aIsCachedResponse) {
  *aIsCachedResponse = mIsCachedResponse;
  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 << ""                  \
       << "\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::SetCancelError(CancelError aCancelError) {
  mCancelError = aCancelError;
}

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;
}

static DefaultResult GetDefaultResultFromPref() {
  uint32_t value = StaticPrefs::browser_contentanalysis_default_result();
  if (value > static_cast<uint32_t>(DefaultResult::eLastValue)) {
    LOGE(
        "Invalid value for browser.contentanalysis.default_result pref "
        "value");
    return DefaultResult::eBlock;
  }
  return static_cast<DefaultResult>(value);
}
}  // 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>();
    if (GetDefaultResultFromPref() == DefaultResult::eAllow) {
      *aShouldAllowContent =
          result != NoContentAnalysisResult::DENY_DUE_TO_CANCELED;
    } else {
      // Note that we allow content if we're unable to get it (for example, if
      // there's clipboard content that is not text or file)
      *aShouldAllowContent =
          result == NoContentAnalysisResult::
                        ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE ||
          result == NoContentAnalysisResult::
                        ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS ||
          result == NoContentAnalysisResult::ALLOW_DUE_TO_SAME_TAB_SOURCE ||
          result == NoContentAnalysisResult::ALLOW_DUE_TO_COULD_NOT_GET_DATA;
    }
  } else {
    *aShouldAllowContent =
        ShouldAllowAction(mValue.as<nsIContentAnalysisResponse::Action>());
  }
  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();

  nsCOMPtr<nsIURI> nsiUrl;
  MOZ_ALWAYS_SUCCEEDS(aRequest->GetUrl(getter_AddRefs(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_match(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_match(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_match(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});
NS_IMPL_ISUPPORTS_CI(ContentAnalysisResponse, nsIContentAnalysisResponse);
NS_IMPL_ISUPPORTS(ContentAnalysisAcknowledgement,
                  nsIContentAnalysisAcknowledgement);
NS_IMPL_ISUPPORTS(ContentAnalysisCallback, nsIContentAnalysisCallback);
NS_IMPL_ISUPPORTS(ContentAnalysisResult, nsIContentAnalysisResult);
NS_IMPL_ISUPPORTS(ContentAnalysisDiagnosticInfo,
                  nsIContentAnalysisDiagnosticInfo);
NS_IMPL_ISUPPORTS(ContentAnalysis, nsIContentAnalysis, ContentAnalysis);

ContentAnalysis::ContentAnalysis()
    : mCaClientPromise(
          new ClientPromise::Private("ContentAnalysis::ContentAnalysis")),
      mClientCreationAttempted(false),
      mSetByEnterprise(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;
  if (!StaticPrefs::browser_contentanalysis_enabled()) {
    LOGD("Local DLP Content Analysis is not active");
    return NS_OK;
  }
  // Accessing mClientCreationAttempted, mSetByEnterprise and non-static prefs
  // so need to be on the main thread
  MOZ_ASSERT(NS_IsMainThread());
  // gAllowContentAnalysisArgPresent is only set in the parent process
  MOZ_ASSERT(XRE_IsParentProcess());
  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");
  // 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 = StaticPrefs::browser_contentanalysis_is_per_user();
    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),
         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__);
      return rv;
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
ContentAnalysis::GetMightBeActive(bool* aMightBeActive) {
  *aMightBeActive = nsIContentAnalysis::MightBeActive();
  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
}

NS_IMETHODIMP ContentAnalysis::SetCachedResponse(
    nsIURI* aURI, int32_t aClipboardSequenceNumber,
    const nsTArray<nsCString>& aFlavors,
    nsIContentAnalysisResponse::Action aAction) {
  mCachedClipboardResponse.SetCachedResponse(aURI, aClipboardSequenceNumber,
                                             aFlavors, aAction);
  return NS_OK;
}

NS_IMETHODIMP ContentAnalysis::GetCachedResponse(
    nsIURI* aURI, int32_t aClipboardSequenceNumber,
    const nsTArray<nsCString>& aFlavors,
    nsIContentAnalysisResponse::Action* aAction, bool* aIsValid) {
  auto action = mCachedClipboardResponse.GetCachedResponse(
      aURI, aClipboardSequenceNumber, aFlavors);
  *aIsValid = action.isSome();
  if (action.isSome()) {
    *aAction = *action;
  }
  return NS_OK;
}

nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken,
                                          nsresult aResult) {
  LOGD(
      "ContentAnalysis::CancelWithError dispatching to main thread for "
      "request %s",
      aRequestToken.get());
  return NS_DispatchToMainThread(NS_NewCancelableRunnableFunction(
      "ContentAnalysis::CancelWithError",
      [aResult, aRequestToken = std::move(aRequestToken)]() mutable {
        LOGD("ContentAnalysis::CancelWithError on main thread for request %s",
             aRequestToken.get());
        RefPtr<ContentAnalysis> owner = GetContentAnalysisFromService();
        if (!owner) {
          // May be shutting down
          return;
        }
        owner->SetLastResult(aResult);
        nsCOMPtr<nsIObserverService> obsServ =
            mozilla::services::GetObserverService();
        nsIContentAnalysisResponse::Action action =
            nsIContentAnalysisResponse::Action::eCanceled;
        // If we're shutting down, ignore the default result and just leave the
        // action as canceled. This fixes a hang if the default result is warn
        // and we shutdown during a request (bug 1912245)
        // Also ignore the default result if we're cancelling because another
        // request in the group said to.
        if (aResult != NS_ERROR_ILLEGAL_DURING_SHUTDOWN &&
            aResult != NS_ERROR_ABORT) {
          DefaultResult defaultResponse = GetDefaultResultFromPref();
          switch (defaultResponse) {
            case DefaultResult::eAllow:
              action = nsIContentAnalysisResponse::Action::eAllow;
              break;
            case DefaultResult::eWarn:
              action = nsIContentAnalysisResponse::Action::eWarn;
              break;
            case DefaultResult::eBlock:
              action = nsIContentAnalysisResponse::Action::eCanceled;
              break;
            default:
              MOZ_ASSERT(false);
              action = nsIContentAnalysisResponse::Action::eCanceled;
          }
        }
        RefPtr<ContentAnalysisResponse> response =
            ContentAnalysisResponse::FromAction(action, aRequestToken);
        response->SetOwner(owner);
        nsIContentAnalysisResponse::CancelError cancelError;
        switch (aResult) {
          case NS_ERROR_NOT_AVAILABLE:
            cancelError = nsIContentAnalysisResponse::CancelError::eNoAgent;
            break;
          case NS_ERROR_INVALID_SIGNATURE:
            cancelError =
                nsIContentAnalysisResponse::CancelError::eInvalidAgentSignature;
            break;
          case NS_ERROR_ABORT:
            cancelError = nsIContentAnalysisResponse::CancelError::
                eOtherRequestInGroupCancelled;
            break;
          case NS_ERROR_ILLEGAL_DURING_SHUTDOWN:
            cancelError = nsIContentAnalysisResponse::CancelError::eShutdown;
            break;
          default:
            cancelError = nsIContentAnalysisResponse::CancelError::eErrorOther;
            break;
        }
        response->SetCancelError(cancelError);
        Maybe<CallbackData> maybeCallbackData;
        {
          auto lock = owner->mCallbackMap.Lock();
          maybeCallbackData = lock->Extract(aRequestToken);
        }
        if (maybeCallbackData.isSome() &&
            action == nsIContentAnalysisResponse::Action::eWarn) {
          owner->SendWarnResponse(std::move(aRequestToken),
                                  std::move(*maybeCallbackData), response);
          return;
        }
        obsServ->NotifyObservers(response, "dlp-response", nullptr);
        if (maybeCallbackData.isNothing()) {
          LOGD("Content analysis did not find callback for token %s",
               aRequestToken.get());
          return;
        }
        nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder =
            maybeCallbackData->TakeCallbackHolder();
        if (callbackHolder) {
          if (action == nsIContentAnalysisResponse::Action::eCanceled) {
            callbackHolder->Error(aResult);
          } else {
            callbackHolder->ContentResult(response);
          }
        }
      }));
}

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,
    int64_t aRequestCount,
    const RefPtr<nsIContentAnalysisCallback>& aCallback) {
  MOZ_ASSERT(NS_IsMainThread());

  nsresult rv = NS_ERROR_FAILURE;
  // Set up the scope exit before checking the return
  // value so we will call Error() if this call failed.
  auto callbackCopy = aCallback;
  auto se = MakeScopeExit([&] {
    if (!NS_SUCCEEDED(rv)) {
      LOGE("RunAnalyzeRequestTask failed");
      callbackCopy->Error(rv);
    }
  });

  nsCString requestToken;
  rv = aRequest->GetRequestToken(requestToken);
  NS_ENSURE_SUCCESS(rv, rv);

  nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolderCopy(
      new nsMainThreadPtrHolder<nsIContentAnalysisCallback>(
          "content analysis callback", aCallback));
  CallbackData callbackData(std::move(callbackHolderCopy), aAutoAcknowledge);
  {
    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;
  }

  content_analysis::sdk::ContentAnalysisRequest pbRequest;
  rv = ConvertToProtobuf(aRequest, aRequestCount, &pbRequest);
  NS_ENSURE_SUCCESS(rv, rv);

  LOGD("Issuing ContentAnalysisRequest for token %s", requestToken.get());
  LogRequest(&pbRequest);
  nsCOMPtr<nsIObserverService> obsServ =
      mozilla::services::GetObserverService();
  // Avoid serializing the string here if no one is observing this message
  if (obsServ->HasObservers("dlp-request-sent-raw")) {
    std::string requestString = pbRequest.SerializeAsString();
    nsTArray<char16_t> requestArray;
    requestArray.SetLength(requestString.size() + 1);
    for (size_t i = 0; i < requestString.size(); ++i) {
      // Since NotifyObservers() expects a null-terminated string,
      // make sure none of these values are 0.
      requestArray[i] = requestString[i] + 0xFF00;
    }
    requestArray[requestString.size()] = 0;
    obsServ->NotifyObservers(this"dlp-request-sent-raw",
                             requestArray.Elements());
  }

  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 {
        LOGD("RunAnalyzeRequestTask on main thread about to send response");
        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;
        }
        owner->IssueResponse(response);
      }));
}

void ContentAnalysis::SendWarnResponse(
    nsCString&& aResponseRequestToken, CallbackData aCallbackData,
    RefPtr<ContentAnalysisResponse>& aResponse) {
  nsCOMPtr<nsIObserverService> obsServ =
      mozilla::services::GetObserverService();
  {
    auto warnResponseDataMap = mWarnResponseDataMap.Lock();
    warnResponseDataMap->InsertOrUpdate(
        aResponseRequestToken,
        WarnResponseData(std::move(aCallbackData), aResponse));
  }
  obsServ->NotifyObservers(aResponse, "dlp-response", nullptr);
}

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;
  }
  // Successfully made a request to the agent, so mark that we succeeded
  mLastResult = NS_OK;

  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);
  LOGD("Content analysis resolving response promise for token %s",
       responseRequestToken.get());
  nsIContentAnalysisResponse::Action action;
  DebugOnly<nsresult> rv = response->GetAction(&action);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  nsCOMPtr<nsIObserverService> obsServ =
      mozilla::services::GetObserverService();
  if (action == nsIContentAnalysisResponse::Action::eWarn) {
    SendWarnResponse(std::move(responseRequestToken),
                     std::move(*maybeCallbackData), response);
    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);
}

// Counts the number of times it receives an "allow content" and (1) calls
// ContentResult on mCallback when all requests are approved, (2) calls
// ContentResult when any one request is rejected, or (3) calls Error when any
// one fails.
class AnalyzeFilesInDirectoryCallback final
    : public mozilla::dom::GetFilesCallback,
      public nsIContentAnalysisCallback {
 public:
  NS_INLINE_DECL_REFCOUNTING_INHERITED(AnalyzeFilesInDirectoryCallback,
                                       mozilla::dom::GetFilesCallback)
  NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;

  AnalyzeFilesInDirectoryCallback(nsIContentAnalysisRequest* aRequest,
                                  bool aAutoAcknowledge,
                                  nsIContentAnalysisCallback* aCallback)
      : mRequest(aRequest),
        mAutoAcknowledge(aAutoAcknowledge),
        mCallback(aCallback) {}

  // -------------------
  // GetFilesCallback
  // -------------------

  void Callback(nsresult aStatus,
                const FallibleTArray<RefPtr<mozilla::dom::BlobImpl>>&
                    aBlobImpls) override {
    nsresult rv = aStatus;
    // Make sure we respond for CA on error.
    auto failedToGenerateCARequests = MakeScopeExit([this, &rv]() {
      // If we submit some CA requests, but hit an error that causes us to
      // stop sending more before we are done, then we don't cancel the
      // earlier requests -- their callbacks will be counted but ignored.
      Error(rv);
    });
    NS_ENSURE_SUCCESS_VOID(rv);

    RefPtr<nsIContentAnalysis> contentAnalysis =
        mozilla::components::nsIContentAnalysis::Service(&rv);
    NS_ENSURE_SUCCESS_VOID(rv);
    NS_ENSURE_TRUE_VOID(contentAnalysis);
    nsCOMPtr<nsIURI> url;
    rv = mRequest->GetUrl(getter_AddRefs(url));
    NS_ENSURE_SUCCESS_VOID(rv);
    nsIContentAnalysisRequest::AnalysisType analysisType;
    rv = mRequest->GetAnalysisType(&analysisType);
    NS_ENSURE_SUCCESS_VOID(rv);
    nsIContentAnalysisRequest::Reason reason;
    rv = mRequest->GetReason(&reason);
    NS_ENSURE_SUCCESS_VOID(rv);
    nsIContentAnalysisRequest::OperationType operationType;
    rv = mRequest->GetOperationTypeForDisplay(&operationType);
    NS_ENSURE_SUCCESS_VOID(rv);
    RefPtr<dom::WindowGlobalParent> windowGlobal;
    rv = mRequest->GetWindowGlobalParent(getter_AddRefs(windowGlobal));
    NS_ENSURE_SUCCESS_VOID(rv);

    for (const auto& blob : aBlobImpls) {
      if (blob->IsFile()) {
        nsAutoString pathString;
        mozilla::ErrorResult error;
        blob->GetMozFullPathInternal(pathString, error);
        rv = error.StealNSResult();
        NS_ENSURE_SUCCESS_VOID(rv);

        // Create and submit a new CA request for pathString.
        nsCString emptyDigestString;
        RefPtr<ContentAnalysisRequest> request = new ContentAnalysisRequest(
            analysisType, reason, pathString, true,
            std::move(emptyDigestString), url, operationType, windowGlobal);
        rv = contentAnalysis->AnalyzeContentRequestCallback(
            request, mAutoAcknowledge, this);
        NS_ENSURE_SUCCESS_VOID(rv);
        ++mNumCARequestsRemaining;
      } else {
        NS_WARNING("Got a non-file blob, can't do content analysis on it");
      }
    }

    failedToGenerateCARequests.release();
  }

  // -------------------
  // nsIContentAnalysisCallback
  // -------------------

  NS_IMETHOD ContentResult(nsIContentAnalysisResponse* aResponse) override {
    // TODO: When replying to a deny or an error, we should block the dialogs
    // for the remaining verdicts.
    if (mResponded) {
      return NS_OK;
    }

    bool allow;
    nsresult rv = aResponse->GetShouldAllowContent(&allow);
    if (NS_FAILED(rv)) {
      mResponded = true;
      mCallback->Error(rv);
      return NS_OK;
    }

    --mNumCARequestsRemaining;
    if (allow && mNumCARequestsRemaining > 0) {
      return NS_OK;
    }

    mResponded = true;
    nsCString token;
    rv = mRequest->GetRequestToken(token);
    if (NS_FAILED(rv)) {
      mCallback->Error(rv);
      return NS_OK;
    }

    auto action = allow ? nsIContentAnalysisResponse::Action::eAllow
                        : nsIContentAnalysisResponse::Action::eBlock;
    auto response = ContentAnalysisResponse::FromAction(action, token);
    mCallback->ContentResult(response);
    return NS_OK;
  }

  NS_IMETHOD Error(nsresult aRv) override {
    if (mResponded) {
      return NS_OK;
    }
    mResponded = true;
    mCallback->Error(aRv);
    return NS_OK;
  }

 private:
  virtual ~AnalyzeFilesInDirectoryCallback() = default;

  RefPtr<nsIContentAnalysisRequest> mRequest;
  bool mAutoAcknowledge;
  RefPtr<nsIContentAnalysisCallback> mCallback;

  // Number of file scans remaining for this folder scan.
  size_t mNumCARequestsRemaining = 0;

  // True if we have issued a response for the folder that this file request
  // came from.
  bool mResponded = false;
};

NS_IMPL_QUERY_INTERFACE(AnalyzeFilesInDirectoryCallback,
                        nsIContentAnalysisCallback)

Result<bool, nsresult>
ContentAnalysis::MaybeExpandAndAnalyzeFolderContentRequest(
    nsIContentAnalysisRequest* aRequest, bool aAutoAcknowledge,
    nsIContentAnalysisCallback* aCallback) {
  nsAutoString filename;
  nsresult rv = aRequest->GetFilePath(filename);
  NS_ENSURE_SUCCESS(rv, Err(rv));
  if (filename.IsEmpty()) {
    return false;
  }

#ifdef DEBUG
  // Confirm that there is no text content to analyze.  See comment on
  // mFilePath.
  nsAutoString textContent;
  rv = aRequest->GetTextContent(textContent);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  MOZ_ASSERT(textContent.IsEmpty());
#endif

  RefPtr<nsIFile> file;
  rv = NS_NewLocalFile(filename, getter_AddRefs(file));
  NS_ENSURE_SUCCESS(rv, Err(rv));

  bool exists;
  rv = file->Exists(&exists);
  NS_ENSURE_SUCCESS(rv, Err(rv));
  NS_ENSURE_TRUE(exists, false);

  bool isDir;
  rv = file->IsDirectory(&isDir);
  NS_ENSURE_SUCCESS(rv, Err(rv));
  if (!isDir) {
    return false;
  }

  // We just need to iterate over the directory, so use the junk scope
  RefPtr<mozilla::dom::Directory> directory = mozilla::dom::Directory::Create(
      xpc::NativeGlobal(xpc::PrivilegedJunkScope()), file);
  NS_ENSURE_TRUE(directory, Err(NS_ERROR_FAILURE));

  mozilla::dom::OwningFileOrDirectory owningDirectory;
  owningDirectory.SetAsDirectory() = directory;
  nsTArray<mozilla::dom::OwningFileOrDirectory> directoryArray{
      std::move(owningDirectory)};

  mozilla::ErrorResult error;
  RefPtr<mozilla::dom::GetFilesHelper> helper =
      mozilla::dom::GetFilesHelper::Create(directoryArray, true, error);
  rv = error.StealNSResult();
  NS_ENSURE_SUCCESS(rv, Err(rv));

  auto analyzeFilesCallback =
      mozilla::MakeRefPtr<AnalyzeFilesInDirectoryCallback>(
          aRequest, aAutoAcknowledge, aCallback);
  helper->AddCallback(analyzeFilesCallback);
  return true;
}

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) {
  MOZ_ASSERT(NS_IsMainThread());
  NS_ENSURE_ARG(aRequest);
  NS_ENSURE_ARG(aCallback);
  nsresult rv = AnalyzeContentRequestCallbackPrivate(aRequest, aAutoAcknowledge,
                                                     aCallback);
  if (NS_FAILED(rv)) {
    nsCString requestToken;
    nsresult requestTokenRv = aRequest->GetRequestToken(requestToken);
    NS_ENSURE_SUCCESS(requestTokenRv, requestTokenRv);
    CancelWithError(requestToken, rv);
  }
  return rv;
}

nsresult ContentAnalysis::AnalyzeContentRequestCallbackPrivate(
    nsIContentAnalysisRequest* aRequest, bool aAutoAcknowledge,
    nsIContentAnalysisCallback* aCallback) {
  bool wasFolder;
  MOZ_TRY_VAR(wasFolder, MaybeExpandAndAnalyzeFolderContentRequest(
                             aRequest, aAutoAcknowledge, aCallback));
  if (wasFolder) {
    return NS_OK;
  }

  // Make sure we send the notification first, so if we later return
  // an error the JS will handle it correctly.
  nsCOMPtr<nsIObserverService> obsServ =
      mozilla::services::GetObserverService();
  obsServ->NotifyObservers(aRequest, "dlp-request-made", nullptr);

  bool isActive;
  nsresult rv = GetIsActive(&isActive);
  NS_ENSURE_SUCCESS(rv, rv);
  if (!isActive) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  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
ContentAnalysis::CancelContentAnalysisRequest(const nsACString& aRequestToken,
                                              bool aNotifyObservers) {
  MOZ_ASSERT(NS_IsMainThread());
  nsCString requestToken(aRequestToken);

  Maybe<CallbackData> maybeEntry;
  {
    auto callbackMap = mCallbackMap.Lock();
    maybeEntry = callbackMap->Extract(requestToken);
  }
  LOGD("Content analysis cancelling request %s", requestToken.get());
  // Make sure the entry hasn't been cancelled already
  if (maybeEntry.isNothing()) {
    LOGD("Content analysis request not found when trying to cancel %s",
         requestToken.get());
    return NS_OK;
  }
  if (aNotifyObservers) {
    // CancelWithError will call the callbackHolder.
    CancelWithError(nsCString(aRequestToken), NS_ERROR_ABORT);
  } else {
    nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder =
        maybeEntry->TakeCallbackHolder();
    // Should only be called once
    MOZ_ASSERT(callbackHolder);
    if (callbackHolder) {
      callbackHolder->Error(NS_ERROR_ABORT);
    }
  }
  mCaClientPromise->Then(
      GetCurrentSerialEventTarget(), __func__,
      [requestToken](std::shared_ptr<content_analysis::sdk::Client> client) {
        auto owner = GetContentAnalysisFromService();
        if (!owner) {
          // May be shutting down
          return;
        }
        if (!client) {
          LOGE("CancelContentAnalysisRequest got a null client");
          return;
        }

        NS_DispatchBackgroundTask(
            NS_NewCancelableRunnableFunction(
                __func__,
                [requestToken = std::move(requestToken),
                 client = std::move(client)]() {
                  auto owner = GetContentAnalysisFromService();
                  if (!owner) {
                    // May be shutting down
                    return;
                  }

                  content_analysis::sdk::ContentAnalysisCancelRequests requests;
                  requests.set_user_action_id(requestToken.get(),
                                              requestToken.Length());
                  int err = client->CancelRequests(requests);
                  if (err != 0) {
                    LOGE(
                        "CancelContentAnalysisRequest got error %d for request "
                        "%s",
                        err, requestToken.get());
                  } else {
                    LOGD(
                        "CancelContentAnalysisRequest successfully cancelled "
                        "request "
                        "%s",
                        requestToken.get());
                  }
                }),
            NS_DISPATCH_EVENT_MAY_BLOCK);
      },
      [](nsresult rv) {
        LOGE("CancelContentAnalysisRequest failed to get the client");
      });
  return NS_OK;
}

NS_IMETHODIMP
ContentAnalysis::CancelAllRequests() {
  LOGD("CancelAllRequests running");
  MOZ_ASSERT(NS_IsMainThread());
  mCaClientPromise->Then(
      GetCurrentSerialEventTarget(), __func__,
      [&](std::shared_ptr<content_analysis::sdk::Client> client) {
        auto owner = GetContentAnalysisFromService();
        if (!owner) {
          // May be shutting down
          return;
        }
        nsTArray<nsCString> requestsToCancel;
        // Calling CancelWithError() with mCallbackMap held can lead
        // to deadlocks, so gather the keys and then call CancelWithError().
        {
          auto callbackMap = owner->mCallbackMap.Lock();
          auto keys = callbackMap->Keys();
          requestsToCancel.SetCapacity(keys.Count());
          for (const auto& key : keys) {
            requestsToCancel.AppendElement(key);
          }
        }
        for (const auto& requestToken : requestsToCancel) {
          owner->CancelWithError(nsCString(requestToken),
                                 NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
        }
        nsTArray<nsCString> warnRequestsToCancel;
        {
          auto warnResponseDataMap = owner->mWarnResponseDataMap.Lock();
          auto keys = warnResponseDataMap->Keys();
          warnRequestsToCancel.SetCapacity(keys.Count());
          for (const auto& key : keys) {
            warnRequestsToCancel.AppendElement(key);
          }
        }
        for (const auto& requestToken : warnRequestsToCancel) {
          LOGD(
              "Responding to warn dialog (from CancelAllRequests) for "
              "request %s",
              nsCString(requestToken).get());
          owner->RespondToWarnDialog(requestToken, false);
        }
        // This is surprisingly low in the method because we want to make sure
        // that we run CancelWithError() and RespondToWarnDialog() for our
        // requests even if we don't have a client.
        if (!client) {
          LOGE("CancelAllRequests got a null client");
          return;
        }
        requestsToCancel.AppendElements(std::move(warnRequestsToCancel));
        NS_DispatchBackgroundTask(
            NS_NewCancelableRunnableFunction(
                __func__,
                [client = std::move(client),
                 requestsToCancel = std::move(requestsToCancel)]() {
                  auto owner = GetContentAnalysisFromService();
                  if (!owner) {
                    // May be shutting down
                    return;
                  }
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=93 H=96 G=94

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.