blob: 561d084e2d49d77390a79eb28cba80f79691fcf9 [file] [log] [blame]
// Copyright 2023 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/plus_addresses/plus_address_parsing_utils.h"
#include <optional>
#include <utility>
#include "base/strings/pattern.h"
#include "base/strings/string_number_conversions.h"
#include "components/affiliations/core/browser/affiliation_utils.h"
#include "components/plus_addresses/plus_address_types.h"
#include "components/plus_addresses/webdata/plus_address_webdata_service.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
namespace plus_addresses {
namespace {
// Creates a PlusProfile for `dict` if it fits this schema (in TS notation):
// {
// "ProfileId": string,
// "facet": string,
// "plusEmail": {
// "plusAddress": string,
// "plusMode": string,
// }
// }
// Returns nullopt if none of the values are parsed.
std::optional<PlusProfile> ParsePlusProfileFromV1Dict(base::Value::Dict dict) {
std::string profile_id;
std::string facet_str;
std::string plus_address;
std::optional<bool> is_confirmed;
for (std::pair<const std::string&, base::Value&> entry : dict) {
auto [key, val] = entry;
if (base::MatchPattern(key, "*ProfileId") && val.is_string()) {
profile_id = std::move(val.GetString());
continue;
}
if (base::MatchPattern(key, "facet") && val.is_string()) {
facet_str = std::move(val.GetString());
continue;
}
if (base::MatchPattern(key, "*Email") && val.is_dict()) {
for (std::pair<const std::string&, base::Value&> email_entry :
val.GetDict()) {
auto [email_key, email_val] = email_entry;
if (!email_val.is_string()) {
continue;
}
if (base::MatchPattern(email_key, "*Address")) {
plus_address = std::move(email_val.GetString());
}
if (base::MatchPattern(email_key, "*Mode")) {
is_confirmed =
!base::MatchPattern(email_val.GetString(), "*UNSPECIFIED");
}
}
}
}
if (profile_id.empty() || facet_str.empty() || plus_address.empty() ||
!is_confirmed.has_value()) {
return std::nullopt;
}
if (!IsSyncingPlusAddresses()) {
return PlusProfile(std::move(profile_id), std::move(facet_str),
std::move(plus_address), *is_confirmed);
}
affiliations::FacetURI facet =
affiliations::FacetURI::FromPotentiallyInvalidSpec(facet_str);
if (!facet.is_valid()) {
return std::nullopt;
}
return PlusProfile(std::move(profile_id), std::move(facet),
std::move(plus_address), *is_confirmed);
}
// Creates a list of PlusProfiles by parsing each dict-value in `list` that
// fits this schema (in TS notation):
// {
// "facet": string,
// "plusEmail": {
// "plusAddress": string
// "plusMode": string,
// }
// }[]
// The returned list only contains PlusProfiles which could be parsed.
std::vector<PlusProfile> ParsePlusProfilesFromV1ProfileList(
base::Value::List list) {
std::vector<PlusProfile> profiles;
profiles.reserve(list.size());
for (base::Value& entry : list) {
if (!entry.is_dict()) {
continue;
}
if (std::optional<PlusProfile> maybe_profile =
ParsePlusProfileFromV1Dict(std::move(entry.GetDict()))) {
profiles.push_back(std::move(*maybe_profile));
}
}
return profiles;
}
} // namespace
std::optional<PlusProfile> ParsePlusProfileFromV1Create(
data_decoder::DataDecoder::ValueOrError response) {
if (!response.has_value() || !response->is_dict()) {
return std::nullopt;
}
// Use iterators to avoid looking up by JSON keys.
for (std::pair<const std::string&, base::Value&> first_level_entry :
response->GetDict()) {
auto [first_key, first_val] = first_level_entry;
if (base::MatchPattern(first_key, "*Profile") && first_val.is_dict()) {
return ParsePlusProfileFromV1Dict(std::move(first_val.GetDict()));
}
}
return std::nullopt;
}
std::optional<PlusAddressMap> ParsePlusAddressMapFromV1List(
data_decoder::DataDecoder::ValueOrError response) {
if (!response.has_value() || !response->is_dict()) {
return std::nullopt;
}
// Use iterators to avoid looking up by JSON keys.
for (std::pair<const std::string&, base::Value&> first_level_entry :
response->GetDict()) {
auto [first_key, first_val] = first_level_entry;
if (base::MatchPattern(first_key, "*Profiles") && first_val.is_list()) {
PlusAddressMap site_to_plus_address;
// Parse the list of profiles and add the result to the mapping.
for (PlusProfile& profile :
ParsePlusProfilesFromV1ProfileList(std::move(first_val.GetList()))) {
site_to_plus_address[std::move(absl::get<std::string>(profile.facet))] =
std::move(profile.plus_address);
}
return site_to_plus_address;
}
}
// Return nullopt if the `*Profiles` key is not present.
return std::nullopt;
}
} // namespace plus_addresses