[iOS DnD] Support .zip archives for file uploads via drag and drop
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 19 Jun 2017 21:12:28 +0000 (21:12 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 19 Jun 2017 21:12:28 +0000 (21:12 +0000)
https://bugs.webkit.org/show_bug.cgi?id=173511
<rdar://problem/32521025>

Reviewed by Tim Horton.

Source/WebCore:

Allows dropped .zip archives to be uploaded as files by accepting types conforming to either
"public.zip-archive" or "public.content" as potential file types. Initially, I opted to accept the more general
"public.data" type; however, this includes UTIs such as "public.url" that should not be represented as files, so
this is a more targeted fix that allows us to very easily add additional content types in the future by adding
more types to supportedFileUploadPasteboardTypes.

Tests:
DataInteractionTests.ExternalSourceZIPArchiveToUploadArea
DataInteractionTests.ExternalSourceZIPArchiveAndURLToSingleFileInput

* page/mac/DragControllerMac.mm:
(WebCore::DragController::updateSupportedTypeIdentifiersForDragHandlingMethod):
* platform/Pasteboard.h:
* platform/ios/PasteboardIOS.mm:
(WebCore::Pasteboard::read):
(WebCore::Pasteboard::supportedWebContentPasteboardTypes):
(WebCore::Pasteboard::supportedFileUploadPasteboardTypes):

Rename supportedPasteboardTypes to supportedWebContentPasteboardTypes, and also introduce
supportedFileUploadPasteboardTypes which returns an list of types, such that if a type conforms to any type in
this array, that type may be represented as a file. So far, this list contains "public.content" and
"public.zip-archive".

(WebCore::Pasteboard::types):
(WebCore::Pasteboard::supportedPasteboardTypes): Deleted.
* platform/ios/WebItemProviderPasteboard.mm:
(typeConformsToTypes):

Remove -typeIsAppropriateForSupportedTypes: and replace it with typeConformsToTypes. Use this both when
determining the number of files on the pasteboard, and when determining preferred UTIs to load when dropping.

(-[WebItemProviderPasteboard numberOfFiles]):
(-[WebItemProviderPasteboard typeIdentifierToLoadForRegisteredTypeIdentfiers:]):
(-[WebItemProviderPasteboard typeIsAppropriateForSupportedTypes:]): Deleted.
* platform/mac/DragDataMac.mm:
(WebCore::DragData::containsFiles):
* platform/mac/PasteboardMac.mm:
(WebCore::Pasteboard::supportedFileUploadPasteboardTypes):

Tools:

Adds tests for dropping .zip archives into a JavaScript-based file upload area, as well as into a file input.
Also verifies that URLs are not handled as file drops. See WebCore ChangeLog for more details.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKit2Cocoa/compressed-files.zip: Added.
* TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
(testZIPArchive):
(TestWebKitAPI::TEST):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@218508 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/page/mac/DragControllerMac.mm
Source/WebCore/platform/Pasteboard.h
Source/WebCore/platform/ios/PasteboardIOS.mm
Source/WebCore/platform/ios/WebItemProviderPasteboard.mm
Source/WebCore/platform/mac/DragDataMac.mm
Source/WebCore/platform/mac/PasteboardMac.mm
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKit2Cocoa/compressed-files.zip [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm

index e565805..97786a3 100644 (file)
@@ -1,3 +1,50 @@
+2017-06-19  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS DnD] Support .zip archives for file uploads via drag and drop
+        https://bugs.webkit.org/show_bug.cgi?id=173511
+        <rdar://problem/32521025>
+
+        Reviewed by Tim Horton.
+
+        Allows dropped .zip archives to be uploaded as files by accepting types conforming to either
+        "public.zip-archive" or "public.content" as potential file types. Initially, I opted to accept the more general
+        "public.data" type; however, this includes UTIs such as "public.url" that should not be represented as files, so
+        this is a more targeted fix that allows us to very easily add additional content types in the future by adding
+        more types to supportedFileUploadPasteboardTypes.
+
+        Tests:
+        DataInteractionTests.ExternalSourceZIPArchiveToUploadArea
+        DataInteractionTests.ExternalSourceZIPArchiveAndURLToSingleFileInput
+
+        * page/mac/DragControllerMac.mm:
+        (WebCore::DragController::updateSupportedTypeIdentifiersForDragHandlingMethod):
+        * platform/Pasteboard.h:
+        * platform/ios/PasteboardIOS.mm:
+        (WebCore::Pasteboard::read):
+        (WebCore::Pasteboard::supportedWebContentPasteboardTypes):
+        (WebCore::Pasteboard::supportedFileUploadPasteboardTypes):
+
+        Rename supportedPasteboardTypes to supportedWebContentPasteboardTypes, and also introduce
+        supportedFileUploadPasteboardTypes which returns an list of types, such that if a type conforms to any type in
+        this array, that type may be represented as a file. So far, this list contains "public.content" and
+        "public.zip-archive".
+
+        (WebCore::Pasteboard::types):
+        (WebCore::Pasteboard::supportedPasteboardTypes): Deleted.
+        * platform/ios/WebItemProviderPasteboard.mm:
+        (typeConformsToTypes):
+
+        Remove -typeIsAppropriateForSupportedTypes: and replace it with typeConformsToTypes. Use this both when
+        determining the number of files on the pasteboard, and when determining preferred UTIs to load when dropping.
+
+        (-[WebItemProviderPasteboard numberOfFiles]):
+        (-[WebItemProviderPasteboard typeIdentifierToLoadForRegisteredTypeIdentfiers:]):
+        (-[WebItemProviderPasteboard typeIsAppropriateForSupportedTypes:]): Deleted.
+        * platform/mac/DragDataMac.mm:
+        (WebCore::DragData::containsFiles):
+        * platform/mac/PasteboardMac.mm:
+        (WebCore::Pasteboard::supportedFileUploadPasteboardTypes):
+
 2017-06-19  Sam Weinig  <sam@webkit.org>
 
         [WebIDL] Remove custom binding for Document.getCSSCanvasContext()
index b812188..c46fc8f 100644 (file)
@@ -118,11 +118,12 @@ void DragController::updateSupportedTypeIdentifiersForDragHandlingMethod(DragHan
         supportedTypes.append(kUTTypePlainText);
         break;
     case DragHandlingMethod::EditRichText:
-        for (NSString *type in Pasteboard::supportedPasteboardTypes())
+        for (NSString *type in Pasteboard::supportedWebContentPasteboardTypes())
             supportedTypes.append(type);
         break;
     default:
-        supportedTypes.append(kUTTypeContent);
+        for (NSString *type in Pasteboard::supportedFileUploadPasteboardTypes())
+            supportedTypes.append(type);
         break;
     }
     platformStrategies()->pasteboardStrategy()->updateSupportedTypeIdentifiers(supportedTypes, dragData.pasteboardName());
index 7d58899..15ab556 100644 (file)
 #include <wtf/text/WTFString.h>
 
 #if PLATFORM(IOS)
-OBJC_CLASS NSArray;
 OBJC_CLASS NSString;
 #endif
 
+#if PLATFORM(COCOA)
+OBJC_CLASS NSArray;
+#endif
+
 #if PLATFORM(WIN)
 #include "COMPtr.h"
 #include "WCDataObject.h"
@@ -206,13 +209,14 @@ public:
 #if PLATFORM(IOS)
     explicit Pasteboard(long changeCount);
 
-    static NSArray* supportedPasteboardTypes();
+    static NSArray *supportedWebContentPasteboardTypes();
     static String resourceMIMEType(const NSString *mimeType);
 #endif
 
 #if PLATFORM(COCOA)
     explicit Pasteboard(const String& pasteboardName);
 
+    WEBCORE_EXPORT static NSArray *supportedFileUploadPasteboardTypes();
     const String& name() const { return m_pasteboardName; }
 #endif
 
index 6041368..57ac447 100644 (file)
@@ -263,7 +263,7 @@ void Pasteboard::read(PasteboardWebContentReader& reader)
     if (!numberOfItems)
         return;
 
-    NSArray *types = supportedPasteboardTypes();
+    NSArray *types = supportedWebContentPasteboardTypes();
     int numberOfTypes = [types count];
 
     for (int i = 0; i < numberOfItems; i++) {
@@ -298,11 +298,16 @@ void Pasteboard::readRespectingUTIFidelities(PasteboardWebContentReader& reader)
     }
 }
 
-NSArray* Pasteboard::supportedPasteboardTypes()
+NSArray *Pasteboard::supportedWebContentPasteboardTypes()
 {
     return @[(id)WebArchivePboardType, (id)kUTTypeFlatRTFD, (id)kUTTypeRTF, (id)kUTTypeHTML, (id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF, (id)kUTTypeURL, (id)kUTTypeText];
 }
 
+NSArray *Pasteboard::supportedFileUploadPasteboardTypes()
+{
+    return @[ (NSString *)kUTTypeContent, (NSString *)kUTTypeZipArchive ];
+}
+
 bool Pasteboard::hasData()
 {
     return !!platformStrategies()->pasteboardStrategy()->getPasteboardItemsCount(m_pasteboardName);
@@ -424,7 +429,7 @@ void Pasteboard::writeString(const String& type, const String& data)
 
 Vector<String> Pasteboard::types()
 {
-    NSArray* types = supportedPasteboardTypes();
+    NSArray *types = supportedWebContentPasteboardTypes();
 
     // 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.
index 3054d0b..736b2d3 100644 (file)
@@ -39,6 +39,7 @@
 #import <UIKit/UIItemProviderReading.h>
 #import <UIKit/UIItemProviderWriting.h>
 #import <WebCore/FileSystemIOS.h>
+#import <WebCore/Pasteboard.h>
 #import <wtf/BlockPtr.h>
 #import <wtf/OSObjectPtr.h>
 #import <wtf/RetainPtr.h>
@@ -48,6 +49,8 @@ SOFT_LINK_CLASS(UIKit, UIColor)
 SOFT_LINK_CLASS(UIKit, UIImage)
 SOFT_LINK_CLASS(UIKit, UIItemProvider)
 
+using namespace WebCore;
+
 typedef void(^ItemProviderDataLoadCompletionHandler)(NSData *, NSError *);
 typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
 
@@ -373,12 +376,23 @@ static Class classForTypeIdentifier(NSString *typeIdentifier, NSString *&outType
     return fileURLs;
 }
 
+static BOOL typeConformsToTypes(NSString *type, NSArray *conformsToTypes)
+{
+    // A type is considered appropriate to load if it conforms to one or more supported types.
+    for (NSString *conformsToType in conformsToTypes) {
+        if (UTTypeConformsTo((CFStringRef)type, (CFStringRef)conformsToType))
+            return YES;
+    }
+    return NO;
+}
+
 - (NSInteger)numberOfFiles
 {
+    NSArray *supportedFileTypes = Pasteboard::supportedFileUploadPasteboardTypes();
     NSInteger numberOfFiles = 0;
     for (UIItemProvider *itemProvider in _itemProviders.get()) {
         for (NSString *identifier in itemProvider.registeredTypeIdentifiers) {
-            if (!UTTypeConformsTo((__bridge CFStringRef)identifier, kUTTypeContent))
+            if (!typeConformsToTypes(identifier, supportedFileTypes))
                 continue;
             ++numberOfFiles;
             break;
@@ -405,21 +419,11 @@ static NSURL *temporaryFileURLForDataInteractionContent(NSURL *url, NSString *su
     return [NSURL fileURLWithPath:[temporaryDataInteractionDirectory stringByAppendingPathComponent:suggestedName ?: url.lastPathComponent]];
 }
 
-- (BOOL)typeIsAppropriateForSupportedTypes:(NSString *)type
-{
-    // A type is considered appropriate to load if it conforms to one or more supported types.
-    for (NSString *supportedTypeIdentifier in _supportedTypeIdentifiers.get()) {
-        if (UTTypeConformsTo((CFStringRef)type, (CFStringRef)supportedTypeIdentifier))
-            return YES;
-    }
-    return NO;
-}
-
 - (NSString *)typeIdentifierToLoadForRegisteredTypeIdentfiers:(NSArray<NSString *> *)registeredTypeIdentifiers
 {
     NSString *highestFidelityContentType = nil;
     for (NSString *registeredTypeIdentifier in registeredTypeIdentifiers) {
-        if ([self typeIsAppropriateForSupportedTypes:registeredTypeIdentifier])
+        if (typeConformsToTypes(registeredTypeIdentifier, _supportedTypeIdentifiers.get()))
             return registeredTypeIdentifier;
 
         if (!highestFidelityContentType && UTTypeConformsTo((CFStringRef)registeredTypeIdentifier, kUTTypeContent))
index 94765a2..8e56b5f 100644 (file)
@@ -160,16 +160,15 @@ bool DragData::containsColor() const
 
 bool DragData::containsFiles() const
 {
+    NSArray *supportedFileTypes = Pasteboard::supportedFileUploadPasteboardTypes();
     Vector<String> types;
     platformStrategies()->pasteboardStrategy()->getTypes(types, m_pasteboardName);
     for (auto& type : types) {
-#if PLATFORM(MAC)
-        if (type == String(NSFilesPromisePboardType) || type == String(NSFilenamesPboardType))
-            return true;
-#else
-        if (UTTypeConformsTo(type.createCFString().autorelease(), kUTTypeContent))
-            return true;
-#endif
+        auto cfType = type.createCFString();
+        for (NSString *fileType in supportedFileTypes) {
+            if (UTTypeConformsTo(cfType.get(), (CFStringRef)fileType))
+                return true;
+        }
     }
     return false;
 }
index cd8ffde..18dfabc 100644 (file)
@@ -104,6 +104,11 @@ static Vector<String> writableTypesForImage()
     return types;
 }
 
+NSArray *Pasteboard::supportedFileUploadPasteboardTypes()
+{
+    return @[ (NSString *)NSFilesPromisePboardType, (NSString *)NSFilenamesPboardType ];
+}
+
 Pasteboard::Pasteboard()
     : m_pasteboardName(emptyString())
     , m_changeCount(0)
index 8fca48e..1888627 100644 (file)
@@ -1,3 +1,20 @@
+2017-06-19  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS DnD] Support .zip archives for file uploads via drag and drop
+        https://bugs.webkit.org/show_bug.cgi?id=173511
+        <rdar://problem/32521025>
+
+        Reviewed by Tim Horton.
+
+        Adds tests for dropping .zip archives into a JavaScript-based file upload area, as well as into a file input.
+        Also verifies that URLs are not handled as file drops. See WebCore ChangeLog for more details.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKit2Cocoa/compressed-files.zip: Added.
+        * TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
+        (testZIPArchive):
+        (TestWebKitAPI::TEST):
+
 2017-06-18  Darin Adler  <darin@apple.com>
 
         Fix Ref to deref before assignment, add tests for this to RefPtr, Ref, Function
index 643bc7a..9d777e8 100644 (file)
                F46A095B1ED8A6E600D4AA55 /* gif-and-file-input.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47D30ED1ED28A6C000482E1 /* gif-and-file-input.html */; };
                F47728991E4AE3C1007ABF6A /* full-page-contenteditable.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47728981E4AE3AD007ABF6A /* full-page-contenteditable.html */; };
                F4856CA31E649EA8009D7EE7 /* attachment-element.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4856CA21E6498A8009D7EE7 /* attachment-element.html */; };
+               F4B825D81EF4DBFB006E417F /* compressed-files.zip in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4B825D61EF4DBD4006E417F /* compressed-files.zip */; };
                F4BFA68E1E4AD08000154298 /* DragAndDropPasteboardTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4BFA68C1E4AD08000154298 /* DragAndDropPasteboardTests.mm */; };
                F4C2AB221DD6D95E00E06D5B /* enormous-video-with-sound.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4C2AB211DD6D94100E06D5B /* enormous-video-with-sound.html */; };
                F4D4F3B61E4E2BCB00BB2767 /* DataInteractionSimulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4D4F3B41E4E2BCB00BB2767 /* DataInteractionSimulator.mm */; };
                        dstPath = TestWebKitAPI.resources;
                        dstSubfolderSpec = 7;
                        files = (
+                               F4B825D81EF4DBFB006E417F /* compressed-files.zip in Copy Resources */,
                                F41AB99F1EF4696B0083FA08 /* autofocus-contenteditable.html in Copy Resources */,
                                F41AB9A01EF4696B0083FA08 /* background-image-link-and-input.html in Copy Resources */,
                                F41AB9A11EF4696B0083FA08 /* contenteditable-and-textarea.html in Copy Resources */,
                F47D30EB1ED28619000482E1 /* apple.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = apple.gif; sourceTree = "<group>"; };
                F47D30ED1ED28A6C000482E1 /* gif-and-file-input.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "gif-and-file-input.html"; sourceTree = "<group>"; };
                F4856CA21E6498A8009D7EE7 /* attachment-element.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "attachment-element.html"; sourceTree = "<group>"; };
+               F4B825D61EF4DBD4006E417F /* compressed-files.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = "compressed-files.zip"; sourceTree = "<group>"; };
                F4BFA68C1E4AD08000154298 /* DragAndDropPasteboardTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DragAndDropPasteboardTests.mm; sourceTree = "<group>"; };
                F4C2AB211DD6D94100E06D5B /* enormous-video-with-sound.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "enormous-video-with-sound.html"; sourceTree = "<group>"; };
                F4D4F3B41E4E2BCB00BB2767 /* DataInteractionSimulator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DataInteractionSimulator.mm; sourceTree = "<group>"; };
                A16F66B81C40E9E100BD4D24 /* Resources */ = {
                        isa = PBXGroup;
                        children = (
+                               F4B825D61EF4DBD4006E417F /* compressed-files.zip */,
                                F41AB9981EF4692C0083FA08 /* autofocus-contenteditable.html */,
                                F41AB9971EF4692C0083FA08 /* background-image-link-and-input.html */,
                                F41AB99C1EF4692C0083FA08 /* contenteditable-and-textarea.html */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKit2Cocoa/compressed-files.zip b/Tools/TestWebKitAPI/Tests/WebKit2Cocoa/compressed-files.zip
new file mode 100644 (file)
index 0000000..1c5921a
Binary files /dev/null and b/Tools/TestWebKitAPI/Tests/WebKit2Cocoa/compressed-files.zip differ
index 39619bf..b366eb5 100644 (file)
@@ -52,6 +52,12 @@ static UIImage *testIconImage()
     return [UIImage imageNamed:@"TestWebKitAPI.resources/icon.png"];
 }
 
+static NSData *testZIPArchive()
+{
+    NSURL *zipFileURL = [[NSBundle mainBundle] URLForResource:@"compressed-files" withExtension:@"zip" subdirectory:@"TestWebKitAPI.resources"];
+    return [NSData dataWithContentsOfURL:zipFileURL];
+}
+
 @implementation UIItemProvider (DataInteractionTests)
 
 - (void)registerDataRepresentationForTypeIdentifier:(NSString *)typeIdentifier withData:(NSData *)data
@@ -455,6 +461,41 @@ TEST(DataInteractionTests, ExternalSourceHTMLToUploadArea)
     EXPECT_WK_STREQ("text/html", outputValue.UTF8String);
 }
 
+TEST(DataInteractionTests, ExternalSourceZIPArchiveAndURLToSingleFileInput)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"file-uploading"];
+
+    auto archiveProvider = adoptNS([[UIItemProvider alloc] init]);
+    [archiveProvider registerDataRepresentationForTypeIdentifier:(NSString *)kUTTypeZipArchive withData:testZIPArchive()];
+
+    auto urlProvider = adoptNS([[UIItemProvider alloc] init]);
+    [urlProvider registerObject:[NSURL URLWithString:@"https://webkit.org"] visibility:UIItemProviderRepresentationOptionsVisibilityAll];
+
+    auto dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+    [dataInteractionSimulator setExternalItemProviders:@[ archiveProvider.get(), urlProvider.get() ]];
+    [dataInteractionSimulator runFrom:CGPointMake(200, 100) to:CGPointMake(100, 100)];
+
+    NSString *outputValue = [webView stringByEvaluatingJavaScript:@"output.value"];
+    EXPECT_WK_STREQ("application/zip", outputValue.UTF8String);
+}
+
+TEST(DataInteractionTests, ExternalSourceZIPArchiveToUploadArea)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"file-uploading"];
+
+    auto itemProvider = adoptNS([[UIItemProvider alloc] init]);
+    [itemProvider registerDataRepresentationForTypeIdentifier:(NSString *)kUTTypeZipArchive withData:testZIPArchive()];
+
+    auto dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+    [dataInteractionSimulator setExternalItemProviders:@[ itemProvider.get() ]];
+    [dataInteractionSimulator runFrom:CGPointMake(200, 300) to:CGPointMake(100, 300)];
+
+    NSString *outputValue = [webView stringByEvaluatingJavaScript:@"output.value"];
+    EXPECT_WK_STREQ("application/zip", outputValue.UTF8String);
+}
+
 TEST(DataInteractionTests, ExternalSourceImageAndHTMLToSingleFileInput)
 {
     RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);