/* POLE - Portable C++ library to access OLE Storage
   SPDX-FileCopyrightText: 2002-2005 Ariya Hidayat <ariya@kde.org>
   SPDX-FileCopyrightText: 2011-2012 Matus Uzak <matus.uzak@ixonos.com>

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:
   * Redistributions of source code must retain the above copyright notice,
     this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above copyright notice,
     this list of conditions and the following disclaimer in the documentation
     and/or other materials provided with the distribution.
   * Neither the name of the authors nor the names of its contributors may be
     used to endorse or promote products derived from this software without
     specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
   THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "pole.h"

#include <fstream>
#include <ios> // for std::hex
#include <iostream>
#include <list>
#include <string.h>
#include <string>
#include <vector>

#include <QDebug>
#include <QList>
#include <QString>

// Enable to activate debugging output.
// #define POLE_DEBUG

// Disabled because of too many false positives, both streams and unknown
// objects MAY be invalid and still have a size set.
// #define POLE_FAIL_ON_NEMPTY_NVALID_OBJS

// Validate stream object against [MS-CFB] — v20110318.  Disabled because of too
// many false positives.
// #define POLE_FAIL_ON_NVALID_STREAM_OBJS

// Validate storage object against [MS-CFB] — v20110318.  Disabled because of
// too many false positives on Word8 documents.
// #define POLE_CHECK_STORAGE_OBJS

// Validate sibling names against positions in the black red tree.  Disabled
// because of too many false positives on Word8 files with embedded documents.
// #define POLE_CHECK_SIBLINGS

#define OLE_HEADER_SIZE 0x200

namespace POLE
{

class Header
{
public:
    unsigned char id[8]; // signature, or magic identifier
    unsigned b_shift; // bbat->blockSize = 1 << b_shift
    unsigned s_shift; // sbat->blockSize = 1 << s_shift
    unsigned num_bat; // blocks allocated for big bat
    unsigned dirent_start; // starting block for directory info
    unsigned threshold; // switch from small to big file (usually 4K)
    unsigned sbat_start; // starting block index to store small bat
    unsigned num_sbat; // blocks allocated for small bat
    unsigned mbat_start; // starting block to store meta bat
    unsigned num_mbat; // blocks allocated for meta bat
    unsigned long bb_blocks[109];

    Header();
    bool valid(const unsigned max_sbat_block, const unsigned max_bbat_block) const;
    void load(const unsigned char *buffer);
    void save(unsigned char *buffer);
    void debug();
};

class AllocTable
{
public:
    static const unsigned Eof;
    static const unsigned Avail;
    static const unsigned Bat;
    static const unsigned MetaBat;
    unsigned blockSize;
    AllocTable();
    bool valid(const unsigned long filesize, const unsigned shift, const bool isFat = true) const;
    void clear();
    unsigned long count();
    void resize(unsigned long newsize);
    void preserve(unsigned long n);
    void set(unsigned long index, unsigned long val);
    unsigned unused();
    void setChain(std::vector<unsigned long>);
    std::vector<unsigned long> follow(unsigned long start, bool &fail);
    unsigned long operator[](unsigned long index);
    void load(const unsigned char *buffer, unsigned len);
    void save(unsigned char *buffer);
    unsigned size();
    void debug();

private:
    std::vector<unsigned long> data;
    AllocTable(const AllocTable &);
    AllocTable &operator=(const AllocTable &);
};

class DirEntry
{
public:
    bool valid; // false if invalid (should be skipped)
    std::string name; // the name, not in unicode anymore
    bool dir; // true if directory
    unsigned long size; // size (not valid if directory)
    unsigned long start; // starting block
    unsigned prev; // previous sibling
    unsigned next; // next sibling
    unsigned child; // first child
};

class DirTree
{
public:
    static const unsigned End;
    DirTree();
    bool valid(void) const;
    void clear();
    unsigned entryCount();
    DirEntry *entry(unsigned index);
    DirEntry *entry(const std::string &name, bool create = false);
    int indexOf(DirEntry *e);
    int parent(unsigned index);
    std::string fullName(unsigned index);
    std::vector<unsigned> children(unsigned index);
    void load(unsigned char *buffer, unsigned len, const unsigned threshold, const unsigned max_sbat, const unsigned max_bbat);
    void save(unsigned char *buffer);
    unsigned size();
    void debug();

private:
    std::vector<DirEntry> entries;
    DirTree(const DirTree &) = delete;
    DirTree &operator=(const DirTree &) = delete;
};

class StorageIO
{
public:
    Storage *storage; // owner
    std::string filename; // filename
    std::fstream file; // associated with above name
    int result; // result of operation
    bool opened; // true if file is opened
    unsigned long filesize; // size of the file

    Header *header; // storage header
    DirTree *dirtree; // directory tree
    AllocTable *bbat; // allocation table for big blocks
    AllocTable *sbat; // allocation table for small blocks

    std::vector<unsigned long> sb_blocks; // blocks for "small" files

    std::list<Stream *> streams;

    StorageIO(Storage *storage, const char *filename);
    ~StorageIO();

    bool open();
    void close();
    void flush();
    void load();
    void create();

    unsigned long loadBigBlocks(const std::vector<unsigned long> &blocks, unsigned char *buffer, unsigned long maxlen);
    unsigned long loadBigBlocks(const unsigned long *blocks, unsigned blockCount, unsigned char *buffer, unsigned long maxlen);

    unsigned long loadBigBlock(unsigned long block, unsigned char *buffer, unsigned long maxlen);

    unsigned long loadSmallBlocks(const std::vector<unsigned long> &blocks, unsigned char *buffer, unsigned long maxlen);
    unsigned long loadSmallBlocks(const unsigned long *blocks, unsigned blockCount, unsigned char *buffer, unsigned long maxlen);

    unsigned long loadSmallBlock(unsigned long block, unsigned char *buffer, unsigned long maxlen);

    StreamIO *streamIO(const std::string &name);

private:
    // no copy or assign
    StorageIO(const StorageIO &) = delete;
    StorageIO &operator=(const StorageIO &) = delete;
};

class StreamIO
{
public:
    StorageIO *io;
    DirEntry *entry;
    std::string fullName;
    bool eof;
    bool fail;

    StreamIO(StorageIO *io, DirEntry *entry);
    ~StreamIO();
    unsigned long size();
    void seek(unsigned long pos);
    unsigned long tell();
    int getch();
    unsigned long read(unsigned char *data, unsigned long maxlen);

private:
    unsigned long readInternal(unsigned char *data, unsigned long maxlen);
    unsigned long readInternal(unsigned long pos, unsigned char *data, unsigned long maxlen);

    std::vector<unsigned long> blocks;

    // no copy or assign
    StreamIO(const StreamIO &);
    StreamIO &operator=(const StreamIO &);

    // pointer for read
    unsigned long m_pos;

    // simple cache system to speed-up getch()
    unsigned char *cache_data;
    unsigned long base_cache_size;
    unsigned long cache_size;
    unsigned long cache_pos;
    void updateCache();
};

} // namespace POLE

using namespace POLE;

static inline unsigned long readU16(const unsigned char *ptr)
{
    return ptr[0] + (ptr[1] << 8);
}

static inline unsigned long readU32(const unsigned char *ptr)
{
    return unsigned(ptr[0]) + (unsigned(ptr[1]) << 8) + (unsigned(ptr[2]) << 16) + (unsigned(ptr[3]) << 24);
}

static inline void writeU16(unsigned char *ptr, unsigned long data)
{
    ptr[0] = (unsigned char)(data & 0xff);
    ptr[1] = (unsigned char)((data >> 8) & 0xff);
}

static inline void writeU32(unsigned char *ptr, unsigned long data)
{
    ptr[0] = (unsigned char)(data & 0xff);
    ptr[1] = (unsigned char)((data >> 8) & 0xff);
    ptr[2] = (unsigned char)((data >> 16) & 0xff);
    ptr[3] = (unsigned char)((data >> 24) & 0xff);
}

static const unsigned char pole_magic[] = {0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1};

// =========== Header ==========

Header::Header()
{
    b_shift = 9;
    s_shift = 6;
    num_bat = 0;
    dirent_start = 0;
    threshold = 4096;
    sbat_start = 0;
    num_sbat = 0;
    mbat_start = 0;
    num_mbat = 0;

    for (unsigned i = 0; i < 8; i++)
        id[i] = pole_magic[i];
    for (unsigned i = 0; i < 109; i++)
        bb_blocks[i] = AllocTable::Avail;
}

bool Header::valid(const unsigned max_sbat_block, const unsigned max_bbat_block) const
{
    if (threshold != 4096)
        return false;
    if (num_bat == 0)
        return false;
    if ((num_bat > 109) && (num_bat > (num_mbat * 127) + 109))
        return false;
    if ((num_bat < 109) && (num_mbat != 0))
        return false;
    if (s_shift > b_shift)
        return false;
    if (b_shift <= 6)
        return false;
    if (b_shift > 12)
        return false;

    // additional heuristics to check the header
    if (num_sbat > max_sbat_block)
        return false;
    if (num_bat > max_bbat_block)
        return false;

#ifdef POLE_DEBUG
    const unsigned ENDOFCHAIN = 0xfffffffe;
    const unsigned FREESECT = 0xffffffff;

    if (num_sbat == 0 && sbat_start != ENDOFCHAIN && sbat_start != FREESECT) {
        qDebug() << Q_FUNC_INFO << "There aren't any minifat sectors, but there are links to some!";
    }
#endif

    return true;
}

void Header::load(const unsigned char *buffer)
{
    b_shift = readU16(buffer + 0x1e); // sector shift
    s_shift = readU16(buffer + 0x20); // mini sector shift
    num_bat = readU32(buffer + 0x2c); // number of fat sectors
    dirent_start = readU32(buffer + 0x30); // first directory sector location
    threshold = readU32(buffer + 0x38); // transaction signature number
    sbat_start = readU32(buffer + 0x3c); // first mini fat sector location
    num_sbat = readU32(buffer + 0x40); // mini stream cutoff size
    mbat_start = readU32(buffer + 0x44); // first mini difat sector location
    num_mbat = readU32(buffer + 0x48); // number of difat sectors

    for (unsigned i = 0; i < 8; i++)
        id[i] = buffer[i];
    for (unsigned i = 0; i < 109; i++)
        bb_blocks[i] = readU32(buffer + 0x4C + i * 4);
}

void Header::save(unsigned char *buffer)
{
    memset(buffer, 0, 0x4c);
    memcpy(buffer, pole_magic, 8); // ole signature
    writeU32(buffer + 8, 0); // unknown
    writeU32(buffer + 12, 0); // unknown
    writeU32(buffer + 16, 0); // unknown
    writeU16(buffer + 24, 0x003e); // revision ?
    writeU16(buffer + 26, 3); // version ?
    writeU16(buffer + 28, 0xfffe); // unknown
    writeU16(buffer + 0x1e, b_shift);
    writeU16(buffer + 0x20, s_shift);
    writeU32(buffer + 0x2c, num_bat);
    writeU32(buffer + 0x30, dirent_start);
    writeU32(buffer + 0x38, threshold);
    writeU32(buffer + 0x3c, sbat_start);
    writeU32(buffer + 0x40, num_sbat);
    writeU32(buffer + 0x44, mbat_start);
    writeU32(buffer + 0x48, num_mbat);

    for (unsigned i = 0; i < 109; i++)
        writeU32(buffer + 0x4C + i * 4, bb_blocks[i]);
}

void Header::debug()
{
    qDebug() << Q_FUNC_INFO;
    qDebug() << "b_shift:" << b_shift;
    qDebug() << "s_shift:" << s_shift;
    qDebug() << "num_bat:" << num_bat;
    qDebug() << "dirent_start: 0x" << Qt::hex << dirent_start;
    qDebug() << "threshold:" << Qt::dec << threshold;
    qDebug() << "sbat_start: 0x" << Qt::hex << sbat_start;
    qDebug() << "num_sbat:" << Qt::dec << num_sbat;
    qDebug() << "mbat_start: 0x" << Qt::hex << mbat_start;
    qDebug() << "num_mbat:" << Qt::dec << num_mbat;

    unsigned s = (num_bat <= 109) ? num_bat : 109;
    std::cout << "bat blocks:";
    for (unsigned i = 0; i < s; i++) {
        std::cout << "0x" << std::hex << bb_blocks[i] << " ";
    }
    std::cout << std::dec << std::endl;
}

// =========== AllocTable ==========

const unsigned AllocTable::Avail = 0xffffffff;
const unsigned AllocTable::Eof = 0xfffffffe;
const unsigned AllocTable::Bat = 0xfffffffd;
const unsigned AllocTable::MetaBat = 0xfffffffc;

AllocTable::AllocTable()
{
    blockSize = 4096;
    // initial size
    resize(128);
}

bool AllocTable::valid(const unsigned long filesize, const unsigned shift, const bool isFat) const
{
    unsigned long offset = 0;
    for (unsigned long i = 0; i < data.size(); i++) {
        switch (data[i]) {
        case AllocTable::Avail:
        case AllocTable::Eof:
        case AllocTable::Bat:
        case AllocTable::MetaBat:
            break;
        default:
            offset = data[i] << shift;
            if (isFat) {
                offset += OLE_HEADER_SIZE;
            }
            if (offset > filesize) {
#ifdef POLE_DEBUG
                qDebug() << "Invalid location of sector in the stream!"
                         << "offset:" << offset << " | filesize:" << filesize;
#endif
                return false;
            }
        }
    }
    return true;
}

unsigned long AllocTable::count()
{
    return data.size();
}

void AllocTable::resize(unsigned long newsize)
{
    unsigned oldsize = data.size();
    data.resize(newsize);
    if (newsize > oldsize)
        for (unsigned i = oldsize; i < newsize; i++)
            data[i] = Avail;
}

// make sure there're still free blocks
void AllocTable::preserve(unsigned long n)
{
    std::vector<unsigned long> pre;
    for (unsigned i = 0; i < n; i++)
        pre.push_back(unused());
}

unsigned long AllocTable::operator[](unsigned long index)
{
    unsigned long result;
    result = data[index];
    return result;
}

void AllocTable::set(unsigned long index, unsigned long value)
{
    if (index >= count())
        resize(index + 1);
    data[index] = value;
}

void AllocTable::setChain(std::vector<unsigned long> chain)
{
    if (chain.size()) {
        for (unsigned i = 0; i < chain.size() - 1; i++)
            set(chain[i], chain[i + 1]);
        set(chain[chain.size() - 1], AllocTable::Eof);
    }
}

// follow
std::vector<unsigned long> AllocTable::follow(unsigned long start, bool &fail)
{
    std::vector<unsigned long> chain;

    if (start >= count()) {
#ifdef POLE_DEBUG
        qDebug() << Q_FUNC_INFO << "start >= count()!";
#endif
        fail = true;
        return chain;
    }

    unsigned long p = start;
    while (p < count()) {
        if (p == (unsigned long)Eof) {
#ifdef POLE_DEBUG
            qDebug() << Q_FUNC_INFO << "Eof detected!";
#endif
            break;
        }
        if (p == (unsigned long)Bat) {
#ifdef POLE_DEBUG
            qDebug() << Q_FUNC_INFO << "Bat detected!";
#endif
            break;
        }
        if (p == (unsigned long)MetaBat) {
#ifdef POLE_DEBUG
            qDebug() << Q_FUNC_INFO << "MetaBat detected!";
#endif
            break;
        }
        if (p >= count()) {
#ifdef POLE_DEBUG
            qDebug() << Q_FUNC_INFO << "Invalid index detected!";
#endif
            fail = true;
            break;
        }
        chain.push_back(p);

        // break if the chain is longer than the total sector count
        if (chain.size() > count()) {
#ifdef POLE_DEBUG
            qDebug() << Q_FUNC_INFO << "Probably a loop detected!";
#endif
            fail = true;
            break;
        }
        p = data[p];
    }
    if (p != (unsigned long)AllocTable::Eof) {
#ifdef POLE_DEBUG
        qDebug() << Q_FUNC_INFO << "Last chain entry MUST be 0x" << hex << AllocTable::Eof << ", detected: 0x" << hex << p;
#endif
        fail = true;
    }

    return chain;
}

unsigned AllocTable::unused()
{
    // find first available block
    for (unsigned i = 0; i < data.size(); i++)
        if (data[i] == Avail)
            return i;

    // completely full, so enlarge the table
    unsigned block = data.size();
    resize(data.size() + 10);
    return block;
}

void AllocTable::load(const unsigned char *buffer, unsigned len)
{
    resize(len / 4);
    for (unsigned i = 0; i < count(); i++)
        set(i, readU32(buffer + i * 4));
}

// return space required to save this dirtree
unsigned AllocTable::size()
{
    return count() * 4;
}

void AllocTable::save(unsigned char *buffer)
{
    for (unsigned i = 0; i < count(); i++)
        writeU32(buffer + i * 4, data[i]);
}

void AllocTable::debug()
{
    qDebug() << "block size " << data.size();
    for (unsigned i = 0; i < data.size(); i++) {
        if (data[i] == Avail)
            continue;
        std::cout << i << ": ";
        if (data[i] == Eof)
            std::cout << "[eof]";
        else if (data[i] == Bat)
            std::cout << "[bat]";
        else if (data[i] == MetaBat)
            std::cout << "[metabat]";
        else
            std::cout << data[i];
        std::cout << std::endl;
    }
}

// =========== DirTree ==========

const unsigned DirTree::End = 0xffffffff;

/*
 * Compare DirEntry names according to the spec.
 */
int ename_cmp(QString &str1, QString &str2)
{
    str1 = str1.toUpper();
    str2 = str2.toUpper();
    if (str1.size() < str2.size())
        return -1;
    else if (str1.size() > str2.size())
        return 1;
    else
        return str1.compare(str2);
}

/*
 * Check if DirEntry elements at this level have unique names.
 */
bool valid_enames(DirTree *dirtree, unsigned index)
{
    std::vector<unsigned> chi = dirtree->children(index);
    QList<std::string> names;
    DirEntry *e = nullptr;

#ifdef POLE_DEBUG
    if (chi.size()) {
        qDebug() << "---------------------";
        qDebug() << Q_FUNC_INFO;
        qDebug() << "[KIDS]:";
    }
    for (unsigned i = 0; i < chi.size(); i++) {
        e = dirtree->entry(chi[i]);
        if (!e->valid)
            std::cout << "[INVALID] ";
        printf("DirEntry: name=%s prev=%i next=%i child=%i start=%lu size=%lu dir=%i\n",
               e->name.c_str(),
               e->prev,
               e->next,
               e->child,
               e->start,
               e->size,
               e->dir);
    }
    if (chi.size()) {
        qDebug() << "---------------------";
    }
#endif

    for (unsigned i = 0; i < chi.size(); i++) {
        e = dirtree->entry(chi[i]);
        if (e->valid) {
            if (names.contains(e->name)) {
                return false;
            } else {
                names.append(e->name);
            }
        }
    }
    return true;
}

bool DirTree::valid() const
{
    const DirEntry *e;
#ifdef POLE_CHECK_SIBLINGS
    QString str1, str2;
#endif

#ifdef POLE_DEBUG
    qDebug() << Q_FUNC_INFO;
#endif
    for (unsigned i = 0; i < entries.size(); i++) {
        e = &entries[i];

#ifdef POLE_DEBUG
        if (!e->valid)
            std::cout << "[INVALID] ";
        printf("DirEntry: name=%s prev=%i next=%i child=%i start=%lu size=%lu dir=%i\n",
               e->name.c_str(),
               e->prev,
               e->next,
               e->child,
               e->start,
               e->size,
               e->dir);
#endif
#ifdef POLE_FAIL_ON_NEMPTY_NVALID_OBJS
        if (!e->valid && e->size) {
#ifdef POLE_DEBUG
            qDebug() << "Invalid DirEntry detected!";
#endif
            return false;
        }
#endif
        // Looking for invalid stream objects.
#ifdef POLE_FAIL_ON_NVALID_STREAM_OBJS
        if (!e->valid && !e->dir) {
#ifdef POLE_DEBUG
            qDebug() << "Invalid DirEntry (stream object) detected!";
#endif
            return false;
        }
#endif
        // Looking for invalid storage objects.
        if (!e->valid && e->dir) {
#ifdef POLE_DEBUG
            qDebug() << "Invalid DirEntry (storage object) detected!";
#endif
            return false;
        }

        // A root storage: size = size of the mini stream, start = first sector
        // of the mini stream, if the mini stream exists
        //
        // A storage object MAY have e->child set - [MS-CFB].
#ifdef POLE_CHECK_STORAGE_OBJS
        if ((e->valid && e->dir) && (i > 0) && ((e->start != 0) || (e->size != 0))) {
#ifdef POLE_DEBUG
            qDebug() << "Invalid DirEntry (storage object) detected!";
#endif
            return false;
        }
#endif
        // Looking for duplicate DirEntries in the storage object.
        if (e->valid && e->dir) {
            if (!valid_enames(const_cast<DirTree *>(this), i)) {
#ifdef POLE_DEBUG
                qDebug() << "Invalid DirEntry (storage object) detected!";
#endif
                return false;
            }
        }

        // Check the name of the left/right DirEntry.
#ifdef POLE_CHECK_SIBLINGS
        if (e->prev != End) {
            str1 = QString(entries[e->prev].name.data());
        }
        if (e->next != End) {
            str2 = QString(entries[e->next].name.data());
        }
        if (!str1.isEmpty() && !str2.isEmpty()) {
            if (ename_cmp(str1, str2) > 0) {
#ifdef POLE_DEBUG
                qDebug() << "DirEntry: [name, position] mismatch!";
#endif
                return false;
            }
        }
#endif
    }
    return true;
}

DirTree::DirTree()
{
    clear();
}

void DirTree::clear()
{
    // leave only root entry
    entries.resize(1);
    entries[0].valid = true;
    entries[0].name = "Root Entry";
    entries[0].dir = true;
    entries[0].size = 0;
    entries[0].start = End;
    entries[0].prev = End;
    entries[0].next = End;
    entries[0].child = End;
}

unsigned DirTree::entryCount()
{
    return entries.size();
}

DirEntry *DirTree::entry(unsigned index)
{
    if (index >= entryCount())
        return (DirEntry *)nullptr;
    return &entries[index];
}

int DirTree::indexOf(DirEntry *e)
{
    for (unsigned i = 0; i < entryCount(); i++)
        if (entry(i) == e)
            return i;

    return -1;
}

int DirTree::parent(unsigned index)
{
    // brute-force, basically we iterate for each entries, find its children
    // and check if one of the children is 'index'
    for (unsigned j = 0; j < entryCount(); j++) {
        std::vector<unsigned> chi = children(j);
        for (unsigned i = 0; i < chi.size(); i++)
            if (chi[i] == index)
                return j;
    }

    return -1;
}

std::string DirTree::fullName(unsigned index)
{
    // don't use root name ("Root Entry"), just give "/"
    if (index == 0)
        return "/";

    std::string result = entry(index)->name;
    result.insert(0, "/");
    int p = parent(index);
    DirEntry *_entry = nullptr;
    while (p > 0) {
        _entry = entry(p);
        if (_entry->dir && _entry->valid) {
            result.insert(0, _entry->name);
            result.insert(0, "/");
        }
        --p;
        index = p;
        if (index <= 0)
            break;
    }
    return result;
}

// given a fullname (e.g "/ObjectPool/_1020961869"), find the entry
// if not found and create is false, return 0
// if create is true, a new entry is returned
DirEntry *DirTree::entry(const std::string &name, bool create)
{
    if (!name.length())
        return (DirEntry *)nullptr;

    // quick check for "/" (that's root)
    if (name == "/")
        return entry(0);

    // split the names, e.g  "/ObjectPool/_1020961869" will become:
    // "ObjectPool" and "_1020961869"
    std::list<std::string> names;
    std::string::size_type start = 0, end = 0;
    if (name[0] == '/')
        start++;
    while (start < name.length()) {
        end = name.find_first_of('/', start);
        if (end == std::string::npos)
            end = name.length();
        names.push_back(name.substr(start, end - start));
        start = end + 1;
    }

    // start from root
    int index = 0;

    // trace one by one
    std::list<std::string>::iterator it;

    for (it = names.begin(); it != names.end(); ++it) {
        // find among the children of index
        std::vector<unsigned> chi = children(index);
        unsigned child = 0;
        for (unsigned i = 0; i < chi.size(); i++) {
            DirEntry *ce = entry(chi[i]);
            if (ce)
                if (ce->valid && (ce->name.length() > 1))
                    if (ce->name == *it)
                        child = chi[i];
        }

        // traverse to the child
        if (child > 0)
            index = child;
        else {
            // not found among children
            if (!create)
                return (DirEntry *)nullptr;

            // create a new entry
            unsigned parent = index;
            entries.push_back(DirEntry());
            index = entryCount() - 1;
            DirEntry *e = entry(index);
            e->valid = true;
            e->name = *it;
            e->dir = false;
            e->size = 0;
            e->start = 0;
            e->child = End;
            e->prev = End;
            e->next = entry(parent)->child;
            entry(parent)->child = index;
        }
    }

    return entry(index);
}

// helper function: recursively find siblings of index
void dirtree_find_siblings(DirTree *dirtree, std::vector<unsigned> &result, unsigned index)
{
    DirEntry *e = dirtree->entry(index);
    if (!e)
        return;
    //     if (!e->valid) return;

    // prevent infinite loop
    for (unsigned i = 0; i < result.size(); i++) {
        if (result[i] == index)
            return;
    }
    // add myself
    result.push_back(index);

    // visit previous sibling, don't go infinitely
    unsigned prev = e->prev;
    if ((prev > 0) && (prev < dirtree->entryCount())) {
        for (unsigned i = 0; i < result.size(); i++)
            if (result[i] == prev)
                prev = 0;
        if (prev)
            dirtree_find_siblings(dirtree, result, prev);
    }

    // visit next sibling, don't go infinitely
    unsigned next = e->next;
    if ((next > 0) && (next < dirtree->entryCount())) {
        for (unsigned i = 0; i < result.size(); i++)
            if (result[i] == next)
                next = 0;
        if (next)
            dirtree_find_siblings(dirtree, result, next);
    }
}

std::vector<unsigned> DirTree::children(unsigned index)
{
    std::vector<unsigned> result;

    DirEntry *e = entry(index);
    if (e) {
        if (e->valid && e->dir) {
            dirtree_find_siblings(this, result, e->child);
        }
    }
    return result;
}

void DirTree::load(unsigned char *buffer, unsigned size, const unsigned threshold, const unsigned max_sbat, const unsigned max_bbat)
{
#ifdef POLE_DEBUG
    qDebug() << "-------------------------------";
    qDebug() << Q_FUNC_INFO;
#endif

    entries.clear();
    unsigned n = (size / 128); // num. of directory entries

    for (unsigned i = 0; i < (size / 128); i++) {
        unsigned p = i * 128;

        // parse name of this entry, which stored as Unicode 16-bit
        int name_len = readU16(buffer + 0x40 + p);
        if (name_len > 64) {
            name_len = 64;
#ifdef POLE_DEBUG
            qDebug() << "DirEntry: Invalid length of name!";
#endif
        }
        std::string name;
        for (int j = 0; (buffer[j + p]) && (j < name_len); j += 2) {
            name.append(1, buffer[j + p]);
        }

        // first char isn't printable ? remove it...
        if (buffer[p] < 32) {
            name.erase(0, 1);
        }

        // [MS-CFB] — v20110318
        // 0x00 = Unknown or unallocated, 0x01 = directory (Storage Object),
        // 0x02 = file (Stream Object), 0x05 = Root Storage Object
        unsigned type = buffer[0x42 + p];

        DirEntry e;
        e.valid = true;
        e.name = name;
        e.start = readU32(buffer + 0x74 + p);
        e.size = readU32(buffer + 0x78 + p);
        e.prev = readU32(buffer + 0x44 + p);
        e.next = readU32(buffer + 0x48 + p);
        e.child = readU32(buffer + 0x4C + p);
        e.dir = false;

        if ((type == 1) || (type == 5)) {
            e.dir = true;
        }

        // sanity checks
        if ((type != 0) && (type != 1) && (type != 2) && (type != 5)) {
            e.valid = false;
#ifdef POLE_DEBUG
            qDebug() << "DirEntry: invalid type!";
#endif
        }
        if ((type != 0) && (name_len < 1)) {
            e.valid = false;
#ifdef POLE_DEBUG
            qDebug() << "DirEntry: invalid (type,name) pair!";
#endif
        }
        // unknown object
        if (type == 0) {
            if ((e.child != End) || (e.prev != End) || (e.next != End)) {
                e.valid = false;
#ifdef POLE_DEBUG
                qDebug() << "DirEntry: reference to prev/next/child != NOSTREAM";
#endif
            }
            if ((e.start != 0) || (e.size != 0)) {
                e.valid = false;
#ifdef POLE_DEBUG
                qDebug() << "DirEntry: start/size != ZERO";
#endif
            }
        }
        // storage objects
        if (type == 1) {
            if (((e.prev != End) && (e.prev >= n)) || ((e.next != End) && (e.next >= n)) || ((e.child != End) && (e.child >= n))) {
                e.valid = false;
#ifdef POLE_DEBUG
                qDebug() << "DirEntry: reference to prev/next/child > object num. (" << n << ")";
#endif
            }
        }
        // stream object
        if (type == 2) {
            // check stream position
            if ((e.size >= threshold) && (e.start >= max_bbat)) {
                e.valid = false;
#ifdef POLE_DEBUG
                qDebug() << "DirEntry: (e.start >= max_bbat)";
#endif
            } else if (e.start >= max_sbat) {
                e.valid = false;
#ifdef POLE_DEBUG
                qDebug() << "DirEntry: (e.start >= max_sbat)";
#endif
            }
            // check stream object
            if (e.child != End) {
                e.valid = false;
#ifdef POLE_DEBUG
                qDebug() << "DirEntry: (e.child != End)";
#endif
            }
            // NOTE: Disabled because of too many false positives.
            //             if ((e->prev != End) || (e->next != End)) {
            //                 e.valid = false;
            //             }
        }

        // CLSID contains an object class GUID (globally unique identifier) if
        // this entry is a storage or root storage.  In a stream object, this
        // field MUST be set to all zeroes.
#ifdef POLE_DEBUG
        if (!e.valid) {
            std::cout << "[INVALID] ";
        }
        printf("DirEntry: name=%s type=%i prev=%i next=%i child=%i start=%lu size=%lu clsid=%lu.%lu.%lu.%lu\n",
               name.c_str(),
               type,
               e.prev,
               e.next,
               e.child,
               e.start,
               e.size,
               readU32(buffer + 0x50 + p),
               readU32(buffer + 0x54 + p),
               readU32(buffer + 0x58 + p),
               readU32(buffer + 0x5C + p));
#endif
        entries.push_back(e);
    }
#ifdef POLE_DEBUG
    qDebug() << "-------------------------------";
#endif
}

// return space required to save this dirtree
unsigned DirTree::size()
{
    return entryCount() * 128;
}

void DirTree::save(unsigned char *buffer)
{
    memset(buffer, 0, size());

    // root is fixed as "Root Entry"
    DirEntry *root = entry(0);
    std::string name = "Root Entry";
    for (unsigned j = 0; j < name.length(); j++)
        buffer[j * 2] = name[j];
    writeU16(buffer + 0x40, name.length() * 2 + 2);
    writeU32(buffer + 0x74, 0xffffffff);
    writeU32(buffer + 0x78, 0);
    writeU32(buffer + 0x44, 0xffffffff);
    writeU32(buffer + 0x48, 0xffffffff);
    writeU32(buffer + 0x4c, root->child);
    buffer[0x42] = 5;
    buffer[0x43] = 1;

    for (unsigned i = 1; i < entryCount(); i++) {
        DirEntry *e = entry(i);
        if (!e)
            continue;
        if (e->dir) {
            e->start = 0xffffffff;
            e->size = 0;
        }

        // max length for name is 32 chars
        std::string name = e->name;
        if (name.length() > 32)
            name.erase(32, name.length());

        // write name as Unicode 16-bit
        for (unsigned j = 0; j < name.length(); j++)
            buffer[i * 128 + j * 2] = name[j];

        writeU16(buffer + i * 128 + 0x40, name.length() * 2 + 2);
        writeU32(buffer + i * 128 + 0x74, e->start);
        writeU32(buffer + i * 128 + 0x78, e->size);
        writeU32(buffer + i * 128 + 0x44, e->prev);
        writeU32(buffer + i * 128 + 0x48, e->next);
        writeU32(buffer + i * 128 + 0x4c, e->child);
        buffer[i * 128 + 0x42] = e->dir ? 1 : 2;
        buffer[i * 128 + 0x43] = 1; // always black
    }
}

void DirTree::debug()
{
    for (unsigned i = 0; i < entryCount(); i++) {
        DirEntry *e = entry(i);
        if (!e)
            continue;
        std::cout << i << ": ";
        if (!e->valid)
            std::cout << "INVALID ";
        std::cout << e->name << " ";
        if (e->dir)
            std::cout << "(Dir) ";
        else
            std::cout << "(File) ";
        std::cout << e->size << " ";
        std::cout << "s:" << e->start << " ";
        std::cout << "(";
        if (e->child == End)
            std::cout << "-";
        else
            std::cout << e->child;
        std::cout << " ";
        if (e->prev == End)
            std::cout << "-";
        else
            std::cout << e->prev;
        std::cout << ":";
        if (e->next == End)
            std::cout << "-";
        else
            std::cout << e->next;
        std::cout << ")";
        std::cout << std::endl;
    }
}

// =========== StorageIO ==========

StorageIO::StorageIO(Storage *st, const char *fname)
{
    storage = st;
    filename = fname;
    result = Storage::Ok;
    opened = false;

    header = new Header();
    dirtree = new DirTree();
    bbat = new AllocTable();
    sbat = new AllocTable();

    filesize = 0;
    bbat->blockSize = 1 << header->b_shift;
    sbat->blockSize = 1 << header->s_shift;
}

StorageIO::~StorageIO()
{
    if (opened)
        close();
    delete sbat;
    delete bbat;
    delete dirtree;
    delete header;
}

bool StorageIO::open()
{
    // already opened ? close first
    if (opened)
        close();

    load();

    return result == Storage::Ok;
}

void StorageIO::load()
{
    unsigned char *buffer = nullptr;
    unsigned long buflen = 0;
    std::vector<unsigned long> blocks;

    // open the file, check for error
    result = Storage::OpenFailed;
    file.open(filename.c_str(), std::ios::binary | std::ios::in);
    if (!file.good())
        return;

    // find size of input file
    file.seekg(0, std::ios::end);
    filesize = file.tellg();

    // load header
    buffer = new unsigned char[OLE_HEADER_SIZE];
    file.seekg(0);
    file.read((char *)buffer, OLE_HEADER_SIZE);
    if (!file.good()) {
        delete[] buffer;
        return;
    }
    header->load(buffer);
    delete[] buffer;

    // check OLE magic id
    result = Storage::NotOLE;
    for (unsigned i = 0; i < 8; i++)
        if (header->id[i] != pole_magic[i])
            return;

    // important block size
    bbat->blockSize = 1 << header->b_shift;
    sbat->blockSize = 1 << header->s_shift;
    const unsigned max_bbat_block = (filesize - OLE_HEADER_SIZE) / bbat->blockSize;
    const unsigned max_sbat_block = (filesize - OLE_HEADER_SIZE) / sbat->blockSize;

    // sanity checks
    result = Storage::BadOLE;
    if (!header->valid(max_sbat_block, max_bbat_block)) {
        return;
    }

    // find blocks allocated to store big bat
    // the first 109 blocks are in header, the rest in meta bat
    blocks.clear();
    blocks.resize(header->num_bat);
    for (unsigned i = 0; i < 109; i++) {
        if (i >= header->num_bat)
            break;
        else
            blocks[i] = header->bb_blocks[i];
    }
    if ((header->num_bat > 109) && (header->num_mbat > 0)) {
        unsigned char *buffer2 = new unsigned char[bbat->blockSize];
        unsigned k = 109;
        unsigned mblock = header->mbat_start;
        for (unsigned r = 0; r < header->num_mbat; r++) {
            unsigned long rr = loadBigBlock(mblock, buffer2, bbat->blockSize);
            if (rr != bbat->blockSize) {
                delete[] buffer2;
                return;
            }
            for (unsigned s = 0; s < bbat->blockSize - 4; s += 4) {
                if (k >= header->num_bat)
                    break;
                else
                    blocks[k++] = readU32(buffer2 + s);
            }
            mblock = readU32(buffer2 + bbat->blockSize - 4);
        }
        delete[] buffer2;
    }

    // load big bat
    buflen = blocks.size() * bbat->blockSize;
    if (buflen > 0) {
        buffer = new unsigned char[buflen];
        unsigned long r = loadBigBlocks(blocks, buffer, buflen);
        if (r != buflen) {
            qCritical() << Q_FUNC_INFO << "SAT construction failed!";
            delete[] buffer;
            return;
        }
        bbat->load(buffer, buflen);
        delete[] buffer;

        if (!bbat->valid(filesize, header->b_shift, true)) {
            return;
        }
    }
    // TODO: make fail affect the result value
    bool fail = false;

    // load small bat
    blocks.clear();
    blocks = bbat->follow(header->sbat_start, fail);
    buflen = blocks.size() * bbat->blockSize;
    if (buflen > 0) {
        buffer = new unsigned char[buflen];
        unsigned long r = loadBigBlocks(blocks, buffer, buflen);
        if (r != buflen) {
            qCritical() << Q_FUNC_INFO << "SSAT construction failed!";
            delete[] buffer;
            return;
        }
        sbat->load(buffer, buflen);
        delete[] buffer;

        if (!sbat->valid(filesize, header->s_shift, false)) {
            return;
        }
    }

    // load directory tree
    blocks.clear();
    blocks = bbat->follow(header->dirent_start, fail);
    buflen = blocks.size() * bbat->blockSize;
    buffer = new unsigned char[buflen];
    unsigned long r = loadBigBlocks(blocks, buffer, buflen);
    if (r != buflen) {
        qCritical() << Q_FUNC_INFO << "DirTree construction failed!";
        delete[] buffer;
        return;
    }
    dirtree->load(buffer, buflen, header->threshold, max_sbat_block, max_bbat_block);
    unsigned sb_start = readU32(buffer + 0x74);
    delete[] buffer;
    if (!dirtree->valid()) {
        qCritical() << Q_FUNC_INFO << "Invalid DirTree!";
        return;
    }

    // fetch block chain as data for small-files
    sb_blocks = bbat->follow(sb_start, fail);

    // for troubleshooting, just enable this block
#ifdef POLE_DEBUG
    header->debug();
    sbat->debug();
    bbat->debug();
    dirtree->debug();
#endif

    // so far so good
    result = Storage::Ok;
    opened = true;
}

void StorageIO::create()
{
    // std::cout << "Creating " << filename << std::endl;

    file.open(filename.c_str(), std::ios::out | std::ios::binary);
    if (!file.good()) {
        qCritical() << Q_FUNC_INFO << "Can't create file:" << filename.c_str();
        result = Storage::OpenFailed;
        return;
    }

    // so far so good
    opened = true;
    result = Storage::Ok;
}

void StorageIO::flush()
{
    /* Note on Microsoft implementation:
       - directory entries are stored in the last block(s)
       - BATs are as second to the last
       - Meta BATs are third to the last
    */
}

void StorageIO::close()
{
    if (!opened)
        return;

    file.close();
    opened = false;

    std::list<Stream *>::iterator it;
    for (it = streams.begin(); it != streams.end(); ++it)
        delete *it;
}

StreamIO *StorageIO::streamIO(const std::string &name)
{
#ifdef POLE_DEBUG
    qDebug() << Q_FUNC_INFO << "preparing stream:" << name.c_str();
#endif
    // sanity check
    if (!name.length())
        return (StreamIO *)nullptr;

    // search in the entries
    DirEntry *entry = dirtree->entry(name);
    // if( entry) std::cout << "FOUND\n";
    if (!entry)
        return (StreamIO *)nullptr;
    // if( !entry->dir ) std::cout << "  NOT DIR\n";
    if (entry->dir)
        return (StreamIO *)nullptr;

    StreamIO *result = new StreamIO(this, entry);
    result->fullName = name;

    return result;
}

unsigned long StorageIO::loadBigBlocks(const std::vector<unsigned long> &blocks, unsigned char *data, unsigned long maxlen)
{
    return loadBigBlocks(&blocks[0], blocks.size(), data, maxlen);
}

unsigned long StorageIO::loadBigBlocks(const unsigned long *blocks, unsigned blockCount, unsigned char *data, unsigned long maxlen)
{
    // sentinel
    if (!data)
        return 0;
    if (!file.good())
        return 0;
    if (!blocks)
        return 0;
    if (blockCount < 1)
        return 0;
    if (maxlen == 0)
        return 0;

    // read block one by one, seems fast enough
    unsigned long bytes = 0;
    for (unsigned long i = 0; (i < blockCount) && (bytes < maxlen); i++) {
        unsigned long block = blocks[i];
        unsigned long pos = bbat->blockSize * (block + 1);
        unsigned long p = (bbat->blockSize < maxlen - bytes) ? bbat->blockSize : maxlen - bytes;
        if (pos + p > filesize)
            p = filesize - pos;
        file.seekg(pos);
        file.read((char *)data + bytes, p);
        if (!file.good())
            return 0;
        bytes += p;
    }

    return bytes;
}

unsigned long StorageIO::loadBigBlock(unsigned long block, unsigned char *data, unsigned long maxlen)
{
    // sentinel
    if (!data)
        return 0;
    if (!file.good())
        return 0;

    return loadBigBlocks(&block, 1, data, maxlen);
}

// return number of bytes which has been read
unsigned long StorageIO::loadSmallBlocks(const std::vector<unsigned long> &blocks, unsigned char *data, unsigned long maxlen)
{
    return loadSmallBlocks(&blocks[0], blocks.size(), data, maxlen);
}

unsigned long StorageIO::loadSmallBlocks(const unsigned long *blocks, unsigned blockCount, unsigned char *data, unsigned long maxlen)
{
    // sentinel
    if (!data)
        return 0;
    if (!file.good())
        return 0;
    if (!blocks)
        return 0;
    if (blockCount < 1)
        return 0;
    if (maxlen == 0)
        return 0;

    // our own local buffer
    unsigned char *buf = new unsigned char[bbat->blockSize];

    // read small block one by one
    unsigned long bytes = 0;
    for (unsigned long i = 0; (i < blockCount) && (bytes < maxlen); i++) {
        unsigned long block = blocks[i];

        // find where the small-block exactly is
        unsigned long pos = block * sbat->blockSize;
        unsigned long bbindex = pos / bbat->blockSize;
        if (bbindex >= sb_blocks.size())
            break;

        unsigned long r = loadBigBlock(sb_blocks[bbindex], buf, bbat->blockSize);
        if (r != bbat->blockSize) {
            delete[] buf;
            return 0;
        }

        // copy the data
        unsigned offset = pos % bbat->blockSize;
        unsigned long p = (maxlen - bytes < bbat->blockSize - offset) ? maxlen - bytes : bbat->blockSize - offset;
        p = (sbat->blockSize < p) ? sbat->blockSize : p;
        memcpy(data + bytes, buf + offset, p);
        bytes += p;
    }

    delete[] buf;

    return bytes;
}

unsigned long StorageIO::loadSmallBlock(unsigned long block, unsigned char *data, unsigned long maxlen)
{
    // sentinel
    if (!data)
        return 0;
    if (!file.good())
        return 0;

    return loadSmallBlocks(&block, 1, data, maxlen);
}

// =========== StreamIO ==========

StreamIO::StreamIO(StorageIO *s, DirEntry *e)
{
    io = s;
    entry = e;
    eof = false;
    fail = false;

    m_pos = 0;

    if (entry->size >= io->header->threshold) {
        blocks = io->bbat->follow(entry->start, fail);
    } else {
        blocks = io->sbat->follow(entry->start, fail);
    }

    // prepare cache
    cache_pos = 0;
    base_cache_size = cache_size = 4096; // optimal ?
    cache_data = new unsigned char[base_cache_size];
    updateCache();
}

// FIXME tell parent we're gone
StreamIO::~StreamIO()
{
    delete[] cache_data;
}

void StreamIO::seek(unsigned long pos)
{
    m_pos = pos;
}

unsigned long StreamIO::tell()
{
    return m_pos;
}

int StreamIO::getch()
{
    // past end-of-file ?
    if (m_pos > entry->size)
        return -1;

    // need to update cache ?
    if (!cache_size || (m_pos < cache_pos) || (m_pos >= cache_pos + cache_size))
        updateCache();

    // something bad if we don't get good cache
    if (!cache_size)
        return -1;

    int data = cache_data[m_pos - cache_pos];
    m_pos++;

    return data;
}

unsigned long StreamIO::read(unsigned char *data, unsigned long maxlen)
{
    // sanity checks
    if (!data)
        return 0;
    if (maxlen == 0)
        return 0;

    unsigned long totalbytes = 0;

    while (totalbytes < maxlen) {
        // need to update cache ?
        if (!cache_size || (m_pos < cache_pos) || (m_pos >= cache_pos + cache_size))
            updateCache();
        if (!cache_size)
            break;

        const unsigned long remaining = cache_size - (m_pos - cache_pos);
        const unsigned long count = std::min(remaining, maxlen - totalbytes);
        memcpy(data + totalbytes, &cache_data[m_pos - cache_pos], count);
        totalbytes += count;
        m_pos += count;
    }
    return totalbytes;
}

unsigned long StreamIO::readInternal(unsigned long pos, unsigned char *data, unsigned long maxlen)
{
    // sanity checks
    if (!data)
        return 0;
    if (maxlen == 0)
        return 0;

    unsigned long totalbytes = 0;

    if (entry->size < io->header->threshold) {
        // small file
        unsigned long index = pos / io->sbat->blockSize;

        if (index >= blocks.size())
            return 0;

        unsigned char buf[4096];
        unsigned long offset = pos % io->sbat->blockSize;
        while (totalbytes < maxlen) {
            if (index >= blocks.size())
                break;
            io->loadSmallBlock(blocks[index], &buf[0], io->bbat->blockSize);
            unsigned long count = io->sbat->blockSize - offset;
            if (count > maxlen - totalbytes)
                count = maxlen - totalbytes;
            memcpy(data + totalbytes, &buf[0] + offset, count);
            totalbytes += count;
            offset = 0;
            index++;
        }

    } else {
        // big file
        unsigned long index = pos / io->bbat->blockSize;

        if (index >= blocks.size())
            return 0;

        unsigned char buf[4096];
        unsigned long offset = pos % io->bbat->blockSize;
        while (totalbytes < maxlen) {
            if (index >= blocks.size())
                break;
            unsigned long r = io->loadBigBlock(blocks[index], &buf[0], io->bbat->blockSize);
            if (r != io->bbat->blockSize) {
                return 0;
            }
            unsigned long count = io->bbat->blockSize - offset;
            if (count > maxlen - totalbytes)
                count = maxlen - totalbytes;
            memcpy(data + totalbytes, &buf[0] + offset, count);
            totalbytes += count;
            index++;
            offset = 0;
        }
    }

    return totalbytes;
}

unsigned long StreamIO::readInternal(unsigned char *data, unsigned long maxlen)
{
    unsigned long bytes = readInternal(tell(), data, maxlen);
    m_pos += bytes;
    return bytes;
}

void StreamIO::updateCache()
{
    // sanity check
    if (!cache_data)
        return;

    cache_pos = m_pos - (m_pos % base_cache_size);
    unsigned long bytes = base_cache_size;
    if (cache_pos + bytes > entry->size)
        bytes = entry->size - cache_pos;
    if (cache_pos + bytes <= m_pos) {
        cache_size = 0;
    } else {
        cache_size = readInternal(cache_pos, cache_data, bytes);
    }
}

// =========== Storage ==========

Storage::Storage(const char *filename)
{
    io = new StorageIO(this, filename);
}

Storage::~Storage()
{
    delete io;
}

int Storage::result()
{
    return io->result;
}

bool Storage::open()
{
    return io->open();
}

void Storage::close()
{
    io->close();
}

std::list<std::string> Storage::entries(const std::string &path)
{
    std::list<std::string> result;
    DirTree *dt = io->dirtree;
    DirEntry *e = dt->entry(path, false);
    if (e) {
        if (e->dir) {
            unsigned parent = dt->indexOf(e);
            std::vector<unsigned> children = dt->children(parent);
            for (unsigned i = 0; i < children.size(); i++)
                result.push_back(dt->entry(children[i])->name);
        }
    }
    return result;
}

bool Storage::isDirectory(const std::string &name)
{
    DirEntry *e = io->dirtree->entry(name, false);
    return e ? e->dir : false;
}

// =========== Stream ==========

Stream::Stream(Storage *storage, const std::string &name)
{
    io = storage->io->streamIO(name);
}

// FIXME tell parent we're gone
Stream::~Stream()
{
    delete io;
}

std::string Stream::fullName()
{
    return io ? io->fullName : std::string();
}

unsigned long Stream::tell()
{
    return io ? io->tell() : 0;
}

void Stream::seek(unsigned long newpos)
{
    if (io)
        io->seek(newpos);
}

unsigned long Stream::size()
{
    return io ? io->entry->size : 0;
}

int Stream::getch()
{
    return io ? io->getch() : 0;
}

unsigned long Stream::read(unsigned char *data, unsigned long maxlen)
{
    return io ? io->read(data, maxlen) : 0;
}

bool Stream::eof()
{
    return io ? io->eof : false;
}

bool Stream::fail()
{
    return io ? io->fail : true;
}
