| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "chrome/browser/webshare/mac/sharing_service_operation.h" |
| |
| #include <AppKit/AppKit.h> |
| |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/i18n/file_util_icu.h" |
| #include "base/no_destructor.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/uuid.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/visibility_timer_tab_helper.h" |
| #include "chrome/browser/webshare/prepare_directory_task.h" |
| #include "chrome/browser/webshare/prepare_subdirectory_task.h" |
| #include "chrome/browser/webshare/share_service_impl.h" |
| #include "chrome/browser/webshare/store_files_task.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/render_widget_host_view.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "net/base/filename_util.h" |
| #include "storage/browser/blob/blob_storage_context.h" |
| #include "url/gurl.h" |
| |
| using content::BrowserContext; |
| using content::StoragePartition; |
| |
| namespace { |
| |
| constexpr base::FilePath::CharType kWebShareDirname[] = |
| FILE_PATH_LITERAL("WebShare"); |
| |
| base::FilePath GenerateUniqueSubDirectory(const base::FilePath& directory) { |
| std::string unique_subdirectory = base::StringPrintf( |
| "share-%s", base::Uuid::GenerateRandomV4().AsLowercaseString().c_str()); |
| return directory.Append(unique_subdirectory); |
| } |
| |
| } // namespace |
| |
| namespace webshare { |
| |
| SharingServiceOperation::SharingServiceOperation( |
| const std::string& title, |
| const std::string& text, |
| const GURL& url, |
| std::vector<blink::mojom::SharedFilePtr> files, |
| content::WebContents* web_contents) |
| : web_contents_(web_contents->GetWeakPtr()), |
| title_(title), |
| text_(text), |
| url_(url), |
| shared_files_(std::move(files)) {} |
| |
| SharingServiceOperation::~SharingServiceOperation() = default; |
| |
| void SharingServiceOperation::Share( |
| blink::mojom::ShareService::ShareCallback callback) { |
| callback_ = std::move(callback); |
| |
| Profile* const profile = |
| Profile::FromBrowserContext(web_contents_->GetBrowserContext()); |
| DCHECK(profile); |
| |
| // File sharing is denied in incognito, as files are written to disk. |
| // To prevent sites from using that to detect whether incognito mode is |
| // active, we deny after a random time delay, to simulate a user cancelling |
| // the share. |
| if (profile->IsIncognitoProfile() && !shared_files_.empty()) { |
| // Random number of seconds in the range [1.0, 2.0). |
| double delay_seconds = 1.0 + 1.0 * base::RandDouble(); |
| VisibilityTimerTabHelper::CreateForWebContents(web_contents_.get()); |
| VisibilityTimerTabHelper::FromWebContents(web_contents_.get()) |
| ->PostTaskAfterVisibleDelay( |
| FROM_HERE, |
| base::BindOnce(std::move(callback_), |
| blink::mojom::ShareError::CANCELED), |
| base::Seconds(delay_seconds)); |
| return; |
| } |
| |
| if (shared_files_.size() == 0) { |
| GetSharePickerCallback().Run( |
| web_contents_.get(), file_paths_, text_, title_, url_, |
| base::BindOnce(&SharingServiceOperation::OnShowSharePicker, |
| weak_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| BrowserContext* browser_context = web_contents_->GetBrowserContext(); |
| StoragePartition* const partition = |
| browser_context->GetDefaultStoragePartition(); |
| directory_ = partition->GetPath().Append(kWebShareDirname); |
| |
| prepare_directory_task_ = std::make_unique<PrepareDirectoryTask>( |
| directory_, kMaxSharedFileBytes, |
| base::BindOnce(&SharingServiceOperation::OnPrepareDirectory, |
| weak_factory_.GetWeakPtr())); |
| |
| prepare_directory_task_->Start(); |
| } |
| |
| // static |
| void SharingServiceOperation::SetSharePickerCallbackForTesting( |
| SharePickerCallback callback) { |
| GetSharePickerCallback() = std::move(callback); |
| } |
| |
| void SharingServiceOperation::OnPrepareDirectory( |
| blink::mojom::ShareError error) { |
| if (!web_contents_ || error != blink::mojom::ShareError::OK) { |
| std::move(callback_).Run(error); |
| return; |
| } |
| |
| for (const auto& file : shared_files_) { |
| // SafeBaseName protects against including paths in a file name. |
| std::string file_name = file->name.path().value(); |
| DCHECK_EQ(file_name.find('/'), std::string::npos); |
| base::i18n::ReplaceIllegalCharactersInPath(&file_name, '_'); |
| file_paths_.push_back( |
| GenerateUniqueSubDirectory(directory_).Append(file_name)); |
| } |
| |
| prepare_subdirectory_task_ = std::make_unique<PrepareSubDirectoryTask>( |
| file_paths_, |
| base::BindOnce(&SharingServiceOperation::OnPrepareSubDirectory, |
| weak_factory_.GetWeakPtr())); |
| |
| prepare_subdirectory_task_->Start(); |
| } |
| |
| void SharingServiceOperation::OnPrepareSubDirectory( |
| blink::mojom::ShareError error) { |
| if (!web_contents_ || error != blink::mojom::ShareError::OK) { |
| std::move(callback_).Run(error); |
| return; |
| } |
| |
| auto store_files_task = std::make_unique<StoreFilesTask>( |
| file_paths_, std::move(shared_files_), kMaxSharedFileBytes, |
| base::BindOnce(&SharingServiceOperation::OnStoreFiles, |
| weak_factory_.GetWeakPtr())); |
| |
| // The StoreFilesTask is self-owned. |
| store_files_task.release()->Start(); |
| } |
| |
| void SharingServiceOperation::OnStoreFiles(blink::mojom::ShareError error) { |
| if (!web_contents_ || error != blink::mojom::ShareError::OK) { |
| PrepareDirectoryTask::ScheduleSharedFileDeletion(std::move(file_paths_), |
| base::Minutes(0)); |
| std::move(callback_).Run(error); |
| return; |
| } |
| |
| GetSharePickerCallback().Run( |
| web_contents_.get(), file_paths_, text_, title_, url_, |
| base::BindOnce(&SharingServiceOperation::OnShowSharePicker, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void SharingServiceOperation::OnShowSharePicker( |
| blink::mojom::ShareError error) { |
| if (file_paths_.size() > 0) { |
| PrepareDirectoryTask::ScheduleSharedFileDeletion(std::move(file_paths_), |
| base::Seconds(60)); |
| } |
| std::move(callback_).Run(error); |
| } |
| |
| // static |
| void SharingServiceOperation::ShowSharePicker( |
| 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 callback) { |
| std::vector<std::string> file_paths_as_utf8; |
| for (const auto& file_path : file_paths) { |
| file_paths_as_utf8.emplace_back(file_path.AsUTF8Unsafe()); |
| } |
| |
| web_contents->GetRenderWidgetHostView()->ShowSharePicker( |
| title, text, url, file_paths_as_utf8, std::move(callback)); |
| } |
| |
| // static |
| SharingServiceOperation::SharePickerCallback& |
| SharingServiceOperation::GetSharePickerCallback() { |
| static base::NoDestructor<SharePickerCallback> callback( |
| base::BindRepeating(&SharingServiceOperation::ShowSharePicker)); |
| return *callback; |
| } |
| |
| } // namespace webshare |