NSPasteboard+Utils.mm revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 39#import "NSPasteboard+Utils.h" 40#import "NSURL+Utils.h" 41#import "NSString+Utils.h" 42 43NSString* const kCorePasteboardFlavorType_url = @"CorePasteboardFlavorType 0x75726C20"; // 'url ' url 44NSString* const kCorePasteboardFlavorType_urln = @"CorePasteboardFlavorType 0x75726C6E"; // 'urln' title 45NSString* const kCorePasteboardFlavorType_urld = @"CorePasteboardFlavorType 0x75726C64"; // 'urld' URL description 46 47NSString* const kCaminoBookmarkListPBoardType = @"MozBookmarkType"; // list of Camino bookmark UIDs 48NSString* const kWebURLsWithTitlesPboardType = @"WebURLsWithTitlesPboardType"; // Safari-compatible URL + title arrays 49 50@interface NSPasteboard(ChimeraPasteboardURLUtilsPrivate) 51 52- (NSString*)cleanedStringWithPasteboardString:(NSString*)aString; 53 54@end 55 56@implementation NSPasteboard(ChimeraPasteboardURLUtilsPrivate) 57 58// 59// Utility method to ensure strings we're using in |containsURLData| 60// and |getURLs:andTitles| are free of internal control characters 61// and leading/trailing whitespace 62// 63- (NSString*)cleanedStringWithPasteboardString:(NSString*)aString 64{ 65 NSString* cleanString = [aString stringByRemovingCharactersInSet:[NSCharacterSet controlCharacterSet]]; 66 return [cleanString stringByTrimmingWhitespace]; 67} 68 69@end 70 71@implementation NSPasteboard(ChimeraPasteboardURLUtils) 72 73- (int)declareURLPasteboardWithAdditionalTypes:(NSArray*)additionalTypes owner:(id)newOwner 74{ 75 NSArray* allTypes = [additionalTypes arrayByAddingObjectsFromArray: 76 [NSArray arrayWithObjects: 77 kWebURLsWithTitlesPboardType, 78 NSURLPboardType, 79 NSStringPboardType, 80 kCorePasteboardFlavorType_url, 81 kCorePasteboardFlavorType_urln, 82 nil]]; 83 return [self declareTypes:allTypes owner:newOwner]; 84} 85 86// 87// Copy a single URL (with an optional title) to the clipboard in all relevant 88// formats. Convenience method for clients that can only ever deal with one 89// URL and shouldn't have to build up the arrays for setURLs:withTitles:. 90// 91- (void)setDataForURL:(NSString*)url title:(NSString*)title 92{ 93 NSArray* urlList = [NSArray arrayWithObject:url]; 94 NSArray* titleList = nil; 95 if (title) 96 titleList = [NSArray arrayWithObject:title]; 97 98 [self setURLs:urlList withTitles:titleList]; 99} 100 101// 102// Copy a set of URLs, each of which may have a title, to the pasteboard 103// using all the available formats. 104// The title array should be nil, or must have the same length as the URL array. 105// 106- (void)setURLs:(NSArray*)inUrls withTitles:(NSArray*)inTitles 107{ 108 unsigned int urlCount = [inUrls count]; 109 110 // Best format that we know about is Safari's URL + title arrays - build these up 111 if (!inTitles) { 112 NSMutableArray* tmpTitleArray = [NSMutableArray arrayWithCapacity:urlCount]; 113 for (unsigned int i = 0; i < urlCount; ++i) 114 [tmpTitleArray addObject:[inUrls objectAtIndex:i]]; 115 inTitles = tmpTitleArray; 116 } 117 118 NSMutableArray* filePaths = [NSMutableArray array]; 119 for (unsigned int i = 0; i < urlCount; ++i) { 120 NSURL* url = [NSURL URLWithString:[inUrls objectAtIndex:i]]; 121 if ([url isFileURL] && [[NSFileManager defaultManager] fileExistsAtPath:[url path]]) 122 [filePaths addObject:[url path]]; 123 } 124 if ([filePaths count] > 0) { 125 [self addTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil]; 126 [self setPropertyList:filePaths forType:NSFilenamesPboardType]; 127 } 128 129 NSMutableArray* clipboardData = [NSMutableArray array]; 130 [clipboardData addObject:[NSArray arrayWithArray:inUrls]]; 131 [clipboardData addObject:inTitles]; 132 133 [self setPropertyList:clipboardData forType:kWebURLsWithTitlesPboardType]; 134 135 if (urlCount == 1) { 136 NSString* url = [inUrls objectAtIndex:0]; 137 NSString* title = [inTitles objectAtIndex:0]; 138 139 [[NSURL URLWithString:url] writeToPasteboard:self]; 140 [self setString:url forType:NSStringPboardType]; 141 142 const char* tempCString = [url UTF8String]; 143 [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType:kCorePasteboardFlavorType_url]; 144 145 if (inTitles) 146 tempCString = [title UTF8String]; 147 [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType:kCorePasteboardFlavorType_urln]; 148 } 149 else if (urlCount > 1) 150 { 151 // With multiple URLs there aren't many other formats we can use 152 // Just write a string of each URL (ignoring titles) on a separate line 153 [self setString:[inUrls componentsJoinedByString:@"\n"] forType:NSStringPboardType]; 154 155 // but we have to put something in the carbon style flavors, otherwise apps will think 156 // there is data there, but get nothing 157 158 NSString* firstURL = [inUrls objectAtIndex:0]; 159 NSString* firstTitle = [inTitles objectAtIndex:0]; 160 161 const char* tempCString = [firstURL UTF8String]; 162 [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType:kCorePasteboardFlavorType_url]; 163 164 tempCString = [firstTitle UTF8String]; // not i18n friendly 165 [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType:kCorePasteboardFlavorType_urln]; 166 } 167} 168 169// Get the set of URLs and their corresponding titles from the pasteboard. 170// If there are no URLs in a format we understand on the pasteboard empty 171// arrays will be returned. The two arrays will always be the same size. 172// The arrays returned are on the auto release pool. If |convertFilenames| 173// is YES, then the function will attempt to convert filenames in the drag 174// to file URLs. 175- (void) getURLs:(NSArray**)outUrls 176 andTitles:(NSArray**)outTitles 177 convertingFilenames:(BOOL)convertFilenames 178{ 179 NSArray* types = [self types]; 180 NSURL* urlFromNSURL = nil; // Used below in getting an URL from the NSURLPboardType. 181 if ([types containsObject:kWebURLsWithTitlesPboardType]) { 182 NSArray* urlAndTitleContainer = [self propertyListForType:kWebURLsWithTitlesPboardType]; 183 *outUrls = [urlAndTitleContainer objectAtIndex:0]; 184 *outTitles = [urlAndTitleContainer objectAtIndex:1]; 185 } else if ([types containsObject:NSFilenamesPboardType]) { 186 NSArray *files = [self propertyListForType:NSFilenamesPboardType]; 187 *outUrls = [NSMutableArray arrayWithCapacity:[files count]]; 188 *outTitles = [NSMutableArray arrayWithCapacity:[files count]]; 189 for ( unsigned int i = 0; i < [files count]; ++i ) { 190 NSString *file = [files objectAtIndex:i]; 191 NSString *ext = [[file pathExtension] lowercaseString]; 192 NSString *urlString = nil; 193 NSString *title = @""; 194 OSType fileType = NSHFSTypeCodeFromFileType(NSHFSTypeOfFile(file)); 195 196 // Check whether the file is a .webloc, a .ftploc, a .url, or some other kind of file. 197 if ([ext isEqualToString:@"webloc"] || [ext isEqualToString:@"ftploc"] || fileType == 'ilht' || fileType == 'ilft') { 198 NSURL* urlFromInetloc = [NSURL URLFromInetloc:file]; 199 if (urlFromInetloc) { 200 urlString = [urlFromInetloc absoluteString]; 201 title = [[file lastPathComponent] stringByDeletingPathExtension]; 202 } 203 } else if ([ext isEqualToString:@"url"] || fileType == 'LINK') { 204 NSURL* urlFromIEURLFile = [NSURL URLFromIEURLFile:file]; 205 if (urlFromIEURLFile) { 206 urlString = [urlFromIEURLFile absoluteString]; 207 title = [[file lastPathComponent] stringByDeletingPathExtension]; 208 } 209 } 210 211 if (!urlString) { 212 if (!convertFilenames) { 213 continue; 214 } 215 // Use the filename if not a .webloc or .url file, or if either of the 216 // functions returns nil. 217 urlString = [[NSURL fileURLWithPath:file] absoluteString]; 218 title = [file lastPathComponent]; 219 } 220 221 [(NSMutableArray*) *outUrls addObject:urlString]; 222 [(NSMutableArray*) *outTitles addObject:title]; 223 } 224 } else if ([types containsObject:NSURLPboardType] && (urlFromNSURL = [NSURL URLFromPasteboard:self])) { 225 *outUrls = [NSArray arrayWithObject:[urlFromNSURL absoluteString]]; 226 NSString* title = nil; 227 if ([types containsObject:kCorePasteboardFlavorType_urld]) 228 title = [self stringForType:kCorePasteboardFlavorType_urld]; 229 if (!title && [types containsObject:kCorePasteboardFlavorType_urln]) 230 title = [self stringForType:kCorePasteboardFlavorType_urln]; 231 if (!title && [types containsObject:NSStringPboardType]) 232 title = [self stringForType:NSStringPboardType]; 233 *outTitles = [NSArray arrayWithObject:(title ? title : @"")]; 234 } else if ([types containsObject:NSStringPboardType]) { 235 NSString* potentialURLString = [self cleanedStringWithPasteboardString:[self stringForType:NSStringPboardType]]; 236 if ([potentialURLString isValidURI]) { 237 *outUrls = [NSArray arrayWithObject:potentialURLString]; 238 NSString* title = nil; 239 if ([types containsObject:kCorePasteboardFlavorType_urld]) 240 title = [self stringForType:kCorePasteboardFlavorType_urld]; 241 if (!title && [types containsObject:kCorePasteboardFlavorType_urln]) 242 title = [self stringForType:kCorePasteboardFlavorType_urln]; 243 *outTitles = [NSArray arrayWithObject:(title ? title : @"")]; 244 } else { 245 // The string doesn't look like a URL - return empty arrays 246 *outUrls = [NSArray array]; 247 *outTitles = [NSArray array]; 248 } 249 } else { 250 // We don't recognise any of these formats - return empty arrays 251 *outUrls = [NSArray array]; 252 *outTitles = [NSArray array]; 253 } 254} 255 256// 257// Indicates if this pasteboard contains URL data that we understand 258// Deals with all our URL formats. Only strings that are valid URLs count. 259// If this returns YES it is safe to use getURLs:andTitles: to retrieve the data. 260// 261// NB: Does not consider our internal bookmark list format, because callers 262// usually need to deal with this separately because it can include folders etc. 263// 264- (BOOL) containsURLData 265{ 266 NSArray* types = [self types]; 267 if ( [types containsObject:kWebURLsWithTitlesPboardType] 268 || [types containsObject:NSURLPboardType] 269 || [types containsObject:NSFilenamesPboardType] ) 270 return YES; 271 272 if ([types containsObject:NSStringPboardType]) { 273 // Trim whitespace off the ends and newlines out of the middle so we don't reject otherwise-valid URLs; 274 // we'll do another cleaning when we set the URLs and titles later, so this is safe. 275 NSString* potentialURLString = [self cleanedStringWithPasteboardString:[self stringForType:NSStringPboardType]]; 276 return [potentialURLString isValidURI]; 277 } 278 279 return NO; 280} 281@end 282 283@implementation NSPasteboard(ChromiumHTMLUtils) 284 285// Convert the RTF to HTML via an NSAttributedString. 286- (NSString*)htmlFromRtf { 287 if (![[self types] containsObject:NSRTFPboardType]) 288 return @""; 289 290 NSAttributedString* attributed = 291 [[[NSAttributedString alloc] 292 initWithRTF:[self dataForType:NSRTFPboardType] 293 documentAttributes:nil] autorelease]; 294 NSDictionary* attributeDict = 295 [NSDictionary dictionaryWithObject:NSHTMLTextDocumentType 296 forKey:NSDocumentTypeDocumentAttribute]; 297 NSData* htmlData = 298 [attributed dataFromRange:NSMakeRange(0, [attributed length]) 299 documentAttributes:attributeDict 300 error:nil]; 301 // According to the docs, NSHTMLTextDocumentType is UTF8. 302 return [[[NSString alloc] 303 initWithData:htmlData encoding:NSUTF8StringEncoding] autorelease]; 304} 305 306@end 307