/* * Copyright (C) 2007, 2008, 2012, 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "Pasteboard.h" #if PLATFORM(IOS_FAMILY) #import "DragData.h" #import "Image.h" #import "NotImplemented.h" #import "PasteboardStrategy.h" #import "PlatformPasteboard.h" #import "PlatformStrategies.h" #import "RuntimeEnabledFeatures.h" #import "SharedBuffer.h" #import "UTIUtilities.h" #import "WebNSAttributedStringExtras.h" #import <MobileCoreServices/MobileCoreServices.h> #import <wtf/URL.h> #import <wtf/text/StringHash.h> @interface NSAttributedString (NSAttributedStringInternal) - (id)initWithRTF:(NSData *)data documentAttributes:(NSDictionary **)dict; - (id)initWithRTFD:(NSData *)data documentAttributes:(NSDictionary **)dict; - (NSData *)RTFFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict; - (NSData *)RTFDFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict; - (BOOL)containsAttachments; @end namespace WebCore { #if ENABLE(DRAG_SUPPORT) Pasteboard::Pasteboard(const String& pasteboardName) : m_pasteboardName(pasteboardName) , m_changeCount(platformStrategies()->pasteboardStrategy()->changeCount(pasteboardName)) { } void Pasteboard::setDragImage(DragImage, const IntPoint&) { notImplemented(); } std::unique_ptr<Pasteboard> Pasteboard::createForDragAndDrop() { return makeUnique<Pasteboard>("data interaction pasteboard"); } std::unique_ptr<Pasteboard> Pasteboard::createForDragAndDrop(const DragData& dragData) { return makeUnique<Pasteboard>(dragData.pasteboardName()); } #endif static int64_t changeCountForPasteboard(const String& pasteboardName = { }) { return platformStrategies()->pasteboardStrategy()->changeCount(pasteboardName); } // FIXME: Does this need to be declared in the header file? WEBCORE_EXPORT NSString *WebArchivePboardType = @"Apple Web Archive pasteboard type"; NSString *UIColorPboardType = @"com.apple.uikit.color"; Pasteboard::Pasteboard() : m_changeCount(0) { } Pasteboard::Pasteboard(int64_t changeCount) : m_changeCount(changeCount) { } void Pasteboard::writeMarkup(const String&) { } std::unique_ptr<Pasteboard> Pasteboard::createForCopyAndPaste() { return makeUnique<Pasteboard>(changeCountForPasteboard()); } void Pasteboard::write(const PasteboardWebContent& content) { platformStrategies()->pasteboardStrategy()->writeToPasteboard(content, m_pasteboardName); } String Pasteboard::resourceMIMEType(NSString *mimeType) { return UTIFromMIMEType(mimeType); } void Pasteboard::write(const PasteboardImage& pasteboardImage) { platformStrategies()->pasteboardStrategy()->writeToPasteboard(pasteboardImage, m_pasteboardName); } void Pasteboard::writePlainText(const String& text, SmartReplaceOption) { // FIXME: We vend "public.text" here for backwards compatibility with pre-iOS 11 apps. In the future, we should stop vending this UTI, // and instead set data for concrete plain text types. See <https://bugs.webkit.org/show_bug.cgi?id=173317>. platformStrategies()->pasteboardStrategy()->writeToPasteboard(kUTTypeText, text, m_pasteboardName); } void Pasteboard::write(const PasteboardURL& pasteboardURL) { platformStrategies()->pasteboardStrategy()->writeToPasteboard(pasteboardURL, m_pasteboardName); } void Pasteboard::writeTrustworthyWebURLsPboardType(const PasteboardURL&) { // A trustworthy URL pasteboard type needs to be decided on // before we allow calls to this function. A page data transfer // should not use the same pasteboard type as this function for // URLs. ASSERT_NOT_REACHED(); } void Pasteboard::write(const Color& color) { platformStrategies()->pasteboardStrategy()->setColor(color, m_pasteboardName); } bool Pasteboard::canSmartReplace() { return true; } void Pasteboard::read(PasteboardPlainText& text, PlainTextURLReadingPolicy allowURL, Optional<size_t> itemIndex) { auto itemIndexToQuery = itemIndex.valueOr(0); PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy(); if (allowURL == PlainTextURLReadingPolicy::AllowURL) { text.text = strategy.readStringFromPasteboard(itemIndexToQuery, kUTTypeURL, m_pasteboardName); if (!text.text.isEmpty()) { text.isURL = true; return; } } // We ask for the "public.text" representation only as a legacy fallback, in case apps still directly write // plain text for this abstract UTI. In almost all cases, the more correct choice would be to write to // one of the concrete "public.plain-text" representations (e.g. kUTTypeUTF8PlainText). In the future, we // should consider removing support for reading plain text from "public.text". text.text = strategy.readStringFromPasteboard(itemIndexToQuery, kUTTypePlainText, m_pasteboardName); if (text.text.isEmpty()) text.text = strategy.readStringFromPasteboard(itemIndexToQuery, kUTTypeText, m_pasteboardName); text.isURL = false; } static NSArray* supportedImageTypes() { return @[(__bridge NSString *)kUTTypePNG, (__bridge NSString *)kUTTypeTIFF, (__bridge NSString *)kUTTypeJPEG, (__bridge NSString *)kUTTypeGIF]; } static bool isTypeAllowedByReadingPolicy(NSString *type, WebContentReadingPolicy policy) { return policy == WebContentReadingPolicy::AnyType || [type isEqualToString:WebArchivePboardType] || [type isEqualToString:(__bridge NSString *)kUTTypeWebArchive] || [type isEqualToString:(__bridge NSString *)kUTTypeHTML] || [type isEqualToString:(__bridge NSString *)kUTTypeRTF] || [type isEqualToString:(__bridge NSString *)kUTTypeFlatRTFD]; } Pasteboard::ReaderResult Pasteboard::readPasteboardWebContentDataForType(PasteboardWebContentReader& reader, PasteboardStrategy& strategy, NSString *type, const PasteboardItemInfo& itemInfo, int itemIndex) { if ([type isEqualToString:WebArchivePboardType] || [type isEqualToString:(__bridge NSString *)kUTTypeWebArchive]) { auto buffer = strategy.readBufferFromPasteboard(itemIndex, type, m_pasteboardName); if (m_changeCount != changeCount()) return ReaderResult::PasteboardWasChangedExternally; return buffer && reader.readWebArchive(*buffer) ? ReaderResult::ReadType : ReaderResult::DidNotReadType; } if ([type isEqualToString:(__bridge NSString *)kUTTypeHTML]) { String htmlString = strategy.readStringFromPasteboard(itemIndex, kUTTypeHTML, m_pasteboardName); if (m_changeCount != changeCount()) return ReaderResult::PasteboardWasChangedExternally; return !htmlString.isNull() && reader.readHTML(htmlString) ? ReaderResult::ReadType : ReaderResult::DidNotReadType; } if ([type isEqualToString:(__bridge NSString *)kUTTypeVCard]) { // When dropping or pasting a virtual contact file in editable content, there's never a case where we // would want to dump the entire contents of the file as plain text. Instead, fall back on another // appropriate representation, such as a URL or plain text. For instance, in the case of an MKMapItem, // we would prefer to insert an Apple Maps link instead. return ReaderResult::DidNotReadType; } #if !PLATFORM(MACCATALYST) if ([type isEqualToString:(__bridge NSString *)kUTTypeFlatRTFD]) { RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, kUTTypeFlatRTFD, m_pasteboardName); if (m_changeCount != changeCount()) return ReaderResult::PasteboardWasChangedExternally; return buffer && reader.readRTFD(*buffer) ? ReaderResult::ReadType : ReaderResult::DidNotReadType; } if ([type isEqualToString:(__bridge NSString *)kUTTypeRTF]) { RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, kUTTypeRTF, m_pasteboardName); if (m_changeCount != changeCount()) return ReaderResult::PasteboardWasChangedExternally; return buffer && reader.readRTF(*buffer) ? ReaderResult::ReadType : ReaderResult::DidNotReadType; } #endif // !PLATFORM(MACCATALYST) if ([supportedImageTypes() containsObject:type]) { RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, type, m_pasteboardName); if (m_changeCount != changeCount()) return ReaderResult::PasteboardWasChangedExternally; return buffer && reader.readImage(buffer.releaseNonNull(), type, itemInfo.preferredPresentationSize) ? ReaderResult::ReadType : ReaderResult::DidNotReadType; } if ([type isEqualToString:(__bridge NSString *)kUTTypeURL]) { String title; URL url = strategy.readURLFromPasteboard(itemIndex, m_pasteboardName, title); if (m_changeCount != changeCount()) return ReaderResult::PasteboardWasChangedExternally; return !url.isNull() && reader.readURL(url, title) ? ReaderResult::ReadType : ReaderResult::DidNotReadType; } if (UTTypeConformsTo((__bridge CFStringRef)type, kUTTypePlainText)) { String string = strategy.readStringFromPasteboard(itemIndex, kUTTypePlainText, m_pasteboardName); if (m_changeCount != changeCount()) return ReaderResult::PasteboardWasChangedExternally; return !string.isNull() && reader.readPlainText(string) ? ReaderResult::ReadType : ReaderResult::DidNotReadType; } if (UTTypeConformsTo((__bridge CFStringRef)type, kUTTypeText)) { String string = strategy.readStringFromPasteboard(itemIndex, kUTTypeText, m_pasteboardName); if (m_changeCount != changeCount()) return ReaderResult::PasteboardWasChangedExternally; return !string.isNull() && reader.readPlainText(string) ? ReaderResult::ReadType : ReaderResult::DidNotReadType; } return ReaderResult::DidNotReadType; } static void readURLAlongsideAttachmentIfNecessary(PasteboardWebContentReader& reader, PasteboardStrategy& strategy, const String& typeIdentifier, const String& pasteboardName, int itemIndex) { if (!UTTypeConformsTo(typeIdentifier.createCFString().get(), kUTTypeVCard)) return; String title; auto url = strategy.readURLFromPasteboard(itemIndex, pasteboardName, title); if (!url.isEmpty()) reader.readURL(url, title); } static bool prefersAttachmentRepresentation(const PasteboardItemInfo& info) { auto contentTypeForHighestFidelityItem = info.contentTypeForHighestFidelityItem(); if (contentTypeForHighestFidelityItem.isEmpty()) return false; if (info.preferredPresentationStyle == PasteboardItemPresentationStyle::Inline) return false; return info.canBeTreatedAsAttachmentOrFile() || UTTypeConformsTo(contentTypeForHighestFidelityItem.createCFString().get(), kUTTypeVCard); } void Pasteboard::read(PasteboardWebContentReader& reader, WebContentReadingPolicy policy, Optional<size_t> itemIndex) { reader.contentOrigin = readOrigin(); if (respectsUTIFidelities()) { readRespectingUTIFidelities(reader, policy, itemIndex); return; } PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy(); size_t numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName); if (!numberOfItems) return; NSArray *types = supportedWebContentPasteboardTypes(); int numberOfTypes = [types count]; #if ENABLE(ATTACHMENT_ELEMENT) bool canReadAttachment = policy == WebContentReadingPolicy::AnyType && RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled(); #else bool canReadAttachment = false; #endif for (size_t i = 0; i < numberOfItems; i++) { if (itemIndex && i != *itemIndex) continue; auto info = strategy.informationForItemAtIndex(i, m_pasteboardName, m_changeCount); if (!info) return; #if ENABLE(ATTACHMENT_ELEMENT) if (canReadAttachment && prefersAttachmentRepresentation(*info)) { auto typeForFileUpload = info->contentTypeForHighestFidelityItem(); if (auto buffer = strategy.readBufferFromPasteboard(i, typeForFileUpload, m_pasteboardName)) { readURLAlongsideAttachmentIfNecessary(reader, strategy, typeForFileUpload, m_pasteboardName, i); reader.readDataBuffer(*buffer, typeForFileUpload, info->suggestedFileName, info->preferredPresentationSize); continue; } } #endif for (int typeIndex = 0; typeIndex < numberOfTypes; typeIndex++) { NSString *type = [types objectAtIndex:typeIndex]; if (!isTypeAllowedByReadingPolicy(type, policy)) continue; auto itemResult = readPasteboardWebContentDataForType(reader, strategy, type, *info, i); if (itemResult == ReaderResult::PasteboardWasChangedExternally) return; if (itemResult == ReaderResult::ReadType) break; } } } bool Pasteboard::respectsUTIFidelities() const { // For now, data interaction is the only feature that uses item-provider-based pasteboard representations. // In the future, we may need to consult the client layer to determine whether or not the pasteboard supports // item types ranked by fidelity. return m_pasteboardName == "data interaction pasteboard"; } void Pasteboard::readRespectingUTIFidelities(PasteboardWebContentReader& reader, WebContentReadingPolicy policy, Optional<size_t> itemIndex) { ASSERT(respectsUTIFidelities()); auto& strategy = *platformStrategies()->pasteboardStrategy(); for (NSUInteger index = 0, numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName); index < numberOfItems; ++index) { if (itemIndex && index != *itemIndex) continue; #if ENABLE(ATTACHMENT_ELEMENT) auto info = strategy.informationForItemAtIndex(index, m_pasteboardName, m_changeCount); if (!info) return; auto attachmentFilePath = info->pathForHighestFidelityItem(); bool canReadAttachment = policy == WebContentReadingPolicy::AnyType && RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled() && !attachmentFilePath.isEmpty(); auto contentType = info->contentTypeForHighestFidelityItem(); if (canReadAttachment && prefersAttachmentRepresentation(*info)) { readURLAlongsideAttachmentIfNecessary(reader, strategy, contentType, m_pasteboardName, index); reader.readFilePath(WTFMove(attachmentFilePath), info->preferredPresentationSize, contentType); continue; } #endif // Try to read data from each type identifier that this pasteboard item supports, and WebKit also recognizes. Type identifiers are // read in order of fidelity, as specified by each pasteboard item. ReaderResult result = ReaderResult::DidNotReadType; for (auto& type : info->platformTypesByFidelity) { if (!isTypeAllowedByReadingPolicy(type, policy)) continue; result = readPasteboardWebContentDataForType(reader, strategy, type, *info, index); if (result == ReaderResult::PasteboardWasChangedExternally) return; if (result == ReaderResult::ReadType) break; } #if ENABLE(ATTACHMENT_ELEMENT) if (canReadAttachment && result == ReaderResult::DidNotReadType) reader.readFilePath(WTFMove(attachmentFilePath), info->preferredPresentationSize, contentType); #endif } } NSArray *Pasteboard::supportedWebContentPasteboardTypes() { return @[ #if !PLATFORM(MACCATALYST) WebArchivePboardType, #endif (__bridge NSString *)kUTTypeWebArchive, #if !PLATFORM(MACCATALYST) (__bridge NSString *)kUTTypeFlatRTFD, (__bridge NSString *)kUTTypeRTF, #endif (__bridge NSString *)kUTTypeHTML, (__bridge NSString *)kUTTypePNG, (__bridge NSString *)kUTTypeTIFF, (__bridge NSString *)kUTTypeJPEG, (__bridge NSString *)kUTTypeGIF, (__bridge NSString *)kUTTypeURL, (__bridge NSString *)kUTTypeText ]; } NSArray *Pasteboard::supportedFileUploadPasteboardTypes() { return @[ (__bridge NSString *)kUTTypeItem, (__bridge NSString *)kUTTypeContent, (__bridge NSString *)kUTTypeZipArchive ]; } bool Pasteboard::hasData() { return !!platformStrategies()->pasteboardStrategy()->getPasteboardItemsCount(m_pasteboardName); } static String utiTypeFromCocoaType(NSString *type) { RetainPtr<CFStringRef> utiType = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)type, NULL)); if (!utiType) return String(); return String(adoptCF(UTTypeCopyPreferredTagWithClass(utiType.get(), kUTTagClassMIMEType)).get()); } static RetainPtr<NSString> cocoaTypeFromHTMLClipboardType(const String& type) { if (NSString *platformType = PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(type)) { if (platformType.length) return platformType; } // Try UTI now. if (NSString *utiType = utiTypeFromCocoaType(type)) return utiType; // No mapping, just pass the whole string though. return (NSString *)type; } void Pasteboard::clear(const String& type) { // Since UIPasteboard enforces changeCount itself on writing, we don't check it here. RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type); if (!cocoaType) return; platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), String(), m_pasteboardName); } void Pasteboard::clear() { platformStrategies()->pasteboardStrategy()->writeToPasteboard(String(), String(), m_pasteboardName); } Vector<String> Pasteboard::readPlatformValuesAsStrings(const String& domType, int64_t changeCount, const String& pasteboardName) { auto& strategy = *platformStrategies()->pasteboardStrategy(); // Grab the value off the pasteboard corresponding to the cocoaType. auto cocoaType = cocoaTypeFromHTMLClipboardType(domType); if (!cocoaType) return { }; auto values = strategy.allStringsForType(cocoaType.get(), pasteboardName); if ([cocoaType isEqualToString:(__bridge NSString *)kUTTypePlainText]) { values = values.map([&] (auto& value) -> String { return [value precomposedStringWithCanonicalMapping]; }); } // Enforce changeCount ourselves for security. We check after reading instead of before to be // sure it doesn't change between our testing the change count and accessing the data. if (changeCount != changeCountForPasteboard(pasteboardName)) return { }; return values; } void Pasteboard::addHTMLClipboardTypesForCocoaType(ListHashSet<String>& resultTypes, const String& cocoaType) { // UTI may not do these right, so make sure we get the right, predictable result. if ([cocoaType isEqualToString:(NSString *)kUTTypePlainText] || [cocoaType isEqualToString:(NSString *)kUTTypeUTF8PlainText] || [cocoaType isEqualToString:(NSString *)kUTTypeUTF16PlainText]) { resultTypes.add("text/plain"_s); return; } if ([cocoaType isEqualToString:(NSString *)kUTTypeURL]) { resultTypes.add("text/uri-list"_s); return; } if ([cocoaType isEqualToString:(NSString *)kUTTypeHTML]) { resultTypes.add("text/html"_s); // We don't return here for App compatibility. } if (Pasteboard::shouldTreatCocoaTypeAsFile(cocoaType)) return; String utiType = utiTypeFromCocoaType(cocoaType); if (!utiType.isEmpty()) { resultTypes.add(utiType); return; } // No mapping, just pass the whole string though. resultTypes.add(cocoaType); } void Pasteboard::writeString(const String& type, const String& data) { RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type); if (!cocoaType) return; platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), data, m_pasteboardName); } Vector<String> Pasteboard::readFilePaths() { Vector<String> filePaths; auto& strategy = *platformStrategies()->pasteboardStrategy(); for (NSUInteger index = 0, numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName); index < numberOfItems; ++index) { // Currently, drag and drop is the only case on iOS where the "pasteboard" may contain file paths. auto info = strategy.informationForItemAtIndex(index, m_pasteboardName, m_changeCount); if (!info) return { }; auto filePath = info->pathForHighestFidelityItem(); if (!filePath.isEmpty()) filePaths.append(WTFMove(filePath)); } return filePaths; } } #endif // PLATFORM(IOS_FAMILY)