// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "base/files/file_path.h"
#include "base/files/safe_base_name.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/uuid.h"
#include "build/build_config.h"
#include "chrome/browser/webshare/share_service_impl.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_impl.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "third_party/blink/public/mojom/blob/serialized_blob.mojom.h"
#include "url/gurl.h"
using blink::mojom::ShareError;
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/sharesheet/sharesheet_types.h"
#include "chrome/browser/webshare/chromeos/sharesheet_client.h"
#include "chromeos/components/sharesheet/constants.h"
#endif
#if BUILDFLAG(IS_MAC)
#include "chrome/browser/webshare/mac/sharing_service_operation.h"
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "chrome/browser/webshare/win/scoped_share_operation_fake_components.h"
#endif
class ShareServiceUnitTest : public ChromeRenderViewHostTestHarness {
public:
ShareServiceUnitTest() {
feature_list_.InitAndEnableFeature(features::kWebShare);
}
~ShareServiceUnitTest() override = default;
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
ShareServiceImpl::Create(
main_rfh(), share_service_remote_.BindNewPipeAndPassReceiver());
#if BUILDFLAG(IS_CHROMEOS)
webshare::SharesheetClient::SetSharesheetCallbackForTesting(
base::BindRepeating(&ShareServiceUnitTest::AcceptShareRequest));
#endif
#if BUILDFLAG(IS_MAC)
webshare::SharingServiceOperation::SetSharePickerCallbackForTesting(
base::BindRepeating(&ShareServiceUnitTest::AcceptShareRequest));
#endif
#if BUILDFLAG(IS_WIN)
ASSERT_NO_FATAL_FAILURE(scoped_fake_components_.SetUp());
#endif
}
ShareError ShareGeneratedFileData(std::string_view extension,
const std::string& content_type,
unsigned file_length = 100,
unsigned file_count = 1) {
const std::string kTitle;
const std::string kText;
const GURL kUrl;
std::vector<blink::mojom::SharedFilePtr> files;
files.reserve(file_count);
for (unsigned index = 0; index < file_count; ++index) {
files.push_back(CreateSharedFile(
base::FilePath::FromASCII(
base::StrCat({"share", base::NumberToString(index), extension})),
content_type, file_length));
}
ShareError result;
base::RunLoop run_loop;
share_service_remote_->Share(
kTitle, kText, kUrl, std::move(files),
base::BindLambdaForTesting([&result, &run_loop](ShareError error) {
result = error;
run_loop.Quit();
}));
run_loop.Run();
return result;
}
bool IsDangerousFilename(base::FilePath::StringPieceType path) {
return ShareServiceImpl::IsDangerousFilename(base::FilePath(path));
}
private:
blink::mojom::SharedFilePtr CreateSharedFile(const base::FilePath& name,
const std::string& content_type,
unsigned file_length) {
const std::string uuid = base::Uuid::GenerateRandomV4().AsLowercaseString();
auto blob = blink::mojom::SerializedBlob::New();
blob->uuid = uuid;
blob->content_type = content_type;
blob->size = file_length;
base::RunLoop run_loop;
auto blob_context_getter = browser_context()->GetBlobStorageContext();
content::GetIOThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE,
base::BindLambdaForTesting(
[&blob_context_getter, &blob, &uuid, &content_type, file_length]() {
storage::BlobImpl::Create(
blob_context_getter.Run()->AddFinishedBlob(
CreateBuilder(uuid, content_type, file_length)),
blob->blob.InitWithNewPipeAndPassReceiver());
}),
base::BindLambdaForTesting([&run_loop]() { run_loop.Quit(); }));
run_loop.Run();
return blink::mojom::SharedFile::New(*base::SafeBaseName::Create(name),
std::move(blob));
}
static std::unique_ptr<storage::BlobDataBuilder> CreateBuilder(
const std::string& uuid,
const std::string& content_type,
unsigned file_length) {
auto builder = std::make_unique<storage::BlobDataBuilder>(uuid);
builder->set_content_type(content_type);
const std::string contents(file_length, '*');
builder->AppendData(contents);
return builder;
}
#if BUILDFLAG(IS_CHROMEOS)
static void AcceptShareRequest(
content::WebContents* web_contents,
const std::vector<base::FilePath>& file_paths,
const std::vector<std::string>& content_types,
const std::vector<uint64_t>& file_sizes,
const std::string& text,
const std::string& title,
sharesheet::DeliveredCallback delivered_callback) {
std::move(delivered_callback).Run(sharesheet::SharesheetResult::kSuccess);
}
#endif
#if BUILDFLAG(IS_MAC)
static void AcceptShareRequest(
content::WebContents* web_contents,
const std::vector<base::FilePath>& file_paths,
const std::string& text,
const std::string& title,
const GURL& url,
blink::mojom::ShareService::ShareCallback close_callback) {
std::move(close_callback).Run(blink::mojom::ShareError::OK);
}
#endif
#if BUILDFLAG(IS_WIN)
webshare::ScopedShareOperationFakeComponents scoped_fake_components_;
#endif
base::test::ScopedFeatureList feature_list_;
mojo::Remote<blink::mojom::ShareService> share_service_remote_;
};
TEST_F(ShareServiceUnitTest, FileCount) {
EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".txt", "text/plain", 1234,
kMaxSharedFileCount));
EXPECT_EQ(ShareError::PERMISSION_DENIED,
ShareGeneratedFileData(".txt", "text/plain", 1234,
kMaxSharedFileCount + 1));
}
TEST_F(ShareServiceUnitTest, DangerousFilename) {
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL(".")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("./")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL(".\\")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("a.a")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("zzz.zzz")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("a/a")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("zzz/zzz")));
EXPECT_FALSE(IsDangerousFilename(FILE_PATH_LITERAL("1.XBM")));
EXPECT_FALSE(IsDangerousFilename(FILE_PATH_LITERAL("2.bMP")));
EXPECT_FALSE(IsDangerousFilename(FILE_PATH_LITERAL("3.Flac")));
EXPECT_FALSE(IsDangerousFilename(FILE_PATH_LITERAL("4.webM")));
}
TEST_F(ShareServiceUnitTest, DangerousMimeType) {
EXPECT_TRUE(ShareServiceImpl::IsDangerousMimeType(""));
EXPECT_TRUE(ShareServiceImpl::IsDangerousMimeType("/"));
EXPECT_TRUE(ShareServiceImpl::IsDangerousMimeType("a/a"));
EXPECT_TRUE(ShareServiceImpl::IsDangerousMimeType("zzz/zzz"));
EXPECT_TRUE(ShareServiceImpl::IsDangerousMimeType("audio/Flac"));
EXPECT_TRUE(ShareServiceImpl::IsDangerousMimeType("Video/webm"));
EXPECT_FALSE(ShareServiceImpl::IsDangerousMimeType("audio/mp3"));
EXPECT_FALSE(ShareServiceImpl::IsDangerousMimeType("audio/mpeg"));
}
TEST_F(ShareServiceUnitTest, Multimedia) {
EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".avif", "image/avif"));
EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".bmp", "image/bmp"));
EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".xbm", "image/x-xbitmap"));
EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".flac", "audio/flac"));
EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".webm", "video/webm"));
}
TEST_F(ShareServiceUnitTest, PortableDocumentFormat) {
EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".pdf", "application/pdf"));
}
#if BUILDFLAG(IS_WIN)
TEST_F(ShareServiceUnitTest, ReservedNames) {
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("CON")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("PRN")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("AUX")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("NUL")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("COM1")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("COM9")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("LPT1")));
EXPECT_TRUE(IsDangerousFilename(FILE_PATH_LITERAL("LPT9")));
}
#endif
#if BUILDFLAG(IS_CHROMEOS)
// On Chrome OS, like Android, we prevent sharing of Android applications.
TEST_F(ShareServiceUnitTest, AndroidPackage) {
EXPECT_EQ(ShareError::PERMISSION_DENIED,
ShareGeneratedFileData(".apk", "text/plain"));
EXPECT_EQ(ShareError::PERMISSION_DENIED,
ShareGeneratedFileData(".dex", "text/plain"));
EXPECT_EQ(ShareError::PERMISSION_DENIED,
ShareGeneratedFileData(".txt", "vnd.android.package-archive"));
}
TEST_F(ShareServiceUnitTest, TotalBytes) {
EXPECT_EQ(ShareError::OK,
ShareGeneratedFileData(".txt", "text/plain",
kMaxSharedFileBytes / kMaxSharedFileCount,
kMaxSharedFileCount));
EXPECT_EQ(
ShareError::PERMISSION_DENIED,
ShareGeneratedFileData(".txt", "text/plain",
(kMaxSharedFileBytes / kMaxSharedFileCount) + 1,
kMaxSharedFileCount));
}
TEST_F(ShareServiceUnitTest, FileBytes) {
EXPECT_EQ(ShareError::OK,
ShareGeneratedFileData(".txt", "text/plain", kMaxSharedFileBytes));
EXPECT_EQ(
ShareError::PERMISSION_DENIED,
ShareGeneratedFileData(".txt", "text/plain", kMaxSharedFileBytes + 1));
}
#endif