Windows Shell Items: C++98/STL parsing library

Windows Shell Items (AKA "shellbags") is an undocumented set of structures used internally within Windows to identify paths in Windows Folder Hierarchy. It is widely used in Windows Shell (and most visible in File Explorer), both as in-memory and in-file structures. Some formats embed them, namely:

  • Windows Shell link files (.lnk) Windows registry
  • Windows registry "ShellBags" keys

The format is mostly undocumented, and is known to vary between various Windows versions.

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of Windows Shell Items using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for C++98/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

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 local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.bin", std::ifstream::binary);
    
    #include <sstream>
    
    std::istringstream is(str);
    
    #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:
    windows_shell_items_t data(&ks);
    

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

data.items() // => get items

C++98/STL source code to parse Windows Shell Items

windows_shell_items.h

#ifndef WINDOWS_SHELL_ITEMS_H_
#define WINDOWS_SHELL_ITEMS_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 < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif

/**
 * Windows Shell Items (AKA "shellbags") is an undocumented set of
 * structures used internally within Windows to identify paths in
 * Windows Folder Hierarchy. It is widely used in Windows Shell (and
 * most visible in File Explorer), both as in-memory and in-file
 * structures. Some formats embed them, namely:
 * 
 * * Windows Shell link files (.lnk) Windows registry
 * * Windows registry "ShellBags" keys
 * 
 * The format is mostly undocumented, and is known to vary between
 * various Windows versions.
 * \sa https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc Source
 */

class windows_shell_items_t : public kaitai::kstruct {

public:
    class shell_item_data_t;
    class shell_item_t;
    class root_folder_body_t;
    class volume_body_t;
    class file_entry_body_t;

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

private:
    void _read();
    void _clean_up();

public:
    ~windows_shell_items_t();

    class shell_item_data_t : public kaitai::kstruct {

    public:

        shell_item_data_t(kaitai::kstream* p__io, windows_shell_items_t::shell_item_t* p__parent = 0, windows_shell_items_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~shell_item_data_t();

    private:
        uint8_t m_code;
        root_folder_body_t* m_body1;
        bool n_body1;

    public:
        bool _is_null_body1() { body1(); return n_body1; };

    private:
        kaitai::kstruct* m_body2;
        bool n_body2;

    public:
        bool _is_null_body2() { body2(); return n_body2; };

    private:
        windows_shell_items_t* m__root;
        windows_shell_items_t::shell_item_t* m__parent;

    public:
        uint8_t code() const { return m_code; }
        root_folder_body_t* body1() const { return m_body1; }
        kaitai::kstruct* body2() const { return m_body2; }
        windows_shell_items_t* _root() const { return m__root; }
        windows_shell_items_t::shell_item_t* _parent() const { return m__parent; }
    };

    /**
     * \sa https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-SHLLINK/[MS-SHLLINK].pdf Section 2.2.2
     */

    class shell_item_t : public kaitai::kstruct {

    public:

        shell_item_t(kaitai::kstream* p__io, windows_shell_items_t* p__parent = 0, windows_shell_items_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~shell_item_t();

    private:
        uint16_t m_len_data;
        shell_item_data_t* m_data;
        bool n_data;

    public:
        bool _is_null_data() { data(); return n_data; };

    private:
        windows_shell_items_t* m__root;
        windows_shell_items_t* m__parent;
        std::string m__raw_data;
        bool n__raw_data;

    public:
        bool _is_null__raw_data() { _raw_data(); return n__raw_data; };

    private:
        kaitai::kstream* m__io__raw_data;

    public:
        uint16_t len_data() const { return m_len_data; }
        shell_item_data_t* data() const { return m_data; }
        windows_shell_items_t* _root() const { return m__root; }
        windows_shell_items_t* _parent() const { return m__parent; }
        std::string _raw_data() const { return m__raw_data; }
        kaitai::kstream* _io__raw_data() const { return m__io__raw_data; }
    };

    /**
     * \sa https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc#32-root-folder-shell-item Source
     */

    class root_folder_body_t : public kaitai::kstruct {

    public:

        root_folder_body_t(kaitai::kstream* p__io, windows_shell_items_t::shell_item_data_t* p__parent = 0, windows_shell_items_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~root_folder_body_t();

    private:
        uint8_t m_sort_index;
        std::string m_shell_folder_id;
        windows_shell_items_t* m__root;
        windows_shell_items_t::shell_item_data_t* m__parent;

    public:
        uint8_t sort_index() const { return m_sort_index; }
        std::string shell_folder_id() const { return m_shell_folder_id; }
        windows_shell_items_t* _root() const { return m__root; }
        windows_shell_items_t::shell_item_data_t* _parent() const { return m__parent; }
    };

    /**
     * \sa https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc#33-volume-shell-item Source
     */

    class volume_body_t : public kaitai::kstruct {

    public:

        volume_body_t(kaitai::kstream* p__io, windows_shell_items_t::shell_item_data_t* p__parent = 0, windows_shell_items_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~volume_body_t();

    private:
        uint8_t m_flags;
        windows_shell_items_t* m__root;
        windows_shell_items_t::shell_item_data_t* m__parent;

    public:
        uint8_t flags() const { return m_flags; }
        windows_shell_items_t* _root() const { return m__root; }
        windows_shell_items_t::shell_item_data_t* _parent() const { return m__parent; }
    };

    /**
     * \sa https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc#34-file-entry-shell-item Source
     */

    class file_entry_body_t : public kaitai::kstruct {

    public:

        file_entry_body_t(kaitai::kstream* p__io, windows_shell_items_t::shell_item_data_t* p__parent = 0, windows_shell_items_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~file_entry_body_t();

    private:
        bool f_is_dir;
        bool m_is_dir;

    public:
        bool is_dir();

    private:
        bool f_is_file;
        bool m_is_file;

    public:
        bool is_file();

    private:
        uint8_t m__unnamed0;
        uint32_t m_file_size;
        uint32_t m_last_mod_time;
        uint16_t m_file_attrs;
        windows_shell_items_t* m__root;
        windows_shell_items_t::shell_item_data_t* m__parent;

    public:
        uint8_t _unnamed0() const { return m__unnamed0; }
        uint32_t file_size() const { return m_file_size; }
        uint32_t last_mod_time() const { return m_last_mod_time; }
        uint16_t file_attrs() const { return m_file_attrs; }
        windows_shell_items_t* _root() const { return m__root; }
        windows_shell_items_t::shell_item_data_t* _parent() const { return m__parent; }
    };

private:
    std::vector<shell_item_t*>* m_items;
    windows_shell_items_t* m__root;
    kaitai::kstruct* m__parent;

public:

    /**
     * \sa https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-SHLLINK/[MS-SHLLINK].pdf Section 2.2.1
     */
    std::vector<shell_item_t*>* items() const { return m_items; }
    windows_shell_items_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

#endif  // WINDOWS_SHELL_ITEMS_H_

windows_shell_items.cpp

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

#include "windows_shell_items.h"

windows_shell_items_t::windows_shell_items_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, windows_shell_items_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_items = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void windows_shell_items_t::_read() {
    m_items = new std::vector<shell_item_t*>();
    {
        int i = 0;
        shell_item_t* _;
        do {
            _ = new shell_item_t(m__io, this, m__root);
            m_items->push_back(_);
            i++;
        } while (!(_->len_data() == 0));
    }
}

windows_shell_items_t::~windows_shell_items_t() {
    _clean_up();
}

void windows_shell_items_t::_clean_up() {
    if (m_items) {
        for (std::vector<shell_item_t*>::iterator it = m_items->begin(); it != m_items->end(); ++it) {
            delete *it;
        }
        delete m_items; m_items = 0;
    }
}

windows_shell_items_t::shell_item_data_t::shell_item_data_t(kaitai::kstream* p__io, windows_shell_items_t::shell_item_t* p__parent, windows_shell_items_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void windows_shell_items_t::shell_item_data_t::_read() {
    m_code = m__io->read_u1();
    n_body1 = true;
    switch (code()) {
    case 31: {
        n_body1 = false;
        m_body1 = new root_folder_body_t(m__io, this, m__root);
        break;
    }
    }
    n_body2 = true;
    switch ((code() & 112)) {
    case 32: {
        n_body2 = false;
        m_body2 = new volume_body_t(m__io, this, m__root);
        break;
    }
    case 48: {
        n_body2 = false;
        m_body2 = new file_entry_body_t(m__io, this, m__root);
        break;
    }
    }
}

windows_shell_items_t::shell_item_data_t::~shell_item_data_t() {
    _clean_up();
}

void windows_shell_items_t::shell_item_data_t::_clean_up() {
    if (!n_body1) {
        if (m_body1) {
            delete m_body1; m_body1 = 0;
        }
    }
    if (!n_body2) {
        if (m_body2) {
            delete m_body2; m_body2 = 0;
        }
    }
}

windows_shell_items_t::shell_item_t::shell_item_t(kaitai::kstream* p__io, windows_shell_items_t* p__parent, windows_shell_items_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_data = 0;
    m__io__raw_data = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void windows_shell_items_t::shell_item_t::_read() {
    m_len_data = m__io->read_u2le();
    n_data = true;
    if (len_data() >= 2) {
        n_data = false;
        m__raw_data = m__io->read_bytes((len_data() - 2));
        m__io__raw_data = new kaitai::kstream(m__raw_data);
        m_data = new shell_item_data_t(m__io__raw_data, this, m__root);
    }
}

windows_shell_items_t::shell_item_t::~shell_item_t() {
    _clean_up();
}

void windows_shell_items_t::shell_item_t::_clean_up() {
    if (!n_data) {
        if (m__io__raw_data) {
            delete m__io__raw_data; m__io__raw_data = 0;
        }
        if (m_data) {
            delete m_data; m_data = 0;
        }
    }
}

windows_shell_items_t::root_folder_body_t::root_folder_body_t(kaitai::kstream* p__io, windows_shell_items_t::shell_item_data_t* p__parent, windows_shell_items_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void windows_shell_items_t::root_folder_body_t::_read() {
    m_sort_index = m__io->read_u1();
    m_shell_folder_id = m__io->read_bytes(16);
}

windows_shell_items_t::root_folder_body_t::~root_folder_body_t() {
    _clean_up();
}

void windows_shell_items_t::root_folder_body_t::_clean_up() {
}

windows_shell_items_t::volume_body_t::volume_body_t(kaitai::kstream* p__io, windows_shell_items_t::shell_item_data_t* p__parent, windows_shell_items_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void windows_shell_items_t::volume_body_t::_read() {
    m_flags = m__io->read_u1();
}

windows_shell_items_t::volume_body_t::~volume_body_t() {
    _clean_up();
}

void windows_shell_items_t::volume_body_t::_clean_up() {
}

windows_shell_items_t::file_entry_body_t::file_entry_body_t(kaitai::kstream* p__io, windows_shell_items_t::shell_item_data_t* p__parent, windows_shell_items_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    f_is_dir = false;
    f_is_file = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void windows_shell_items_t::file_entry_body_t::_read() {
    m__unnamed0 = m__io->read_u1();
    m_file_size = m__io->read_u4le();
    m_last_mod_time = m__io->read_u4le();
    m_file_attrs = m__io->read_u2le();
}

windows_shell_items_t::file_entry_body_t::~file_entry_body_t() {
    _clean_up();
}

void windows_shell_items_t::file_entry_body_t::_clean_up() {
}

bool windows_shell_items_t::file_entry_body_t::is_dir() {
    if (f_is_dir)
        return m_is_dir;
    m_is_dir = (_parent()->code() & 1) != 0;
    f_is_dir = true;
    return m_is_dir;
}

bool windows_shell_items_t::file_entry_body_t::is_file() {
    if (f_is_file)
        return m_is_file;
    m_is_file = (_parent()->code() & 2) != 0;
    f_is_file = true;
    return m_is_file;
}