AppleSingle / AppleDouble: C++/STL parsing library

AppleSingle and AppleDouble files are used by certain Mac applications (e.g. Finder) to store Mac-specific file attributes on filesystems that do not support that.

Syntactically, both formats are the same, the only difference is how they are being used:

  • AppleSingle means that only one file will be created on external filesystem that will hold both the data (AKA "data fork" in Apple terminology), and the attributes (AKA "resource fork").
  • AppleDouble means that two files will be created: a normal file that keeps the data ("data fork") is kept separately from an auxiliary file that contains attributes ("resource fork"), which is kept with the same name, but starting with an extra dot and underscore ._ to keep it hidden.

In modern practice (Mac OS X), Finder only uses AppleDouble to keep compatibility with other OSes, as virtually nobody outside of Mac understands how to access data in AppleSingle container.

This page hosts a formal specification of AppleSingle / AppleDouble using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream).
    • One can open a stream for reading from a local file:
      #include <fstream>
      
      std::ifstream is("path/to/local/file.apple_single_double", std::ifstream::binary);
    • Or one can prepare a stream for reading from existing std::string str:
      #include <sstream>
      
      std::istringstream is(str);
    • Or one can parse arbitrary char* buffer in memory, given that we know its size:
      #include <sstream>
      
      const char buf[] = { ... };
      std::string str(buf, sizeof buf);
      std::istringstream is(str);
  2. We need to wrap our input stream into Kaitai stream:
    #include <kaitai/kaitaistream.h>
    
    kaitai::kstream ks(&is);
  3. And finally, we can invoke the parsing:
    apple_single_double_t data(&ks);

After that, one can get various attributes from the structure by invoking getter methods like:

data.reserved() // => Must be all 0.

C++/STL source code to parse AppleSingle / AppleDouble

apple_single_double.h

#ifndef APPLE_SINGLE_DOUBLE_H_
#define APPLE_SINGLE_DOUBLE_H_

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "kaitai/kaitaistruct.h"

#include <stdint.h>
#include <vector>

#if KAITAI_STRUCT_VERSION < 7000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.7 or later is required"
#endif

/**
 * AppleSingle and AppleDouble files are used by certain Mac
 * applications (e.g. Finder) to store Mac-specific file attributes on
 * filesystems that do not support that.
 * 
 * Syntactically, both formats are the same, the only difference is how
 * they are being used:
 * 
 * * AppleSingle means that only one file will be created on external
 *   filesystem that will hold both the data (AKA "data fork" in Apple
 *   terminology), and the attributes (AKA "resource fork").
 * * AppleDouble means that two files will be created: a normal file
 *   that keeps the data ("data fork") is kept separately from an
 *   auxiliary file that contains attributes ("resource fork"), which
 *   is kept with the same name, but starting with an extra dot and
 *   underscore `._` to keep it hidden.
 * 
 * In modern practice (Mac OS X), Finder only uses AppleDouble to keep
 * compatibility with other OSes, as virtually nobody outside of Mac
 * understands how to access data in AppleSingle container.
 * \sa Source
 */

class apple_single_double_t : public kaitai::kstruct {

public:
    class entry_t;
    class finder_info_t;
    class point_t;

    enum file_type_t {
        FILE_TYPE_APPLE_SINGLE = 333312,
        FILE_TYPE_APPLE_DOUBLE = 333319
    };

    apple_single_double_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, apple_single_double_t* p__root = 0);

private:
    void _read();

public:
    ~apple_single_double_t();

    class entry_t : public kaitai::kstruct {

    public:

        enum types_t {
            TYPES_DATA_FORK = 1,
            TYPES_RESOURCE_FORK = 2,
            TYPES_REAL_NAME = 3,
            TYPES_COMMENT = 4,
            TYPES_ICON_BW = 5,
            TYPES_ICON_COLOR = 6,
            TYPES_FILE_DATES_INFO = 8,
            TYPES_FINDER_INFO = 9,
            TYPES_MACINTOSH_FILE_INFO = 10,
            TYPES_PRODOS_FILE_INFO = 11,
            TYPES_MSDOS_FILE_INFO = 12,
            TYPES_AFP_SHORT_NAME = 13,
            TYPES_AFP_FILE_INFO = 14,
            TYPES_AFP_DIRECTORY_ID = 15
        };

        entry_t(kaitai::kstream* p__io, apple_single_double_t* p__parent = 0, apple_single_double_t* p__root = 0);

    private:
        void _read();

    public:
        ~entry_t();

    private:
        bool f_body;
        finder_info_t* m_body;

    public:
        finder_info_t* body();

    private:
        types_t m_type;
        uint32_t m_ofs_body;
        uint32_t m_len_body;
        apple_single_double_t* m__root;
        apple_single_double_t* m__parent;
        std::string m__raw_body;
        kaitai::kstream* m__io__raw_body;

    public:
        types_t type() const { return m_type; }
        uint32_t ofs_body() const { return m_ofs_body; }
        uint32_t len_body() const { return m_len_body; }
        apple_single_double_t* _root() const { return m__root; }
        apple_single_double_t* _parent() const { return m__parent; }
        std::string _raw_body() const { return m__raw_body; }
        kaitai::kstream* _io__raw_body() const { return m__io__raw_body; }
    };

    /**
     * Information specific to Finder
     * \sa older Inside Macintosh, Volume II page 84 or Volume IV page 104.
     */

    class finder_info_t : public kaitai::kstruct {

    public:

        finder_info_t(kaitai::kstream* p__io, apple_single_double_t::entry_t* p__parent = 0, apple_single_double_t* p__root = 0);

    private:
        void _read();

    public:
        ~finder_info_t();

    private:
        std::string m_file_type;
        std::string m_file_creator;
        uint16_t m_flags;
        point_t* m_location;
        uint16_t m_folder_id;
        apple_single_double_t* m__root;
        apple_single_double_t::entry_t* m__parent;

    public:
        std::string file_type() const { return m_file_type; }
        std::string file_creator() const { return m_file_creator; }
        uint16_t flags() const { return m_flags; }

        /**
         * File icon's coordinates when displaying this folder.
         */
        point_t* location() const { return m_location; }

        /**
         * File folder ID (=window).
         */
        uint16_t folder_id() const { return m_folder_id; }
        apple_single_double_t* _root() const { return m__root; }
        apple_single_double_t::entry_t* _parent() const { return m__parent; }
    };

    /**
     * Specifies 2D coordinate in QuickDraw grid.
     */

    class point_t : public kaitai::kstruct {

    public:

        point_t(kaitai::kstream* p__io, apple_single_double_t::finder_info_t* p__parent = 0, apple_single_double_t* p__root = 0);

    private:
        void _read();

    public:
        ~point_t();

    private:
        uint16_t m_x;
        uint16_t m_y;
        apple_single_double_t* m__root;
        apple_single_double_t::finder_info_t* m__parent;

    public:
        uint16_t x() const { return m_x; }
        uint16_t y() const { return m_y; }
        apple_single_double_t* _root() const { return m__root; }
        apple_single_double_t::finder_info_t* _parent() const { return m__parent; }
    };

private:
    file_type_t m_magic;
    uint32_t m_version;
    std::string m_reserved;
    uint16_t m_num_entries;
    std::vector<entry_t*>* m_entries;
    apple_single_double_t* m__root;
    kaitai::kstruct* m__parent;

public:
    file_type_t magic() const { return m_magic; }
    uint32_t version() const { return m_version; }

    /**
     * Must be all 0.
     */
    std::string reserved() const { return m_reserved; }
    uint16_t num_entries() const { return m_num_entries; }
    std::vector<entry_t*>* entries() const { return m_entries; }
    apple_single_double_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

#endif  // APPLE_SINGLE_DOUBLE_H_

apple_single_double.cpp

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "apple_single_double.h"



apple_single_double_t::apple_single_double_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, apple_single_double_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    _read();
}

void apple_single_double_t::_read() {
    m_magic = static_cast<apple_single_double_t::file_type_t>(m__io->read_u4be());
    m_version = m__io->read_u4be();
    m_reserved = m__io->read_bytes(16);
    m_num_entries = m__io->read_u2be();
    int l_entries = num_entries();
    m_entries = new std::vector<entry_t*>();
    m_entries->reserve(l_entries);
    for (int i = 0; i < l_entries; i++) {
        m_entries->push_back(new entry_t(m__io, this, m__root));
    }
}

apple_single_double_t::~apple_single_double_t() {
    for (std::vector<entry_t*>::iterator it = m_entries->begin(); it != m_entries->end(); ++it) {
        delete *it;
    }
    delete m_entries;
}

apple_single_double_t::entry_t::entry_t(kaitai::kstream* p__io, apple_single_double_t* p__parent, apple_single_double_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    f_body = false;
    _read();
}

void apple_single_double_t::entry_t::_read() {
    m_type = static_cast<apple_single_double_t::entry_t::types_t>(m__io->read_u4be());
    m_ofs_body = m__io->read_u4be();
    m_len_body = m__io->read_u4be();
}

apple_single_double_t::entry_t::~entry_t() {
    if (f_body && !n_body) {
        delete m__io__raw_body;
        delete m_body;
    }
}

apple_single_double_t::finder_info_t* apple_single_double_t::entry_t::body() {
    if (f_body)
        return m_body;
    std::streampos _pos = m__io->pos();
    m__io->seek(ofs_body());
    n_body = true;
    switch (type()) {
    case TYPES_FINDER_INFO: {
        n_body = false;
        m__raw_body = m__io->read_bytes(len_body());
        m__io__raw_body = new kaitai::kstream(m__raw_body);
        m_body = new finder_info_t(m__io__raw_body, this, m__root);
        break;
    }
    default: {
        m__raw_body = m__io->read_bytes(len_body());
        break;
    }
    }
    m__io->seek(_pos);
    f_body = true;
    return m_body;
}

apple_single_double_t::finder_info_t::finder_info_t(kaitai::kstream* p__io, apple_single_double_t::entry_t* p__parent, apple_single_double_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void apple_single_double_t::finder_info_t::_read() {
    m_file_type = m__io->read_bytes(4);
    m_file_creator = m__io->read_bytes(4);
    m_flags = m__io->read_u2be();
    m_location = new point_t(m__io, this, m__root);
    m_folder_id = m__io->read_u2be();
}

apple_single_double_t::finder_info_t::~finder_info_t() {
    delete m_location;
}

apple_single_double_t::point_t::point_t(kaitai::kstream* p__io, apple_single_double_t::finder_info_t* p__parent, apple_single_double_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void apple_single_double_t::point_t::_read() {
    m_x = m__io->read_u2be();
    m_y = m__io->read_u2be();
}

apple_single_double_t::point_t::~point_t() {
}