Commit d94535b3 authored by Bin Zhao's avatar Bin Zhao Committed by Commit Bot

[DIAL] (1st) Added SafeDialAppInfoParser class to parse DIAL app info XML

Recent changes removed dial_device_description_parser_impl* from
chrome/utility/media_router and introduces a new SafeXmlParser.
(https://chromium-review.googlesource.com/c/chromium/src/+/770920)

Abandoned previous DialAppInfoParser patch
(https://chromium-review.googlesource.com/c/chromium/src/+/745538),
resolved most comments in the new patch and added a SafeDialAppInfoParser
class which uses SafeXmlParser.

Bug: 779892
Change-Id: I1c0d141381e1a8c992888476e8643385a96db06f
Reviewed-on: https://chromium-review.googlesource.com/826126Reviewed-by: default avatarJay Civelli <jcivelli@chromium.org>
Reviewed-by: default avatarmark a. foltz <mfoltz@chromium.org>
Reviewed-by: default avatarDerek Cheng <imcheng@chromium.org>
Commit-Queue: Bin Zhao <zhaobin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#524412}
parent 2fce2f62
......@@ -33,8 +33,12 @@ static_library("discovery") {
"dial/dial_registry.h",
"dial/dial_service.cc",
"dial/dial_service.h",
"dial/parsed_dial_app_info.cc",
"dial/parsed_dial_app_info.h",
"dial/parsed_dial_device_description.cc",
"dial/parsed_dial_device_description.h",
"dial/safe_dial_app_info_parser.cc",
"dial/safe_dial_app_info_parser.h",
"dial/safe_dial_device_description_parser.cc",
"dial/safe_dial_device_description_parser.h",
"mdns/cast_media_sink_service.cc",
......
......@@ -8,3 +8,7 @@ Consoles, etc.).
* [Dial Discovery design doc](https://docs.google.com/a/chromium.org/document/d/1vLpUgp5mJi6KFaCV3HEMQEZYDKtbcGdwcKNADuzuLzw/edit?usp=sharing)
* [Media Router design doc](https://www.chromium.org/developers/design-documents/media-router)
## DIAL specification
* [DIAL Protocol Specification](http://www.dial-multiscreen.org/dial-protocol-specification)
// 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 "chrome/browser/media/router/discovery/dial/parsed_dial_app_info.h"
namespace media_router {
ParsedDialAppInfo::ParsedDialAppInfo() = default;
ParsedDialAppInfo::~ParsedDialAppInfo() = default;
} // namespace media_router
// 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.
#ifndef CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DIAL_PARSED_DIAL_APP_INFO_H_
#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DIAL_PARSED_DIAL_APP_INFO_H_
#include <map>
#include <string>
namespace media_router {
// Possible states of a DIAL application.
enum class DialAppState {
kUnknown = 0,
// The application is installed and either starting or running.
kRunning,
// The application is installed and not running.
kStopped
};
struct ParsedDialAppInfo {
ParsedDialAppInfo();
~ParsedDialAppInfo();
// Identifies the DIAL protocol version associated with the response.
std::string dial_version;
// The application name. Mandatory.
std::string name;
// Whether the DELETE operation is supported to stop a running application.
bool allow_stop = true;
// The reported state of the application.
DialAppState state = DialAppState::kUnknown;
// If the applications's state is RUNNING, the resource name for the running
// application.
std::string href;
// Application-specific data included with the GET response that is not part
// of the official specifications. Map of additional data value keyed by XML
// tag name.
std::map<std::string, std::string> extra_data;
};
} // namespace media_router
#endif // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DIAL_PARSED_DIAL_APP_INFO_H_
// 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 "chrome/browser/media/router/discovery/dial/safe_dial_app_info_parser.h"
#include <utility>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/unguessable_token.h"
#include "services/data_decoder/public/cpp/safe_xml_parser.h"
#include "url/gurl.h"
namespace media_router {
namespace {
DialAppState ParseDialAppState(const std::string& app_state) {
if (base::ToLowerASCII(app_state) == "running") {
return DialAppState::kRunning;
} else if (base::ToLowerASCII(app_state) == "stopped") {
return DialAppState::kStopped;
}
return DialAppState::kUnknown;
}
// Parses |child_element| content, and sets corresponding fields of
// |out_app_info|. Returns ParsingError::kNone if parsing succeeds.
SafeDialAppInfoParser::ParsingError ProcessChildElement(
const base::Value& child_element,
ParsedDialAppInfo* out_app_info) {
std::string tag_name;
if (!data_decoder::GetXmlElementTagName(child_element, &tag_name))
return SafeDialAppInfoParser::ParsingError::kInvalidXML;
if (tag_name == "name") {
if (!data_decoder::GetXmlElementText(child_element, &out_app_info->name))
return SafeDialAppInfoParser::ParsingError::kFailToReadName;
} else if (tag_name == "options") {
out_app_info->allow_stop = data_decoder::GetXmlElementAttribute(
child_element, "allowStop") != "false";
} else if (tag_name == "link") {
out_app_info->href =
data_decoder::GetXmlElementAttribute(child_element, "href");
} else if (tag_name == "state") {
std::string state;
if (!data_decoder::GetXmlElementText(child_element, &state))
return SafeDialAppInfoParser::ParsingError::kFailToReadState;
out_app_info->state = ParseDialAppState(state);
} else {
std::string extra_data;
if (!data_decoder::GetXmlElementText(child_element, &extra_data)) {
DVLOG(2) << "Fail to read extra data, <" << tag_name << ">"
<< " is not a plain text element.";
} else {
out_app_info->extra_data[tag_name] = extra_data;
}
}
return SafeDialAppInfoParser::ParsingError::kNone;
}
// Returns ParsingError::kNone if mandatory fields (name, state) are valid.
// |app_info|: app info object to be validated.
SafeDialAppInfoParser::ParsingError ValidateParsedAppInfo(
const ParsedDialAppInfo& app_info) {
if (app_info.name.empty())
return SafeDialAppInfoParser::ParsingError::kMissingName;
if (app_info.state == DialAppState::kUnknown)
return SafeDialAppInfoParser::ParsingError::kInvalidState;
return SafeDialAppInfoParser::ParsingError::kNone;
}
} // namespace
// Note we generate a random batch ID so that parsing operations started from
// this SafeDialAppInfoParser instance run in the same utility process.
SafeDialAppInfoParser::SafeDialAppInfoParser(
service_manager::Connector* connector)
: connector_(connector),
xml_parser_batch_id_(base::UnguessableToken::Create().ToString()),
weak_factory_(this) {}
SafeDialAppInfoParser::~SafeDialAppInfoParser() {}
void SafeDialAppInfoParser::Parse(const std::string& xml_text,
ParseCallback callback) {
DVLOG(2) << "Parsing app info...";
DCHECK(callback);
data_decoder::ParseXml(
connector_, xml_text,
base::BindOnce(&SafeDialAppInfoParser::OnXmlParsingDone,
weak_factory_.GetWeakPtr(), std::move(callback)),
xml_parser_batch_id_);
}
void SafeDialAppInfoParser::OnXmlParsingDone(
SafeDialAppInfoParser::ParseCallback callback,
std::unique_ptr<base::Value> value,
const base::Optional<std::string>& error) {
if (error) {
DVLOG(1) << "Fail to parse XML in utility process, error: "
<< error.value();
}
if (!value || !value->is_dict()) {
std::move(callback).Run(nullptr, ParsingError::kInvalidXML);
return;
}
// NOTE: enforce namespace check for <service> element in future. Namespace
// value will be "urn:dial-multiscreen-org:schemas:dial".
bool unique_service = true;
const base::Value* service_element =
data_decoder::FindXmlElementPath(*value, {"service"}, &unique_service);
if (!service_element || !unique_service) {
std::move(callback).Run(nullptr, ParsingError::kInvalidXML);
return;
}
// Read optional @dialVer.
std::unique_ptr<ParsedDialAppInfo> app_info =
base::MakeUnique<ParsedDialAppInfo>();
app_info->dial_version =
data_decoder::GetXmlElementAttribute(*service_element, "dialVer");
// Fetch all the children of <service> element.
const base::Value* child_elements =
data_decoder::GetXmlElementChildren(*service_element);
if (!child_elements || !child_elements->is_list()) {
std::move(callback).Run(nullptr, ParsingError::kInvalidXML);
return;
}
ParsingError parsing_error = ParsingError::kNone;
for (const auto& child_element : child_elements->GetList()) {
parsing_error = ProcessChildElement(child_element, app_info.get());
if (parsing_error != ParsingError::kNone) {
std::move(callback).Run(nullptr, parsing_error);
return;
}
}
// Validate mandatory fields (name, state).
parsing_error = ValidateParsedAppInfo(*app_info);
if (parsing_error != ParsingError::kNone) {
std::move(callback).Run(nullptr, parsing_error);
return;
}
std::move(callback).Run(std::move(app_info), ParsingError::kNone);
}
} // namespace media_router
// 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.
#ifndef CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DIAL_SAFE_DIAL_APP_INFO_PARSER_H_
#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DIAL_SAFE_DIAL_APP_INFO_PARSER_H_
#include <memory>
#include <string>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/values.h"
#include "chrome/browser/media/router/discovery/dial/parsed_dial_app_info.h"
namespace service_manager {
class Connector;
}
namespace media_router {
// SafeDialAppInfoParser parses the given app info XML file safely via a utility
// process.
// Spec for DIAL app info XML:
// http://www.dial-multiscreen.org/dial-protocol-specification
// Section 6.1.2 Server response.
class SafeDialAppInfoParser {
public:
enum class ParsingError {
kNone = 0,
kInvalidXML = 1,
kFailToReadName = 2,
kFailToReadState = 3,
kMissingName = 4,
kInvalidState = 5
};
// |connector| should be a valid connector to the ServiceManager.
explicit SafeDialAppInfoParser(service_manager::Connector* connector);
~SafeDialAppInfoParser();
// Callback function invoked when done parsing DIAL app info XML.
// |app_info|: app info object. Empty if parsing failed.
// |parsing_error|: error encountered while parsing the DIAL app info XML.
using ParseCallback =
base::OnceCallback<void(std::unique_ptr<ParsedDialAppInfo> app_info,
ParsingError parsing_error)>;
// Parses the DIAL app info in |xml_text| in a utility process.
// If the parsing succeeds, invokes callback with a valid
// |app_info|, otherwise invokes callback with an empty
// |app_info| and sets parsing error to detail the failure.
// Note that it's safe to call this method multiple times and when making
// multiple calls they may be grouped in the same utility process. The
// utility process is still cleaned up automatically if unused after some
// time, even if this object is still alive.
// Note also that the callback is not called if the object is deleted.
void Parse(const std::string& xml_text, ParseCallback callback);
private:
void OnXmlParsingDone(ParseCallback callback,
std::unique_ptr<base::Value> value,
const base::Optional<std::string>& error);
// Connector to the ServiceManager, used to retrieve the XmlParser service.
service_manager::Connector* const connector_;
// The batch ID used to group XML parsing calls to SafeXmlParser into a single
// process.
std::string xml_parser_batch_id_;
base::WeakPtrFactory<SafeDialAppInfoParser> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(SafeDialAppInfoParser);
};
} // namespace media_router
#endif // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DIAL_SAFE_DIAL_APP_INFO_PARSER_H_
// 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 "chrome/browser/media/router/discovery/dial/safe_dial_app_info_parser.h"
#include <string>
#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "services/data_decoder/data_decoder_service.h"
#include "services/data_decoder/public/cpp/safe_xml_parser.h"
#include "services/service_manager/public/cpp/test/test_connector_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media_router {
namespace {
constexpr char kValidAppInfoXml[] =
R"(<?xml version="1.0" encoding="UTF-8"?>
<service xmlns="urn:dial-multiscreen-org:schemas:dial">
<name>YouTube</name>
<options allowStop="false"/>
<state>running</state>
<link rel="run" href="run"/>
</service>
)";
constexpr char kValidAppInfoXmlExtraData[] =
R"(<?xml version="1.0" encoding="UTF-8"?>
<service xmlns="urn:dial-multiscreen-org:schemas:dial">
<name>YouTube</name>
<state>Running</state>
<options allowStop="false"/>
<link rel="run" href="run"/>
<port>8080</port>
<capabilities>websocket</capabilities>
<additionalData>
<screenId>e5n3112oskr42pg0td55b38nh4</screenId>
<otherField>2</otherField>
</additionalData>
</service>
)";
constexpr char kInvalidXmlNoState[] =
R"(<?xml version="1.0" encoding="UTF-8"?>
<service xmlns="urn:dial-multiscreen-org:schemas:dial">
<name>YouTube</name>
<state></state>
<options allowStop="false"/>
<link rel="run" href="run"/>
</service>
)";
constexpr char kInvalidXmlInvalidState[] =
R"(<?xml version="1.0" encoding="UTF-8"?>
<service xmlns="urn:dial-multiscreen-org:schemas:dial">
<name>YouTube</name>
<options allowStop="false"/>
<state>xyzzy</state>
<link rel="run" href="run"/>
</service>
)";
constexpr char kInvalidXmlNoName[] =
R"(<?xml version="1.0" encoding="UTF-8"?>
<service xmlns="urn:dial-multiscreen-org:schemas:dial">
<options allowStop="false"/>
<state>running</state>
<link rel="run" href="run"/>
</service>
)";
constexpr char kInvalidXmlMultipleServices[] =
R"(<?xml version="1.0" encoding="UTF-8"?>
<root>
<service xmlns="urn:dial-multiscreen-org:schemas:dial">
<name>YouTube</name>
<options allowStop="false"/>
<state>running</state>
</service>
<service xmlns="urn:dial-multiscreen-org:schemas:dial">
<name>Netflix</name>
<options allowStop="false"/>
<state>running</state>
</service>
</root>
)";
} // namespace
class SafeDialAppInfoParserTest : public testing::Test {
public:
SafeDialAppInfoParserTest()
: connector_factory_(
std::make_unique<data_decoder::DataDecoderService>()) {
connector_ = connector_factory_.CreateConnector();
}
std::unique_ptr<ParsedDialAppInfo> Parse(
const std::string& xml,
SafeDialAppInfoParser::ParsingError expected_error) {
base::RunLoop run_loop;
SafeDialAppInfoParser parser(connector_.get());
parser.Parse(xml,
base::BindOnce(&SafeDialAppInfoParserTest::OnParsingCompleted,
base::Unretained(this), expected_error));
base::RunLoop().RunUntilIdle();
return std::move(app_info_);
}
void OnParsingCompleted(SafeDialAppInfoParser::ParsingError expected_error,
std::unique_ptr<ParsedDialAppInfo> app_info,
SafeDialAppInfoParser::ParsingError error) {
app_info_ = std::move(app_info);
EXPECT_EQ(expected_error, error);
}
private:
content::TestBrowserThreadBundle test_browser_thread_bundle_;
std::unique_ptr<service_manager::Connector> connector_;
service_manager::TestConnectorFactory connector_factory_;
std::unique_ptr<ParsedDialAppInfo> app_info_;
DISALLOW_COPY_AND_ASSIGN(SafeDialAppInfoParserTest);
};
TEST_F(SafeDialAppInfoParserTest, TestInvalidXmlNoService) {
std::unique_ptr<ParsedDialAppInfo> app_info =
Parse("", SafeDialAppInfoParser::ParsingError::kInvalidXML);
EXPECT_FALSE(app_info);
}
TEST_F(SafeDialAppInfoParserTest, TestValidXml) {
std::string xml_text(kValidAppInfoXml);
std::unique_ptr<ParsedDialAppInfo> app_info =
Parse(xml_text, SafeDialAppInfoParser::ParsingError::kNone);
EXPECT_EQ("YouTube", app_info->name);
EXPECT_EQ(DialAppState::kRunning, app_info->state);
EXPECT_FALSE(app_info->allow_stop);
EXPECT_EQ("run", app_info->href);
EXPECT_TRUE(app_info->extra_data.empty());
}
TEST_F(SafeDialAppInfoParserTest, TestValidXmlExtraData) {
std::string xml_text(kValidAppInfoXmlExtraData);
std::unique_ptr<ParsedDialAppInfo> app_info =
Parse(xml_text, SafeDialAppInfoParser::ParsingError::kNone);
EXPECT_EQ("YouTube", app_info->name);
EXPECT_EQ(DialAppState::kRunning, app_info->state);
EXPECT_EQ(2u, app_info->extra_data.size());
EXPECT_EQ("8080", app_info->extra_data["port"]);
EXPECT_EQ("websocket", app_info->extra_data["capabilities"]);
}
TEST_F(SafeDialAppInfoParserTest, TestInvalidXmlNoState) {
std::string xml_text(kInvalidXmlNoState);
std::unique_ptr<ParsedDialAppInfo> app_info =
Parse(xml_text, SafeDialAppInfoParser::ParsingError::kFailToReadState);
EXPECT_FALSE(app_info);
}
TEST_F(SafeDialAppInfoParserTest, TestInvalidXmlInvalidState) {
std::string xml_text(kInvalidXmlInvalidState);
std::unique_ptr<ParsedDialAppInfo> app_info =
Parse(xml_text, SafeDialAppInfoParser::ParsingError::kInvalidState);
EXPECT_FALSE(app_info);
}
TEST_F(SafeDialAppInfoParserTest, TestInvalidXmlNoName) {
std::string xml_text(kInvalidXmlNoName);
std::unique_ptr<ParsedDialAppInfo> app_info =
Parse(xml_text, SafeDialAppInfoParser::ParsingError::kMissingName);
EXPECT_FALSE(app_info);
}
TEST_F(SafeDialAppInfoParserTest, TestInvalidXmlMultipleServices) {
std::string xml_text(kInvalidXmlMultipleServices);
std::unique_ptr<ParsedDialAppInfo> app_info =
Parse(xml_text, SafeDialAppInfoParser::ParsingError::kInvalidXML);
EXPECT_FALSE(app_info);
}
} // namespace media_router
......@@ -2959,6 +2959,7 @@ test("unit_tests") {
"../browser/media/router/discovery/dial/dial_media_sink_service_unittest.cc",
"../browser/media/router/discovery/dial/dial_registry_unittest.cc",
"../browser/media/router/discovery/dial/dial_service_unittest.cc",
"../browser/media/router/discovery/dial/safe_dial_app_info_parser_unittest.cc",
"../browser/media/router/discovery/dial/safe_dial_device_description_parser_unittest.cc",
"../browser/media/router/discovery/mdns/cast_media_sink_service_impl_unittest.cc",
"../browser/media/router/discovery/mdns/cast_media_sink_service_unittest.cc",
......
......@@ -76,15 +76,15 @@ void SafeXmlParser::ReportResults(std::unique_ptr<base::Value> parsed_xml,
delete this;
}
const base::Value* GetChildren(const base::Value& element) {
} // 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);
}
} // namespace
std::string GetXmlQualifiedName(const std::string& name_space,
const std::string& name) {
return name_space.empty() ? name : name_space + ":" + name;
......@@ -127,7 +127,7 @@ bool GetXmlElementTagName(const base::Value& element, std::string* tag_name) {
bool GetXmlElementText(const base::Value& element, std::string* text) {
DCHECK(text);
const base::Value* children = GetChildren(element);
const base::Value* children = GetXmlElementChildren(element);
if (!children)
return false;
......@@ -171,7 +171,7 @@ bool GetXmlElementNamespacePrefix(const base::Value& element,
int GetXmlElementChildrenCount(const base::Value& element,
const std::string& name) {
const base::Value* children = GetChildren(element);
const base::Value* children = GetXmlElementChildren(element);
if (!children)
return 0;
int child_count = 0;
......@@ -187,7 +187,7 @@ int GetXmlElementChildrenCount(const base::Value& element,
const base::Value* GetXmlElementChildWithType(const base::Value& element,
const std::string& type) {
const base::Value* children = GetChildren(element);
const base::Value* children = GetXmlElementChildren(element);
if (!children)
return nullptr;
for (const base::Value& value : children->GetList()) {
......@@ -201,7 +201,7 @@ const base::Value* GetXmlElementChildWithType(const base::Value& element,
const base::Value* GetXmlElementChildWithTag(const base::Value& element,
const std::string& tag) {
const base::Value* children = GetChildren(element);
const base::Value* children = GetXmlElementChildren(element);
if (!children)
return nullptr;
for (const base::Value& value : children->GetList()) {
......@@ -216,7 +216,7 @@ bool GetAllXmlElementChildrenWithTag(
const base::Value& element,
const std::string& tag,
std::vector<const base::Value*>* children_out) {
const base::Value* children = GetChildren(element);
const base::Value* children = GetXmlElementChildren(element);
if (!children)
return false;
bool found = false;
......
......@@ -52,6 +52,9 @@ void ParseXml(service_manager::Connector* connector,
// Below are convenience methods for handling the elements returned by
// ParseXml().
// Returns all the children of |element|.
const base::Value* GetXmlElementChildren(const base::Value& element);
// Returns the qualified name |name_space|:|name| or simply |name| if
// |name_space| is empty.
std::string GetXmlQualifiedName(const std::string& name_space,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment