blob: dc28aebb70320695c657ee6d77e07e9eef6a5d49 [file] [log] [blame]
// 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,
bool escape_apostrophes) {
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, escape_apostrophes))
return false;
}
out->swap(lines);
}
return true;
}
bool LegalMessageLine::ParseLine(const base::DictionaryValue& line,
bool escape_apostrophes) {
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;
if (escape_apostrophes) {
// The ICU standard counts "'{" as beginning an escaped string literal, even
// if there's no closing apostrophe. This fails legal message templates
// where an apostrophe precedes the template parameter, like "l'{1}" in
// Italian. Therefore, when |escape_apostrophes| is true, escape all
// apostrophes in the string by doubling them up.
// http://www.icu-project.org/apiref/icu4c/messagepattern_8h.html#af6e0757e0eb81c980b01ee5d68a9978b
base::ReplaceChars(template_icu, base::ASCIIToUTF16("'"),
base::ASCIIToUTF16("''"), &template_icu);
}
// 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