| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| #include "third_party/blink/renderer/modules/clipboard/clipboard_reader.h" |
| |
| #include "base/task/single_thread_task_runner.h" |
| #include "third_party/blink/public/mojom/clipboard/clipboard.mojom-blink.h" |
| #include "third_party/blink/renderer/core/clipboard/system_clipboard.h" |
| #include "third_party/blink/renderer/core/dom/document_fragment.h" |
| #include "third_party/blink/renderer/core/editing/serializers/serialization.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/web_feature.h" |
| #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h" |
| #include "third_party/blink/renderer/modules/clipboard/clipboard.h" |
| #include "third_party/blink/renderer/modules/clipboard/clipboard_promise.h" |
| #include "third_party/blink/renderer/platform/heap/cross_thread_handle.h" |
| #include "third_party/blink/renderer/platform/image-encoders/image_encoder.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/worker_pool.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_base.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" |
| #include "third_party/blink/renderer/platform/wtf/wtf.h" |
| #include "ui/base/clipboard/clipboard_constants.h" |
| |
| namespace blink { |
| |
| namespace { // anonymous namespace for ClipboardReader's derived classes. |
| |
| // Reads a PNG from the System Clipboard as a Blob with image/png content. |
| // Since the data returned from ReadPng() is already in the desired format, no |
| // encoding is required and the blob is created directly from Read(). |
| class ClipboardPngReader final : public ClipboardReader { |
| public: |
| explicit ClipboardPngReader(SystemClipboard* system_clipboard, |
| ClipboardPromise* promise) |
| : ClipboardReader(system_clipboard, promise) {} |
| ~ClipboardPngReader() override = default; |
| |
| ClipboardPngReader(const ClipboardPngReader&) = delete; |
| ClipboardPngReader& operator=(const ClipboardPngReader&) = delete; |
| |
| void Read() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| mojo_base::BigBuffer data = |
| system_clipboard()->ReadPng(mojom::blink::ClipboardBuffer::kStandard); |
| |
| Blob* blob = nullptr; |
| if (data.size()) { |
| blob = Blob::Create(data, ui::kMimeTypePng); |
| } |
| promise_->OnRead(blob); |
| } |
| |
| private: |
| void NextRead(Vector<uint8_t> utf8_bytes) override { NOTREACHED(); } |
| }; |
| |
| // Reads an image from the System Clipboard as a Blob with text/plain content. |
| class ClipboardTextReader final : public ClipboardReader { |
| public: |
| explicit ClipboardTextReader(SystemClipboard* system_clipboard, |
| ClipboardPromise* promise) |
| : ClipboardReader(system_clipboard, promise) {} |
| ~ClipboardTextReader() override = default; |
| |
| ClipboardTextReader(const ClipboardTextReader&) = delete; |
| ClipboardTextReader& operator=(const ClipboardTextReader&) = delete; |
| |
| void Read() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| system_clipboard()->ReadPlainText( |
| mojom::blink::ClipboardBuffer::kStandard, |
| BindOnce(&ClipboardTextReader::OnRead, WrapPersistent(this))); |
| } |
| |
| private: |
| void OnRead(const String& plain_text) { |
| if (plain_text.empty()) { |
| NextRead(Vector<uint8_t>()); |
| return; |
| } |
| |
| worker_pool::PostTask( |
| FROM_HERE, |
| CrossThreadBindOnce(&ClipboardTextReader::EncodeOnBackgroundThread, |
| std::move(plain_text), MakeCrossThreadHandle(this), |
| std::move(clipboard_task_runner_))); |
| } |
| |
| static void EncodeOnBackgroundThread( |
| String plain_text, |
| CrossThreadHandle<ClipboardTextReader> reader, |
| scoped_refptr<base::SingleThreadTaskRunner> clipboard_task_runner) { |
| DCHECK(!IsMainThread()); |
| |
| // Encode WTF String to UTF-8, the standard text format for Blobs. |
| StringUtf8Adaptor utf8_text(plain_text); |
| Vector<uint8_t> utf8_bytes; |
| utf8_bytes.ReserveInitialCapacity(utf8_text.size()); |
| utf8_bytes.AppendSpan(base::span(utf8_text)); |
| |
| PostCrossThreadTask( |
| *clipboard_task_runner, FROM_HERE, |
| CrossThreadBindOnce( |
| &ClipboardTextReader::NextRead, |
| MakeUnwrappingCrossThreadHandle<ClipboardTextReader>( |
| std::move(reader)), |
| std::move(utf8_bytes))); |
| } |
| |
| void NextRead(Vector<uint8_t> utf8_bytes) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| Blob* blob = nullptr; |
| if (utf8_bytes.size()) { |
| blob = Blob::Create(utf8_bytes, ui::kMimeTypePlainText); |
| } |
| promise_->OnRead(blob); |
| } |
| }; |
| |
| // Reads HTML from the System Clipboard as a Blob with text/html content. |
| class ClipboardHtmlReader final : public ClipboardReader { |
| public: |
| explicit ClipboardHtmlReader(SystemClipboard* system_clipboard, |
| ClipboardPromise* promise, |
| bool sanitize_html) |
| : ClipboardReader(system_clipboard, promise), |
| sanitize_html_(sanitize_html) {} |
| ~ClipboardHtmlReader() override = default; |
| |
| void Read() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| promise_->GetExecutionContext()->CountUse( |
| sanitize_html_ ? WebFeature::kHtmlClipboardApiRead |
| : WebFeature::kHtmlClipboardApiUnsanitizedRead); |
| system_clipboard()->ReadHTML( |
| BindOnce(&ClipboardHtmlReader::OnRead, WrapPersistent(this))); |
| } |
| |
| private: |
| void OnRead(const String& html_string, |
| const KURL& url, |
| unsigned fragment_start, |
| unsigned fragment_end) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_GE(fragment_start, 0u); |
| DCHECK_LE(fragment_end, html_string.length()); |
| DCHECK_LE(fragment_start, fragment_end); |
| |
| LocalFrame* frame = promise_->GetLocalFrame(); |
| if (!frame || html_string.empty()) { |
| NextRead(Vector<uint8_t>()); |
| return; |
| } |
| |
| // Process the HTML string and strip out certain security sensitive tags if |
| // needed. `CreateStrictlyProcessedMarkupWithContext` must be called on the |
| // main thread because HTML DOM nodes can only be used on the main thread. |
| String final_html = |
| sanitize_html_ ? CreateStrictlyProcessedMarkupWithContext( |
| *frame->GetDocument(), html_string, fragment_start, |
| fragment_end, url, kIncludeNode, kResolveAllURLs) |
| : html_string; |
| if (final_html.empty()) { |
| NextRead(Vector<uint8_t>()); |
| return; |
| } |
| worker_pool::PostTask( |
| FROM_HERE, |
| CrossThreadBindOnce(&ClipboardHtmlReader::EncodeOnBackgroundThread, |
| std::move(final_html), MakeCrossThreadHandle(this), |
| std::move(clipboard_task_runner_))); |
| } |
| |
| static void EncodeOnBackgroundThread( |
| String plain_text, |
| CrossThreadHandle<ClipboardHtmlReader> reader, |
| scoped_refptr<base::SingleThreadTaskRunner> clipboard_task_runner) { |
| DCHECK(!IsMainThread()); |
| |
| // Encode WTF String to UTF-8, the standard text format for blobs. |
| StringUtf8Adaptor utf8_text(plain_text); |
| Vector<uint8_t> utf8_bytes; |
| utf8_bytes.ReserveInitialCapacity(utf8_text.size()); |
| utf8_bytes.AppendSpan(base::span(utf8_text)); |
| |
| PostCrossThreadTask( |
| *clipboard_task_runner, FROM_HERE, |
| CrossThreadBindOnce(&ClipboardHtmlReader::NextRead, |
| MakeUnwrappingCrossThreadHandle(std::move(reader)), |
| std::move(utf8_bytes))); |
| } |
| |
| void NextRead(Vector<uint8_t> utf8_bytes) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| Blob* blob = nullptr; |
| if (utf8_bytes.size()) { |
| blob = Blob::Create(utf8_bytes, ui::kMimeTypeHtml); |
| } |
| promise_->OnRead(blob); |
| } |
| |
| bool sanitize_html_ = true; |
| }; |
| |
| // Reads SVG from the System Clipboard as a Blob with image/svg+xml content. |
| class ClipboardSvgReader final : public ClipboardReader { |
| public: |
| ClipboardSvgReader(SystemClipboard* system_clipboard, |
| ClipboardPromise* promise) |
| : ClipboardReader(system_clipboard, promise) {} |
| ~ClipboardSvgReader() override = default; |
| |
| // This must be called on the main thread because XML DOM nodes can |
| // only be used on the main thread. |
| void Read() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| promise_->GetExecutionContext()->CountUse(WebFeature::kClipboardSvgRead); |
| system_clipboard()->ReadSvg( |
| BindOnce(&ClipboardSvgReader::OnRead, WrapPersistent(this))); |
| } |
| |
| private: |
| void OnRead(const String& svg_string) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| LocalFrame* frame = promise_->GetLocalFrame(); |
| if (!frame) { |
| NextRead(Vector<uint8_t>()); |
| return; |
| } |
| |
| // Now process the SVG string and strip out certain security sensitive tags. |
| KURL url; |
| unsigned fragment_start = 0; |
| String strictly_processed_svg = CreateStrictlyProcessedMarkupWithContext( |
| *frame->GetDocument(), svg_string, fragment_start, svg_string.length(), |
| url, kIncludeNode, kResolveAllURLs); |
| |
| if (strictly_processed_svg.empty()) { |
| NextRead(Vector<uint8_t>()); |
| return; |
| } |
| worker_pool::PostTask( |
| FROM_HERE, |
| CrossThreadBindOnce(&ClipboardSvgReader::EncodeOnBackgroundThread, |
| std::move(strictly_processed_svg), |
| MakeCrossThreadHandle(this), |
| std::move(clipboard_task_runner_))); |
| } |
| |
| static void EncodeOnBackgroundThread( |
| String plain_text, |
| CrossThreadHandle<ClipboardSvgReader> reader, |
| scoped_refptr<base::SingleThreadTaskRunner> clipboard_task_runner) { |
| DCHECK(!IsMainThread()); |
| |
| // Encode WTF String to UTF-8, the standard text format for Blobs. |
| StringUtf8Adaptor utf8_text(plain_text); |
| Vector<uint8_t> utf8_bytes; |
| utf8_bytes.ReserveInitialCapacity(utf8_text.size()); |
| utf8_bytes.AppendSpan(base::span(utf8_text)); |
| |
| PostCrossThreadTask( |
| *clipboard_task_runner, FROM_HERE, |
| CrossThreadBindOnce(&ClipboardSvgReader::NextRead, |
| MakeUnwrappingCrossThreadHandle(std::move(reader)), |
| std::move(utf8_bytes))); |
| } |
| |
| void NextRead(Vector<uint8_t> utf8_bytes) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| Blob* blob = nullptr; |
| if (utf8_bytes.size()) { |
| blob = Blob::Create(utf8_bytes, ui::kMimeTypeSvg); |
| } |
| promise_->OnRead(blob); |
| } |
| }; |
| |
| // Reads unsanitized custom formats from the System Clipboard as a Blob with |
| // custom MIME type content. |
| class ClipboardCustomFormatReader final : public ClipboardReader { |
| public: |
| explicit ClipboardCustomFormatReader(SystemClipboard* system_clipboard, |
| ClipboardPromise* promise, |
| const String& mime_type) |
| : ClipboardReader(system_clipboard, promise), mime_type_(mime_type) {} |
| ~ClipboardCustomFormatReader() override = default; |
| |
| void Read() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| promise_->GetExecutionContext()->CountUse( |
| WebFeature::kClipboardCustomFormatRead); |
| system_clipboard()->ReadUnsanitizedCustomFormat( |
| mime_type_, BindOnce(&ClipboardCustomFormatReader::OnCustomFormatRead, |
| WrapPersistent(this))); |
| } |
| |
| void OnCustomFormatRead(mojo_base::BigBuffer data) { |
| Blob* blob = Blob::Create(data, mime_type_); |
| promise_->OnRead(blob); |
| } |
| |
| private: |
| void NextRead(Vector<uint8_t> utf8_bytes) override {} |
| |
| String mime_type_; |
| }; |
| |
| } // anonymous namespace |
| |
| // ClipboardReader functions. |
| |
| // static |
| ClipboardReader* ClipboardReader::Create(SystemClipboard* system_clipboard, |
| const String& mime_type, |
| ClipboardPromise* promise, |
| bool sanitize_html) { |
| CHECK(ClipboardItem::supports(mime_type)); |
| // If this is a web custom format then read the unsanitized version. |
| if (!Clipboard::ParseWebCustomFormat(mime_type).empty()) { |
| // We read the custom MIME type that has the "web " prefix. |
| // These MIME types are found in the web custom format map written by |
| // native applications. |
| return MakeGarbageCollected<ClipboardCustomFormatReader>( |
| system_clipboard, promise, mime_type); |
| } |
| |
| if (mime_type == ui::kMimeTypePng) { |
| return MakeGarbageCollected<ClipboardPngReader>(system_clipboard, promise); |
| } |
| |
| if (mime_type == ui::kMimeTypePlainText) { |
| return MakeGarbageCollected<ClipboardTextReader>(system_clipboard, promise); |
| } |
| |
| if (mime_type == ui::kMimeTypeHtml) { |
| return MakeGarbageCollected<ClipboardHtmlReader>(system_clipboard, promise, |
| sanitize_html); |
| } |
| |
| if (mime_type == ui::kMimeTypeSvg) { |
| return MakeGarbageCollected<ClipboardSvgReader>(system_clipboard, promise); |
| } |
| |
| NOTREACHED() |
| << "IsValidType() and Create() have inconsistent implementations."; |
| } |
| |
| ClipboardReader::ClipboardReader(SystemClipboard* system_clipboard, |
| ClipboardPromise* promise) |
| : clipboard_task_runner_(promise->GetExecutionContext()->GetTaskRunner( |
| TaskType::kUserInteraction)), |
| promise_(promise), |
| system_clipboard_(system_clipboard) {} |
| |
| ClipboardReader::~ClipboardReader() = default; |
| |
| void ClipboardReader::Trace(Visitor* visitor) const { |
| visitor->Trace(system_clipboard_); |
| visitor->Trace(promise_); |
| } |
| |
| } // namespace blink |