// 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_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/data_decoder.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, bool, bool> ReadAndDeleteBinaryFile( const base::FilePath& path) { std::vector 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(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> EncodeImage(const SkBitmap& image) { std::vector 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& data) { return base::WriteFile(path, reinterpret_cast(data.data()), base::checked_cast(data.size())); } } // namespace // static std::unique_ptr ImageSanitizer::CreateAndStart( data_decoder::DataDecoder* decoder, const base::FilePath& image_dir, const std::set& image_paths, ImageDecodedCallback image_decoded_callback, SanitizationDoneCallback done_callback, const scoped_refptr& io_task_runner) { std::unique_ptr sanitizer(new ImageSanitizer( image_dir, image_paths, std::move(image_decoded_callback), std::move(done_callback), io_task_runner)); sanitizer->Start(decoder); return sanitizer; } ImageSanitizer::ImageSanitizer( const base::FilePath& image_dir, const std::set& image_relative_paths, ImageDecodedCallback image_decoded_callback, SanitizationDoneCallback done_callback, const scoped_refptr& io_task_runner) : image_dir_(image_dir), image_paths_(image_relative_paths), image_decoded_callback_(std::move(image_decoded_callback)), done_callback_(std::move(done_callback)), io_task_runner_(io_task_runner) {} ImageSanitizer::~ImageSanitizer() = default; void ImageSanitizer::Start(data_decoder::DataDecoder* decoder) { if (image_paths_.empty()) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&ImageSanitizer::ReportSuccess, weak_factory_.GetWeakPtr())); return; } decoder->GetService()->BindImageDecoder( image_decoder_.BindNewPipeAndPassReceiver()); image_decoder_.set_disconnect_handler( base::BindOnce(&ImageSanitizer::ReportError, weak_factory_.GetWeakPtr(), Status::kServiceError, base::FilePath())); std::set 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, 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& image_data = std::get<0>(read_and_delete_result); image_decoder_->DecodeImage( 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; } if (image_decoded_callback_) image_decoded_callback_.Run(image_path, decoded_image); // 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)); } void ImageSanitizer::ImageReencoded( const base::FilePath& image_path, std::pair> result) { bool success = result.first; std::vector image_data = std::move(result.second); if (!success) { ReportError(Status::kEncodingError, image_path); return; } int size = base::checked_cast(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() { CleanUp(); std::move(done_callback_).Run(Status::kSuccess, base::FilePath()); } void ImageSanitizer::ReportError(Status status, const base::FilePath& path) { CleanUp(); // Prevent any other task from reporting, we want to notify only once. weak_factory_.InvalidateWeakPtrs(); std::move(done_callback_).Run(status, path); } void ImageSanitizer::CleanUp() { image_decoder_.reset(); // It's important to clear the repeating callback as it may cause a circular // reference (the callback holds a ref to an object that has a ref to |this|) // that would cause a leak. image_decoded_callback_.Reset(); } } // namespace extensions