910e62b5创建于 1月15日历史提交
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/updater/event_logger.h"

#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>

#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/system/sys_info.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/sequence_bound.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "chrome/enterprise_companion/telemetry_logger/proto/log_request.pb.h"
#include "chrome/updater/branded_constants.h"
#include "chrome/updater/configurator.h"
#include "chrome/updater/persisted_data.h"
#include "chrome/updater/protos/omaha_usage_stats_event.pb.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/updater_version.h"
#include "components/update_client/network.h"
#include "event_logger.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "url/gurl.h"

namespace updater {
namespace {

// Returns the logging cookie value which should be used in an event
// transmission. If a cookie is expired or not persisted, a special value is
// used to request a new token from the server. Expired cookies are cleared from
// the store.
std::string GetLoggingCookieValue(base::Time now,
                                  scoped_refptr<PersistedData> persisted_data) {
  static constexpr char kDefaultLoggingCookie[] = "\"\"";
  std::optional<PersistedData::Cookie> logging_cookie =
      persisted_data->GetRemoteLoggingCookie();
  if (!logging_cookie) {
    return kDefaultLoggingCookie;
  }
  if (logging_cookie->expiration <= now) {
    VLOG(1) << "Clearing expired logging cookie";
    persisted_data->ClearRemoteLoggingCookie();
    return kDefaultLoggingCookie;
  }
  return logging_cookie->value;
}

proto::Omaha4UsageStatsMetadata GetMetadata(
    UpdaterScope scope,
    scoped_refptr<Configurator> configurator,
    bool is_cloud_managed) {
  proto::Omaha4UsageStatsMetadata metadata;

  metadata.set_platform(configurator->GetOSLongName());
  metadata.set_os_version(base::SysInfo::OperatingSystemVersion());
  metadata.set_cpu_architecture(base::SysInfo::OperatingSystemArchitecture());
  metadata.set_o4_omaha_cohort_id(
      configurator->GetPersistedData()->GetCohort(kUpdaterAppId));
  metadata.set_app_version(kUpdaterVersion);
  metadata.set_is_machine(IsSystemInstall(scope));
  metadata.set_is_cbcm_managed(is_cloud_managed);
  std::optional<bool> externally_managed =
      configurator->IsMachineExternallyManaged();
  if (externally_managed.has_value()) {
    metadata.set_is_domain_joined(*externally_managed);
  }
  return metadata;
}

}  // namespace

using HttpRequestCallback =
    base::OnceCallback<void(std::optional<int> http_status,
                            std::optional<std::string> response_body)>;
using ::updater::proto::Omaha4Metric;
using ::updater::proto::Omaha4UsageStatsExtension;
using ::updater::proto::Omaha4UsageStatsMetadata;

RemoteLoggingDelegate::RemoteLoggingDelegate(
    UpdaterScope scope,
    const GURL& event_logging_url,
    bool is_cloud_managed,
    scoped_refptr<Configurator> configurator,
    std::unique_ptr<base::Clock> clock)
    : event_logging_url_(event_logging_url),
      metadata_(::updater::GetMetadata(scope, configurator, is_cloud_managed)),
      minimum_cooldown_(configurator->MinimumEventLoggingCooldown()),
      configurator_(configurator),
      clock_(std::move(clock)),
      persisted_data_(configurator_->GetUpdaterPersistedData()),
      main_sequence_(base::SequencedTaskRunner::GetCurrentDefault()) {
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

RemoteLoggingDelegate::~RemoteLoggingDelegate() = default;

void RemoteLoggingDelegate::StoreNextAllowedAttemptTime(
    base::Time time,
    base::OnceClosure callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  main_sequence_->PostTaskAndReply(
      FROM_HERE,
      base::BindOnce(
          [](scoped_refptr<PersistedData> persisted_data, base::Time time) {
            persisted_data->SetNextAllowedLoggingAttemptTime(time);
          },
          persisted_data_, time),
      std::move(callback));
}

void RemoteLoggingDelegate::DoPostRequest(
    const std::string& request_body,
    base::OnceCallback<void(std::optional<int> http_status,
                            std::optional<std::string> response_body)>
        callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  main_sequence_->PostTask(
      FROM_HERE,
      base::BindOnce(
          [](scoped_refptr<Configurator> configurator, const GURL& url,
             const std::string& request_body, base::Time now,
             base::OnceCallback<void(std::optional<int>,
                                     std::optional<std::string> response_body)>
                 callback) {
            std::unique_ptr<update_client::NetworkFetcher> fetcher =
                configurator->GetNetworkFetcherFactory()->Create();
            if (!fetcher) {
              VLOG(1) << "Failed to create network fetcher for logging request";
              base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
                  FROM_HERE, base::BindOnce(std::move(callback), std::nullopt,
                                            std::nullopt));
              return;
            }
            // The fetcher and response code are retained by
            // the completion callback.
            auto response_code =
                std::make_unique<std::optional<int>>(std::nullopt);
            std::optional<int>* response_code_ptr = response_code.get();
            update_client::NetworkFetcher* fetcher_ptr = fetcher.get();

            fetcher_ptr->PostRequest(
                url, request_body, "application/x-protobuffer",
                {{update_client::NetworkFetcher::kHeaderCookie,
                  base::StrCat(
                      {"NID=",
                       GetLoggingCookieValue(
                           now, configurator->GetUpdaterPersistedData())})}},
                base::BindRepeating(
                    [](std::optional<int>* response_code_out, int response_code,
                       int64_t content_length) {
                      *response_code_out = response_code;
                    },
                    response_code_ptr),
                /*progress_callback=*/base::DoNothing(),
                base::BindOnce(
                    [](base::Time now,
                       scoped_refptr<PersistedData> persisted_data,
                       HttpRequestCallback callback,
                       std::unique_ptr<std::optional<int>> response_code,
                       std::unique_ptr<update_client::NetworkFetcher> fetcher,
                       std::optional<std::string> response_body, int net_error,
                       const std::string& header_etag,
                       const std::string& header_x_cup_server_proof,
                       const std::string& header_set_cookie,
                       int64_t xheader_retry_after_sec) {
                      if (net_error) {
                        VLOG(1)
                            << "Upload failed due to net error " << net_error;
                        VLOG_IF(1, response_code->has_value())
                            << "HTTP response code: " << response_code->value();
                        base::SequencedTaskRunner::GetCurrentDefault()
                            ->PostTask(
                                FROM_HERE,
                                base::BindOnce(std::move(callback),
                                               std::nullopt, std::nullopt));
                        return;
                      }
                      std::optional<PersistedData::Cookie> logging_cookie =
                          ExtractEventLoggingCookie(now, header_set_cookie);
                      if (logging_cookie) {
                        persisted_data->SetRemoteLoggingCookie(*logging_cookie);
                      }
                      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
                          FROM_HERE,
                          base::BindOnce(std::move(callback), *response_code,
                                         response_body));
                    },
                    now, configurator->GetUpdaterPersistedData(),
                    std::move(callback), std::move(response_code),
                    std::move(fetcher)));
          },
          configurator_, event_logging_url_, request_body, clock_->Now(),
          base::BindPostTaskToCurrentDefault(std::move(callback))));
}

int RemoteLoggingDelegate::GetLogIdentifier() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return enterprise_companion::telemetry_logger::proto::CHROME_UPDATER;
}

std::string RemoteLoggingDelegate::AggregateAndSerializeEvents(
    base::span<Omaha4Metric> events) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  Omaha4UsageStatsExtension extension;
  *extension.mutable_metadata() = GetMetadata();
  for (const Omaha4Metric& metric : events) {
    *extension.add_metric() = metric;
  }
  return extension.SerializeAsString();
}

base::TimeDelta RemoteLoggingDelegate::MinimumCooldownTime() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return minimum_cooldown_;
}

Omaha4UsageStatsMetadata RemoteLoggingDelegate::GetMetadata() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return metadata_;
}

// Parses the NID cookie from a returned Cookie: HTTP header.
std::optional<PersistedData::Cookie> ExtractEventLoggingCookie(
    base::Time now,
    const std::string& set_cookie_value) {
  static constexpr base::TimeDelta kDefaultTtl = base::Days(180);
  std::optional<std::string> cookie_value;
  std::optional<base::Time> expiration;
  for (const std::string& s :
       base::SplitString(set_cookie_value, ";", base::TRIM_WHITESPACE,
                         base::SPLIT_WANT_NONEMPTY)) {
    std::optional<std::pair<std::string_view, std::string_view>> kv_pair =
        base::SplitStringOnce(s, "=");
    if (!kv_pair.has_value()) {
      continue;
    }
    auto [key, value] = *kv_pair;
    if (key == "NID") {
      if (!value.empty()) {
        cookie_value = value;
      }
    } else if (base::EqualsCaseInsensitiveASCII(key, "expires")) {
      base::Time parsed_time;
      if (base::Time::FromString(std::string(value).c_str(), &parsed_time)) {
        expiration = parsed_time;
      }
    }
  }

  if (!cookie_value) {
    return std::nullopt;
  }

  return PersistedData::Cookie{
      .value = *std::move(cookie_value),
      .expiration = expiration ? *expiration : now + kDefaultTtl,
  };
}

}  // namespace updater