Home | History | Annotate | Download | only in mozilla
      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 (at) netscape.com>
     23  *   Bruce Davidson <Bruce.Davidson (at) 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 
     43 NSString* const kCorePasteboardFlavorType_url  = @"CorePasteboardFlavorType 0x75726C20"; // 'url '  url
     44 NSString* const kCorePasteboardFlavorType_urln = @"CorePasteboardFlavorType 0x75726C6E"; // 'urln'  title
     45 NSString* const kCorePasteboardFlavorType_urld = @"CorePasteboardFlavorType 0x75726C64"; // 'urld' URL description
     46 
     47 NSString* const kCaminoBookmarkListPBoardType = @"MozBookmarkType"; // list of Camino bookmark UIDs
     48 NSString* 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