diff options
-rw-r--r-- | chrome/browser/tab_contents/tab_contents_view_mac.h | 4 | ||||
-rw-r--r-- | chrome/browser/tab_contents/tab_contents_view_mac.mm | 175 | ||||
-rw-r--r-- | chrome/chrome.gyp | 6 | ||||
-rw-r--r-- | third_party/mozilla/include/NSPasteboard+Utils.h | 58 | ||||
-rw-r--r-- | third_party/mozilla/include/NSPasteboard+Utils.mm | 278 | ||||
-rw-r--r-- | third_party/mozilla/include/NSString+Utils.h | 101 | ||||
-rw-r--r-- | third_party/mozilla/include/NSString+Utils.m | 362 | ||||
-rw-r--r-- | third_party/mozilla/include/NSURL+Utils.h | 51 | ||||
-rw-r--r-- | third_party/mozilla/include/NSURL+Utils.m | 135 |
9 files changed, 1153 insertions, 17 deletions
diff --git a/chrome/browser/tab_contents/tab_contents_view_mac.h b/chrome/browser/tab_contents/tab_contents_view_mac.h index 5b464b9..563fc79 100644 --- a/chrome/browser/tab_contents/tab_contents_view_mac.h +++ b/chrome/browser/tab_contents/tab_contents_view_mac.h @@ -74,7 +74,9 @@ class TabContentsViewMac : public TabContentsView, const NotificationDetails& details); private: - // --------------------------------------------------------------------------- + // Returns a drag pasteboard filled with the appropriate data. The types are + // populated in decending order of richness. + NSPasteboard* FillDragData(const WebDropData& drop_data); // The Cocoa NSView that lives in the view hierarchy. scoped_nsobject<TabContentsViewCocoa> cocoa_view_; diff --git a/chrome/browser/tab_contents/tab_contents_view_mac.mm b/chrome/browser/tab_contents/tab_contents_view_mac.mm index 1731658..fced871 100644 --- a/chrome/browser/tab_contents/tab_contents_view_mac.mm +++ b/chrome/browser/tab_contents/tab_contents_view_mac.mm @@ -4,7 +4,9 @@ #include "chrome/browser/tab_contents/tab_contents_view_mac.h" +#include "base/sys_string_conversions.h" #include "chrome/browser/browser.h" // TODO(beng): this dependency is awful. +#include "chrome/browser/cocoa/nsimage_cache.h" #include "chrome/browser/cocoa/sad_tab_view.h" #include "chrome/browser/renderer_host/render_widget_host.h" #include "chrome/browser/renderer_host/render_widget_host_view_mac.h" @@ -13,6 +15,8 @@ #include "chrome/common/notification_type.h" #include "chrome/common/notification_service.h" #include "chrome/common/render_messages.h" +#include "net/base/net_util.h" +#import "third_party/mozilla/include/NSPasteboard+Utils.h" #include "chrome/common/temp_scaffolding_stubs.h" @@ -78,15 +82,100 @@ void TabContentsViewMac::GetContainerBounds(gfx::Rect* out) const { *out = [cocoa_view_.get() NSRectToRect:[cocoa_view_.get() bounds]]; } -void TabContentsViewMac::StartDragging(const WebDropData& drop_data) { - NOTIMPLEMENTED(); +// Returns a drag pasteboard filled with the appropriate data. The types are +// populated in decending order of richness. +NSPasteboard* TabContentsViewMac::FillDragData( + const WebDropData& drop_data) { + NSPasteboard* pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard]; + [pasteboard declareTypes:[NSArray array] owner:nil]; + + // HTML. + if (!drop_data.text_html.empty()) { + [pasteboard addTypes:[NSArray arrayWithObject:NSHTMLPboardType] + owner:nil]; + [pasteboard setString:base::SysUTF16ToNSString(drop_data.text_html) + forType:NSHTMLPboardType]; + } + + // URL. + if (drop_data.url.is_valid()) { + // TODO(pinkerton/jrg): special javascript: handling for bookmark bar. Win + // doesn't allow you to drop js: bookmarks on the desktop (since they're + // meaningless) but does allow you to drop them on the bookmark bar (where + // they're intended to go generally). We need to figure out a private + // flavor for Bookmark dragging and then flag this down in the drag source. + [pasteboard addTypes:[NSArray arrayWithObject:NSURLPboardType] + owner:nil]; + NSString* url = base::SysUTF8ToNSString(drop_data.url.spec()); + NSString* title = base::SysUTF16ToNSString(drop_data.url_title); + [pasteboard setURLs:[NSArray arrayWithObject:url] + withTitles:[NSArray arrayWithObject:title]]; + } + + // Files. + // TODO(pinkerton): Hook up image drags, data is in drop_data.file_contents. + if (!drop_data.file_contents.empty()) { +#if 0 + // Images without ALT text will only have a file extension so we need to + // synthesize one from the provided extension and URL. + std::string filename_utf8 = + [base::SysUTF16ToNSString(drop_data.file_description_filename) + fileSystemRepresentation]; + FilePath file_name(filename_utf8); + file_name = file_name.BaseName().RemoveExtension(); + if (file_name.value().empty()) { + // Retrieve the name from the URL. + file_name = FilePath::FromWStringHack( + net::GetSuggestedFilename(drop_data.url, "", "", L"")); + } + std::string file_extension_utf8 = + [base::SysUTF16ToNSString(drop_data.file_extension) + fileSystemRepresentation]; + file_name = file_name.ReplaceExtension(file_extension_utf8); + NSArray* types = [NSArray arrayWithObjects:NSFileContentsPboardType, + NSFilenamesPboardType, + nil]; + [pasteboard addTypes:types owner:nil]; + NSArray* file_name_array = + [NSArray arrayWithObject:base::SysUTF8ToNSString(file_name.value())]; + [pasteboard setPropertyList:file_name_array forType:NSFilenamesPboardType]; + NSData* data = [NSData dataWithBytes:drop_data.file_contents.data() + length:drop_data.file_contents.length()]; + [pasteboard setData:data forType:NSFileContentsPboardType]; +#endif + } - // Until we have d'n'd implemented, just immediately pretend we're - // already done with the drag and drop so we don't get stuck - // thinking we're in mid-drag. - // TODO(port): remove me when the above NOTIMPLEMENTED is fixed. - if (tab_contents()->render_view_host()) - tab_contents()->render_view_host()->DragSourceSystemDragEnded(); + // Plain text. + if (!drop_data.plain_text.empty()) { + [pasteboard addTypes:[NSArray arrayWithObject:NSStringPboardType] + owner:nil]; + [pasteboard setString:base::SysUTF16ToNSString(drop_data.plain_text) + forType:NSStringPboardType]; + } + return pasteboard; +} + +void TabContentsViewMac::StartDragging(const WebDropData& drop_data) { + // Create an image to use for the drag. + // TODO(pinkerton): Generate the proper image. This one will do in a pinch. + NSImage* dragImage = nsimage_cache::ImageNamed(@"nav.pdf"); + + NSPasteboard* pasteboard = FillDragData(drop_data); + + // Tell the view to start a drag using |cocoa_view_| as the drag source. The + // source will get notified when the drag completes (success or failure) so + // it can tell the render view host the drag is done. Windows does this with + // a nested event loop, we get called back. + NSEvent* currentEvent = [NSApp currentEvent]; + NSPoint mousePoint = [currentEvent locationInWindow]; + mousePoint = [cocoa_view_ convertPoint:mousePoint fromView:nil]; + [cocoa_view_ dragImage:dragImage + at:mousePoint + offset:NSZeroSize + event:currentEvent + pasteboard:pasteboard + source:cocoa_view_ + slideBack:YES]; } void TabContentsViewMac::OnContentsDestroy() { @@ -246,6 +335,10 @@ void TabContentsViewMac::Observe(NotificationType type, return self; } +- (TabContents*)tabContents { + return TabContentsView_->tab_contents(); +} + - (void)processKeyboardEvent:(NSEvent*)event { if ([event type] == NSKeyDown) [super keyDown:event]; @@ -254,13 +347,12 @@ void TabContentsViewMac::Observe(NotificationType type, } - (void)mouseEvent:(NSEvent *)theEvent { - if (TabContentsView_->tab_contents()->delegate()) { + TabContents* tabContents = [self tabContents]; + if (tabContents->delegate()) { if ([theEvent type] == NSMouseMoved) - TabContentsView_->tab_contents()->delegate()-> - ContentsMouseEvent(TabContentsView_->tab_contents(), true); + tabContents->delegate()->ContentsMouseEvent(tabContents, true); if ([theEvent type] == NSMouseExited) - TabContentsView_->tab_contents()->delegate()-> - ContentsMouseEvent(TabContentsView_->tab_contents(), false); + tabContents->delegate()->ContentsMouseEvent(tabContents, false); } } @@ -279,15 +371,66 @@ void TabContentsViewMac::Observe(NotificationType type, // WebCore. - (void)cut:(id)sender { - TabContentsView_->tab_contents()->Cut(); + [self tabContents]->Cut(); } - (void)copy:(id)sender { - TabContentsView_->tab_contents()->Copy(); + [self tabContents]->Copy(); } - (void)paste:(id)sender { - TabContentsView_->tab_contents()->Paste(); + [self tabContents]->Paste(); +} + +// NSDraggingSource methods + +// Returns what kind of drag operations are available. This is a required +// method for NSDraggingSource. +- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { + // TODO(pinkerton): I think this is right... + return NSDragOperationCopy; +} + +// Called when a drag initiated in our view ends. We need to make sure that +// we tell WebCore so that it can go about processing things as normal. +- (void)draggedImage:(NSImage*)anImage + endedAt:(NSPoint)aPoint + operation:(NSDragOperation)operation { + RenderViewHost* rvh = [self tabContents]->render_view_host(); + if (rvh) + rvh->DragSourceSystemDragEnded(); +} + +// NSDraggingDestination methods + +- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { + TabContents* tabContents = [self tabContents]; + if (tabContents->showing_interstitial_page()) { + // TODO(pinkerton): hook up dropping only urls + return NSDragOperationNone; + } + + // TODO(pinkerton): Fill this in when we're tracking drags w/in the content. + // Fill out a WebDropData from pasteboard + // Convert event point to gfx::Point + // Pass to tabContents->render_view_host()->DragTargetDragEnter(...) + + return NSDragOperationCopy; +} + +- (void)draggingExited:(id<NSDraggingInfo>)sender { + // TODO(pinkerton): Fill this in when we're tracking drags w/in the content. +} + +- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender { + // TODO(pinkerton): Fill this in when we're tracking drags w/in the content. + return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { + // TODO(pinkerton): Fill this in when we're tracking drags w/in the content. + // Reject all drops until then. + return NO; } // Tons of stuff goes here, where we grab events going on in Cocoaland and send diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index e05ea9f..29802db 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -1871,8 +1871,14 @@ '../third_party/GTM/AppKit/GTMUILocalizer.h', '../third_party/GTM/AppKit/GTMUILocalizer.m', # Build necessary Mozilla sources + '../third_party/mozilla/include/NSPasteboard+Utils.h', + '../third_party/mozilla/include/NSPasteboard+Utils.mm', '../third_party/mozilla/include/NSScreen+Utils.h', '../third_party/mozilla/include/NSScreen+Utils.m', + '../third_party/mozilla/include/NSString+Utils.h', + '../third_party/mozilla/include/NSString+Utils.m', + '../third_party/mozilla/include/NSURL+Utils.h', + '../third_party/mozilla/include/NSURL+Utils.m', '../third_party/mozilla/include/NSWorkspace+Utils.h', '../third_party/mozilla/include/NSWorkspace+Utils.m', ], diff --git a/third_party/mozilla/include/NSPasteboard+Utils.h b/third_party/mozilla/include/NSPasteboard+Utils.h new file mode 100644 index 0000000..5c2f1e1 --- /dev/null +++ b/third_party/mozilla/include/NSPasteboard+Utils.h @@ -0,0 +1,58 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser <sfraser@netscape.com> + * Bruce Davidson <Bruce.Davidson@ipl.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import <AppKit/AppKit.h> + +extern NSString* const kCorePasteboardFlavorType_url; +extern NSString* const kCorePasteboardFlavorType_urln; +extern NSString* const kCorePasteboardFlavorType_urld; + +extern NSString* const kCaminoBookmarkListPBoardType; +extern NSString* const kWebURLsWithTitlesPboardType; + +@interface NSPasteboard(ChimeraPasteboardURLUtils) + +- (int) declareURLPasteboardWithAdditionalTypes:(NSArray*)additionalTypes owner:(id)newOwner; +- (void) setDataForURL:(NSString*)url title:(NSString*)title; + +- (void) setURLs:(NSArray*)inUrls withTitles:(NSArray*)inTitles; +- (void) getURLs:(NSArray**)outUrls andTitles:(NSArray**)outTitles; +- (BOOL) containsURLData; + +@end + diff --git a/third_party/mozilla/include/NSPasteboard+Utils.mm b/third_party/mozilla/include/NSPasteboard+Utils.mm new file mode 100644 index 0000000..2a7ee5e --- /dev/null +++ b/third_party/mozilla/include/NSPasteboard+Utils.mm @@ -0,0 +1,278 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser <sfraser@netscape.com> + * Bruce Davidson <Bruce.Davidson@ipl.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import "NSPasteboard+Utils.h" +#import "NSURL+Utils.h" +#import "NSString+Utils.h" + +NSString* const kCorePasteboardFlavorType_url = @"CorePasteboardFlavorType 0x75726C20"; // 'url ' url +NSString* const kCorePasteboardFlavorType_urln = @"CorePasteboardFlavorType 0x75726C6E"; // 'urln' title +NSString* const kCorePasteboardFlavorType_urld = @"CorePasteboardFlavorType 0x75726C64"; // 'urld' URL description + +NSString* const kCaminoBookmarkListPBoardType = @"MozBookmarkType"; // list of Camino bookmark UIDs +NSString* const kWebURLsWithTitlesPboardType = @"WebURLsWithTitlesPboardType"; // Safari-compatible URL + title arrays + +@interface NSPasteboard(ChimeraPasteboardURLUtilsPrivate) + +- (NSString*)cleanedStringWithPasteboardString:(NSString*)aString; + +@end + +@implementation NSPasteboard(ChimeraPasteboardURLUtilsPrivate) + +// +// Utility method to ensure strings we're using in |containsURLData| +// and |getURLs:andTitles| are free of internal control characters +// and leading/trailing whitespace +// +- (NSString*)cleanedStringWithPasteboardString:(NSString*)aString +{ + NSString* cleanString = [aString stringByRemovingCharactersInSet:[NSCharacterSet controlCharacterSet]]; + return [cleanString stringByTrimmingWhitespace]; +} + +@end + +@implementation NSPasteboard(ChimeraPasteboardURLUtils) + +- (int)declareURLPasteboardWithAdditionalTypes:(NSArray*)additionalTypes owner:(id)newOwner +{ + NSArray* allTypes = [additionalTypes arrayByAddingObjectsFromArray: + [NSArray arrayWithObjects: + kWebURLsWithTitlesPboardType, + NSURLPboardType, + NSStringPboardType, + kCorePasteboardFlavorType_url, + kCorePasteboardFlavorType_urln, + nil]]; + return [self declareTypes:allTypes owner:newOwner]; +} + +// +// Copy a single URL (with an optional title) to the clipboard in all relevant +// formats. Convenience method for clients that can only ever deal with one +// URL and shouldn't have to build up the arrays for setURLs:withTitles:. +// +- (void)setDataForURL:(NSString*)url title:(NSString*)title +{ + NSArray* urlList = [NSArray arrayWithObject:url]; + NSArray* titleList = nil; + if (title) + titleList = [NSArray arrayWithObject:title]; + + [self setURLs:urlList withTitles:titleList]; +} + +// +// Copy a set of URLs, each of which may have a title, to the pasteboard +// using all the available formats. +// The title array should be nil, or must have the same length as the URL array. +// +- (void)setURLs:(NSArray*)inUrls withTitles:(NSArray*)inTitles +{ + unsigned int urlCount = [inUrls count]; + + // Best format that we know about is Safari's URL + title arrays - build these up + if (!inTitles) { + NSMutableArray* tmpTitleArray = [NSMutableArray arrayWithCapacity:urlCount]; + for (unsigned int i = 0; i < urlCount; ++i) + [tmpTitleArray addObject:@""]; + inTitles = tmpTitleArray; + } + + NSMutableArray* filePaths = [NSMutableArray array]; + for (unsigned int i = 0; i < urlCount; ++i) { + NSURL* url = [NSURL URLWithString:[inUrls objectAtIndex:i]]; + if ([url isFileURL] && [[NSFileManager defaultManager] fileExistsAtPath:[url path]]) + [filePaths addObject:[url path]]; + } + if ([filePaths count] > 0) { + [self addTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil]; + [self setPropertyList:filePaths forType:NSFilenamesPboardType]; + } + + NSMutableArray* clipboardData = [NSMutableArray array]; + [clipboardData addObject:[NSArray arrayWithArray:inUrls]]; + [clipboardData addObject:inTitles]; + + [self setPropertyList:clipboardData forType:kWebURLsWithTitlesPboardType]; + + if (urlCount == 1) { + NSString* title = @""; + if (inTitles) + title = [inTitles objectAtIndex:0]; + + NSString* url = [inUrls objectAtIndex:0]; + + [[NSURL URLWithString:url] writeToPasteboard:self]; + [self setString:url forType:NSStringPboardType]; + + const char* tempCString = [url UTF8String]; + [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType:kCorePasteboardFlavorType_url]; + + if (inTitles) + tempCString = [title UTF8String]; + [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType:kCorePasteboardFlavorType_urln]; + } + else if (urlCount > 1) + { + // With multiple URLs there aren't many other formats we can use + // Just write a string of each URL (ignoring titles) on a separate line + [self setString:[inUrls componentsJoinedByString:@"\n"] forType:NSStringPboardType]; + + // but we have to put something in the carbon style flavors, otherwise apps will think + // there is data there, but get nothing + + NSString* firstURL = [inUrls objectAtIndex:0]; + NSString* firstTitle = ([inTitles count] > 0) ? [inTitles objectAtIndex:0] : @""; + + const char* tempCString = [firstURL UTF8String]; + [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType:kCorePasteboardFlavorType_url]; + + tempCString = [firstTitle UTF8String]; // not i18n friendly + [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType:kCorePasteboardFlavorType_urln]; + } +} + +// +// Get the set of URLs and their corresponding titles from the clipboards +// If there are no URLs in a format we understand on the pasteboard empty +// arrays will be returned. The two arrays will always be the same size. +// The arrays returned are on the auto release pool. +// +- (void) getURLs:(NSArray**)outUrls andTitles:(NSArray**)outTitles +{ + NSArray* types = [self types]; + if ([types containsObject:kWebURLsWithTitlesPboardType]) { + NSArray* urlAndTitleContainer = [self propertyListForType:kWebURLsWithTitlesPboardType]; + *outUrls = [urlAndTitleContainer objectAtIndex:0]; + *outTitles = [urlAndTitleContainer objectAtIndex:1]; + } else if ([types containsObject:NSFilenamesPboardType]) { + NSArray *files = [self propertyListForType:NSFilenamesPboardType]; + *outUrls = [NSMutableArray arrayWithCapacity:[files count]]; + *outTitles = [NSMutableArray arrayWithCapacity:[files count]]; + for ( unsigned int i = 0; i < [files count]; ++i ) { + NSString *file = [files objectAtIndex:i]; + NSString *ext = [[file pathExtension] lowercaseString]; + NSString *urlString = nil; + NSString *title = @""; + OSType fileType = NSHFSTypeCodeFromFileType(NSHFSTypeOfFile(file)); + + // Check whether the file is a .webloc, a .ftploc, a .url, or some other kind of file. + if ([ext isEqualToString:@"webloc"] || [ext isEqualToString:@"ftploc"] || fileType == 'ilht' || fileType == 'ilft') { + NSURL* urlFromInetloc = [NSURL URLFromInetloc:file]; + if (urlFromInetloc) { + urlString = [urlFromInetloc absoluteString]; + title = [[file lastPathComponent] stringByDeletingPathExtension]; + } + } else if ([ext isEqualToString:@"url"] || fileType == 'LINK') { + NSURL* urlFromIEURLFile = [NSURL URLFromIEURLFile:file]; + if (urlFromIEURLFile) { + urlString = [urlFromIEURLFile absoluteString]; + title = [[file lastPathComponent] stringByDeletingPathExtension]; + } + } + + // Use the filename if not a .webloc or .url file, or if either of the + // functions returns nil. + if (!urlString) { + urlString = file; + title = [file lastPathComponent]; + } + + [(NSMutableArray*) *outUrls addObject:urlString]; + [(NSMutableArray*) *outTitles addObject:title]; + } + } else if ([types containsObject:NSURLPboardType]) { + *outUrls = [NSArray arrayWithObject:[[NSURL URLFromPasteboard:self] absoluteString]]; + NSString* title = nil; + if ([types containsObject:kCorePasteboardFlavorType_urld]) + title = [self stringForType:kCorePasteboardFlavorType_urld]; + if (!title && [types containsObject:kCorePasteboardFlavorType_urln]) + title = [self stringForType:kCorePasteboardFlavorType_urln]; + if (!title && [types containsObject:NSStringPboardType]) + title = [self stringForType:NSStringPboardType]; + *outTitles = [NSArray arrayWithObject:(title ? title : @"")]; + } else if ([types containsObject:NSStringPboardType]) { + NSString* potentialURLString = [self cleanedStringWithPasteboardString:[self stringForType:NSStringPboardType]]; + if ([potentialURLString isValidURI]) { + *outUrls = [NSArray arrayWithObject:potentialURLString]; + NSString* title = nil; + if ([types containsObject:kCorePasteboardFlavorType_urld]) + title = [self stringForType:kCorePasteboardFlavorType_urld]; + if (!title && [types containsObject:kCorePasteboardFlavorType_urln]) + title = [self stringForType:kCorePasteboardFlavorType_urln]; + *outTitles = [NSArray arrayWithObject:(title ? title : @"")]; + } else { + // The string doesn't look like a URL - return empty arrays + *outUrls = [NSArray array]; + *outTitles = [NSArray array]; + } + } else { + // We don't recognise any of these formats - return empty arrays + *outUrls = [NSArray array]; + *outTitles = [NSArray array]; + } +} + +// +// Indicates if this pasteboard contains URL data that we understand +// Deals with all our URL formats. Only strings that are valid URLs count. +// If this returns YES it is safe to use getURLs:andTitles: to retrieve the data. +// +// NB: Does not consider our internal bookmark list format, because callers +// usually need to deal with this separately because it can include folders etc. +// +- (BOOL) containsURLData +{ + NSArray* types = [self types]; + if ( [types containsObject:kWebURLsWithTitlesPboardType] + || [types containsObject:NSURLPboardType] + || [types containsObject:NSFilenamesPboardType] ) + return YES; + + if ([types containsObject:NSStringPboardType]) { + // Trim whitespace off the ends and newlines out of the middle so we don't reject otherwise-valid URLs; + // we'll do another cleaning when we set the URLs and titles later, so this is safe. + NSString* potentialURLString = [self cleanedStringWithPasteboardString:[self stringForType:NSStringPboardType]]; + return [potentialURLString isValidURI]; + } + + return NO; +} +@end diff --git a/third_party/mozilla/include/NSString+Utils.h b/third_party/mozilla/include/NSString+Utils.h new file mode 100644 index 0000000..f556cc6 --- /dev/null +++ b/third_party/mozilla/include/NSString+Utils.h @@ -0,0 +1,101 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser <sfraser@netscape.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import <Foundation/Foundation.h> + +typedef enum +{ + kTruncateAtStart, + kTruncateAtMiddle, + kTruncateAtEnd +} ETruncationType; + + +// a category to extend NSString +@interface NSString (ChimeraStringUtils) + ++ (id)ellipsisString; ++ (NSString*)stringWithUUID; + +- (BOOL)isEqualToStringIgnoringCase:(NSString*)inString; +- (BOOL)hasCaseInsensitivePrefix:(NSString*)inString; + +// Some URIs can contain spaces and still work, even though they aren't strictly valid +// per RFC2396. This method allows us to account for those URIs. +- (BOOL)isLooselyValidatedURI; + +// Utility method to identify URIs that can be run in the context of the current page. +// These URIs could be used as attack vectors via AppleScript, for example. +- (BOOL)isPotentiallyDangerousURI; + +// Utility method to ensure validity of URI strings. NSURL is used to validate +// most of them, but the NSURL test may fail for |javascript:| and |data:| URIs +// because they often contain invalid (per RFC2396) characters such as spaces. +- (BOOL)isValidURI; + +- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*)characterSet; +- (NSString *)stringByReplacingCharactersInSet:(NSCharacterSet*)characterSet withString:(NSString*)string; +- (NSString *)stringByTruncatingTo:(unsigned int)maxCharacters at:(ETruncationType)truncationType; +- (NSString *)stringByTruncatingToWidth:(float)inWidth at:(ETruncationType)truncationType withAttributes:(NSDictionary *)attributes; +- (NSString *)stringByTrimmingWhitespace; +- (NSString *)stringByRemovingAmpEscapes; +- (NSString *)stringByAddingAmpEscapes; + +@end + +@interface NSMutableString (ChimeraMutableStringUtils) + +- (void)truncateTo:(unsigned)maxCharacters at:(ETruncationType)truncationType; +- (void)truncateToWidth:(float)maxWidth at:(ETruncationType)truncationType withAttributes:(NSDictionary *)attributes; + +@end + +@interface NSString (ChimeraFilePathStringUtils) + +- (NSString*)volumeNamePathComponent; +- (NSString*)displayNameOfLastPathComponent; + +@end + +@interface NSString (CaminoURLStringUtils) + +// Returns true if the string represents a "blank" URL ("" or "about:blank") +- (BOOL)isBlankURL; +// Returns a URI that looks good in a location field +- (NSString *)unescapedURI; + +@end diff --git a/third_party/mozilla/include/NSString+Utils.m b/third_party/mozilla/include/NSString+Utils.m new file mode 100644 index 0000000..c145505 --- /dev/null +++ b/third_party/mozilla/include/NSString+Utils.m @@ -0,0 +1,362 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser <sfraser@netscape.com> + * David Haas <haasd@cae.wisc.edu> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import <AppKit/AppKit.h> // for NSStringDrawing.h + +#import "NSString+Utils.h" + + +@implementation NSString (ChimeraStringUtils) + ++ (id)ellipsisString +{ + static NSString* sEllipsisString = nil; + if (!sEllipsisString) { + unichar ellipsisChar = 0x2026; + sEllipsisString = [[NSString alloc] initWithCharacters:&ellipsisChar length:1]; + } + + return sEllipsisString; +} + ++ (NSString*)stringWithUUID +{ + NSString* uuidString = nil; + CFUUIDRef newUUID = CFUUIDCreate(kCFAllocatorDefault); + if (newUUID) { + uuidString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, newUUID); + CFRelease(newUUID); + } + return [uuidString autorelease]; +} + +- (BOOL)isEqualToStringIgnoringCase:(NSString*)inString +{ + return ([self compare:inString options:NSCaseInsensitiveSearch] == NSOrderedSame); +} + +- (BOOL)hasCaseInsensitivePrefix:(NSString*)inString +{ + if ([self length] < [inString length]) + return NO; + return ([self compare:inString options:NSCaseInsensitiveSearch range:NSMakeRange(0, [inString length])] == NSOrderedSame); +} + +- (BOOL)isLooselyValidatedURI +{ + return ([self hasCaseInsensitivePrefix:@"javascript:"] || [self hasCaseInsensitivePrefix:@"data:"]); +} + +- (BOOL)isPotentiallyDangerousURI +{ + return ([self hasCaseInsensitivePrefix:@"javascript:"] || [self hasCaseInsensitivePrefix:@"data:"]); +} + +- (BOOL)isValidURI +{ + // This will only return a non-nil object for valid, well-formed URI strings + NSURL* testURL = [NSURL URLWithString:self]; + + // |javascript:| and |data:| URIs might not have passed the test, + // but spaces will work OK, so evaluate them separately. + if ((testURL) || [self isLooselyValidatedURI]) { + return YES; + } + return NO; +} + +- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*)characterSet +{ + NSScanner* cleanerScanner = [NSScanner scannerWithString:self]; + NSMutableString* cleanString = [NSMutableString stringWithCapacity:[self length]]; + // Make sure we don't skip whitespace, which NSScanner does by default + [cleanerScanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@""]]; + + while (![cleanerScanner isAtEnd]) { + NSString* stringFragment; + if ([cleanerScanner scanUpToCharactersFromSet:characterSet intoString:&stringFragment]) + [cleanString appendString:stringFragment]; + + [cleanerScanner scanCharactersFromSet:characterSet intoString:nil]; + } + + return cleanString; +} + +- (NSString *)stringByReplacingCharactersInSet:(NSCharacterSet*)characterSet + withString:(NSString*)string +{ + NSScanner* cleanerScanner = [NSScanner scannerWithString:self]; + NSMutableString* cleanString = [NSMutableString stringWithCapacity:[self length]]; + // Make sure we don't skip whitespace, which NSScanner does by default + [cleanerScanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@""]]; + + while (![cleanerScanner isAtEnd]) + { + NSString* stringFragment; + if ([cleanerScanner scanUpToCharactersFromSet:characterSet intoString:&stringFragment]) + [cleanString appendString:stringFragment]; + + if ([cleanerScanner scanCharactersFromSet:characterSet intoString:nil]) + [cleanString appendString:string]; + } + + return cleanString; +} + +- (NSString*)stringByTruncatingTo:(unsigned int)maxCharacters at:(ETruncationType)truncationType +{ + if ([self length] > maxCharacters) + { + NSMutableString *mutableCopy = [self mutableCopy]; + [mutableCopy truncateTo:maxCharacters at:truncationType]; + return [mutableCopy autorelease]; + } + + return self; +} + +- (NSString *)stringByTruncatingToWidth:(float)inWidth at:(ETruncationType)truncationType + withAttributes:(NSDictionary *)attributes +{ + if ([self sizeWithAttributes:attributes].width > inWidth) + { + NSMutableString *mutableCopy = [self mutableCopy]; + [mutableCopy truncateToWidth:inWidth at:truncationType withAttributes:attributes]; + return [mutableCopy autorelease]; + } + + return self; +} + +- (NSString *)stringByTrimmingWhitespace +{ + return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; +} + +-(NSString *)stringByRemovingAmpEscapes +{ + NSMutableString* dirtyStringMutant = [NSMutableString stringWithString:self]; + [dirtyStringMutant replaceOccurrencesOfString:@"&" + withString:@"&" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@""" + withString:@"\"" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@"<" + withString:@"<" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@">" + withString:@">" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@"—" + withString:@"-" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@"'" + withString:@"'" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + // fix import from old Firefox versions, which exported ' instead of a plain apostrophe + [dirtyStringMutant replaceOccurrencesOfString:@"'" + withString:@"'" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + return [dirtyStringMutant stringByRemovingCharactersInSet:[NSCharacterSet controlCharacterSet]]; +} + +-(NSString *)stringByAddingAmpEscapes +{ + NSMutableString* dirtyStringMutant = [NSMutableString stringWithString:self]; + [dirtyStringMutant replaceOccurrencesOfString:@"&" + withString:@"&" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@"\"" + withString:@""" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@"<" + withString:@"<" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@">" + withString:@">" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + return [NSString stringWithString:dirtyStringMutant]; +} + +@end + + +@implementation NSMutableString (ChimeraMutableStringUtils) + +- (void)truncateTo:(unsigned)maxCharacters at:(ETruncationType)truncationType +{ + if ([self length] <= maxCharacters) + return; + + NSRange replaceRange; + replaceRange.length = [self length] - maxCharacters; + + switch (truncationType) { + case kTruncateAtStart: + replaceRange.location = 0; + break; + + case kTruncateAtMiddle: + replaceRange.location = maxCharacters / 2; + break; + + case kTruncateAtEnd: + replaceRange.location = maxCharacters; + break; + + default: +#if DEBUG + NSLog(@"Unknown truncation type in stringByTruncatingTo::"); +#endif + replaceRange.location = maxCharacters; + break; + } + + [self replaceCharactersInRange:replaceRange withString:[NSString ellipsisString]]; +} + + +- (void)truncateToWidth:(float)maxWidth + at:(ETruncationType)truncationType + withAttributes:(NSDictionary *)attributes +{ + // First check if we have to truncate at all. + if ([self sizeWithAttributes:attributes].width <= maxWidth) + return; + + // Essentially, we perform a binary search on the string length + // which fits best into maxWidth. + + float width = maxWidth; + int lo = 0; + int hi = [self length]; + int mid; + + // Make a backup copy of the string so that we can restore it if we fail low. + NSMutableString *backup = [self mutableCopy]; + + while (hi >= lo) { + mid = (hi + lo) / 2; + + // Cut to mid chars and calculate the resulting width + [self truncateTo:mid at:truncationType]; + width = [self sizeWithAttributes:attributes].width; + + if (width > maxWidth) { + // Fail high - string is still to wide. For the next cut, we can simply + // work on the already cut string, so we don't restore using the backup. + hi = mid - 1; + } + else if (width == maxWidth) { + // Perfect match, abort the search. + break; + } + else { + // Fail low - we cut off too much. Restore the string before cutting again. + lo = mid + 1; + [self setString:backup]; + } + } + // Perform the final cut (unless this was already a perfect match). + if (width != maxWidth) + [self truncateTo:hi at:truncationType]; + [backup release]; +} + +@end + +@implementation NSString (ChimeraFilePathStringUtils) + +- (NSString*)volumeNamePathComponent +{ + // if the file doesn't exist, then componentsToDisplayForPath will return nil, + // so back up to the nearest existing dir + NSString* curPath = self; + while (![[NSFileManager defaultManager] fileExistsAtPath:curPath]) + { + NSString* parentDirPath = [curPath stringByDeletingLastPathComponent]; + if ([parentDirPath isEqualToString:curPath]) + break; // avoid endless loop + curPath = parentDirPath; + } + + NSArray* displayComponents = [[NSFileManager defaultManager] componentsToDisplayForPath:curPath]; + if ([displayComponents count] > 0) + return [displayComponents objectAtIndex:0]; + + return self; +} + +- (NSString*)displayNameOfLastPathComponent +{ + return [[NSFileManager defaultManager] displayNameAtPath:self]; +} + +@end + +@implementation NSString (CaminoURLStringUtils) + +- (BOOL)isBlankURL +{ + return ([self isEqualToString:@"about:blank"] || [self isEqualToString:@""]); +} + +// Excluded character list comes from RFC2396 and by examining Safari's behaviour +- (NSString*)unescapedURI +{ + NSString *unescapedURI = (NSString*)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, + (CFStringRef)self, + CFSTR(" \"\';/?:@&=+$,#"), + kCFStringEncodingUTF8); + return unescapedURI ? [unescapedURI autorelease] : self; +} + +@end diff --git a/third_party/mozilla/include/NSURL+Utils.h b/third_party/mozilla/include/NSURL+Utils.h new file mode 100644 index 0000000..dc7501e --- /dev/null +++ b/third_party/mozilla/include/NSURL+Utils.h @@ -0,0 +1,51 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Camino code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Nate Weaver (Wevah) - wevah@derailer.org + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import <Cocoa/Cocoa.h> + + +@interface NSURL (CaminoExtensions) + +// This takes an NSURL to a local file, and if that file is a file that +// represents a URL, returns the URL it contains. Otherwise, returns the +// passed URL. Supports .url, .webloc and .ftploc files. ++ (NSURL*)decodeLocalFileURL:(NSURL*)url; + ++(NSURL*)URLFromInetloc:(NSString*)inFile; ++(NSURL*)URLFromIEURLFile:(NSString*)inFile; + +@end diff --git a/third_party/mozilla/include/NSURL+Utils.m b/third_party/mozilla/include/NSURL+Utils.m new file mode 100644 index 0000000..a3b9098 --- /dev/null +++ b/third_party/mozilla/include/NSURL+Utils.m @@ -0,0 +1,135 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Camino code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Nate Weaver (Wevah) - wevah@derailer.org + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import "NSURL+Utils.h" + + +@implementation NSURL (CaminoExtensions) + ++ (NSURL*)decodeLocalFileURL:(NSURL*)url +{ + NSString* urlPathString = [url path]; + NSString* ext = [[urlPathString pathExtension] lowercaseString]; + OSType fileType = NSHFSTypeCodeFromFileType(NSHFSTypeOfFile(urlPathString)); + + if ([ext isEqualToString:@"url"] || fileType == 'LINK') { + url = [NSURL URLFromIEURLFile:urlPathString]; + } + else if ([ext isEqualToString:@"webloc"] || [ext isEqualToString:@"ftploc"] || + fileType == 'ilht' || fileType == 'ilft') + { + url = [NSURL URLFromInetloc:urlPathString]; + } + + return url; +} + +// +// Reads the URL from a .webloc/.ftploc file. +// Returns the URL, or nil on failure. +// ++(NSURL*)URLFromInetloc:(NSString*)inFile +{ + FSRef ref; + NSURL *ret = nil; + + if (inFile && FSPathMakeRef((UInt8 *)[inFile fileSystemRepresentation], &ref, NULL) == noErr) { + short resRef; + + resRef = FSOpenResFile(&ref, fsRdPerm); + + if (resRef != -1) { // Has resouce fork. + Handle urlResHandle; + + if ((urlResHandle = Get1Resource('url ', 256))) { // Has 'url ' resource with ID 256. + long size; + + size = GetMaxResourceSize(urlResHandle); + ret = [NSURL URLWithString:[NSString stringWithCString:(char *)*urlResHandle length:size]]; + } + + CloseResFile(resRef); + } + + if (!ret) { // Look for valid plist data. + NSDictionary *plist; + if ((plist = [[NSDictionary alloc] initWithContentsOfFile:inFile])) { + ret = [NSURL URLWithString:[plist objectForKey:@"URL"]]; + [plist release]; + } + } + } + + return ret; +} + +// +// Reads the URL from a .url file. +// Returns the URL or nil on failure. +// ++(NSURL*)URLFromIEURLFile:(NSString*)inFile +{ + NSURL *ret = nil; + + // Is this really an IE .url file? + if (inFile) { + NSCharacterSet *newlines = [NSCharacterSet characterSetWithCharactersInString:@"\r\n"]; + NSScanner *scanner = [NSScanner scannerWithString:[NSString stringWithContentsOfFile:inFile]]; + [scanner scanUpToString:@"[InternetShortcut]" intoString:nil]; + + if ([scanner scanString:@"[InternetShortcut]" intoString:nil]) { + // Scan each non-empty line in this section. We don't need to explicitly scan the newlines or + // whitespace because NSScanner ignores these by default. + NSString *line; + + while ([scanner scanUpToCharactersFromSet:newlines intoString:&line]) { + if ([line hasPrefix:@"URL="]) { + ret = [NSURL URLWithString:[line substringFromIndex:4]]; + break; + } + else if ([line hasPrefix:@"["]) { + // This is the start of a new section, so if we haven't found an URL yet, we should bail. + break; + } + } + } + } + + return ret; +} + +@end |