blob: 9d44f603086ff95fedcf34fecd04fcf812f31dcb [file] [log] [blame]
// Copyright 2017 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 "services/data_decoder/public/cpp/safe_xml_parser.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/threading/thread_checker.h"
#include "base/unguessable_token.h"
#include "base/values.h"
#include "services/data_decoder/public/mojom/constants.mojom.h"
#include "services/data_decoder/public/mojom/xml_parser.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/service_manager/public/mojom/constants.mojom.h"
namespace data_decoder {
namespace {
// Class that does the actual parsing. Deletes itself when parsing is done.
class SafeXmlParser {
public:
SafeXmlParser(service_manager::Connector* connector,
const std::string& unsafe_xml,
XmlParserCallback callback,
const std::string& batch_id);
~SafeXmlParser();
private:
void ReportResults(base::Optional<base::Value> parsed_json,
const base::Optional<std::string>& error);
XmlParserCallback callback_;
mojom::XmlParserPtr xml_parser_ptr_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(SafeXmlParser);
};
SafeXmlParser::SafeXmlParser(service_manager::Connector* connector,
const std::string& unsafe_xml,
XmlParserCallback callback,
const std::string& batch_id)
: callback_(std::move(callback)) {
DCHECK(callback_); // Parsing without a callback is useless.
// If no batch ID has been provided, use a random instance ID to guarantee the
// connection is to a new service running in its own process.
service_manager::Identity identity(
mojom::kServiceName, service_manager::mojom::kInheritUserID,
batch_id.empty() ? base::UnguessableToken::Create().ToString()
: batch_id);
connector->BindInterface(identity, &xml_parser_ptr_);
// Unretained(this) is safe as the xml_parser_ptr_ is owned by this class.
xml_parser_ptr_.set_connection_error_handler(base::BindOnce(
&SafeXmlParser::ReportResults, base::Unretained(this),
/*parsed_xml=*/base::nullopt,
base::make_optional(
std::string("Connection error with the XML parser process."))));
xml_parser_ptr_->Parse(
unsafe_xml,
base::BindOnce(&SafeXmlParser::ReportResults, base::Unretained(this)));
}
SafeXmlParser::~SafeXmlParser() = default;
void SafeXmlParser::ReportResults(base::Optional<base::Value> parsed_xml,
const base::Optional<std::string>& error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::unique_ptr<base::Value> parsed_xml_ptr =
parsed_xml ? base::Value::ToUniquePtrValue(std::move(parsed_xml.value()))
: nullptr;
std::move(callback_).Run(std::move(parsed_xml_ptr), error);
// This should be the last interaction with this instance, safely delete.
delete this;
}
} // namespace
const base::Value* GetXmlElementChildren(const base::Value& element) {
if (!element.is_dict())
return nullptr;
return element.FindKeyOfType(mojom::XmlParser::kChildrenKey,
base::Value::Type::LIST);
}
std::string GetXmlQualifiedName(const std::string& name_space,
const std::string& name) {
return name_space.empty() ? name : name_space + ":" + name;
}
void ParseXml(service_manager::Connector* connector,
const std::string& unsafe_xml,
XmlParserCallback callback,
const std::string& batch_id) {
new SafeXmlParser(connector, unsafe_xml, std::move(callback), batch_id);
}
bool IsXmlElementNamed(const base::Value& element, const std::string& name) {
if (!element.is_dict())
return false;
const base::Value* tag_text = element.FindKeyOfType(
mojom::XmlParser::kTagKey, base::Value::Type::STRING);
return tag_text ? tag_text->GetString() == name : false;
}
bool IsXmlElementOfType(const base::Value& element, const std::string& type) {
if (!element.is_dict())
return false;
const base::Value* type_text = element.FindKeyOfType(
mojom::XmlParser::kTypeKey, base::Value::Type::STRING);
return type_text ? type_text->GetString() == type : false;
}
bool GetXmlElementTagName(const base::Value& element, std::string* tag_name) {
DCHECK(tag_name);
if (!element.is_dict())
return false;
const base::Value* tag_text = element.FindKeyOfType(
mojom::XmlParser::kTagKey, base::Value::Type::STRING);
if (!tag_text)
return false;
*tag_name = tag_text->GetString();
return true;
}
bool GetXmlElementText(const base::Value& element, std::string* text) {
DCHECK(text);
const base::Value* children = GetXmlElementChildren(element);
if (!children)
return false;
const base::Value* text_node = nullptr;
for (const base::Value& value : children->GetList()) {
if (IsXmlElementOfType(value, mojom::XmlParser::kTextNodeType) ||
IsXmlElementOfType(value, mojom::XmlParser::kCDataNodeType)) {
text_node = &value;
break;
}
}
if (!text_node)
return false;
const base::Value* text_value = text_node->FindKeyOfType(
mojom::XmlParser::kTextKey, base::Value::Type::STRING);
;
*text = text_value ? text_value->GetString() : "";
return true;
}
bool GetXmlElementNamespacePrefix(const base::Value& element,
const std::string& namespace_uri,
std::string* prefix) {
prefix->clear();
const base::Value* namespaces = element.FindKeyOfType(
mojom::XmlParser::kNamespacesKey, base::Value::Type::DICTIONARY);
if (!namespaces)
return false;
// The namespaces dictionary is prefix -> URI, so we have to do a reverse
// lookup.
for (const auto& item : namespaces->DictItems()) {
if (item.second.GetString() == namespace_uri) {
*prefix = item.first;
return true;
}
}
return false;
}
int GetXmlElementChildrenCount(const base::Value& element,
const std::string& name) {
const base::Value* children = GetXmlElementChildren(element);
if (!children)
return 0;
int child_count = 0;
for (const base::Value& value : children->GetList()) {
DCHECK(value.is_dict());
std::string tag_name;
bool success = GetXmlElementTagName(value, &tag_name);
if (success && tag_name == name)
child_count++;
}
return child_count;
}
const base::Value* GetXmlElementChildWithType(const base::Value& element,
const std::string& type) {
const base::Value* children = GetXmlElementChildren(element);
if (!children)
return nullptr;
for (const base::Value& value : children->GetList()) {
DCHECK(value.is_dict());
if (IsXmlElementOfType(value, type)) {
return &value;
}
}
return nullptr;
}
const base::Value* GetXmlElementChildWithTag(const base::Value& element,
const std::string& tag) {
const base::Value* children = GetXmlElementChildren(element);
if (!children)
return nullptr;
for (const base::Value& value : children->GetList()) {
DCHECK(value.is_dict());
if (IsXmlElementNamed(value, tag))
return &value;
}
return nullptr;
}
bool GetAllXmlElementChildrenWithTag(
const base::Value& element,
const std::string& tag,
std::vector<const base::Value*>* children_out) {
const base::Value* children = GetXmlElementChildren(element);
if (!children)
return false;
bool found = false;
for (const base::Value& child : children->GetList()) {
DCHECK(child.is_dict());
if (IsXmlElementNamed(child, tag)) {
found = true;
children_out->push_back(&child);
}
}
return found;
}
const base::Value* FindXmlElementPath(
const base::Value& element,
std::initializer_list<base::StringPiece> path,
bool* unique_path) {
const base::Value* cur = nullptr;
if (unique_path)
*unique_path = true;
for (const base::StringPiece component_piece : path) {
std::string component(component_piece);
if (!cur) {
// First element has to match the current node.
if (!IsXmlElementNamed(element, component))
return nullptr;
cur = &element;
continue;
}
const base::Value* new_cur = GetXmlElementChildWithTag(*cur, component);
if (!new_cur)
return nullptr;
if (unique_path && *unique_path &&
GetXmlElementChildrenCount(*cur, component) > 1)
*unique_path = false;
cur = new_cur;
}
return cur;
}
std::string GetXmlElementAttribute(const base::Value& element,
const std::string& element_name) {
if (!element.is_dict())
return "";
const base::Value* attributes = element.FindKeyOfType(
mojom::XmlParser::kAttributesKey, base::Value::Type::DICTIONARY);
if (!attributes)
return "";
const base::Value* value =
attributes->FindKeyOfType(element_name, base::Value::Type::STRING);
return value ? value->GetString() : "";
}
} // namespace data_decoder