summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/tab_contents/tab_contents_view_mac.h4
-rw-r--r--chrome/browser/tab_contents/tab_contents_view_mac.mm175
-rw-r--r--chrome/chrome.gyp6
-rw-r--r--third_party/mozilla/include/NSPasteboard+Utils.h58
-rw-r--r--third_party/mozilla/include/NSPasteboard+Utils.mm278
-rw-r--r--third_party/mozilla/include/NSString+Utils.h101
-rw-r--r--third_party/mozilla/include/NSString+Utils.m362
-rw-r--r--third_party/mozilla/include/NSURL+Utils.h51
-rw-r--r--third_party/mozilla/include/NSURL+Utils.m135
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:@"&amp;"
+ withString:@"&"
+ options:NSLiteralSearch
+ range:NSMakeRange(0,[dirtyStringMutant length])];
+ [dirtyStringMutant replaceOccurrencesOfString:@"&quot;"
+ withString:@"\""
+ options:NSLiteralSearch
+ range:NSMakeRange(0,[dirtyStringMutant length])];
+ [dirtyStringMutant replaceOccurrencesOfString:@"&lt;"
+ withString:@"<"
+ options:NSLiteralSearch
+ range:NSMakeRange(0,[dirtyStringMutant length])];
+ [dirtyStringMutant replaceOccurrencesOfString:@"&gt;"
+ withString:@">"
+ options:NSLiteralSearch
+ range:NSMakeRange(0,[dirtyStringMutant length])];
+ [dirtyStringMutant replaceOccurrencesOfString:@"&mdash;"
+ withString:@"-"
+ options:NSLiteralSearch
+ range:NSMakeRange(0,[dirtyStringMutant length])];
+ [dirtyStringMutant replaceOccurrencesOfString:@"&apos;"
+ withString:@"'"
+ options:NSLiteralSearch
+ range:NSMakeRange(0,[dirtyStringMutant length])];
+ // fix import from old Firefox versions, which exported &#39; instead of a plain apostrophe
+ [dirtyStringMutant replaceOccurrencesOfString:@"&#39;"
+ withString:@"'"
+ options:NSLiteralSearch
+ range:NSMakeRange(0,[dirtyStringMutant length])];
+ return [dirtyStringMutant stringByRemovingCharactersInSet:[NSCharacterSet controlCharacterSet]];
+}
+
+-(NSString *)stringByAddingAmpEscapes
+{
+ NSMutableString* dirtyStringMutant = [NSMutableString stringWithString:self];
+ [dirtyStringMutant replaceOccurrencesOfString:@"&"
+ withString:@"&amp;"
+ options:NSLiteralSearch
+ range:NSMakeRange(0,[dirtyStringMutant length])];
+ [dirtyStringMutant replaceOccurrencesOfString:@"\""
+ withString:@"&quot;"
+ options:NSLiteralSearch
+ range:NSMakeRange(0,[dirtyStringMutant length])];
+ [dirtyStringMutant replaceOccurrencesOfString:@"<"
+ withString:@"&lt;"
+ options:NSLiteralSearch
+ range:NSMakeRange(0,[dirtyStringMutant length])];
+ [dirtyStringMutant replaceOccurrencesOfString:@">"
+ withString:@"&gt;"
+ 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