blob: d2e0f7dcf924597c2c0dda27e397e5fca0b37e45 [file] [log] [blame]
// Copyright 2020 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/ui/webui/sanitized_image_source.h"
#include "base/memory/ref_counted_memory.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/strcat.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/image_fetcher/image_decoder_impl.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/webui_url_constants.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/url_util.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image.h"
#include "url/url_util.h"
SanitizedImageSource::SanitizedImageSource(Profile* profile)
: SanitizedImageSource(
profile,
content::BrowserContext::GetDefaultStoragePartition(profile)
->GetURLLoaderFactoryForBrowserProcess(),
std::make_unique<ImageDecoderImpl>()) {}
SanitizedImageSource::SanitizedImageSource(
Profile* profile,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
std::unique_ptr<image_fetcher::ImageDecoder> image_decoder)
: url_loader_factory_(url_loader_factory),
image_decoder_(std::move(image_decoder)),
encode_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {}
SanitizedImageSource::~SanitizedImageSource() = default;
std::string SanitizedImageSource::GetSource() {
return chrome::kChromeUIImageHost;
}
void SanitizedImageSource::StartDataRequest(
const GURL& url,
const content::WebContents::Getter& wc_getter,
content::URLDataSource::GotDataCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
GURL url_param = GURL(url.query());
if (!url_param.is_valid() ||
url != GURL(base::StrCat(
{chrome::kChromeUIImageURL, "?", url_param.spec()}))) {
std::move(callback).Run(base::MakeRefCounted<base::RefCountedString>());
return;
}
// Download the image body.
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("sanitized_image_source", R"(
semantics {
sender: "WebUI Sanitized Image Source"
description:
"This data source fetches an arbitrary image to be displayed in a "
"WebUI."
trigger:
"When a WebUI triggers the download of chrome://image?<URL> by "
"e.g. setting that URL as a src on an img tag."
data: "NONE"
destination: WEBSITE
}
policy {
cookies_allowed: NO
setting: "This feature cannot be disabled by settings."
policy_exception_justification:
"This is a helper data source. It can be indirectly disabled by "
"disabling the requester WebUI."
})");
auto request = std::make_unique<network::ResourceRequest>();
request->url = url_param;
loaders_.push_back(
network::SimpleURLLoader::Create(std::move(request), traffic_annotation));
loaders_.back()->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&SanitizedImageSource::OnImageLoaded,
weak_ptr_factory_.GetWeakPtr(), loaders_.back().get(),
std::move(callback)),
network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
}
std::string SanitizedImageSource::GetMimeType(const std::string& path) {
return "image/png";
}
bool SanitizedImageSource::ShouldReplaceExistingSource() {
// Leave the existing DataSource in place, otherwise we'll drop any pending
// requests on the floor.
return false;
}
void SanitizedImageSource::OnImageLoaded(
network::SimpleURLLoader* loader,
content::URLDataSource::GotDataCallback callback,
std::unique_ptr<std::string> body) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Take loader out of |loaders_| list and store it in a unique_ptr on the
// stack to make sure the loader gets cleaned upon return.
auto it = std::find_if(
loaders_.begin(), loaders_.end(),
[loader](const std::unique_ptr<network::SimpleURLLoader>& target) {
return loader == target.get();
});
std::unique_ptr<network::SimpleURLLoader> loader_ptr(std::move(*it));
loaders_.erase(it);
if (loader->NetError() != net::OK || !body) {
std::move(callback).Run(base::MakeRefCounted<base::RefCountedString>());
return;
}
// Send image body to image decoder in isolated process.
image_decoder_->DecodeImage(
*body, gfx::Size() /* No particular size desired. */,
base::BindOnce(&SanitizedImageSource::OnImageDecoded,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void SanitizedImageSource::OnImageDecoded(
content::URLDataSource::GotDataCallback callback,
const gfx::Image& image) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Re-encode vetted image as PNG and send to requester.
encode_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(
[](const SkBitmap& bitmap) {
auto encoded = base::MakeRefCounted<base::RefCountedBytes>();
return gfx::PNGCodec::EncodeBGRASkBitmap(
bitmap, /*discard_transparency=*/false, &encoded->data())
? encoded
: base::MakeRefCounted<base::RefCountedBytes>();
},
image.AsBitmap()),
std::move(callback));
}