blob: 90bcea8ee83edd2bb3df7d4c9155b1bec2f6d0a3 [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 <map>
#include <memory>
#include <string>
#include "base/containers/contains.h"
#include "base/memory/ref_counted_memory.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.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/browser/signin/identity_manager_factory.h"
#include "chrome/common/webui_url_constants.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
#include "components/signin/public/identity_manager/scope_set.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "net/base/url_util.h"
#include "net/http/http_response_headers.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.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"
namespace {
std::map<std::string, std::string> ParseParams(
const std::string& param_string) {
url::Component query(0, param_string.size());
url::Component key;
url::Component value;
constexpr int kMaxUriDecodeLen = 2048;
std::map<std::string, std::string> params;
while (
url::ExtractQueryKeyValue(param_string.c_str(), &query, &key, &value)) {
url::RawCanonOutputW<kMaxUriDecodeLen> output;
url::DecodeURLEscapeSequences(param_string.c_str() + value.begin, value.len,
url::DecodeURLMode::kUTF8OrIsomorphic,
&output);
params.insert({param_string.substr(key.begin, key.len),
base::UTF16ToUTF8(
base::StringPiece16(output.data(), output.length()))});
}
return params;
}
bool IsGooglePhotosUrl(const GURL& url) {
static const char* const kGooglePhotosHostSuffixes[] = {
".ggpht.com",
".google.com",
".googleusercontent.com",
};
for (const char* const suffix : kGooglePhotosHostSuffixes) {
if (base::EndsWith(url.host_piece(), suffix))
return true;
}
return false;
}
} // namespace
SanitizedImageSource::SanitizedImageSource(Profile* profile)
: SanitizedImageSource(profile,
profile->GetDefaultStoragePartition()
->GetURLLoaderFactoryForBrowserProcess(),
std::make_unique<ImageDecoderImpl>()) {}
SanitizedImageSource::SanitizedImageSource(
Profile* profile,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
std::unique_ptr<image_fetcher::ImageDecoder> image_decoder)
: identity_manager_(IdentityManagerFactory::GetForProfile(profile)),
url_loader_factory_(url_loader_factory),
image_decoder_(std::move(image_decoder)) {}
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_);
std::string image_url_or_params = url.query();
if (url != GURL(base::StrCat(
{chrome::kChromeUIImageURL, "?", image_url_or_params}))) {
std::move(callback).Run(base::MakeRefCounted<base::RefCountedString>());
return;
}
GURL image_url = GURL(image_url_or_params);
bool send_auth_token = false;
if (!image_url.is_valid()) {
// Attempt to parse URL and additional options from params.
auto params = ParseParams(image_url_or_params);
auto url_it = params.find("url");
if (url_it == params.end()) {
std::move(callback).Run(base::MakeRefCounted<base::RefCountedString>());
return;
}
image_url = GURL(url_it->second);
auto google_photos_it = params.find("isGooglePhotos");
if (google_photos_it != params.end() &&
google_photos_it->second == "true" && IsGooglePhotosUrl(image_url)) {
send_auth_token = true;
}
}
// Download the image body.
if (!send_auth_token) {
StartImageDownload(std::move(image_url), std::move(callback),
absl::nullopt);
return;
}
// Request an auth token for downloading the image body.
auto fetcher = std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
"sanitized_image_source", identity_manager_,
signin::ScopeSet({GaiaConstants::kPhotosModuleImageOAuth2Scope}),
signin::PrimaryAccountAccessTokenFetcher::Mode::kImmediate,
signin::ConsentLevel::kSignin);
auto* fetcher_ptr = fetcher.get();
fetcher_ptr->Start(base::BindOnce(
[](const base::WeakPtr<SanitizedImageSource>& self,
std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher> fetcher,
GURL image_url, content::URLDataSource::GotDataCallback callback,
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info) {
if (error.state() != GoogleServiceAuthError::NONE) {
LOG(ERROR) << "Failed to authenticate for Google Photos in order to "
"download "
<< image_url.spec()
<< ". Error message: " << error.ToString();
return;
}
if (self) {
self->StartImageDownload(std::move(image_url), std::move(callback),
std::move(access_token_info));
}
},
weak_ptr_factory_.GetWeakPtr(), std::move(fetcher), std::move(image_url),
std::move(callback)));
}
void SanitizedImageSource::StartImageDownload(
GURL image_url,
content::URLDataSource::GotDataCallback callback,
absl::optional<signin::AccessTokenInfo> access_token_info) {
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> or "
"chrome://image?url=<URL>&isGooglePhotos=<bool> by e.g. setting "
"that URL as a src on an img tag."
data: "OAuth credentials for the user's Google Photos account when "
"isGooglePhotos is true."
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 = image_url;
request->credentials_mode = network::mojom::CredentialsMode::kOmit;
if (access_token_info) {
request->headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
"Bearer " + access_token_info->token);
}
auto loader =
network::SimpleURLLoader::Create(std::move(request), traffic_annotation);
auto* loader_ptr = loader.get();
loader_ptr->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&SanitizedImageSource::OnImageLoaded,
weak_ptr_factory_.GetWeakPtr(), std::move(loader),
std::move(callback)),
network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
}
std::string SanitizedImageSource::GetMimeType(const GURL& url) {
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(
std::unique_ptr<network::SimpleURLLoader> loader,
content::URLDataSource::GotDataCallback callback,
std::unique_ptr<std::string> body) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
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. */,
/*data_decoder=*/nullptr,
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.
base::ThreadPool::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));
}