1// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/base/clipboard/clipboard_util_mac.h"
6
7#include "base/mac/foundation_util.h"
8#import "base/mac/mac_util.h"
9#include "base/mac/scoped_cftyperef.h"
10
11namespace ui {
12
13NSString* const kUTTypeURLName = @"public.url-name";
14
15namespace {
16
17NSString* const kWebURLsWithTitlesPboardType = @"WebURLsWithTitlesPboardType";
18
19// It's much more convenient to return an NSString than a
20// base::ScopedCFTypeRef<CFStringRef>, since the methods on NSPasteboardItem
21// require an NSString*.
22NSString* UTIFromPboardType(NSString* type) {
23  return [base::mac::CFToNSCast(UTTypeCreatePreferredIdentifierForTag(
24      kUTTagClassNSPboardType, base::mac::NSToCFCast(type), kUTTypeData))
25      autorelease];
26}
27
28bool ReadWebURLsWithTitlesPboardType(NSPasteboard* pboard,
29                                     NSArray** urls,
30                                     NSArray** titles) {
31  NSArray* bookmarkPairs = base::mac::ObjCCast<NSArray>([pboard
32      propertyListForType:UTIFromPboardType(kWebURLsWithTitlesPboardType)]);
33  if (!bookmarkPairs)
34    return false;
35
36  if ([bookmarkPairs count] != 2)
37    return false;
38
39  NSArray* urlsArr = base::mac::ObjCCast<NSArray>(bookmarkPairs[0]);
40  NSArray* titlesArr = base::mac::ObjCCast<NSArray>(bookmarkPairs[1]);
41
42  if (!urlsArr || !titlesArr)
43    return false;
44  if ([urlsArr count] < 1)
45    return false;
46  if ([urlsArr count] != [titlesArr count])
47    return false;
48
49  for (id obj in urlsArr) {
50    if (![obj isKindOfClass:[NSString class]])
51      return false;
52  }
53
54  for (id obj in titlesArr) {
55    if (![obj isKindOfClass:[NSString class]])
56      return false;
57  }
58
59  *urls = urlsArr;
60  *titles = titlesArr;
61  return true;
62}
63
64bool ReadURLItemsWithTitles(NSPasteboard* pboard,
65                            NSArray** urls,
66                            NSArray** titles) {
67  NSMutableArray* urlsArr = [NSMutableArray array];
68  NSMutableArray* titlesArr = [NSMutableArray array];
69
70  NSArray* items = [pboard pasteboardItems];
71  for (NSPasteboardItem* item : items) {
72    NSString* url = [item stringForType:base::mac::CFToNSCast(kUTTypeURL)];
73    NSString* title = [item stringForType:kUTTypeURLName];
74
75    if (url) {
76      [urlsArr addObject:url];
77      if (title)
78        [titlesArr addObject:title];
79      else
80        [titlesArr addObject:@""];
81    }
82  }
83
84  if ([urlsArr count]) {
85    *urls = urlsArr;
86    *titles = titlesArr;
87    return true;
88  } else {
89    return false;
90  }
91}
92
93}  // namespace
94
95UniquePasteboard::UniquePasteboard()
96    : pasteboard_([[NSPasteboard pasteboardWithUniqueName] retain]) {}
97
98UniquePasteboard::~UniquePasteboard() {
99  [pasteboard_ releaseGlobally];
100
101  if (base::mac::IsOS10_12()) {
102    // On 10.12, move ownership to the autorelease pool rather than possibly
103    // triggering -[NSPasteboard dealloc] here. This is a speculative workaround
104    // for https://crbug.com/877979 where a call to __CFPasteboardDeallocate
105    // from here is triggering "Semaphore object deallocated while in use".
106    pasteboard_.autorelease();
107  }
108}
109
110// static
111base::scoped_nsobject<NSPasteboardItem> ClipboardUtil::PasteboardItemFromUrl(
112    NSString* urlString,
113    NSString* title) {
114  DCHECK(urlString);
115  if (!title)
116    title = urlString;
117
118  base::scoped_nsobject<NSPasteboardItem> item([[NSPasteboardItem alloc] init]);
119
120  NSURL* url = [NSURL URLWithString:urlString];
121  if ([url isFileURL] &&
122      [[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
123    [item setPropertyList:@[ [url path] ]
124                  forType:UTIFromPboardType(NSFilenamesPboardType)];
125  }
126
127  // Set Safari's URL + title arrays Pboard type.
128  NSArray* urlsAndTitles = @[ @[ urlString ], @[ title ] ];
129  [item setPropertyList:urlsAndTitles
130                forType:UTIFromPboardType(kWebURLsWithTitlesPboardType)];
131
132  // Set NSURLPboardType. The format of the property list is divined from
133  // Webkit's function PlatformPasteboard::setStringForType.
134  // https://github.com/WebKit/webkit/blob/master/Source/WebCore/platform/mac/PlatformPasteboardMac.mm
135  NSURL* base = [url baseURL];
136  if (base) {
137    [item setPropertyList:@[ [url relativeString], [base absoluteString] ]
138                  forType:UTIFromPboardType(NSURLPboardType)];
139  } else if (url) {
140    [item setPropertyList:@[ [url absoluteString], @"" ]
141                  forType:UTIFromPboardType(NSURLPboardType)];
142  }
143
144  [item setString:urlString forType:NSPasteboardTypeString];
145  [item setString:urlString forType:base::mac::CFToNSCast(kUTTypeURL)];
146  [item setString:title forType:kUTTypeURLName];
147  return item;
148}
149
150// static
151base::scoped_nsobject<NSPasteboardItem> ClipboardUtil::PasteboardItemFromUrls(
152    NSArray* urls,
153    NSArray* titles) {
154  base::scoped_nsobject<NSPasteboardItem> item([[NSPasteboardItem alloc] init]);
155
156  // Set Safari's URL + title arrays Pboard type.
157  NSArray* urlsAndTitles = @[ urls, titles ];
158  [item setPropertyList:urlsAndTitles
159                forType:UTIFromPboardType(kWebURLsWithTitlesPboardType)];
160
161  return item;
162}
163
164// static
165base::scoped_nsobject<NSPasteboardItem> ClipboardUtil::PasteboardItemFromString(
166    NSString* string) {
167  base::scoped_nsobject<NSPasteboardItem> item([[NSPasteboardItem alloc] init]);
168  [item setString:string forType:NSPasteboardTypeString];
169  return item;
170}
171
172//static
173NSString* ClipboardUtil::GetTitleFromPasteboardURL(NSPasteboard* pboard) {
174  return [pboard stringForType:kUTTypeURLName];
175}
176
177//static
178NSString* ClipboardUtil::GetURLFromPasteboardURL(NSPasteboard* pboard) {
179  return [pboard stringForType:base::mac::CFToNSCast(kUTTypeURL)];
180}
181
182// static
183NSString* ClipboardUtil::UTIForPasteboardType(NSString* type) {
184  return UTIFromPboardType(type);
185}
186
187// static
188NSString* ClipboardUtil::UTIForWebURLsAndTitles() {
189  return UTIFromPboardType(kWebURLsWithTitlesPboardType);
190}
191
192// static
193void ClipboardUtil::AddDataToPasteboard(NSPasteboard* pboard,
194                                        NSPasteboardItem* item) {
195  NSSet* oldTypes = [NSSet setWithArray:[pboard types]];
196  NSMutableSet* newTypes = [NSMutableSet setWithArray:[item types]];
197  [newTypes minusSet:oldTypes];
198
199  [pboard addTypes:[newTypes allObjects] owner:nil];
200  for (NSString* type in newTypes) {
201    // Technically, the object associated with |type| might be an NSString or a
202    // property list. It doesn't matter though, since the type gets pulled from
203    // and shoved into an NSDictionary.
204    [pboard setData:[item dataForType:type] forType:type];
205  }
206}
207
208// static
209bool ClipboardUtil::URLsAndTitlesFromPasteboard(NSPasteboard* pboard,
210                                                NSArray** urls,
211                                                NSArray** titles) {
212  return ReadWebURLsWithTitlesPboardType(pboard, urls, titles) ||
213         ReadURLItemsWithTitles(pboard, urls, titles);
214}
215
216// static
217NSPasteboard* ClipboardUtil::PasteboardFromBuffer(ClipboardBuffer buffer) {
218  NSString* buffer_type = nil;
219  switch (buffer) {
220    case ClipboardBuffer::kCopyPaste:
221      buffer_type = NSGeneralPboard;
222      break;
223    case ClipboardBuffer::kDrag:
224      buffer_type = NSDragPboard;
225      break;
226    case ClipboardBuffer::kSelection:
227      NOTREACHED();
228      break;
229  }
230
231  return [NSPasteboard pasteboardWithName:buffer_type];
232}
233
234// static
235NSString* ClipboardUtil::GetHTMLFromRTFOnPasteboard(NSPasteboard* pboard) {
236  NSData* rtfData = [pboard dataForType:NSRTFPboardType];
237  if (!rtfData)
238    return nil;
239
240  NSAttributedString* attributed =
241      [[[NSAttributedString alloc] initWithRTF:rtfData
242                            documentAttributes:nil] autorelease];
243  NSData* htmlData =
244      [attributed dataFromRange:NSMakeRange(0, [attributed length])
245             documentAttributes:@{
246               NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType
247             }
248                          error:nil];
249
250  // According to the docs, NSHTMLTextDocumentType is UTF8.
251  return [[[NSString alloc] initWithData:htmlData
252                                encoding:NSUTF8StringEncoding] autorelease];
253}
254
255}  // namespace ui
256