/* * Copyright (C) 2017-2022 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" #import "LegacyNSPasteboardTypes.h" #import "PasteboardStrategy.h" #import "PlatformStrategies.h" #import "SharedBuffer.h" #import #import #import #if PLATFORM(IOS_FAMILY) #import #endif namespace WebCore { #if PLATFORM(MAC) static NSBitmapImageFileType bitmapPNGFileType() { return NSBitmapImageFileTypePNG; } #endif // PLATFORM(MAC) // Making this non-inline so that WebKit 2's decoding doesn't have to include FragmentedSharedBuffer.h. PasteboardWebContent::PasteboardWebContent() = default; PasteboardWebContent::~PasteboardWebContent() = default; enum class ImageType { Invalid = 0, TIFF, PNG, JPEG, GIF, }; static ImageType cocoaTypeToImageType(const String& cocoaType) { ALLOW_DEPRECATED_DECLARATIONS_BEGIN #if PLATFORM(MAC) if (cocoaType == String(legacyTIFFPasteboardType())) return ImageType::TIFF; #endif if (cocoaType == String(kUTTypeTIFF)) return ImageType::TIFF; #if PLATFORM(MAC) if (cocoaType == String(legacyPNGPasteboardType())) // NSPNGPboardType return ImageType::PNG; #endif if (cocoaType == String(kUTTypePNG)) return ImageType::PNG; if (cocoaType == String(kUTTypeJPEG)) return ImageType::JPEG; if (cocoaType == String(kUTTypeGIF)) return ImageType::GIF; ALLOW_DEPRECATED_DECLARATIONS_END return ImageType::Invalid; } // String literals returned by this function must be defined exactly once // since read(PasteboardFileReader&) uses HashMap to check uniqueness. static const char* imageTypeToMIMEType(ImageType type) { switch (type) { case ImageType::Invalid: return nullptr; case ImageType::TIFF: #if PLATFORM(MAC) return "image/png"; // For Web compatibility, we pretend to have PNG instead. #else return nullptr; // Don't support pasting TIFF on iOS for now. #endif case ImageType::PNG: return "image/png"; case ImageType::JPEG: return "image/jpeg"; case ImageType::GIF: return "image/gif"; } } static ASCIILiteral imageTypeToFakeFilename(ImageType type) { switch (type) { case ImageType::Invalid: ASSERT_NOT_REACHED(); return { }; case ImageType::TIFF: #if PLATFORM(MAC) return "image.png"_s; // For Web compatibility, we pretend to have PNG instead. #else ASSERT_NOT_REACHED(); return { }; #endif case ImageType::PNG: return "image.png"_s; case ImageType::JPEG: return "image.jpeg"_s; case ImageType::GIF: return "image.gif"_s; } } bool Pasteboard::shouldTreatCocoaTypeAsFile(const String& cocoaType) { return cocoaTypeToImageType(cocoaType) != ImageType::Invalid; } Pasteboard::FileContentState Pasteboard::fileContentState() { bool mayContainFilePaths = platformStrategies()->pasteboardStrategy()->getNumberOfFiles(m_pasteboardName, context()); #if PLATFORM(IOS_FAMILY) if (mayContainFilePaths) { // On iOS, files are not written to the pasteboard using file URLs, so we need a heuristic to determine // whether or not the pasteboard contains items that represent files. An example of when this gets tricky // is differentiating between cases where the user is dragging a plain text file, versus selected text. // Also, the presence of any other declared non-text data in the same item indicates that the content // being dropped can take on another non-text format, which could be a file. // If the item can't be treated as an attachment, it's very likely that the content being dropped is just // an inline piece of text, with no files in the pasteboard (and therefore, no risk of leaking file paths // to web content). In cases such as these, we should not suppress DataTransfer access. auto items = allPasteboardItemInfo(); if (!items) return FileContentState::NoFileOrImageData; mayContainFilePaths = items->size() != 1 || notFound != items->findIf([] (auto& item) { return item.canBeTreatedAsAttachmentOrFile() || item.isNonTextType || item.containsFileURLAndFileUploadContent; }); } #endif if (!mayContainFilePaths) { Vector cocoaTypes; platformStrategies()->pasteboardStrategy()->getTypes(cocoaTypes, m_pasteboardName, context()); if (cocoaTypes.findIf([](const String& cocoaType) { return shouldTreatCocoaTypeAsFile(cocoaType); }) == notFound) return FileContentState::NoFileOrImageData; auto indexOfURL = cocoaTypes.findIf([](auto& cocoaType) { #if PLATFORM(MAC) if (cocoaType == String(legacyURLPasteboardType())) return true; #endif ALLOW_DEPRECATED_DECLARATIONS_BEGIN return cocoaType == String(kUTTypeURL); ALLOW_DEPRECATED_DECLARATIONS_END }); mayContainFilePaths = indexOfURL != notFound && !platformStrategies()->pasteboardStrategy()->containsStringSafeForDOMToReadForType(cocoaTypes[indexOfURL], m_pasteboardName, context()); } // 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 (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName, context())) return FileContentState::NoFileOrImageData; // Even when there's only image data in the pasteboard and no file representations, we still run the risk of exposing file paths // to the page if the app has written image data to the pasteboard with a corresponding file path as plain text. An example of // this is copying an image with a local `src` in Safari. To mitigate this, we additionally require that the app has not also // written URLs to the pasteboard, as this would suggest that the plain text data might contain file paths. return mayContainFilePaths ? FileContentState::MayContainFilePaths : FileContentState::InMemoryImage; } Vector Pasteboard::typesSafeForBindings(const String& origin) { Vector types = platformStrategies()->pasteboardStrategy()->typesSafeForDOMToReadAndWrite(m_pasteboardName, origin, context()); // 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 (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName, context())) return { }; return types; } Vector Pasteboard::typesForLegacyUnsafeBindings() { auto cocoaTypes = readTypesWithSecurityCheck(); if (cocoaTypes.isEmpty()) return cocoaTypes; ListHashSet result; for (auto& cocoaType : cocoaTypes) addHTMLClipboardTypesForCocoaType(result, cocoaType); return copyToVector(result); } #if PLATFORM(MAC) static Ref convertTIFFToPNG(FragmentedSharedBuffer& tiffBuffer) { auto image = adoptNS([[NSBitmapImageRep alloc] initWithData: tiffBuffer.makeContiguous()->createNSData().get()]); NSData *pngData = [image representationUsingType:bitmapPNGFileType() properties:@{ }]; return SharedBuffer::create(pngData); } #endif void Pasteboard::read(PasteboardFileReader& reader, std::optional itemIndex) { if (!itemIndex) { auto filenames = readFilePaths(); if (!filenames.isEmpty()) { for (auto& filename : filenames) reader.readFilename(filename); return; } } auto readBufferAtIndex = [&](const PasteboardItemInfo& info, size_t itemIndex) { for (auto cocoaType : info.platformTypesByFidelity) { auto imageType = cocoaTypeToImageType(cocoaType); auto* mimeType = imageTypeToMIMEType(imageType); if (!mimeType || !reader.shouldReadBuffer(String::fromLatin1(mimeType))) continue; auto buffer = readBuffer(itemIndex, cocoaType); #if PLATFORM(MAC) if (buffer && imageType == ImageType::TIFF) buffer = convertTIFFToPNG(buffer.releaseNonNull()); #endif if (buffer) { reader.readBuffer(imageTypeToFakeFilename(imageType), String::fromLatin1(mimeType), buffer.releaseNonNull()); break; } } }; if (itemIndex) { if (auto info = pasteboardItemInfo(*itemIndex)) readBufferAtIndex(*info, *itemIndex); return; } if (auto allInfo = allPasteboardItemInfo()) { for (size_t itemIndex = 0; itemIndex < allInfo->size(); ++itemIndex) readBufferAtIndex(allInfo->at(itemIndex), itemIndex); } } Vector Pasteboard::readAllStrings(const String& type) { return readPlatformValuesAsStrings(type, m_changeCount, m_pasteboardName); } String Pasteboard::readString(const String& type) { auto values = readPlatformValuesAsStrings(type, m_changeCount, m_pasteboardName); return values.isEmpty() ? String() : values.first(); } String Pasteboard::readStringInCustomData(const String& type) { return readCustomData().readStringInCustomData(type); } String Pasteboard::readOrigin() { return readCustomData().origin(); } const PasteboardCustomData& Pasteboard::readCustomData() { if (m_customDataCache) return *m_customDataCache; if (auto buffer = readBufferForTypeWithSecurityCheck(PasteboardCustomData::cocoaType())) m_customDataCache = PasteboardCustomData::fromSharedBuffer(*buffer); else m_customDataCache = PasteboardCustomData { }; return *m_customDataCache; } void Pasteboard::writeCustomData(const Vector& data) { m_changeCount = platformStrategies()->pasteboardStrategy()->writeCustomData(data, name(), context()); } int64_t Pasteboard::changeCount() const { return platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName, context()); } Vector Pasteboard::readTypesWithSecurityCheck() { Vector cocoaTypes; platformStrategies()->pasteboardStrategy()->getTypes(cocoaTypes, m_pasteboardName, context()); // 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 (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName, context())) return { }; return cocoaTypes; } RefPtr Pasteboard::readBufferForTypeWithSecurityCheck(const String& type) { auto buffer = platformStrategies()->pasteboardStrategy()->bufferForType(type, m_pasteboardName, context()); // 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 (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName, context())) return nullptr; return buffer; } }