| // 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 "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)); |
| } |