Question

I'm writing a program (for Mac OS X, using Objective-C) and I need to create a bunch of .webloc files programmatically.

The .webloc file is simply file which is created after you drag-n-drop an URL from Safari's location bar to some folder.

Generally speaking, I need an approach to create items in a filesystem which point to some location in the Web. As I understand .webloc files should be used for this on Mac OS X.

So, is it possible to craft a .webloc file having a valid url and some title for it?

Was it helpful?

Solution

It is little known - but there is also a simple plist based file format for weblocs.

When creating webloc files you DO NOT NEED to save them using the resource method the other three posters describe. You can also write a simple plist:

?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>URL</key>
    <string>http://apple.com</string>
</dict>
</plist>

The binary resource format is still in active use and if you want to read a plist file - then sure you need to read both file formats. But when writing the file - use the plist based format - it is a lot easier.

OTHER TIPS

.webloc files (more generically, Internet location files) are written in a format whose definition goes back to Mac OS 8.x. It is resource-based, derived from the clipping format you get when you create a file from dragged objects such as text or images.

The resources written are 'url ' 256 and 'TEXT' 256, which store the URL, and optionally 'urln' 256, containing the text associated with the URL. 'drag' 128 points to the other two (or three) resources.

NTWeblocFile, part of the Cocoatech Open Source framework CocoaTechFoundation (BSD licensed), supports writing these files from Objective-C. If you want to specify a title separately to the URL, you'll need to modify the class so it writes something other than the URL into the 'urln' resource.

In Mac OS X 10.3 and later, the URL is also written (may also be written) into a property list in the file's data fork. See the other answer for how this works...

A .webloc file doesn't have anything in its data fork; instead, it stores the URL it refers to as a resource in its resource fork. You can see this on the command line using the DeRez(1) tool

Here I've run it on a .webloc file that I dragged out of my Safari address bar for this question:

% DeRez "Desktop/Crafting .webloc file - Stack Overflow.webloc"
data 'drag' (128, "Crafting .webloc file -#1701953") {
    $"0000 0001 0000 0000 0000 0000 0000 0003"            /* ................ */
    $"5445 5854 0000 0100 0000 0000 0000 0000"            /* TEXT............ */
    $"7572 6C20 0000 0100 0000 0000 0000 0000"            /* url ............ */
    $"7572 6C6E 0000 0100 0000 0000 0000 0000"            /* urln............ */
};

data 'url ' (256, "Crafting .webloc file -#1701953") {
    $"6874 7470 3A2F 2F73 7461 636B 6F76 6572"            /* http://stackover */
    $"666C 6F77 2E63 6F6D 2F71 7565 7374 696F"            /* flow.com/questio */
    $"6E73 2F31 3436 3537 352F 6372 6166 7469"            /* ns/146575/crafti */
    $"6E67 2D77 6562 6C6F 632D 6669 6C65"                 /* ng-webloc-file */
};

data 'TEXT' (256, "Crafting .webloc file -#1701953") {
    $"6874 7470 3A2F 2F73 7461 636B 6F76 6572"            /* http://stackover */
    $"666C 6F77 2E63 6F6D 2F71 7565 7374 696F"            /* flow.com/questio */
    $"6E73 2F31 3436 3537 352F 6372 6166 7469"            /* ns/146575/crafti */
    $"6E67 2D77 6562 6C6F 632D 6669 6C65"                 /* ng-webloc-file */
};

data 'urln' (256, "Crafting .webloc file -#1701953") {
    $"4372 6166 7469 6E67 202E 7765 626C 6F63"            /* Crafting .webloc */
    $"2066 696C 6520 2D20 5374 6163 6B20 4F76"            /*  file - Stack Ov */
    $"6572 666C 6F77"                                     /* erflow */
};

The only resources that probably needs to be in there are the 'url ' and 'TEXT' resources of ID 256, and those probably don't need resource names either. The 'urln' resource might be handy if you want to include the title of the document the URL points to as well. The 'drag' resource tells the system that this is a clipping file, but I'm unsure of whether it needs to be there in this day and age.

To work with resources and the resource fork of a file, you use the Resource Manager — one of the underlying pieces of Carbon which goes back to the original Mac. There are, however, a couple of Cocoa wrappers for the Resource Manager, such as Nathan Day's NDResourceFork.

Another way to make "web shortcut" is .url file mentioned here already.
The contents look like (much simplier than plist xml-based):

[InternetShortcut]
URL=http://www.apple.com/

Note the file has 3 lines, the last line is empty.

More info on .url file format

It uses a resource fork-based binary format.

Valid workarounds:

  • Have the user drag a URL from your application (NSURLPboardType) to the Finder. The Finder will create a webloc for you.
  • Create a Windows Web Shortcut (.URL file). These have a INI-like data fork-based format and should be documented somewhere on the Internet; the OS supports them as it supports weblocs.

Here's how Google Chrome does it: WriteURLToNewWebLocFileResourceFork

This does the basic task, without needing any third party libraries. (Be warned: minimal error checking.)

// data for 'drag' resource (it's always the same)
#define DRAG_DATA_LENGTH 64
static const unsigned char _dragData[DRAG_DATA_LENGTH]={
    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
    0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x75, 0x72, 0x6C, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x75, 0x72, 0x6C, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

static void _addData(NSData *data, ResType type, short resId, ResFileRefNum refNum)
{
    Handle handle;
    if (PtrToHand([data bytes], &handle, [data length])==noErr) {
        ResFileRefNum previousRefNum=CurResFile();
        UseResFile(refNum);

        HLock(handle);
        AddResource(handle, type, resId, "\p");
        HUnlock(handle);

        UseResFile(previousRefNum);
    }
}

void WeblocCreateFile(NSString *location, NSString *name, NSURL *fileUrl)
{
    NSString *contents=[NSString stringWithFormat:
                        @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
                        @"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
                        @"<plist version=\"1.0\">\n"
                        @"<dict>\n"
                        @"<key>URL</key>\n"
                        @"<string>%@</string>\n"
                        @"</dict>\n"
                        @"</plist>\n", location];

    if ([[contents dataUsingEncoding:NSUTF8StringEncoding] writeToURL:fileUrl options:NSDataWritingAtomic error:nil])
    {        
        // split into parent and filename parts
        NSString *parentPath=[[fileUrl URLByDeletingLastPathComponent] path];
        NSString *fileName=[fileUrl lastPathComponent];

        FSRef parentRef;
        if(FSPathMakeRef((const UInt8 *)[parentPath fileSystemRepresentation], &parentRef, NULL)==noErr)
        {
            unichar fileNameBuffer[[fileName length]];
            [fileName getCharacters:fileNameBuffer];

            FSCreateResFile(&parentRef, [fileName length], fileNameBuffer, 0, NULL, NULL, NULL);
            if (ResError()==noErr)
            {
                FSRef fileRef;
                if(FSPathMakeRef((const UInt8 *)[[fileUrl path] fileSystemRepresentation], &fileRef, NULL)==noErr)
                {
                    ResFileRefNum resFileReference = FSOpenResFile(&fileRef, fsWrPerm);
                    if (resFileReference>0 && ResError()==noErr)
                    {
                        _addData([NSData dataWithBytes:_dragData length:DRAG_DATA_LENGTH], 'drag', 128, resFileReference);
                        _addData([location dataUsingEncoding:NSUTF8StringEncoding], 'url ', 256, resFileReference);
                        _addData([location dataUsingEncoding:NSUTF8StringEncoding], 'TEXT', 256, resFileReference);
                        _addData([name dataUsingEncoding:NSUTF8StringEncoding], 'urln', 256, resFileReference);
                        CloseResFile(resFileReference);
                    }
                }
            }
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top