NSPasteboard+Utils.mm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. /* ***** BEGIN LICENSE BLOCK *****
  2. * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. *
  4. * The contents of this file are subject to the Mozilla Public License Version
  5. * 1.1 (the "License"); you may not use this file except in compliance with
  6. * the License. You may obtain a copy of the License at
  7. * http://www.mozilla.org/MPL/
  8. *
  9. * Software distributed under the License is distributed on an "AS IS" basis,
  10. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. * for the specific language governing rights and limitations under the
  12. * License.
  13. *
  14. * The Original Code is Chimera code.
  15. *
  16. * The Initial Developer of the Original Code is
  17. * Netscape Communications Corporation.
  18. * Portions created by the Initial Developer are Copyright (C) 2002
  19. * the Initial Developer. All Rights Reserved.
  20. *
  21. * Contributor(s):
  22. * Simon Fraser <sfraser@netscape.com>
  23. * Bruce Davidson <Bruce.Davidson@ipl.com>
  24. *
  25. * Alternatively, the contents of this file may be used under the terms of
  26. * either the GNU General Public License Version 2 or later (the "GPL"), or
  27. * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28. * in which case the provisions of the GPL or the LGPL are applicable instead
  29. * of those above. If you wish to allow use of your version of this file only
  30. * under the terms of either the GPL or the LGPL, and not to allow others to
  31. * use your version of this file under the terms of the MPL, indicate your
  32. * decision by deleting the provisions above and replace them with the notice
  33. * and other provisions required by the GPL or the LGPL. If you do not delete
  34. * the provisions above, a recipient may use your version of this file under
  35. * the terms of any one of the MPL, the GPL or the LGPL.
  36. *
  37. * ***** END LICENSE BLOCK ***** */
  38. #import "NSPasteboard+Utils.h"
  39. #import "NSURL+Utils.h"
  40. #import "NSString+Utils.h"
  41. #import "base/mac/scoped_nsobject.h"
  42. NSString* const kCorePasteboardFlavorType_url = @"CorePasteboardFlavorType 0x75726C20"; // 'url ' url
  43. NSString* const kCorePasteboardFlavorType_urln = @"CorePasteboardFlavorType 0x75726C6E"; // 'urln' title
  44. NSString* const kCorePasteboardFlavorType_urld = @"CorePasteboardFlavorType 0x75726C64"; // 'urld' URL description
  45. NSString* const kWebURLsWithTitlesPboardType = @"WebURLsWithTitlesPboardType"; // Safari-compatible URL + title arrays
  46. @interface NSPasteboard(ChimeraPasteboardURLUtilsPrivate)
  47. - (NSString*)cleanedStringWithPasteboardString:(NSString*)aString;
  48. @end
  49. @implementation NSPasteboard(ChimeraPasteboardURLUtilsPrivate)
  50. //
  51. // Utility method to ensure strings we're using in |containsURLData|
  52. // and |getURLs:andTitles| are free of internal control characters
  53. // and leading/trailing whitespace
  54. //
  55. - (NSString*)cleanedStringWithPasteboardString:(NSString*)aString
  56. {
  57. NSString* cleanString = [aString stringByRemovingCharactersInSet:[NSCharacterSet controlCharacterSet]];
  58. return [cleanString stringByTrimmingWhitespace];
  59. }
  60. @end
  61. @implementation NSPasteboard(ChimeraPasteboardURLUtils)
  62. //
  63. // Copy a single URL (with an optional title) to the clipboard in all relevant
  64. // formats. Convenience method for clients that can only ever deal with one
  65. // URL and shouldn't have to build up the arrays for setURLs:withTitles:.
  66. //
  67. - (void)setDataForURL:(NSString*)url title:(NSString*)title
  68. {
  69. NSArray* urlList = [NSArray arrayWithObject:url];
  70. NSArray* titleList = nil;
  71. if (title)
  72. titleList = [NSArray arrayWithObject:title];
  73. [self setURLs:urlList withTitles:titleList];
  74. }
  75. //
  76. // Copy a set of URLs, each of which may have a title, to the pasteboard
  77. // using all the available formats.
  78. // The title array should be nil, or must have the same length as the URL array.
  79. //
  80. - (void)setURLs:(NSArray*)inUrls withTitles:(NSArray*)inTitles
  81. {
  82. unsigned int urlCount = [inUrls count];
  83. // Best format that we know about is Safari's URL + title arrays - build these up
  84. if (!inTitles) {
  85. NSMutableArray* tmpTitleArray = [NSMutableArray arrayWithCapacity:urlCount];
  86. for (unsigned int i = 0; i < urlCount; ++i)
  87. [tmpTitleArray addObject:[inUrls objectAtIndex:i]];
  88. inTitles = tmpTitleArray;
  89. }
  90. NSMutableArray* filePaths = [NSMutableArray array];
  91. for (unsigned int i = 0; i < urlCount; ++i) {
  92. NSURL* url = [NSURL URLWithString:[inUrls objectAtIndex:i]];
  93. if ([url isFileURL] && [[NSFileManager defaultManager] fileExistsAtPath:[url path]])
  94. [filePaths addObject:[url path]];
  95. }
  96. if ([filePaths count] > 0) {
  97. [self addTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];
  98. [self setPropertyList:filePaths forType:NSFilenamesPboardType];
  99. }
  100. NSMutableArray* clipboardData = [NSMutableArray array];
  101. [clipboardData addObject:[NSArray arrayWithArray:inUrls]];
  102. [clipboardData addObject:inTitles];
  103. [self setPropertyList:clipboardData forType:kWebURLsWithTitlesPboardType];
  104. if (urlCount == 1) {
  105. NSString* url = [inUrls objectAtIndex:0];
  106. NSString* title = [inTitles objectAtIndex:0];
  107. [[NSURL URLWithString:url] writeToPasteboard:self];
  108. [self setString:url forType:NSStringPboardType];
  109. const char* tempCString = [url UTF8String];
  110. [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType:kCorePasteboardFlavorType_url];
  111. if (inTitles)
  112. tempCString = [title UTF8String];
  113. [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType:kCorePasteboardFlavorType_urln];
  114. }
  115. else if (urlCount > 1)
  116. {
  117. // With multiple URLs there aren't many other formats we can use
  118. // Just write a string of each URL (ignoring titles) on a separate line
  119. [self setString:[inUrls componentsJoinedByString:@"\n"] forType:NSStringPboardType];
  120. // but we have to put something in the carbon style flavors, otherwise apps will think
  121. // there is data there, but get nothing
  122. NSString* firstURL = [inUrls objectAtIndex:0];
  123. NSString* firstTitle = [inTitles objectAtIndex:0];
  124. const char* tempCString = [firstURL UTF8String];
  125. [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType:kCorePasteboardFlavorType_url];
  126. tempCString = [firstTitle UTF8String]; // not i18n friendly
  127. [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType:kCorePasteboardFlavorType_urln];
  128. }
  129. }
  130. // Get the set of URLs and their corresponding titles from the pasteboard.
  131. // If there are no URLs in a format we understand on the pasteboard empty
  132. // arrays will be returned. The two arrays will always be the same size.
  133. // The arrays returned are on the auto release pool. If |convertFilenames|
  134. // is YES, then the function will attempt to convert filenames in the drag
  135. // to file URLs.
  136. - (void) getURLs:(NSArray**)outUrls
  137. andTitles:(NSArray**)outTitles
  138. convertingFilenames:(BOOL)convertFilenames
  139. convertingTextToURL:(BOOL)convertTextToURL
  140. {
  141. // -types returns an ivar that might be invalidated by further manipulation of
  142. // NSPasteboard; retain it. https://crbug.com/1016740#c21
  143. base::scoped_nsobject<NSArray> types([[self types] retain]);
  144. NSURL* urlFromNSURL = nil; // Used below in getting an URL from the NSURLPboardType.
  145. if ([types containsObject:kWebURLsWithTitlesPboardType]) {
  146. NSArray* urlAndTitleContainer = [self propertyListForType:kWebURLsWithTitlesPboardType];
  147. if ([urlAndTitleContainer count] >= 2) {
  148. *outUrls = [urlAndTitleContainer objectAtIndex:0];
  149. *outTitles = [urlAndTitleContainer objectAtIndex:1];
  150. return;
  151. }
  152. }
  153. if ([types containsObject:NSFilenamesPboardType]) {
  154. NSArray *files = [self propertyListForType:NSFilenamesPboardType];
  155. *outUrls = [NSMutableArray arrayWithCapacity:[files count]];
  156. *outTitles = [NSMutableArray arrayWithCapacity:[files count]];
  157. for ( unsigned int i = 0; i < [files count]; ++i ) {
  158. NSString *file = [files objectAtIndex:i];
  159. NSString *ext = [[file pathExtension] lowercaseString];
  160. NSString *urlString = nil;
  161. NSString *title = @"";
  162. OSType fileType = NSHFSTypeCodeFromFileType(NSHFSTypeOfFile(file));
  163. // Check whether the file is a .webloc, a .ftploc, a .url, or some other kind of file.
  164. if ([ext isEqualToString:@"webloc"] || [ext isEqualToString:@"ftploc"] || fileType == 'ilht' || fileType == 'ilft') {
  165. NSURL* urlFromInetloc = [NSURL URLFromInetloc:file];
  166. if (urlFromInetloc) {
  167. urlString = [urlFromInetloc absoluteString];
  168. title = [[file lastPathComponent] stringByDeletingPathExtension];
  169. }
  170. } else if ([ext isEqualToString:@"url"] || fileType == 'LINK') {
  171. NSURL* urlFromIEURLFile = [NSURL URLFromIEURLFile:file];
  172. if (urlFromIEURLFile) {
  173. urlString = [urlFromIEURLFile absoluteString];
  174. title = [[file lastPathComponent] stringByDeletingPathExtension];
  175. }
  176. }
  177. if (!urlString) {
  178. if (!convertFilenames) {
  179. continue;
  180. }
  181. // Use the filename if not a .webloc or .url file, or if either of the
  182. // functions returns nil.
  183. urlString = [[NSURL fileURLWithPath:file] absoluteString];
  184. title = [file lastPathComponent];
  185. }
  186. [(NSMutableArray*) *outUrls addObject:urlString];
  187. [(NSMutableArray*) *outTitles addObject:title];
  188. }
  189. } else if ([types containsObject:NSURLPboardType] && (urlFromNSURL = [NSURL URLFromPasteboard:self])) {
  190. *outUrls = [NSArray arrayWithObject:[urlFromNSURL absoluteString]];
  191. NSString* title = nil;
  192. if ([types containsObject:kCorePasteboardFlavorType_urld])
  193. title = [self stringForType:kCorePasteboardFlavorType_urld];
  194. if (!title && [types containsObject:kCorePasteboardFlavorType_urln])
  195. title = [self stringForType:kCorePasteboardFlavorType_urln];
  196. if (!title && [types containsObject:NSStringPboardType])
  197. title = [self stringForType:NSStringPboardType];
  198. *outTitles = [NSArray arrayWithObject:(title ? title : @"")];
  199. } else if (convertTextToURL && [types containsObject:NSStringPboardType]) {
  200. NSString* potentialURLString = [self cleanedStringWithPasteboardString:[self stringForType:NSStringPboardType]];
  201. if ([potentialURLString isValidURI]) {
  202. *outUrls = [NSArray arrayWithObject:potentialURLString];
  203. NSString* title = nil;
  204. if ([types containsObject:kCorePasteboardFlavorType_urld])
  205. title = [self stringForType:kCorePasteboardFlavorType_urld];
  206. if (!title && [types containsObject:kCorePasteboardFlavorType_urln])
  207. title = [self stringForType:kCorePasteboardFlavorType_urln];
  208. *outTitles = [NSArray arrayWithObject:(title ? title : @"")];
  209. } else {
  210. // The string doesn't look like a URL - return empty arrays
  211. *outUrls = [NSArray array];
  212. *outTitles = [NSArray array];
  213. }
  214. } else {
  215. // We don't recognise any of these formats - return empty arrays
  216. *outUrls = [NSArray array];
  217. *outTitles = [NSArray array];
  218. }
  219. }
  220. //
  221. // Indicates if this pasteboard contains URL data that we understand
  222. // Deals with all our URL formats. Only strings that are valid URLs count.
  223. // If this returns YES it is safe to use getURLs:andTitles: to retrieve the data.
  224. //
  225. // NB: Does not consider our internal bookmark list format, because callers
  226. // usually need to deal with this separately because it can include folders etc.
  227. //
  228. - (BOOL) containsURLDataConvertingTextToURL:(BOOL)convertTextToURL
  229. {
  230. NSArray* types = [self types];
  231. if ([types containsObject:kWebURLsWithTitlesPboardType] ||
  232. [types containsObject:NSURLPboardType] ||
  233. [types containsObject:NSFilenamesPboardType])
  234. return YES;
  235. if (convertTextToURL && [types containsObject:NSStringPboardType]) {
  236. // Trim whitespace off the ends and newlines out of the middle so we don't reject otherwise-valid URLs;
  237. // we'll do another cleaning when we set the URLs and titles later, so this is safe.
  238. NSString* potentialURLString = [self cleanedStringWithPasteboardString:[self stringForType:NSStringPboardType]];
  239. return [potentialURLString isValidURI];
  240. }
  241. return NO;
  242. }
  243. @end