blob: 44e311ffe49bf645029ae67f472685235980e936 [file] [log] [blame]
// 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 <map>
#include <memory>
#include "base/base64.h"
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/post_task.h"
#include "build/build_config.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "services/data_decoder/public/cpp/test_data_decoder_service.h"
#include "services/data_decoder/public/mojom/constants.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
namespace {
constexpr char kBase64edValidPng[] =
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd"
"1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC";
constexpr char kBase64edInvalidPng[] =
"Rw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd"
"1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC";
class RefCountedSanitizerCallback
: public base::RefCountedThreadSafe<RefCountedSanitizerCallback> {
public:
explicit RefCountedSanitizerCallback(bool* deleted) : deleted_(deleted) {}
void ImageSanitizationDone(ImageSanitizer::Status status,
const base::FilePath& path) {
if (done_callback_)
std::move(done_callback_).Run();
}
void ImageSanitizerDecodedImage(const base::FilePath& path, SkBitmap image) {}
void WaitForSanitizationDone() {
ASSERT_FALSE(done_callback_);
base::RunLoop run_loop;
done_callback_ = run_loop.QuitClosure();
run_loop.Run();
}
private:
friend class base::RefCountedThreadSafe<RefCountedSanitizerCallback>;
~RefCountedSanitizerCallback() {
if (deleted_)
*deleted_ = true;
}
base::OnceClosure done_callback_;
bool* deleted_;
};
class ImageSanitizerTest : public testing::Test {
public:
ImageSanitizerTest() { InitTestDataDecoderService(/*service=*/nullptr); }
protected:
void InitTestDataDecoderService(
std::unique_ptr<service_manager::Service> service) {
if (service) {
test_data_decoder_service_ = std::move(service);
} else {
test_data_decoder_service_ =
std::make_unique<data_decoder::DataDecoderService>(
RegisterDataDecoder());
}
connector_ = connector_factory_.CreateConnector();
}
service_manager::mojom::ServiceRequest RegisterDataDecoder() {
return connector_factory_.RegisterInstance(
data_decoder::mojom::kServiceName);
}
void CreateValidImage(const base::FilePath::StringPieceType& file_name) {
ASSERT_TRUE(WriteBase64DataToFile(kBase64edValidPng, file_name));
}
void CreateInvalidImage(const base::FilePath::StringPieceType& file_name) {
ASSERT_TRUE(WriteBase64DataToFile(kBase64edInvalidPng, file_name));
}
const base::FilePath& GetImagePath() const { return temp_dir_.GetPath(); }
void WaitForSanitizationDone() {
ASSERT_FALSE(done_callback_);
base::RunLoop run_loop;
done_callback_ = run_loop.QuitClosure();
run_loop.Run();
}
void CreateAndStartSanitizer(
const std::set<base::FilePath>& image_relative_paths) {
CreateAndStartSanitizerWithCallbacks(
image_relative_paths,
base::BindRepeating(&ImageSanitizerTest::ImageSanitizerDecodedImage,
base::Unretained(this)),
base::BindOnce(&ImageSanitizerTest::ImageSanitizationDone,
base::Unretained(this)));
}
void TestDontHoldOnToCallback(const base::FilePath& image_path) {
bool callback_deleted = false;
scoped_refptr<RefCountedSanitizerCallback> sanitizer_callback =
base::MakeRefCounted<RefCountedSanitizerCallback>(&callback_deleted);
CreateAndStartSanitizerWithCallbacks(
{image_path},
base::BindRepeating(
&RefCountedSanitizerCallback::ImageSanitizerDecodedImage,
sanitizer_callback),
base::BindOnce(&RefCountedSanitizerCallback::ImageSanitizationDone,
sanitizer_callback));
sanitizer_callback->WaitForSanitizationDone();
sanitizer_callback = nullptr;
// The sanitizer should have cleared its reference to |sanitizer_callback|.
EXPECT_TRUE(callback_deleted);
}
ImageSanitizer::Status last_reported_status() const { return last_status_; }
const base::FilePath& last_reported_path() const {
return last_reported_path_;
}
std::map<base::FilePath, SkBitmap>* decoded_images() {
return &decoded_images_;
}
void ClearSanitizer() { sanitizer_.reset(); }
bool done_callback_called() const { return done_callback_called_; }
bool decoded_image_callback_called() const {
return decoded_image_callback_called_;
}
private:
void CreateAndStartSanitizerWithCallbacks(
const std::set<base::FilePath>& image_relative_paths,
ImageSanitizer::ImageDecodedCallback image_decoded_callback,
ImageSanitizer::SanitizationDoneCallback done_callback) {
sanitizer_ = ImageSanitizer::CreateAndStart(
connector_.get(),
service_manager::ServiceFilter::ByName(
data_decoder::mojom::kServiceName),
temp_dir_.GetPath(), image_relative_paths,
std::move(image_decoded_callback), std::move(done_callback));
}
bool WriteBase64DataToFile(const std::string& base64_data,
const base::FilePath::StringPieceType& file_name) {
std::string binary;
if (!base::Base64Decode(base64_data, &binary))
return false;
base::FilePath path = temp_dir_.GetPath().Append(file_name);
return base::WriteFile(path, binary.data(), binary.size());
}
void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
void ImageSanitizationDone(ImageSanitizer::Status status,
const base::FilePath& path) {
done_callback_called_ = true;
last_status_ = status;
last_reported_path_ = path;
if (done_callback_)
std::move(done_callback_).Run();
}
void ImageSanitizerDecodedImage(const base::FilePath& path, SkBitmap image) {
EXPECT_EQ(decoded_images()->find(path), decoded_images()->end());
(*decoded_images())[path] = image;
decoded_image_callback_called_ = true;
}
content::TestBrowserThreadBundle thread_bundle_;
service_manager::TestConnectorFactory connector_factory_;
std::unique_ptr<service_manager::Connector> connector_;
std::unique_ptr<service_manager::Service> test_data_decoder_service_;
ImageSanitizer::Status last_status_ = ImageSanitizer::Status::kSuccess;
base::FilePath last_reported_path_;
base::OnceClosure done_callback_;
std::unique_ptr<ImageSanitizer> sanitizer_;
base::ScopedTempDir temp_dir_;
std::map<base::FilePath, SkBitmap> decoded_images_;
bool done_callback_called_ = false;
bool decoded_image_callback_called_ = false;
DISALLOW_COPY_AND_ASSIGN(ImageSanitizerTest);
};
} // namespace
TEST_F(ImageSanitizerTest, NoImagesProvided) {
CreateAndStartSanitizer(std::set<base::FilePath>());
WaitForSanitizationDone();
EXPECT_TRUE(done_callback_called());
EXPECT_EQ(last_reported_status(), ImageSanitizer::Status::kSuccess);
EXPECT_FALSE(decoded_image_callback_called());
EXPECT_TRUE(last_reported_path().empty());
}
TEST_F(ImageSanitizerTest, InvalidPathAbsolute) {
base::FilePath normal_path(FILE_PATH_LITERAL("hello.png"));
#if defined(OS_WIN)
base::FilePath absolute_path(FILE_PATH_LITERAL("c:\\Windows\\win32"));
#else
base::FilePath absolute_path(FILE_PATH_LITERAL("/usr/bin/root"));
#endif
CreateAndStartSanitizer({normal_path, absolute_path});
WaitForSanitizationDone();
EXPECT_EQ(last_reported_status(), ImageSanitizer::Status::kImagePathError);
EXPECT_EQ(last_reported_path(), absolute_path);
}
TEST_F(ImageSanitizerTest, InvalidPathReferenceParent) {
base::FilePath good_path(FILE_PATH_LITERAL("hello.png"));
base::FilePath bad_path(FILE_PATH_LITERAL("hello"));
bad_path = bad_path.Append(base::FilePath::kParentDirectory)
.Append(base::FilePath::kParentDirectory)
.Append(base::FilePath::kParentDirectory)
.Append(FILE_PATH_LITERAL("usr"))
.Append(FILE_PATH_LITERAL("bin"));
CreateAndStartSanitizer({good_path, bad_path});
WaitForSanitizationDone();
EXPECT_EQ(last_reported_status(), ImageSanitizer::Status::kImagePathError);
EXPECT_EQ(last_reported_path(), bad_path);
}
TEST_F(ImageSanitizerTest, ValidCase) {
constexpr std::array<const base::FilePath::CharType* const, 10> kFileNames{
{FILE_PATH_LITERAL("image0.png"), FILE_PATH_LITERAL("image1.png"),
FILE_PATH_LITERAL("image2.png"), FILE_PATH_LITERAL("image3.png"),
FILE_PATH_LITERAL("image4.png"), FILE_PATH_LITERAL("image5.png"),
FILE_PATH_LITERAL("image6.png"), FILE_PATH_LITERAL("image7.png"),
FILE_PATH_LITERAL("image8.png"), FILE_PATH_LITERAL("image9.png")}};
std::set<base::FilePath> paths;
for (const base::FilePath::CharType* file_name : kFileNames) {
CreateValidImage(file_name);
paths.insert(base::FilePath(file_name));
}
CreateAndStartSanitizer(paths);
WaitForSanitizationDone();
EXPECT_TRUE(done_callback_called());
EXPECT_EQ(last_reported_status(), ImageSanitizer::Status::kSuccess);
EXPECT_TRUE(last_reported_path().empty());
// Make sure the image files are there and non empty, and that the
// ImageSanitizerDecodedImage callback was invoked for every image.
for (const auto& path : paths) {
int64_t file_size = 0;
base::FilePath full_path = GetImagePath().Append(path);
EXPECT_TRUE(base::GetFileSize(full_path, &file_size));
EXPECT_GT(file_size, 0);
ASSERT_TRUE(base::ContainsKey(*decoded_images(), path));
EXPECT_FALSE((*decoded_images())[path].drawsNothing());
}
// No extra images should have been reported.
EXPECT_EQ(decoded_images()->size(), 10U);
}
TEST_F(ImageSanitizerTest, MissingImage) {
constexpr base::FilePath::CharType kGoodPngName[] =
FILE_PATH_LITERAL("image.png");
constexpr base::FilePath::CharType kNonExistingName[] =
FILE_PATH_LITERAL("i_don_t_exist.png");
CreateValidImage(kGoodPngName);
base::FilePath good_png(kGoodPngName);
base::FilePath bad_png(kNonExistingName);
CreateAndStartSanitizer({good_png, bad_png});
WaitForSanitizationDone();
EXPECT_EQ(last_reported_status(), ImageSanitizer::Status::kFileReadError);
EXPECT_EQ(last_reported_path(), bad_png);
}
TEST_F(ImageSanitizerTest, InvalidImage) {
constexpr base::FilePath::CharType kGoodPngName[] =
FILE_PATH_LITERAL("good.png");
constexpr base::FilePath::CharType kBadPngName[] =
FILE_PATH_LITERAL("bad.png");
CreateValidImage(kGoodPngName);
CreateInvalidImage(kBadPngName);
base::FilePath good_png(kGoodPngName);
base::FilePath bad_png(kBadPngName);
CreateAndStartSanitizer({good_png, bad_png});
WaitForSanitizationDone();
EXPECT_EQ(last_reported_status(), ImageSanitizer::Status::kDecodingError);
EXPECT_EQ(last_reported_path(), bad_png);
}
TEST_F(ImageSanitizerTest, NoCallbackAfterDelete) {
constexpr base::FilePath::CharType kBadPngName[] =
FILE_PATH_LITERAL("bad.png");
CreateInvalidImage(kBadPngName);
base::FilePath bad_png(kBadPngName);
CreateAndStartSanitizer({bad_png});
// Delete the sanitizer before we have received the callback.
ClearSanitizer();
// Wait a bit and ensure no callback has been called.
base::RunLoop run_loop;
base::PostDelayedTask(FROM_HERE, run_loop.QuitClosure(),
base::TimeDelta::FromMilliseconds(200));
run_loop.Run();
EXPECT_FALSE(done_callback_called());
EXPECT_FALSE(decoded_image_callback_called());
}
// Ensures the sanitizer does not keep a reference to the callbacks to prevent
// memory leaks. (it's typical to have a ref counted object A own an
// ImageSanitizer which is given callbacks bound to A, creating a circular
// reference)
TEST_F(ImageSanitizerTest, DontHoldOnToCallbacksOnFailure) {
constexpr base::FilePath::CharType kBadPngName[] =
FILE_PATH_LITERAL("bad.png");
CreateInvalidImage(kBadPngName);
TestDontHoldOnToCallback(base::FilePath(kBadPngName));
}
TEST_F(ImageSanitizerTest, DontHoldOnToCallbacksOnSuccess) {
constexpr base::FilePath::CharType kGoodPngName[] =
FILE_PATH_LITERAL("good.png");
CreateValidImage(kGoodPngName);
TestDontHoldOnToCallback(base::FilePath(kGoodPngName));
}
// Tests that the callback is invoked if the data decoder service crashes.
TEST_F(ImageSanitizerTest, DataDecoderServiceCrashes) {
InitTestDataDecoderService(
std::make_unique<data_decoder::CrashyDataDecoderService>(
RegisterDataDecoder(), /*crash_json=*/false, /*crash_image=*/true));
constexpr base::FilePath::CharType kGoodPngName[] =
FILE_PATH_LITERAL("good.png");
CreateValidImage(kGoodPngName);
base::FilePath good_png(kGoodPngName);
CreateAndStartSanitizer({good_png});
WaitForSanitizationDone();
EXPECT_EQ(last_reported_status(), ImageSanitizer::Status::kServiceError);
}
} // namespace extensions