Pasting single words copied to UIPasteboard inserts URLs in editable areas
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 3 Jul 2017 11:26:36 +0000 (11:26 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 3 Jul 2017 11:26:36 +0000 (11:26 +0000)
https://bugs.webkit.org/show_bug.cgi?id=174082
<rdar://problem/33046992>

Reviewed by Tim Horton.

Source/WebCore:

Currently, our heuristics for coercing plain text to URLs when reading URLs off of the UIPasteboard allows URLs
to be created as long as -[UIPasteboard valuesForPasteboardType:inItemSet:] returns a non-null NSURL. However,
UIPasteboard automatically coerces any NSString into an NSURL if it initializes an NSURL via +URLWithString:.
Thus, single-word strings such as "hello" that are written to the pasteboard as "public.utf8-plain-text" can
be read back as NSURLs for "public.url". This currently causes bugs in shipping software: e.g. copying and
pasting a single word from an editable input or textarea and pasting into a rich contenteditable area using
WebKit1 inserts a link. However, when combined with another change in WebKit that attempts to read "public.url"
before "public.text" when reading plain text from the pasteboard, this now also affects pasting in plain text
areas, where pasted plain-text strings that are not URLs will paste as URL-encoded strings anyways (for
instance, replacing "[hello]" with "%5Bhello%5D").

To fix this, and existing issues with pasting single words in contenteditables, we make
PlatformPasteboard::readString and PlatformPasteboard::readURL only accept a coerced NSURL as an URL if it also
parses as a valid URL in WebKit (otherwise, we return an empty string).

Tests:
    UIPasteboardTests.DoNotPastePlainTextAsURL
    UIPasteboardTests.PastePlainTextAsURL
    UIPasteboardTests.PasteURLWithPlainTextAsURL

* platform/PlatformPasteboard.h:
* platform/ios/AbstractPasteboard.h:
* platform/ios/PlatformPasteboardIOS.mm:
(WebCore::PlatformPasteboard::allowReadingURLAtIndex):

Allow an URL to be read if either (1) an URL was explicitly specified in the UIPasteboard, or (2) the "proposed"
URL returned from -valuesForPasteboardType: is valid.

(WebCore::PlatformPasteboard::readString):
(WebCore::PlatformPasteboard::readURL):

Consult allowReadingURLAtIndex here (in the case of ::readString, only if the given pasteboard type is
"public.url").

* platform/ios/WebItemProviderPasteboard.h:
* platform/ios/WebItemProviderPasteboard.mm:
(-[WebItemProviderPasteboard itemProviders]):
(-[WebItemProviderPasteboard setItemProviders:]):

Source/WebKit2:

Add a hook to WKPreferences to allow programatic pasting.

* UIProcess/API/Cocoa/WKPreferences.mm:
(-[WKPreferences _setDOMPasteAllowed:]):
(-[WKPreferences _domPasteAllowed]):
* UIProcess/API/Cocoa/WKPreferencesPrivate.h:

Tools:

Adds 3 new unit tests to UIPasteboardTests to test cases of pasting plain text and URLs.

* TestWebKitAPI/Tests/ios/UIPasteboardTests.mm:
(TestWebKitAPI::setUpWebViewForPasteboardTests):
(TestWebKitAPI::TEST):

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

Source/WebCore/ChangeLog
Source/WebCore/platform/PlatformPasteboard.h
Source/WebCore/platform/ios/AbstractPasteboard.h
Source/WebCore/platform/ios/PlatformPasteboardIOS.mm
Source/WebCore/platform/ios/WebItemProviderPasteboard.h
Source/WebCore/platform/ios/WebItemProviderPasteboard.mm
Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/API/Cocoa/WKPreferences.mm
Source/WebKit2/UIProcess/API/Cocoa/WKPreferencesPrivate.h
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/ios/UIPasteboardTests.mm

index 2ad3b1f160d99e6dbdddfd50eb5f37a8ed00bab3..68f67508d44a1238e26ac0df868d1a740e8ebc36 100644 (file)
@@ -1,3 +1,50 @@
+2017-07-03  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Pasting single words copied to UIPasteboard inserts URLs in editable areas
+        https://bugs.webkit.org/show_bug.cgi?id=174082
+        <rdar://problem/33046992>
+
+        Reviewed by Tim Horton.
+
+        Currently, our heuristics for coercing plain text to URLs when reading URLs off of the UIPasteboard allows URLs
+        to be created as long as -[UIPasteboard valuesForPasteboardType:inItemSet:] returns a non-null NSURL. However,
+        UIPasteboard automatically coerces any NSString into an NSURL if it initializes an NSURL via +URLWithString:.
+        Thus, single-word strings such as "hello" that are written to the pasteboard as "public.utf8-plain-text" can
+        be read back as NSURLs for "public.url". This currently causes bugs in shipping software: e.g. copying and
+        pasting a single word from an editable input or textarea and pasting into a rich contenteditable area using
+        WebKit1 inserts a link. However, when combined with another change in WebKit that attempts to read "public.url"
+        before "public.text" when reading plain text from the pasteboard, this now also affects pasting in plain text
+        areas, where pasted plain-text strings that are not URLs will paste as URL-encoded strings anyways (for
+        instance, replacing "[hello]" with "%5Bhello%5D").
+
+        To fix this, and existing issues with pasting single words in contenteditables, we make
+        PlatformPasteboard::readString and PlatformPasteboard::readURL only accept a coerced NSURL as an URL if it also
+        parses as a valid URL in WebKit (otherwise, we return an empty string).
+
+        Tests:
+            UIPasteboardTests.DoNotPastePlainTextAsURL
+            UIPasteboardTests.PastePlainTextAsURL
+            UIPasteboardTests.PasteURLWithPlainTextAsURL
+
+        * platform/PlatformPasteboard.h:
+        * platform/ios/AbstractPasteboard.h:
+        * platform/ios/PlatformPasteboardIOS.mm:
+        (WebCore::PlatformPasteboard::allowReadingURLAtIndex):
+
+        Allow an URL to be read if either (1) an URL was explicitly specified in the UIPasteboard, or (2) the "proposed"
+        URL returned from -valuesForPasteboardType: is valid.
+
+        (WebCore::PlatformPasteboard::readString):
+        (WebCore::PlatformPasteboard::readURL):
+
+        Consult allowReadingURLAtIndex here (in the case of ::readString, only if the given pasteboard type is
+        "public.url").
+
+        * platform/ios/WebItemProviderPasteboard.h:
+        * platform/ios/WebItemProviderPasteboard.mm:
+        (-[WebItemProviderPasteboard itemProviders]):
+        (-[WebItemProviderPasteboard setItemProviders:]):
+
 2017-07-03  Zan Dobersek  <zdobersek@igalia.com>
 
         [GCrypt] Implement CryptoKeyEC SPKI exports
index f4439deb8639a30b545b333478e51932bbb6a97f..700e4287436b759ddaebeaec0fa3f8660a6ea13c 100644 (file)
@@ -102,6 +102,7 @@ private:
     WEBCORE_EXPORT void writeObjectRepresentations(const PasteboardImage&);
     WEBCORE_EXPORT void writeObjectRepresentations(const String& pasteboardType, const String& text);
     WEBCORE_EXPORT void writeObjectRepresentations(const PasteboardURL&);
+    bool allowReadingURLAtIndex(const URL&, int index) const;
 #endif
 
 #if PLATFORM(MAC)
index b744fe25dea93718a26a4b91bf86803571a2f871..a4130759dcaa5cca988845b15b0f7861d3377a4d 100644 (file)
@@ -34,6 +34,10 @@ NS_ASSUME_NONNULL_BEGIN
 
 @property (readonly, nonatomic) NSInteger numberOfItems;
 
+#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
+@property (nonatomic, copy, nullable) NSArray<__kindof NSItemProvider *> *itemProviders;
+#endif
+
 - (NSArray<NSString *> *)pasteboardTypes;
 - (NSArray *)dataForPasteboardType:(NSString *)pasteboardType inItemSet:(NSIndexSet *)itemSet;
 - (NSArray *)valuesForPasteboardType:(NSString *)pasteboardType inItemSet:(NSIndexSet *)itemSet;
index 5b87ed681ce73f7daa6239aab78111b81cd54489..0da70382bf76e0e9de9829c268cf070bef74df17 100644 (file)
@@ -215,6 +215,24 @@ static void addRepresentationsForPlainText(WebItemProviderRegistrationInfoList *
     [itemsToRegister addData:[(NSString *)plainText dataUsingEncoding:NSUTF8StringEncoding] forType:(NSString *)kUTTypeUTF8PlainText];
 }
 
+bool PlatformPasteboard::allowReadingURLAtIndex(const URL& url, int index) const
+{
+    NSItemProvider *itemProvider = (NSUInteger)index < [m_pasteboard itemProviders].count ? [[m_pasteboard itemProviders] objectAtIndex:index] : nil;
+    for (NSString *type in itemProvider.registeredTypeIdentifiers) {
+        if (UTTypeConformsTo((CFStringRef)type, kUTTypeURL))
+            return true;
+    }
+
+    return url.isValid();
+}
+
+#else
+
+bool PlatformPasteboard::allowReadingURLAtIndex(const URL&, int) const
+{
+    return true;
+}
+
 #endif
 
 void PlatformPasteboard::writeObjectRepresentations(const PasteboardWebContent& content)
@@ -426,7 +444,7 @@ String PlatformPasteboard::readString(int index, const String& type)
             return [(NSAttributedString *)value string];
     } else if (type == String(kUTTypeURL)) {
         ASSERT([value isKindOfClass:[NSURL class]]);
-        if ([value isKindOfClass:[NSURL class]])
+        if ([value isKindOfClass:[NSURL class]] && allowReadingURLAtIndex((NSURL *)value, index))
             return [(NSURL *)value absoluteString];
     }
 
@@ -447,6 +465,9 @@ URL PlatformPasteboard::readURL(int index, const String& type, String& title)
     if (![value isKindOfClass:[NSURL class]])
         return URL();
 
+    if (!allowReadingURLAtIndex((NSURL *)value, index))
+        return { };
+
 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
     title = [value _title];
 #else
index 9f302fd0632a1e5c02bb17ee4d8e53b35f7464b9..3048371cb279c247144b5884f790abb743c66660 100644 (file)
@@ -79,7 +79,7 @@ WEBCORE_EXPORT @interface WebItemProviderPasteboard : NSObject<AbstractPasteboar
 - (WebItemProviderRegistrationInfoList *)registrationInfoAtIndex:(NSUInteger)index;
 - (UIItemProvider *)itemProviderAtIndex:(NSUInteger)index;
 
-@property (copy, nonatomic, nullable) NSArray<UIItemProvider *> *itemProviders;
+@property (copy, nonatomic, nullable) NSArray<__kindof NSItemProvider *> *itemProviders;
 @property (readonly, nonatomic) NSInteger numberOfItems;
 @property (readonly, nonatomic) NSInteger changeCount;
 
index a4dd0c6dba1652e5f13a21bd1c5970951d87a34a..8d4e62edc2abbf84cca9a1f87ff7927353f848c1 100644 (file)
@@ -222,12 +222,12 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
     return _cachedTypeIdentifiers.get();
 }
 
-- (NSArray<UIItemProvider *> *)itemProviders
+- (NSArray<__kindof NSItemProvider *> *)itemProviders
 {
     return _itemProviders.get();
 }
 
-- (void)setItemProviders:(NSArray<UIItemProvider *> *)itemProviders
+- (void)setItemProviders:(NSArray<__kindof NSItemProvider *> *)itemProviders
 {
     itemProviders = itemProviders ?: [NSArray array];
     if (_itemProviders == itemProviders || [_itemProviders isEqualToArray:itemProviders])
index 1910597f5f828f8cb9ec38d46c480811f1b4d2aa..a3b2df5d85b207876bbd696f719626af1cd62f2d 100644 (file)
@@ -1,3 +1,18 @@
+2017-07-03  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Pasting single words copied to UIPasteboard inserts URLs in editable areas
+        https://bugs.webkit.org/show_bug.cgi?id=174082
+        <rdar://problem/33046992>
+
+        Reviewed by Tim Horton.
+
+        Add a hook to WKPreferences to allow programatic pasting.
+
+        * UIProcess/API/Cocoa/WKPreferences.mm:
+        (-[WKPreferences _setDOMPasteAllowed:]):
+        (-[WKPreferences _domPasteAllowed]):
+        * UIProcess/API/Cocoa/WKPreferencesPrivate.h:
+
 2017-07-03  Zan Dobersek  <zdobersek@igalia.com>
 
         Unreviewed GTK+ and WPE build fix when building with GCC 4.9.
index e28a5cf93f6c91b1daf4fa786345a7e842514e1b..48c1577083ebfce1359fa936086f6fd8999e7d73 100644 (file)
@@ -627,6 +627,16 @@ static _WKStorageBlockingPolicy toAPI(WebCore::SecurityOrigin::StorageBlockingPo
     return _preferences->javaScriptCanAccessClipboard();
 }
 
+- (void)_setDOMPasteAllowed:(BOOL)domPasteAllowed
+{
+    _preferences->setDOMPasteAllowed(domPasteAllowed);
+}
+
+- (BOOL)_domPasteAllowed
+{
+    return _preferences->domPasteAllowed();
+}
+
 - (void)_setMediaDocumentEntersFullscreenAutomatically:(BOOL)mediaDocumentEntersFullscreenAutomatically
 {
     _preferences->setMediaDocumentEntersFullscreenAutomatically(mediaDocumentEntersFullscreenAutomatically);
index 0dcc409074e6f4d2b3f86ac67a2557874561aa47..19295d6e1fc5f98f8c26711be13898b6f427bd89 100644 (file)
@@ -107,6 +107,7 @@ typedef NS_OPTIONS(NSUInteger, _WKJavaScriptRuntimeFlags) {
 @property (nonatomic, setter=_setWebRTCLegacyAPIEnabled:) BOOL _webRTCLegacyAPIEnabled WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 @property (nonatomic, setter=_setJavaScriptCanAccessClipboard:) BOOL _javaScriptCanAccessClipboard WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+@property (nonatomic, setter=_setDOMPasteAllowed:) BOOL _domPasteAllowed WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 @property (nonatomic, setter=_setMediaDocumentEntersFullscreenAutomatically:) BOOL _mediaDocumentEntersFullscreenAutomatically WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
index b0104b22bc8302755b9328f038418c2d71643ab4..8db993f40bd2f0764624161183218bf057e19019 100644 (file)
@@ -1,3 +1,17 @@
+2017-07-03  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Pasting single words copied to UIPasteboard inserts URLs in editable areas
+        https://bugs.webkit.org/show_bug.cgi?id=174082
+        <rdar://problem/33046992>
+
+        Reviewed by Tim Horton.
+
+        Adds 3 new unit tests to UIPasteboardTests to test cases of pasting plain text and URLs.
+
+        * TestWebKitAPI/Tests/ios/UIPasteboardTests.mm:
+        (TestWebKitAPI::setUpWebViewForPasteboardTests):
+        (TestWebKitAPI::TEST):
+
 2017-07-02  Brady Eidson  <beidson@apple.com>
 
         Add API test for all parts of WebKit1 API related to favicons.
index c3adebb3bfc39b529080b41e54e8f5772df504e1..1d33ac639ab5e06311ef831a981b4640e7ceb82e 100644 (file)
 #import "TestWKWebView.h"
 #import <MobileCoreServices/MobileCoreServices.h>
 #import <UIKit/UIPasteboard.h>
+#import <WebCore/SoftLinking.h>
 #import <WebKit/WKPreferencesPrivate.h>
 #import <WebKit/WKWebViewPrivate.h>
 
+SOFT_LINK_FRAMEWORK(UIKit)
+SOFT_LINK(UIKit, UIApplicationInitialize, void, (void), ())
+
 namespace TestWebKitAPI {
 
 NSData *dataForPasteboardType(CFStringRef type)
@@ -43,12 +47,17 @@ NSData *dataForPasteboardType(CFStringRef type)
 
 RetainPtr<TestWKWebView> setUpWebViewForPasteboardTests()
 {
+    // UIPasteboard's type coercion codepaths only take effect when the UIApplication has been initialized.
+    UIApplicationInitialize();
+
     [UIPasteboard generalPasteboard].items = @[];
     EXPECT_TRUE(!dataForPasteboardType(kUTTypeUTF8PlainText).length);
     EXPECT_TRUE(!dataForPasteboardType(kUTTypeUTF16PlainText).length);
 
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
-    [webView configuration].preferences._javaScriptCanAccessClipboard = YES;
+    WKPreferences *preferences = [webView configuration].preferences;
+    preferences._javaScriptCanAccessClipboard = YES;
+    preferences._domPasteAllowed = YES;
     [webView synchronouslyLoadTestPageNamed:@"rich-and-plain-text"];
     return webView;
 }
@@ -77,6 +86,57 @@ TEST(UIPasteboardTests, CopyRichTextWritesConcreteTypes)
     EXPECT_WK_STREQ("Hello world", [utf16Result UTF8String]);
 }
 
+TEST(UIPasteboardTests, DoNotPastePlainTextAsURL)
+{
+    auto webView = setUpWebViewForPasteboardTests();
+
+    NSString *testString = @"[helloworld]";
+    [UIPasteboard generalPasteboard].string = testString;
+
+    [webView stringByEvaluatingJavaScript:@"selectPlainText()"];
+    [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"];
+    EXPECT_WK_STREQ(testString, [webView stringByEvaluatingJavaScript:@"plain.value"]);
+
+    [webView stringByEvaluatingJavaScript:@"selectRichText()"];
+    [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"];
+    EXPECT_WK_STREQ(testString, [webView stringByEvaluatingJavaScript:@"rich.textContent"]);
+    EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"!!rich.querySelector('a')"].boolValue);
+}
+
+TEST(UIPasteboardTests, PastePlainTextAsURL)
+{
+    auto webView = setUpWebViewForPasteboardTests();
+
+    NSString *testString = @"https://www.apple.com/iphone";
+    [UIPasteboard generalPasteboard].string = testString;
+
+    [webView stringByEvaluatingJavaScript:@"selectPlainText()"];
+    [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"];
+    EXPECT_WK_STREQ(testString, [webView stringByEvaluatingJavaScript:@"plain.value"]);
+
+    [webView stringByEvaluatingJavaScript:@"selectRichText()"];
+    [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"];
+    EXPECT_WK_STREQ(testString, [webView stringByEvaluatingJavaScript:@"rich.textContent"]);
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"!!rich.querySelector('a')"].boolValue);
+}
+
+TEST(UIPasteboardTests, PasteURLWithPlainTextAsURL)
+{
+    auto webView = setUpWebViewForPasteboardTests();
+
+    NSString *testString = @"thisisdefinitelyaurl";
+    [UIPasteboard generalPasteboard].URL = [NSURL URLWithString:testString];
+
+    [webView stringByEvaluatingJavaScript:@"selectPlainText()"];
+    [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"];
+    EXPECT_WK_STREQ(testString, [webView stringByEvaluatingJavaScript:@"plain.value"]);
+
+    [webView stringByEvaluatingJavaScript:@"selectRichText()"];
+    [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"];
+    EXPECT_WK_STREQ(testString, [webView stringByEvaluatingJavaScript:@"rich.textContent"]);
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"!!rich.querySelector('a')"].boolValue);
+}
+
 } // namespace TestWebKitAPI
 
 #endif