Commit 9d594ea6 authored by Michael Martis's avatar Michael Martis Committed by Commit Bot

Image annotation service: use dedicated JSON parsing service for safety.

This CL switches out our usage of base::JSONReader for the data decoder service
(in another process on desktop Chrome), which is safer for parsing JSON
returned from an external server.

Bug: 916420
Change-Id: I3cb9b20af19064cfa877d88f06baeb417322f0a0
Reviewed-on: https://chromium-review.googlesource.com/c/1491068Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Reviewed-by: default avatarAndrew Moylan <amoylan@chromium.org>
Reviewed-by: default avatarSam McNally <sammc@chromium.org>
Commit-Queue: Michael Martis <martis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#636608}
parent 4f4df5f4
......@@ -15,8 +15,10 @@ source_set("lib") {
"//components/google/core/common",
"//mojo/public/cpp/bindings",
"//net",
"//services/data_decoder/public/mojom",
"//services/image_annotation/public/mojom",
"//services/network/public/cpp",
"//services/service_manager/public/cpp",
"//url",
]
}
......@@ -51,10 +53,14 @@ source_set("tests") {
"//base/test:test_support",
"//mojo/public/cpp/bindings",
"//net",
"//services/data_decoder/public/cpp:test_support",
"//services/data_decoder/public/mojom",
"//services/image_annotation/public/cpp",
"//services/image_annotation/public/mojom",
"//services/network:test_support",
"//services/network/public/cpp",
"//services/service_manager/public/cpp",
"//services/service_manager/public/cpp/test:test_support",
"//testing/gmock",
"//testing/gtest",
"//url",
......
include_rules = [
"+components/google",
"+net",
"+services/data_decoder",
"+services/network",
"+third_party/skia",
"+ui/gfx",
......
......@@ -11,13 +11,14 @@
#include "base/base64.h"
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/location.h"
#include "base/stl_util.h"
#include "components/google/core/common/google_util.h"
#include "net/base/load_flags.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/data_decoder/public/mojom/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "url/gurl.h"
namespace image_annotation {
......@@ -153,18 +154,13 @@ std::tuple<bool, std::vector<mojom::AnnotationPtr>> ParseJsonDescAnnotations(
// Attempts to extract annotation results from the server response, returning a
// map from each source ID to its annotations (if successfully extracted).
std::map<std::string, mojom::AnnotateImageResultPtr> ParseJsonResponse(
const std::string* const json_response,
std::map<std::string, mojom::AnnotateImageResultPtr> UnpackJsonResponse(
const base::Value& json_data,
const double min_ocr_confidence) {
if (!json_response)
if (!json_data.is_dict())
return {};
const base::Optional<base::Value> response =
base::JSONReader::Read(*json_response);
if (!response.has_value() || !response->is_dict())
return {};
const base::Value* const results = response->FindKey("results");
const base::Value* const results = json_data.FindKey("results");
if (!results || !results->is_list())
return {};
......@@ -240,8 +236,10 @@ Annotator::Annotator(
const base::TimeDelta throttle,
const int batch_size,
const double min_ocr_confidence,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
service_manager::Connector* const connector)
: url_loader_factory_(std::move(url_loader_factory)),
connector_(connector),
http_request_timer_(
FROM_HERE,
throttle,
......@@ -250,7 +248,9 @@ Annotator::Annotator(
server_url_(std::move(server_url)),
api_key_(std::move(api_key)),
batch_size_(batch_size),
min_ocr_confidence_(min_ocr_confidence) {}
min_ocr_confidence_(min_ocr_confidence) {
DCHECK(connector_);
}
Annotator::~Annotator() {}
......@@ -465,10 +465,37 @@ void Annotator::OnServerResponseReceived(
const std::unique_ptr<std::string> json_response) {
http_requests_.erase(http_request_it);
if (!json_response) {
DVLOG(1) << "HTTP request to image annotation server failed.";
ProcessResults(source_ids, {});
return;
}
// Send JSON string to a dedicated service for safe parsing.
GetJsonParser().Parse(*json_response,
base::BindOnce(&Annotator::OnResponseJsonParsed,
base::Unretained(this), source_ids));
}
void Annotator::OnResponseJsonParsed(
const std::set<std::string>& source_ids,
const base::Optional<base::Value> json_data,
const base::Optional<std::string>& error) {
// Extract annotation results for each source ID with valid results.
const std::map<std::string, mojom::AnnotateImageResultPtr> results =
ParseJsonResponse(json_response.get(), min_ocr_confidence_);
std::map<std::string, mojom::AnnotateImageResultPtr> results;
if (!json_data.has_value() || error.has_value()) {
DVLOG(1) << "Parsing server response JSON failed with error: "
<< error.value_or("No reason reported.");
ProcessResults(source_ids, {});
} else {
ProcessResults(source_ids,
UnpackJsonResponse(*json_data, min_ocr_confidence_));
}
}
void Annotator::ProcessResults(
const std::set<std::string>& source_ids,
const std::map<std::string, mojom::AnnotateImageResultPtr>& results) {
// Process each source ID for which we expect to have results.
for (const std::string& source_id : source_ids) {
pending_source_ids_.erase(source_id);
......@@ -502,6 +529,18 @@ void Annotator::OnServerResponseReceived(
}
}
data_decoder::mojom::JsonParser& Annotator::GetJsonParser() {
if (!json_parser_) {
connector_->BindInterface(data_decoder::mojom::kServiceName,
mojo::MakeRequest(&json_parser_));
json_parser_.set_connection_error_handler(base::BindOnce(
[](Annotator* const annotator) { annotator->json_parser_.reset(); },
base::Unretained(this)));
}
return *json_parser_;
}
void Annotator::RemoveRequestInfo(
const std::string& source_id,
const RequestInfoList::iterator request_info_it,
......
......@@ -18,11 +18,16 @@
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "services/data_decoder/public/mojom/json_parser.mojom.h"
#include "services/image_annotation/public/mojom/image_annotation.mojom.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h"
namespace service_manager {
class Connector;
} // namespace service_manager
namespace image_annotation {
// The annotator communicates with the external image annotation server to
......@@ -61,7 +66,8 @@ class Annotator : public mojom::Annotator {
base::TimeDelta throttle,
int batch_size,
double min_ocr_confidence,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
service_manager::Connector* connector);
~Annotator() override;
// Start providing behavior for the given Mojo request.
......@@ -101,6 +107,10 @@ class Annotator : public mojom::Annotator {
HttpRequestQueue::iterator begin_it,
HttpRequestQueue::iterator end_it);
// Create or reuse a connection to the data decoder service for safe JSON
// parsing.
data_decoder::mojom::JsonParser& GetJsonParser();
// Removes the given request, reassigning local processing if its associated
// image processor had some ongoing.
void RemoveRequestInfo(const std::string& source_id,
......@@ -123,6 +133,17 @@ class Annotator : public mojom::Annotator {
UrlLoaderList::iterator http_request_it,
std::unique_ptr<std::string> json_response);
// Called when the data decoder service provides parsed JSON data for a server
// response.
void OnResponseJsonParsed(const std::set<std::string>& source_ids,
base::Optional<base::Value> json_data,
const base::Optional<std::string>& error);
// Adds the given results to the cache (if successful) and notifies clients.
void ProcessResults(
const std::set<std::string>& source_ids,
const std::map<std::string, mojom::AnnotateImageResultPtr>& results);
// Maps from source ID to previously-obtained annotation results.
// TODO(crbug.com/916420): periodically clear entries from this cache.
std::map<std::string, mojom::AnnotateImageResultPtr> cached_results_;
......@@ -150,8 +171,13 @@ class Annotator : public mojom::Annotator {
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
service_manager::Connector* const connector_;
mojo::BindingSet<mojom::Annotator> bindings_;
// Should not be used directly; GetJsonParser() should be called instead.
data_decoder::mojom::JsonParserPtr json_parser_;
// A timer used to throttle HTTP request frequency.
base::RepeatingTimer http_request_timer_;
......
......@@ -29,7 +29,8 @@ ImageAnnotationService::ImageAnnotationService(
base::TimeDelta::FromMilliseconds(kThrottleMs.Get()),
kBatchSize.Get(),
kMinOcrConfidence.Get(),
shared_url_loader_factory) {}
shared_url_loader_factory,
service_binding_.GetConnector()) {}
ImageAnnotationService::~ImageAnnotationService() = default;
......
......@@ -24,6 +24,7 @@ source_set("manifest") {
public_deps = [
"//base",
"//services/data_decoder/public/mojom:constants",
"//services/image_annotation/public/mojom",
"//services/service_manager/public/cpp",
]
......
......@@ -5,6 +5,7 @@
#include "services/image_annotation/public/cpp/manifest.h"
#include "base/no_destructor.h"
#include "services/data_decoder/public/mojom/constants.mojom.h"
#include "services/image_annotation/public/mojom/constants.mojom.h"
#include "services/image_annotation/public/mojom/image_annotation.mojom.h"
#include "services/service_manager/public/cpp/manifest_builder.h"
......@@ -19,6 +20,7 @@ const service_manager::Manifest& GetManifest() {
.ExposeCapability(
mojom::kAnnotationCapability,
service_manager::Manifest::InterfaceList<mojom::Annotator>())
.RequireCapability(data_decoder::mojom::kServiceName, "json_parser")
.Build()};
return *manifest;
}
......
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