| // 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 "chrome/browser/webshare/share_service_impl.h" |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "base/feature_list.h" |
| #include "base/files/safe_base_name.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/bad_message.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/safe_browsing/safe_browsing_service.h" |
| #include "chrome/common/chrome_features.h" |
| #include "components/safe_browsing/content/common/file_type_policies.h" |
| #include "components/safe_browsing/core/browser/db/database_manager.h" |
| #include "content/public/browser/web_contents.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom.h" |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "chrome/browser/webshare/mac/sharing_service_operation.h" |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "chrome/browser/webshare/win/share_operation.h" |
| #endif |
| |
| // IsDangerousFilename() and IsDangerousMimeType() should be kept in sync with |
| // //third_party/blink/renderer/modules/webshare/FILE_TYPES.md |
| // //components/browser_ui/webshare/android/java/src/org/chromium/components/browser_ui/webshare/ShareServiceImpl.java |
| |
| ShareServiceImpl::ShareServiceImpl( |
| content::RenderFrameHost& render_frame_host, |
| mojo::PendingReceiver<blink::mojom::ShareService> receiver) |
| : content::DocumentService<blink::mojom::ShareService>(render_frame_host, |
| std::move(receiver)) |
| #if BUILDFLAG(IS_CHROMEOS) |
| , |
| sharesheet_client_( |
| content::WebContents::FromRenderFrameHost(&render_frame_host)) |
| #endif |
| { |
| DCHECK(base::FeatureList::IsEnabled(features::kWebShare)); |
| } |
| |
| ShareServiceImpl::~ShareServiceImpl() = default; |
| |
| // static |
| void ShareServiceImpl::Create( |
| content::RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<blink::mojom::ShareService> receiver) { |
| CHECK(render_frame_host); |
| if (render_frame_host->IsNestedWithinFencedFrame()) { |
| // The renderer should have checked and disallowed the request for fenced |
| // frames in NavigatorShare and thrown a DOMException. Ignore the request |
| // and mark it as bad if it didn't happen for some reason. |
| bad_message::ReceivedBadMessage(render_frame_host->GetProcess(), |
| bad_message::SSI_CREATE_FENCED_FRAME); |
| return; |
| } |
| |
| new ShareServiceImpl(*render_frame_host, std::move(receiver)); |
| } |
| |
| // static |
| bool ShareServiceImpl::IsDangerousFilename(const base::FilePath& path) { |
| constexpr const base::FilePath::CharType* kPermitted[] = { |
| FILE_PATH_LITERAL(".avif"), // image/avif |
| FILE_PATH_LITERAL(".bmp"), // image/bmp / image/x-ms-bmp |
| FILE_PATH_LITERAL(".css"), // text/css |
| FILE_PATH_LITERAL(".csv"), // text/csv / text/comma-separated-values |
| FILE_PATH_LITERAL(".ehtml"), // text/html |
| FILE_PATH_LITERAL(".flac"), // audio/flac |
| FILE_PATH_LITERAL(".gif"), // image/gif |
| FILE_PATH_LITERAL(".htm"), // text/html |
| FILE_PATH_LITERAL(".html"), // text/html |
| FILE_PATH_LITERAL(".ico"), // image/x-icon |
| FILE_PATH_LITERAL(".jfif"), // image/jpeg |
| FILE_PATH_LITERAL(".jpeg"), // image/jpeg |
| FILE_PATH_LITERAL(".jpg"), // image/jpeg |
| FILE_PATH_LITERAL(".m4a"), // audio/x-m4a |
| FILE_PATH_LITERAL(".m4v"), // video/mp4 |
| FILE_PATH_LITERAL(".mp3"), // audio/mpeg audio/mp3 |
| FILE_PATH_LITERAL(".mp4"), // video/mp4 |
| FILE_PATH_LITERAL(".mpeg"), // video/mpeg |
| FILE_PATH_LITERAL(".mpg"), // video/mpeg |
| FILE_PATH_LITERAL(".oga"), // audio/ogg |
| FILE_PATH_LITERAL(".ogg"), // audio/ogg |
| FILE_PATH_LITERAL(".ogm"), // video/ogg |
| FILE_PATH_LITERAL(".ogv"), // video/ogg |
| FILE_PATH_LITERAL(".opus"), // audio/ogg |
| FILE_PATH_LITERAL(".pdf"), // application/pdf |
| FILE_PATH_LITERAL(".pjp"), // image/jpeg |
| FILE_PATH_LITERAL(".pjpeg"), // image/jpeg |
| FILE_PATH_LITERAL(".png"), // image/png |
| FILE_PATH_LITERAL(".shtm"), // text/html |
| FILE_PATH_LITERAL(".shtml"), // text/html |
| FILE_PATH_LITERAL(".svg"), // image/svg+xml |
| FILE_PATH_LITERAL(".svgz"), // image/svg+xml |
| FILE_PATH_LITERAL(".text"), // text/plain |
| FILE_PATH_LITERAL(".tif"), // image/tiff |
| FILE_PATH_LITERAL(".tiff"), // image/tiff |
| FILE_PATH_LITERAL(".txt"), // text/plain |
| FILE_PATH_LITERAL(".wav"), // audio/wav |
| FILE_PATH_LITERAL(".weba"), // audio/webm |
| FILE_PATH_LITERAL(".webm"), // video/webm |
| FILE_PATH_LITERAL(".webp"), // image/webp |
| FILE_PATH_LITERAL(".xbm"), // image/x-xbitmap |
| }; |
| |
| for (const base::FilePath::CharType* permitted : kPermitted) { |
| if (base::EndsWith(path.value(), permitted, |
| base::CompareCase::INSENSITIVE_ASCII)) |
| return false; |
| } |
| return true; |
| } |
| |
| // static |
| bool ShareServiceImpl::IsDangerousMimeType(base::StringPiece content_type) { |
| constexpr std::array<const char*, 28> kPermitted = { |
| "application/pdf", |
| "audio/flac", |
| "audio/mp3", |
| "audio/mpeg", |
| "audio/ogg", |
| "audio/wav", |
| "audio/webm", |
| "audio/x-m4a", |
| "image/avif", |
| "image/bmp", |
| "image/gif", |
| "image/jpeg", |
| "image/png", |
| "image/svg+xml", |
| "image/tiff", |
| "image/webp", |
| "image/x-icon", |
| "image/x-ms-bmp", |
| "image/x-xbitmap", |
| "text/comma-separated-values", |
| "text/css", |
| "text/csv", |
| "text/html", |
| "text/plain", |
| "video/mp4", |
| "video/mpeg", |
| "video/ogg", |
| "video/webm", |
| }; |
| |
| for (const char* permitted : kPermitted) { |
| if (content_type == permitted) |
| return false; |
| } |
| return true; |
| } |
| |
| void ShareServiceImpl::Share(const std::string& title, |
| const std::string& text, |
| const GURL& share_url, |
| std::vector<blink::mojom::SharedFilePtr> files, |
| ShareCallback callback) { |
| UMA_HISTOGRAM_ENUMERATION(kWebShareApiCountMetric, WebShareMethod::kShare); |
| |
| if (!render_frame_host().IsFeatureEnabled( |
| blink::mojom::PermissionsPolicyFeature::kWebShare)) { |
| std::move(callback).Run(blink::mojom::ShareError::PERMISSION_DENIED); |
| ReportBadMessageAndDeleteThis("Feature policy blocks Web Share"); |
| return; |
| } |
| |
| content::WebContents* const web_contents = |
| content::WebContents::FromRenderFrameHost(&render_frame_host()); |
| if (!web_contents) { |
| VLOG(1) << "Cannot share after navigating away"; |
| std::move(callback).Run(blink::mojom::ShareError::PERMISSION_DENIED); |
| return; |
| } |
| |
| if (files.size() > kMaxSharedFileCount) { |
| VLOG(1) << "Share too large: " << files.size() << " files"; |
| std::move(callback).Run(blink::mojom::ShareError::PERMISSION_DENIED); |
| return; |
| } |
| |
| bool should_check_url = false; |
| for (auto& file : files) { |
| if (!file || !file->blob || !file->blob->blob) { |
| mojo::ReportBadMessage("Invalid file to share()"); |
| return; |
| } |
| |
| const base::FilePath& path = file->name.path(); |
| if (IsDangerousFilename(path) || |
| IsDangerousMimeType(file->blob->content_type)) { |
| VLOG(1) << "File type is not supported: " << path << " has mime type " |
| << file->blob->content_type; |
| std::move(callback).Run(blink::mojom::ShareError::PERMISSION_DENIED); |
| return; |
| } |
| |
| // Check if at least one file is marked by the download protection service |
| // to send a ping to check this file type. |
| if (!should_check_url && |
| safe_browsing::FileTypePolicies::GetInstance()->IsCheckedBinaryFile( |
| path)) { |
| should_check_url = true; |
| } |
| |
| // In the case where the original blob handle was to a native file (of |
| // unknown size), the serialized data does not contain an accurate file |
| // size. To handle this, the comparison against kMaxSharedFileBytes should |
| // be done by the platform-specific implementations as part of processing |
| // the blobs. |
| } |
| |
| DCHECK(!safe_browsing_request_); |
| if (should_check_url && g_browser_process->safe_browsing_service()) { |
| safe_browsing_request_.emplace( |
| g_browser_process->safe_browsing_service()->database_manager(), |
| web_contents->GetLastCommittedURL(), |
| base::BindOnce(&ShareServiceImpl::OnSafeBrowsingResultReceived, |
| weak_factory_.GetWeakPtr(), title, text, share_url, |
| std::move(files), std::move(callback))); |
| return; |
| } |
| |
| OnSafeBrowsingResultReceived(title, text, share_url, std::move(files), |
| std::move(callback), |
| /*is_url_safe=*/true); |
| } |
| |
| void ShareServiceImpl::OnSafeBrowsingResultReceived( |
| const std::string& title, |
| const std::string& text, |
| const GURL& share_url, |
| std::vector<blink::mojom::SharedFilePtr> files, |
| ShareCallback callback, |
| bool is_url_safe) { |
| safe_browsing_request_.reset(); |
| |
| content::WebContents* const web_contents = |
| content::WebContents::FromRenderFrameHost(&render_frame_host()); |
| if (!web_contents) { |
| VLOG(1) << "Cannot share after navigating away"; |
| std::move(callback).Run(blink::mojom::ShareError::PERMISSION_DENIED); |
| return; |
| } |
| |
| if (!is_url_safe) { |
| VLOG(1) << "File not safe to share from this website"; |
| std::move(callback).Run(blink::mojom::ShareError::PERMISSION_DENIED); |
| return; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| sharesheet_client_.Share(title, text, share_url, std::move(files), |
| std::move(callback)); |
| #elif BUILDFLAG(IS_MAC) |
| auto sharing_service_operation = |
| std::make_unique<webshare::SharingServiceOperation>( |
| title, text, share_url, std::move(files), web_contents); |
| |
| // grab a safe reference to |sharing_service_operation| before calling move on |
| // it. |
| webshare::SharingServiceOperation* sharing_service_operation_ptr = |
| sharing_service_operation.get(); |
| |
| // Wrap the |callback| in a binding that owns the |sharing_service_operation| |
| // so its lifetime can be preserved till its done. |
| sharing_service_operation_ptr->Share(base::BindOnce( |
| [](std::unique_ptr<webshare::SharingServiceOperation> |
| sharing_service_operation, |
| ShareCallback callback, |
| blink::mojom::ShareError result) { std::move(callback).Run(result); }, |
| std::move(sharing_service_operation), std::move(callback))); |
| #elif BUILDFLAG(IS_WIN) |
| auto share_operation = std::make_unique<webshare::ShareOperation>( |
| title, text, share_url, std::move(files), web_contents); |
| auto* const share_operation_ptr = share_operation.get(); |
| share_operation_ptr->Run(base::BindOnce( |
| [](std::unique_ptr<webshare::ShareOperation> share_operation, |
| ShareCallback callback, |
| blink::mojom::ShareError result) { std::move(callback).Run(result); }, |
| std::move(share_operation), std::move(callback))); |
| #else |
| NOTREACHED(); |
| std::move(callback).Run(blink::mojom::ShareError::INTERNAL_ERROR); |
| #endif |
| } |