Crafting .webloc file
Asked Answered
B

7

20

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?

Burchell answered 28/9, 2008 at 19:44 Comment(0)
C
23

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.

Cushy answered 28/9, 2008 at 23:4 Comment(2)
Can anyone confirm a webloc file with only a data fork works with Safari? I tried saving Schwa's example XML plist as a webloc file but when dragging it to Safari it just opens a blank window. I also tried saving a webloc file with Firefox. Interestingly, Firefox writes a file with both a data fork (XML plist as in Schwa's example) and a resource fork. Dragging that file to Safari works. But when I tried removing the resource fork (by overwriting it on the command line with an empty stream) it no longer worked. Test done using Safari 4.0.5 and Firefox 3.6.3.Murry
Further clarification to my earlier comment: I hadn't tried simply double clicking the file to open it. I just did and that DOES work with the data-fork-only webloc file, which opens the URL in Safari. But as I said above, DRAGGING the webloc file to Safari does NOT work, it brings up an empty window. ( Also, dragging the file to a Safari bookmark window creates a bookmark with a file:// URL for the webloc file, instead of a bookmark for the URL contained in the webloc file. )Murry
T
8

.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...

Tati answered 28/9, 2008 at 20:16 Comment(2)
The plist format has been around since 10.3 - it is fully supported. Create one in the finder and double click it and it'll be opened by the FInder in Safari (or whatever). A great way to create a plist webloc is by dragigng a URL from BBEdit for example.Cushy
(For whatever reason I produced invalid plists during my tests. You're right, it works.)Tati
P
7

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.

Pappano answered 28/9, 2008 at 20:15 Comment(1)
In my testing with Safari and TextEdit, 'drag' needs to be there. The resource names are ignored; dragging a link from a NSTextView doesn't produce them.Tati
B
7

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

Burchell answered 29/9, 2008 at 8:25 Comment(2)
Unfortunately, those are handled by Safari, not the system. This means that Safari will attempt to handle the URL in the file, regardless of whether Safari is the default app for that URL's scheme, or whether it can even handle that scheme.Coexist
Here in the distant future (e.g. on macOS Monterey), my default web browser (Chrome) opens a .url file without interference from Safari.Lamm
G
2

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.
Guam answered 28/9, 2008 at 20:2 Comment(0)
R
1

Here's how Google Chrome does it: WriteURLToNewWebLocFileResourceFork

Ryswick answered 5/9, 2012 at 14:28 Comment(0)
R
0

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);
                    }
                }
            }
        }
    }
}
Ryswick answered 5/9, 2012 at 9:53 Comment(1)
much easier to just let the nsdictionary do the plist for you. NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:1]; [dict setObject:@"http://" forKey:@"URL"]; [dict writeToFile:@"/file" atomically:NO];Stephen

© 2022 - 2024 — McMap. All rights reserved.