123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- // Copyright 2018 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 "extensions/browser/image_sanitizer.h"
- #include "base/bind.h"
- #include "base/debug/dump_without_crashing.h"
- #include "base/files/file_util.h"
- #include "base/task/task_runner_util.h"
- #include "extensions/browser/extension_file_task_runner.h"
- #include "extensions/common/extension_resource_path_normalizer.h"
- #include "services/data_decoder/public/cpp/decode_image.h"
- #include "ui/gfx/codec/png_codec.h"
- namespace extensions {
- namespace {
- // We don't expect icons and other extension's images to be big.
- // We use this limit to prevent from opening too large images.
- const int kMaxImageCanvas = 4096 * 4096; // 16MB
- // Reads the file in |path| and then deletes it.
- // Returns a tuple containing: the file content, whether the read was
- // successful, whether the delete was successful.
- std::tuple<std::vector<uint8_t>, bool, bool> ReadAndDeleteBinaryFile(
- const base::FilePath& path) {
- std::vector<uint8_t> contents;
- bool read_success = false;
- int64_t file_size;
- if (base::GetFileSize(path, &file_size)) {
- contents.resize(file_size);
- read_success =
- base::ReadFile(path, reinterpret_cast<char*>(contents.data()),
- file_size) == file_size;
- }
- bool delete_success = base::DeleteFile(path);
- return std::make_tuple(std::move(contents), read_success, delete_success);
- }
- std::pair<bool, std::vector<unsigned char>> EncodeImage(const SkBitmap& image) {
- std::vector<unsigned char> image_data;
- bool success = gfx::PNGCodec::EncodeBGRASkBitmap(
- image,
- /*discard_transparency=*/false, &image_data);
- return std::make_pair(success, std::move(image_data));
- }
- int WriteFile(const base::FilePath& path,
- const std::vector<unsigned char>& data) {
- return base::WriteFile(path, reinterpret_cast<const char*>(data.data()),
- base::checked_cast<int>(data.size()));
- }
- } // namespace
- // static
- std::unique_ptr<ImageSanitizer> ImageSanitizer::CreateAndStart(
- scoped_refptr<Client> client,
- const base::FilePath& image_dir,
- const std::set<base::FilePath>& image_paths,
- const scoped_refptr<base::SequencedTaskRunner>& io_task_runner) {
- std::unique_ptr<ImageSanitizer> sanitizer(
- new ImageSanitizer(client, image_dir, image_paths, io_task_runner));
- sanitizer->Start();
- return sanitizer;
- }
- ImageSanitizer::ImageSanitizer(
- scoped_refptr<Client> client,
- const base::FilePath& image_dir,
- const std::set<base::FilePath>& image_relative_paths,
- const scoped_refptr<base::SequencedTaskRunner>& io_task_runner)
- : image_dir_(image_dir),
- image_paths_(image_relative_paths),
- client_(std::move(client)),
- io_task_runner_(io_task_runner) {
- DCHECK(client_);
- }
- ImageSanitizer::~ImageSanitizer() = default;
- ImageSanitizer::Client::~Client() = default;
- void ImageSanitizer::Start() {
- if (image_paths_.empty()) {
- base::SequencedTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(&ImageSanitizer::ReportSuccess,
- weak_factory_.GetWeakPtr()));
- return;
- }
- std::set<base::FilePath> normalized_image_paths;
- for (const base::FilePath& path : image_paths_) {
- // Normalize paths as |image_paths_| can contain duplicates like "icon.png"
- // and "./icon.png" to avoid unpacking the same image twice.
- base::FilePath normalized_path;
- if (path.IsAbsolute() || path.ReferencesParent() ||
- !NormalizeExtensionResourcePath(path, &normalized_path)) {
- // Report the error asynchronously so the caller stack has chance to
- // unwind.
- base::SequencedTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(&ImageSanitizer::ReportError,
- weak_factory_.GetWeakPtr(),
- Status::kImagePathError, path));
- return;
- }
- normalized_image_paths.insert(normalized_path);
- }
- // Update |image_paths_| as some of the path might have been changed by
- // normalization.
- image_paths_ = std::move(normalized_image_paths);
- // Note that we use 2 for loops instead of one to prevent a race and flakyness
- // in tests: if |image_paths_| contains 2 paths, a valid one that points to a
- // file that does not exist and an invalid one, there is a race that can cause
- // either error to be reported (kImagePathError or kFileReadError).
- for (const base::FilePath& path : image_paths_) {
- base::FilePath full_image_path = image_dir_.Append(path);
- base::PostTaskAndReplyWithResult(
- io_task_runner_.get(), FROM_HERE,
- base::BindOnce(&ReadAndDeleteBinaryFile, full_image_path),
- base::BindOnce(&ImageSanitizer::ImageFileRead,
- weak_factory_.GetWeakPtr(), path));
- }
- }
- void ImageSanitizer::ImageFileRead(
- const base::FilePath& image_path,
- std::tuple<std::vector<uint8_t>, bool, bool> read_and_delete_result) {
- if (!std::get<1>(read_and_delete_result)) {
- ReportError(Status::kFileReadError, image_path);
- return;
- }
- if (!std::get<2>(read_and_delete_result)) {
- ReportError(Status::kFileDeleteError, image_path);
- return;
- }
- const std::vector<uint8_t>& image_data = std::get<0>(read_and_delete_result);
- data_decoder::DecodeImage(
- client_->GetDataDecoder(), image_data,
- data_decoder::mojom::ImageCodec::kDefault,
- /*shrink_to_fit=*/false, kMaxImageCanvas, gfx::Size(),
- base::BindOnce(&ImageSanitizer::ImageDecoded, weak_factory_.GetWeakPtr(),
- image_path));
- }
- void ImageSanitizer::ImageDecoded(const base::FilePath& image_path,
- const SkBitmap& decoded_image) {
- if (decoded_image.isNull()) {
- ReportError(Status::kDecodingError, image_path);
- return;
- }
- if (decoded_image.colorType() != kN32_SkColorType) {
- // The renderer should be sending us N32 32bpp bitmaps in reply, otherwise
- // this can lead to out-of-bounds mistakes when transferring the pixels out
- // of the bitmap into other buffers.
- base::debug::DumpWithoutCrashing();
- ReportError(Status::kDecodingError, image_path);
- return;
- }
- // TODO(mpcomplete): It's lame that we're encoding all images as PNG, even
- // though they may originally be .jpg, etc. Figure something out.
- // http://code.google.com/p/chromium/issues/detail?id=12459
- base::PostTaskAndReplyWithResult(
- io_task_runner_.get(), FROM_HERE,
- base::BindOnce(&EncodeImage, decoded_image),
- base::BindOnce(&ImageSanitizer::ImageReencoded,
- weak_factory_.GetWeakPtr(), image_path));
- client_->OnImageDecoded(image_path, decoded_image);
- // Note that the `client` callback could potentially delete `this` object.
- }
- void ImageSanitizer::ImageReencoded(
- const base::FilePath& image_path,
- std::pair<bool, std::vector<unsigned char>> result) {
- bool success = result.first;
- std::vector<unsigned char> image_data = std::move(result.second);
- if (!success) {
- ReportError(Status::kEncodingError, image_path);
- return;
- }
- int size = base::checked_cast<int>(image_data.size());
- base::PostTaskAndReplyWithResult(
- io_task_runner_.get(), FROM_HERE,
- base::BindOnce(&WriteFile, image_dir_.Append(image_path),
- std::move(image_data)),
- base::BindOnce(&ImageSanitizer::ImageWritten, weak_factory_.GetWeakPtr(),
- image_path, size));
- }
- void ImageSanitizer::ImageWritten(const base::FilePath& image_path,
- int expected_size,
- int actual_size) {
- if (expected_size != actual_size) {
- ReportError(Status::kFileWriteError, image_path);
- return;
- }
- // We have finished with this path.
- size_t removed_count = image_paths_.erase(image_path);
- DCHECK_EQ(1U, removed_count);
- if (image_paths_.empty()) {
- // This was the last path, we are done.
- ReportSuccess();
- }
- }
- void ImageSanitizer::ReportSuccess() {
- // Reset `client_` early, before the callback potentially deletes `this`.
- scoped_refptr<Client> client = std::move(client_);
- DCHECK(!client_);
- // The `client_` callback is the last statement, because it can potentially
- // delete `this` object.
- client->OnImageSanitizationDone(Status::kSuccess, base::FilePath());
- }
- void ImageSanitizer::ReportError(Status status, const base::FilePath& path) {
- // Prevent any other task from reporting, we want to notify only once.
- weak_factory_.InvalidateWeakPtrs();
- // Reset `client_` early, before the callback potentially deletes `this`.
- scoped_refptr<Client> client = std::move(client_);
- DCHECK(!client_);
- // The `client_` callback is the last statement, because it can potentially
- // delete `this` object.
- client->OnImageSanitizationDone(status, path);
- }
- } // namespace extensions
|