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