// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/autofill/core/browser/legal_message_line.h" #include "base/i18n/message_formatter.h" #include "base/logging.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" namespace autofill { namespace { // Replace "{0}", "{1}", ... in |template_icu| with corresponding strings // from |display_texts|. Sets |out_message| to the resulting string, with // start position of each replacement in |out_offsets|. // Return false on failure. If false is returned then contents of |out_message| // and |out_offsets| are undefined. bool ReplaceTemplatePlaceholders( const base::string16& template_icu, const std::vector<base::string16>& display_texts, base::string16* out_message, std::vector<size_t>* out_offsets) { // Escape "$" -> "$$" for ReplaceStringPlaceholders(). // // Edge cases: // 1. Two or more consecutive $ characters will be incorrectly expanded // ("$$" -> "$$$$", which ReplaceStringPlaceholders() then turns into // "$$$"). // // 2. "${" will cause false to be returned. "${0}" will expand to "$${0}". // FormatWithNumberedArgs() turns it into "$$$1", which // ReplaceStringPlaceholders() then turns into "$$1" without doing the // parameter replacement. This causes false to be returned because each // parameter is not used exactly once. // // Both of these cases are noted in the header file, and are unlikely to // occur in any actual legal message. base::string16 template_icu_escaped; base::ReplaceChars(template_icu, base::ASCIIToUTF16("$"), base::ASCIIToUTF16("$$"), &template_icu_escaped); // Replace "{0}" -> "$1", "{1}" -> "$2", ... to prepare |template_dollars| // for ReplaceStringPlaceholders(). base::string16 template_dollars = base::i18n::MessageFormatter::FormatWithNumberedArgs( template_icu_escaped, "$1", "$2", "$3", "$4", "$5", "$6", "$7"); // FormatWithNumberedArgs() returns an empty string on failure. if (template_dollars.empty() && !template_icu.empty()) return false; // Replace "$1", "$2", ... with the display text of each parameter. *out_message = base::ReplaceStringPlaceholders(template_dollars, display_texts, out_offsets); // Each parameter must be used exactly once. If a parameter is unused or // used more than once then it can't be determined which |offsets| entry // corresponds to which parameter. return out_offsets->size() == display_texts.size(); } } // namespace LegalMessageLine::Link::Link(size_t start, size_t end, const std::string& url_spec) : range(start, end), url(url_spec) {} LegalMessageLine::Link::~Link() {} LegalMessageLine::LegalMessageLine() {} LegalMessageLine::LegalMessageLine(const LegalMessageLine& other) = default; LegalMessageLine::~LegalMessageLine() {} // static bool LegalMessageLine::Parse(const base::DictionaryValue& legal_message, LegalMessageLines* out) { const base::ListValue* lines_list = nullptr; if (legal_message.GetList("line", &lines_list)) { LegalMessageLines lines; lines.reserve(lines_list->GetSize()); for (size_t i = 0; i < lines_list->GetSize(); ++i) { lines.emplace_back(LegalMessageLine()); const base::DictionaryValue* single_line; if (!lines_list->GetDictionary(i, &single_line) || !lines.back().ParseLine(*single_line)) return false; } out->swap(lines); } return true; } bool LegalMessageLine::ParseLine(const base::DictionaryValue& line) { DCHECK(text_.empty()); DCHECK(links_.empty()); // |display_texts| elements are the strings that will be substituted for // "{0}", "{1}", etc. in the template string. std::vector<base::string16> display_texts; // Process all the template parameters. const base::ListValue* template_parameters = nullptr; if (line.GetList("template_parameter", &template_parameters)) { display_texts.resize(template_parameters->GetSize()); links_.reserve(template_parameters->GetSize()); for (size_t parameter_index = 0; parameter_index < template_parameters->GetSize(); ++parameter_index) { const base::DictionaryValue* single_parameter; std::string url; if (!template_parameters->GetDictionary(parameter_index, &single_parameter) || !single_parameter->GetString("display_text", &display_texts[parameter_index]) || !single_parameter->GetString("url", &url)) return false; links_.emplace_back(0, 0, url); } } // Read the template string. It's a small subset of the ICU message format // syntax. base::string16 template_icu; if (!line.GetString("template", &template_icu)) return false; // Replace the placeholders in |template_icu| with strings from // |display_texts|, and store the start position of each replacement in // |offsets|. std::vector<size_t> offsets; if (!ReplaceTemplatePlaceholders(template_icu, display_texts, &text_, &offsets)) return false; // Fill in range values for all links. for (size_t offset_index = 0; offset_index < offsets.size(); ++offset_index) { size_t range_start = offsets[offset_index]; links_[offset_index].range = gfx::Range( range_start, range_start + display_texts[offset_index].size()); } return true; } } // namespace autofill