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

#include "components/attribution_reporting/parsing_utils.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <cmath>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/containers/flat_set.h"
#include "base/containers/flat_tree.h"
#include "base/containers/to_vector.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/abseil_string_number_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "base/values.h"
#include "components/aggregation_service/parsing_utils.h"
#include "components/attribution_reporting/aggregatable_utils.h"
#include "components/attribution_reporting/constants.h"
#include "components/attribution_reporting/suitable_origin.h"
#include "third_party/abseil-cpp/absl/numeric/int128.h"
#include "url/origin.h"

namespace attribution_reporting {

namespace {

constexpr char kDebugKey[] = "debug_key";
constexpr char kDebugReporting[] = "debug_reporting";

template <typename T>
base::expected<std::optional<T>, ParseError> ParseIntegerFromString(
    const base::Value::Dict& dict,
    std::string_view key,
    bool (*parse)(std::string_view, T*)) {
  const base::Value* value = dict.Find(key);
  if (!value) {
    return std::nullopt;
  }

  T parsed_val;
  if (const std::string* str = value->GetIfString();
      !str || !parse(*str, &parsed_val)) {
    return base::unexpected(ParseError());
  }
  return parsed_val;
}

}  // namespace

base::expected<absl::uint128, ParseError> ParseAggregationKeyPiece(
    const base::Value& value) {
  const std::string* str = value.GetIfString();
  if (!str) {
    return base::unexpected(ParseError());
  }

  absl::uint128 key_piece;

  if (!base::StartsWith(*str, "0x", base::CompareCase::INSENSITIVE_ASCII) ||
      !base::HexStringToUInt128(*str, &key_piece)) {
    return base::unexpected(ParseError());
  }

  return key_piece;
}

std::string HexEncodeAggregationKey(absl::uint128 value) {
  std::ostringstream out;
  out << "0x";
  out.setf(out.hex, out.basefield);
  out << value;
  return std::move(out).str();
}

base::expected<std::optional<uint64_t>, ParseError> ParseUint64(
    const base::Value::Dict& dict,
    std::string_view key) {
  return ParseIntegerFromString<uint64_t>(dict, key, &base::StringToUint64);
}

base::expected<std::optional<int64_t>, ParseError> ParseInt64(
    const base::Value::Dict& dict,
    std::string_view key) {
  return ParseIntegerFromString<int64_t>(dict, key, &base::StringToInt64);
}

base::expected<int64_t, ParseError> ParsePriority(
    const base::Value::Dict& dict) {
  return ParseInt64(dict, kPriority).transform(&ValueOrZero<int64_t>);
}

std::optional<uint64_t> ParseDebugKey(const base::Value::Dict& dict) {
  return ParseUint64(dict, kDebugKey).value_or(std::nullopt);
}

base::expected<std::optional<uint64_t>, ParseError> ParseDeduplicationKey(
    const base::Value::Dict& dict) {
  return ParseUint64(dict, kDeduplicationKey);
}

bool ParseDebugReporting(const base::Value::Dict& dict) {
  return dict.FindBool(kDebugReporting).value_or(false);
}

bool HasFractionalPart(double v) {
  double int_part;
  return std::modf(v, &int_part) != 0;
}

template <typename T>
base::expected<T, ParseError> ParseIntFromIntOrDouble(
    const base::Value& value) {
  // JSON serialization does not distinguish between integer and non-integer
  // numbers, but `base::Value` does. To be maximally compatible, we permit
  // `double` in addition to `int` as long as the `double`'s fractional part is
  // 0.

  if (std::optional<int> int_value = value.GetIfInt()) {
    if (!base::IsValueInRangeForNumericType<T>(*int_value)) {
      return base::unexpected(ParseError());
    }
    return static_cast<T>(*int_value);
  } else if (std::optional<double> double_value = value.GetIfDouble()) {
    if (HasFractionalPart(*double_value) ||
        !base::IsValueInRangeForNumericType<T>(*double_value)) {
      return base::unexpected(ParseError());
    }
    return static_cast<T>(*double_value);
  } else {
    return base::unexpected(ParseError());
  }
}

base::expected<base::TimeDelta, ParseError> ParseLegacyDuration(
    const base::Value& value,
    const base::TimeDelta clamp_min,
    const base::TimeDelta clamp_max) {
  // Note: The full range of uint64 seconds cannot be represented in the
  // resulting `base::TimeDelta`, but this is fine because `base::Seconds()`
  // properly clamps out-of-bound values and because the Attribution
  // Reporting API itself clamps values to 30 days:
  // https://wicg.github.io/attribution-reporting-api/#valid-source-expiry-range

  base::TimeDelta duration;

  if (const std::string* str = value.GetIfString()) {
    uint64_t seconds;
    if (!base::StringToUint64(*str, &seconds)) {
      return base::unexpected(ParseError());
    }
    duration = base::Seconds(seconds);
  } else {
    ASSIGN_OR_RETURN(duration, ParseDuration(value));
  }

  if (duration.is_negative()) {
    return base::unexpected(ParseError());
  }

  return std::clamp(duration, clamp_min, clamp_max);
}

base::expected<base::TimeDelta, ParseError> ParseDuration(
    const base::Value& value) {
  if (std::optional<int> int_value = value.GetIfInt()) {
    return base::Seconds(*int_value);
  } else if (std::optional<double> double_value = value.GetIfDouble()) {
    if (HasFractionalPart(*double_value)) {
      return base::unexpected(ParseError());
    }
    return base::Seconds(*double_value);
  } else {
    return base::unexpected(ParseError());
  }
}

base::expected<std::optional<SuitableOrigin>, ParseError>
ParseAggregationCoordinator(const base::Value::Dict& dict) {
  const base::Value* value = dict.Find(kAggregationCoordinatorOrigin);

  // The default value is used for backward compatibility prior to this
  // attribute being added, but ideally this would invalidate the registration
  // if other aggregatable fields were present.
  if (!value) {
    return std::nullopt;
  }

  const std::string* str = value->GetIfString();
  if (!str) {
    return base::unexpected(ParseError());
  }

  std::optional<url::Origin> aggregation_coordinator =
      aggregation_service::ParseAggregationCoordinator(*str);
  if (!aggregation_coordinator.has_value()) {
    return base::unexpected(ParseError());
  }
  auto aggregation_coordinator_origin =
      SuitableOrigin::Create(*std::move(aggregation_coordinator));
  CHECK(aggregation_coordinator_origin.has_value());
  return *std::move(aggregation_coordinator_origin);
}

base::expected<int, ParseError> ParseAggregatableValue(const base::Value& v) {
  ASSIGN_OR_RETURN(int value, ParseInt(v));
  if (!IsAggregatableValueInRange(value)) {
    return base::unexpected(ParseError());
  }
  return value;
}

void SerializeUint64(base::Value::Dict& dict,
                     std::string_view key,
                     uint64_t value) {
  dict.Set(key, base::NumberToString(value));
}

void SerializeInt64(base::Value::Dict& dict,
                    std::string_view key,
                    int64_t value) {
  dict.Set(key, base::NumberToString(value));
}

void SerializePriority(base::Value::Dict& dict, int64_t priority) {
  SerializeInt64(dict, kPriority, priority);
}

void SerializeDebugKey(base::Value::Dict& dict,
                       std::optional<uint64_t> debug_key) {
  if (debug_key) {
    SerializeUint64(dict, kDebugKey, *debug_key);
  }
}

void SerializeDebugReporting(base::Value::Dict& dict, bool debug_reporting) {
  dict.Set(kDebugReporting, debug_reporting);
}

void SerializeDeduplicationKey(base::Value::Dict& dict,
                               std::optional<uint64_t> dedup_key) {
  if (dedup_key) {
    SerializeUint64(dict, kDeduplicationKey, *dedup_key);
  }
}

void SerializeTimeDeltaInSeconds(base::Value::Dict& dict,
                                 std::string_view key,
                                 base::TimeDelta value) {
  int64_t seconds = value.InSeconds();
  if (base::IsValueInRangeForNumericType<int>(seconds)) {
    dict.Set(key, static_cast<int>(seconds));
  } else {
    SerializeInt64(dict, key, seconds);
  }
}

base::expected<int, ParseError> ParseInt(const base::Value& value) {
  return ParseIntFromIntOrDouble<int>(value);
}

base::expected<uint32_t, ParseError> ParseUint32(const base::Value& value) {
  // Assumes that all `uint32_t` can be represented either by `int` or `double`,
  // and that when represented internally by `base::Value` as an `int`, can be
  // precisely represented by `double`.
  //
  // TODO(apaseltiner): Consider test coverage for all `uint32_t` values, or
  // some kind of fuzzer.
  return ParseIntFromIntOrDouble<uint32_t>(value);
}

base::expected<uint32_t, ParseError> ParsePositiveUint32(
    const base::Value& value) {
  ASSIGN_OR_RETURN(uint32_t int_value, ParseUint32(value));
  if (int_value == 0) {
    return base::unexpected(ParseError());
  }
  return int_value;
}

base::Value Uint32ToJson(uint32_t value) {
  // All `uint32_t` can be represented exactly by `double`.
  return base::IsValueInRangeForNumericType<int>(value)
             ? base::Value(static_cast<int>(value))
             : base::Value(static_cast<double>(value));
}

base::expected<base::flat_set<std::string>, StringSetError> ExtractStringSet(
    base::Value::List list,
    const size_t max_string_size,
    const size_t max_set_size) {
  for (const base::Value& item : list) {
    const std::string* string = item.GetIfString();
    if (!string) {
      return base::unexpected(StringSetError::kWrongType);
    }

    if (string->size() > max_string_size) {
      return base::unexpected(StringSetError::kStringTooLong);
    }
  }

  std::ranges::sort(list);
  auto repeated = std::ranges::unique(list);
  list.erase(repeated.begin(), repeated.end());

  if (list.size() > max_set_size) {
    return base::unexpected(StringSetError::kSetTooLong);
  }

  return base::flat_set<std::string>(
      base::sorted_unique, base::ToVector(list, [](base::Value& item) {
        return std::move(item).TakeString();
      }));
}

}  // namespace attribution_reporting