/////////////////////////////////////////////////////////////////////////////
// Name:        zipstrm.cpp
// Purpose:     Streams for Zip files
// Author:      Mike Wetherell
// RCS-ID:      $Id: zipstrm.cpp,v 1.31.2.4 2006/01/18 16:32:47 JS Exp $
// Copyright:   (c) Mike Wetherell
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
  #pragma implementation "zipstrm.h"
#endif

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
  #pragma hdrstop
#endif

#ifndef WX_PRECOMP
  #include "wx/defs.h"
#endif

#if wxUSE_ZLIB && wxUSE_STREAMS && wxUSE_ZIPSTREAM

#include "wx/zipstrm.h"
#include "wx/log.h"
#include "wx/intl.h"
#include "wx/datstrm.h"
#include "wx/zstream.h"
#include "wx/mstream.h"
#include "wx/utils.h"
#include "wx/buffer.h"
#include "wx/ptr_scpd.h"
#include "wx/wfstream.h"
#include "zlib.h"

// value for the 'version needed to extract' field (20 means 2.0)
enum {
    VERSION_NEEDED_TO_EXTRACT = 20
};

// signatures for the various records (PKxx)
enum {
    CENTRAL_MAGIC = 0x02014b50,     // central directory record
    LOCAL_MAGIC   = 0x04034b50,     // local header
    END_MAGIC     = 0x06054b50,     // end of central directory record
    SUMS_MAGIC    = 0x08074b50      // data descriptor (info-zip)
};

// unix file attributes. zip stores them in the high 16 bits of the
// 'external attributes' field, hence the extra zeros.
enum {
    wxZIP_S_IFMT  = 0xF0000000,
    wxZIP_S_IFDIR = 0x40000000,
    wxZIP_S_IFREG = 0x80000000
};

// minimum sizes for the various records
enum {
    CENTRAL_SIZE  = 46,
    LOCAL_SIZE    = 30,
    END_SIZE      = 22,
    SUMS_SIZE     = 12
};

// The number of bytes that must be written to an wxZipOutputStream before
// a zip entry is created. The purpose of this latency is so that
// OpenCompressor() can see a little data before deciding which compressor
// it should use.
enum {
    OUTPUT_LATENCY = 4096
};

// Some offsets into the local header
enum {
    SUMS_OFFSET  = 14
};

IMPLEMENT_DYNAMIC_CLASS(wxZipEntry, wxArchiveEntry)
IMPLEMENT_DYNAMIC_CLASS(wxZipClassFactory, wxArchiveClassFactory)

//FORCE_LINK_ME(zipstrm)
int _wx_link_dummy_func_zipstrm();
int _wx_link_dummy_func_zipstrm()
{
    return 1;
}


/////////////////////////////////////////////////////////////////////////////
// Helpers

// read a string of a given length
//
static wxString ReadString(wxInputStream& stream, wxUint16 len, wxMBConv& conv)
{
#if wxUSE_UNICODE
    wxCharBuffer buf(len);
    stream.Read(buf.data(), len);
    wxString str(buf, conv);
#else
    wxString str;
    (void)conv;
    {
        wxStringBuffer buf(str, len);
        stream.Read(buf, len);
    }
#endif

    return str;
}

// Decode a little endian wxUint32 number from a character array
//
static inline wxUint32 CrackUint32(const char *m)
{
    const unsigned char *n = (const unsigned char*)m;
    return (n[3] << 24) | (n[2] << 16) | (n[1] << 8) | n[0];
}

// Temporarily lower the logging level in debug mode to avoid a warning
// from SeekI about seeking on a stream with data written back to it.
//
static wxFileOffset QuietSeek(wxInputStream& stream, wxFileOffset pos)
{
#ifdef __WXDEBUG__
    wxLogLevel level = wxLog::GetLogLevel();
    wxLog::SetLogLevel(wxLOG_Debug - 1);
    wxFileOffset result = stream.SeekI(pos);
    wxLog::SetLogLevel(level);
    return result;
#else
    return stream.SeekI(pos);
#endif
}


/////////////////////////////////////////////////////////////////////////////
// Stored input stream
// Trival decompressor for files which are 'stored' in the zip file.

class wxStoredInputStream : public wxFilterInputStream
{
public:
    wxStoredInputStream(wxInputStream& stream);

    void Open(wxFileOffset len) { Close(); m_len = len; }
    void Close() { m_pos = 0; m_lasterror = wxSTREAM_NO_ERROR; }

    virtual char Peek() { return wxInputStream::Peek(); }
    virtual wxFileOffset GetLength() const { return m_len; }

protected:
    virtual size_t OnSysRead(void *buffer, size_t size);
    virtual wxFileOffset OnSysTell() const { return m_pos; }

private:
    wxFileOffset m_pos;
    wxFileOffset m_len;

    DECLARE_NO_COPY_CLASS(wxStoredInputStream)
};

wxStoredInputStream::wxStoredInputStream(wxInputStream& stream)
  : wxFilterInputStream(stream),
    m_pos(0),
    m_len(0)
{
}

size_t wxStoredInputStream::OnSysRead(void *buffer, size_t size)
{
    size_t count = wx_truncate_cast(size_t,
                wxMin(size + wxFileOffset(0), m_len - m_pos + size_t(0)));
    count = m_parent_i_stream->Read(buffer, count).LastRead();
    m_pos += count;

    if (m_pos == m_len)
        m_lasterror = wxSTREAM_EOF;
    else if (!*m_parent_i_stream)
        m_lasterror = wxSTREAM_READ_ERROR;

    return count;
}


/////////////////////////////////////////////////////////////////////////////
// Stored output stream
// Trival compressor for files which are 'stored' in the zip file.

class wxStoredOutputStream : public wxFilterOutputStream
{
public:
    wxStoredOutputStream(wxOutputStream& stream) :
        wxFilterOutputStream(stream), m_pos(0) { }

    bool Close() {
        m_pos = 0;
        m_lasterror = wxSTREAM_NO_ERROR;
        return true;
    }

protected:
    virtual size_t OnSysWrite(const void *buffer, size_t size);
    virtual wxFileOffset OnSysTell() const { return m_pos; }

private:
    wxFileOffset m_pos;
    DECLARE_NO_COPY_CLASS(wxStoredOutputStream)
};

size_t wxStoredOutputStream::OnSysWrite(const void *buffer, size_t size)
{
    if (!IsOk() || !size)
        return 0;
    size_t count = m_parent_o_stream->Write(buffer, size).LastWrite();
    if (count != size)
        m_lasterror = wxSTREAM_WRITE_ERROR;
    m_pos += count;
    return count;
}


/////////////////////////////////////////////////////////////////////////////
// wxRawInputStream
//
// Used to handle the unusal case of raw copying an entry of unknown
// length. This can only happen when the zip being copied from is being
// read from a non-seekable stream, and also was original written to a
// non-seekable stream.
//
// In this case there's no option but to decompress the stream to find
// it's length, but we can still write the raw compressed data to avoid the
// compression overhead (which is the greater one).
//
// Usage is like this:
//  m_rawin = new wxRawInputStream(*m_parent_i_stream);
//  m_decomp = m_rawin->Open(OpenDecompressor(m_rawin->GetTee()));
//
// The wxRawInputStream owns a wxTeeInputStream object, the role of which
// is something like the unix 'tee' command; it is a transparent filter, but
// allows the data read to be read a second time via an extra method 'GetData'.
//
// The wxRawInputStream then draws data through the tee using a decompressor
// then instead of returning the decompressed data, retuns the raw data
// from wxTeeInputStream::GetData().

class wxTeeInputStream : public wxFilterInputStream
{
public:
    wxTeeInputStream(wxInputStream& stream);

    size_t GetCount() const { return m_end - m_start; }
    size_t GetData(char *buffer, size_t size);

    void Open();
    bool Final();

    wxInputStream& Read(void *buffer, size_t size);

protected:
    virtual size_t OnSysRead(void *buffer, size_t size);
    virtual wxFileOffset OnSysTell() const { return m_pos; }

private:
    wxFileOffset m_pos;
    wxMemoryBuffer m_buf;
    size_t m_start;
    size_t m_end;

    DECLARE_NO_COPY_CLASS(wxTeeInputStream)
};

wxTeeInputStream::wxTeeInputStream(wxInputStream& stream)
  : wxFilterInputStream(stream),
    m_pos(0), m_buf(8192), m_start(0), m_end(0)
{
}

void wxTeeInputStream::Open()
{
    m_pos = m_start = m_end = 0;
    m_lasterror = wxSTREAM_NO_ERROR;
}

bool wxTeeInputStream::Final()
{
    bool final = m_end == m_buf.GetDataLen();
    m_end = m_buf.GetDataLen();
    return final;
}

wxInputStream& wxTeeInputStream::Read(void *buffer, size_t size)
{
    size_t count = wxInputStream::Read(buffer, size).LastRead();
    m_end = m_buf.GetDataLen();
    m_buf.AppendData(buffer, count);
    return *this;
}

size_t wxTeeInputStream::OnSysRead(void *buffer, size_t size)
{
    size_t count = m_parent_i_stream->Read(buffer, size).LastRead();
    m_lasterror = m_parent_i_stream->GetLastError();
    return count;
}

size_t wxTeeInputStream::GetData(char *buffer, size_t size)
{
    if (m_wbacksize) {
        size_t len = m_buf.GetDataLen();
        len = len > m_wbacksize ? len - m_wbacksize : 0;
        m_buf.SetDataLen(len);
        if (m_end > len) {
            wxFAIL; // we've already returned data that's now being ungot
            m_end = len;
        }
        m_parent_i_stream->Reset();
        m_parent_i_stream->Ungetch(m_wback, m_wbacksize);
        free(m_wback);
        m_wback = NULL;
        m_wbacksize = 0;
        m_wbackcur = 0;
    }

    if (size > GetCount())
        size = GetCount();
    if (size) {
        memcpy(buffer, m_buf + m_start, size);
        m_start += size;
        wxASSERT(m_start <= m_end);
    }

    if (m_start == m_end && m_start > 0 && m_buf.GetDataLen() > 0) {
        size_t len = m_buf.GetDataLen();
        char *buf = (char*)m_buf.GetWriteBuf(len);
        len -= m_end;
        memmove(buf, buf + m_end, len);
        m_buf.UngetWriteBuf(len);
        m_start = m_end = 0;
    }

    return size;
}

class wxRawInputStream : public wxFilterInputStream
{
public:
    wxRawInputStream(wxInputStream& stream);
    virtual ~wxRawInputStream() { delete m_tee; }

    wxInputStream* Open(wxInputStream *decomp);
    wxInputStream& GetTee() const { return *m_tee; }

protected:
    virtual size_t OnSysRead(void *buffer, size_t size);
    virtual wxFileOffset OnSysTell() const { return m_pos; }

private:
    wxFileOffset m_pos;
    wxTeeInputStream *m_tee;

    enum { BUFSIZE = 8192 };
    wxCharBuffer m_dummy;

    DECLARE_NO_COPY_CLASS(wxRawInputStream)
};

wxRawInputStream::wxRawInputStream(wxInputStream& stream)
  : wxFilterInputStream(stream),
    m_pos(0),
    m_tee(new wxTeeInputStream(stream)),
    m_dummy(BUFSIZE)
{
}

wxInputStream *wxRawInputStream::Open(wxInputStream *decomp)
{
    if (decomp) {
        m_parent_i_stream = decomp;
        m_pos = 0;
        m_lasterror = wxSTREAM_NO_ERROR;
        m_tee->Open();
        return this;
    } else {
        return NULL;
    }
}

size_t wxRawInputStream::OnSysRead(void *buffer, size_t size)
{
    char *buf = (char*)buffer;
    size_t count = 0;

    while (count < size && IsOk())
    {
        while (m_parent_i_stream->IsOk() && m_tee->GetCount() == 0)
            m_parent_i_stream->Read(m_dummy.data(), BUFSIZE);

        size_t n = m_tee->GetData(buf + count, size - count);
        count += n;

        if (n == 0 && m_tee->Final())
            m_lasterror = m_parent_i_stream->GetLastError();
    }

    m_pos += count;
    return count;
}


/////////////////////////////////////////////////////////////////////////////
// Zlib streams than can be reused without recreating.

class wxZlibOutputStream2 : public wxZlibOutputStream
{
public:
    wxZlibOutputStream2(wxOutputStream& stream, int level) :
        wxZlibOutputStream(stream, level, wxZLIB_NO_HEADER) { }

    bool Open(wxOutputStream& stream);
    bool Close() { DoFlush(true); m_pos = wxInvalidOffset; return IsOk(); }
};

bool wxZlibOutputStream2::Open(wxOutputStream& stream)
{
    wxCHECK(m_pos == wxInvalidOffset, false);

    m_deflate->next_out = m_z_buffer;
    m_deflate->avail_out = m_z_size;
    m_pos = 0;
    m_lasterror = wxSTREAM_NO_ERROR;
    m_parent_o_stream = &stream;

    if (deflateReset(m_deflate) != Z_OK) {
        wxLogError(_("can't re-initialize zlib deflate stream"));
        m_lasterror = wxSTREAM_WRITE_ERROR;
        return false;
    }

    return true;
}

class wxZlibInputStream2 : public wxZlibInputStream
{
public:
    wxZlibInputStream2(wxInputStream& stream) :
        wxZlibInputStream(stream, wxZLIB_NO_HEADER) { }

    bool Open(wxInputStream& stream);
};

bool wxZlibInputStream2::Open(wxInputStream& stream)
{
    m_inflate->avail_in = 0;
    m_pos = 0;
    m_lasterror = wxSTREAM_NO_ERROR;
    m_parent_i_stream = &stream;

    if (inflateReset(m_inflate) != Z_OK) {
        wxLogError(_("can't re-initialize zlib inflate stream"));
        m_lasterror = wxSTREAM_READ_ERROR;
        return false;
    }

    return true;
}


/////////////////////////////////////////////////////////////////////////////
// Class to hold wxZipEntry's Extra and LocalExtra fields

class wxZipMemory
{
public:
    wxZipMemory() : m_data(NULL), m_size(0), m_capacity(0), m_ref(1) { }

    wxZipMemory *AddRef() { m_ref++; return this; }
    void Release() { if (--m_ref == 0) delete this; }

    char *GetData() const { return m_data; }
    size_t GetSize() const { return m_size; }
    size_t GetCapacity() const { return m_capacity; }

    wxZipMemory *Unique(size_t size);

private:
    ~wxZipMemory() { delete [] m_data; }

    char *m_data;
    size_t m_size;
    size_t m_capacity;
    int m_ref;
};

wxZipMemory *wxZipMemory::Unique(size_t size)
{
    wxZipMemory *zm;

    if (m_ref > 1) {
        --m_ref;
        zm = new wxZipMemory;
    } else {
        zm = this;
    }

    if (zm->m_capacity < size) {
        delete [] zm->m_data;
        zm->m_data = new char[size];
        zm->m_capacity = size;
    }

    zm->m_size = size;
    return zm;
}

static inline wxZipMemory *AddRef(wxZipMemory *zm)
{
    if (zm)
        zm->AddRef();
    return zm;
}

static inline void Release(wxZipMemory *zm)
{
    if (zm)
        zm->Release();
}

static void Copy(wxZipMemory*& dest, wxZipMemory *src)
{
    Release(dest);
    dest = AddRef(src);
}

static void Unique(wxZipMemory*& zm, size_t size)
{
    if (!zm && size)
        zm = new wxZipMemory;
    if (zm)
        zm = zm->Unique(size);
}


/////////////////////////////////////////////////////////////////////////////
// Collection of weak references to entries

WX_DECLARE_HASH_MAP(long, wxZipEntry*, wxIntegerHash,
                    wxIntegerEqual, wx__OffsetZipEntryMap);

class wxZipWeakLinks
{
public:
    wxZipWeakLinks() : m_ref(1) { }

    void Release(const wxZipInputStream* WXUNUSED(x))
        { if (--m_ref == 0) delete this; }
    void Release(wxFileOffset key)
        { RemoveEntry(key); if (--m_ref == 0) delete this; }

    wxZipWeakLinks *AddEntry(wxZipEntry *entry, wxFileOffset key);
    void RemoveEntry(wxFileOffset key)
        { m_entries.erase(wx_truncate_cast(key_type, key)); }
    wxZipEntry *GetEntry(wxFileOffset key) const;
    bool IsEmpty() const { return m_entries.empty(); }

private:
    typedef wx__OffsetZipEntryMap::key_type key_type;

    ~wxZipWeakLinks() { wxASSERT(IsEmpty()); }

    int m_ref;
    wx__OffsetZipEntryMap m_entries;
};

wxZipWeakLinks *wxZipWeakLinks::AddEntry(wxZipEntry *entry, wxFileOffset key)
{
    m_entries[wx_truncate_cast(key_type, key)] = entry;
    m_ref++;
    return this;
}

wxZipEntry *wxZipWeakLinks::GetEntry(wxFileOffset key) const
{
    wx__OffsetZipEntryMap::const_iterator it =
        m_entries.find(wx_truncate_cast(key_type, key));
    return it != m_entries.end() ?  it->second : NULL;
}


/////////////////////////////////////////////////////////////////////////////
// ZipEntry

wxZipEntry::wxZipEntry(
    const wxString& name /*=wxEmptyString*/,
    const wxDateTime& dt /*=wxDateTime::Now()*/,
    wxFileOffset size    /*=wxInvalidOffset*/)
  :
    m_SystemMadeBy(wxZIP_SYSTEM_MSDOS),
    m_VersionMadeBy(wxMAJOR_VERSION * 10 + wxMINOR_VERSION),
    m_VersionNeeded(VERSION_NEEDED_TO_EXTRACT),
    m_Flags(0),
    m_Method(wxZIP_METHOD_DEFAULT),
    m_DateTime(dt),
    m_Crc(0),
    m_CompressedSize(wxInvalidOffset),
    m_Size(size),
    m_Key(wxInvalidOffset),
    m_Offset(wxInvalidOffset),
    m_DiskStart(0),
    m_InternalAttributes(0),
    m_ExternalAttributes(0),
    m_Extra(NULL),
    m_LocalExtra(NULL),
    m_zipnotifier(NULL),
    m_backlink(NULL)
{
    if (!name.empty())
        SetName(name);
}

wxZipEntry::~wxZipEntry()
{
    if (m_backlink)
        m_backlink->Release(m_Key);
    Release(m_Extra);
    Release(m_LocalExtra);
}

wxZipEntry::wxZipEntry(const wxZipEntry& e)
  : wxArchiveEntry(e),
    m_SystemMadeBy(e.m_SystemMadeBy),
    m_VersionMadeBy(e.m_VersionMadeBy),
    m_VersionNeeded(e.m_VersionNeeded),
    m_Flags(e.m_Flags),
    m_Method(e.m_Method),
    m_DateTime(e.m_DateTime),
    m_Crc(e.m_Crc),
    m_CompressedSize(e.m_CompressedSize),
    m_Size(e.m_Size),
    m_Name(e.m_Name),
    m_Key(e.m_Key),
    m_Offset(e.m_Offset),
    m_Comment(e.m_Comment),
    m_DiskStart(e.m_DiskStart),
    m_InternalAttributes(e.m_InternalAttributes),
    m_ExternalAttributes(e.m_ExternalAttributes),
    m_Extra(AddRef(e.m_Extra)),
    m_LocalExtra(AddRef(e.m_LocalExtra)),
    m_zipnotifier(NULL),
    m_backlink(NULL)
{
}

wxZipEntry& wxZipEntry::operator=(const wxZipEntry& e)
{
    if (&e != this) {
        m_SystemMadeBy = e.m_SystemMadeBy;
        m_VersionMadeBy = e.m_VersionMadeBy;
        m_VersionNeeded = e.m_VersionNeeded;
        m_Flags = e.m_Flags;
        m_Method = e.m_Method;
        m_DateTime = e.m_DateTime;
        m_Crc = e.m_Crc;
        m_CompressedSize = e.m_CompressedSize;
        m_Size = e.m_Size;
        m_Name = e.m_Name;
        m_Key = e.m_Key;
        m_Offset = e.m_Offset;
        m_Comment = e.m_Comment;
        m_DiskStart = e.m_DiskStart;
        m_InternalAttributes = e.m_InternalAttributes;
        m_ExternalAttributes = e.m_ExternalAttributes;
        Copy(m_Extra, e.m_Extra);
        Copy(m_LocalExtra, e.m_LocalExtra);
        m_zipnotifier = NULL;
        if (m_backlink) {
            m_backlink->Release(m_Key);
            m_backlink = NULL;
        }
    }
    return *this;
}

wxString wxZipEntry::GetName(wxPathFormat format /*=wxPATH_NATIVE*/) const
{
    bool isDir = IsDir() && !m_Name.empty();

    // optimisations for common (and easy) cases
    switch (wxFileName::GetFormat(format)) {
        case wxPATH_DOS:
        {
            wxString name(isDir ? m_Name + _T("\\") : m_Name);
            for (size_t i = name.length() - 1; i > 0; --i)
                if (name[i] == _T('/'))
                    name[i] = _T('\\');
            return name;
        }

        case wxPATH_UNIX:
            return isDir ? m_Name + _T("/") : m_Name;

        default:
            ;
    }

    wxFileName fn;

    if (isDir)
        fn.AssignDir(m_Name, wxPATH_UNIX);
    else
        fn.Assign(m_Name, wxPATH_UNIX);

    return fn.GetFullPath(format);
}

// Static - Internally tars and zips use forward slashes for the path
// separator, absolute paths aren't allowed, and directory names have a
// trailing slash.  This function converts a path into this internal format,
// but without a trailing slash for a directory.
//
wxString wxZipEntry::GetInternalName(const wxString& name,
                                     wxPathFormat format /*=wxPATH_NATIVE*/,
                                     bool *pIsDir        /*=NULL*/)
{
    wxString internal;

    if (wxFileName::GetFormat(format) != wxPATH_UNIX)
        internal = wxFileName(name, format).GetFullPath(wxPATH_UNIX);
    else
        internal = name;

    bool isDir = !internal.empty() && internal.Last() == '/';
    if (pIsDir)
        *pIsDir = isDir;
    if (isDir)
        internal.erase(internal.length() - 1);

    while (!internal.empty() && *internal.begin() == '/')
        internal.erase(0, 1);
    while (!internal.empty() && internal.compare(0, 2, _T("./")) == 0)
        internal.erase(0, 2);
    if (internal == _T(".") || internal == _T(".."))
        internal = wxEmptyString;

    return internal;
}

void wxZipEntry::SetSystemMadeBy(int system)
{
    int mode = GetMode();
    bool wasUnix = IsMadeByUnix();

    m_SystemMadeBy = (wxUint8)system;

    if (!wasUnix && IsMadeByUnix()) {
        SetIsDir(IsDir());
        SetMode(mode);
    } else if (wasUnix && !IsMadeByUnix()) {
        m_ExternalAttributes &= 0xffff;
    }
}

void wxZipEntry::SetIsDir(bool isDir /*=true*/)
{
    if (isDir)
        m_ExternalAttributes |= wxZIP_A_SUBDIR;
    else
        m_ExternalAttributes &= ~wxZIP_A_SUBDIR;

    if (IsMadeByUnix()) {
        m_ExternalAttributes &= ~wxZIP_S_IFMT;
        if (isDir)
            m_ExternalAttributes |= wxZIP_S_IFDIR;
        else
            m_ExternalAttributes |= wxZIP_S_IFREG;
    }
}

// Return unix style permission bits
//
int wxZipEntry::GetMode() const
{
    // return unix permissions if present
    if (IsMadeByUnix())
        return (m_ExternalAttributes >> 16) & 0777;

    // otherwise synthesize from the dos attribs
    int mode = 0644;
    if (m_ExternalAttributes & wxZIP_A_RDONLY)
        mode &= ~0200;
    if (m_ExternalAttributes & wxZIP_A_SUBDIR)
        mode |= 0111;

    return mode;
}

// Set unix permissions
//
void wxZipEntry::SetMode(int mode)
{
    // Set dos attrib bits to be compatible
    if (mode & 0222)
        m_ExternalAttributes &= ~wxZIP_A_RDONLY;
    else
        m_ExternalAttributes |= wxZIP_A_RDONLY;

    // set the actual unix permission bits if the system type allows
    if (IsMadeByUnix()) {
        m_ExternalAttributes &= ~(0777L << 16);
        m_ExternalAttributes |= (mode & 0777L) << 16;
    }
}

const char *wxZipEntry::GetExtra() const
{
    return m_Extra ? m_Extra->GetData() : NULL;
}

size_t wxZipEntry::GetExtraLen() const
{
    return m_Extra ? m_Extra->GetSize() : 0;
}

void wxZipEntry::SetExtra(const char *extra, size_t len)
{
    Unique(m_Extra, len);
    if (len)
        memcpy(m_Extra->GetData(), extra, len);
}

const char *wxZipEntry::GetLocalExtra() const
{
    return m_LocalExtra ? m_LocalExtra->GetData() : NULL;
}

size_t  wxZipEntry::GetLocalExtraLen() const
{
    return m_LocalExtra ? m_LocalExtra->GetSize() : 0;
}

void wxZipEntry::SetLocalExtra(const char *extra, size_t len)
{
    Unique(m_LocalExtra, len);
    if (len)
        memcpy(m_LocalExtra->GetData(), extra, len);
}

void wxZipEntry::SetNotifier(wxZipNotifier& notifier)
{
    wxArchiveEntry::UnsetNotifier();
    m_zipnotifier = &notifier;
    m_zipnotifier->OnEntryUpdated(*this);
}

void wxZipEntry::Notify()
{
    if (m_zipnotifier)
        m_zipnotifier->OnEntryUpdated(*this);
    else if (GetNotifier())
        GetNotifier()->OnEntryUpdated(*this);
}

void wxZipEntry::UnsetNotifier()
{
    wxArchiveEntry::UnsetNotifier();
    m_zipnotifier = NULL;
}

size_t wxZipEntry::ReadLocal(wxInputStream& stream, wxMBConv& conv)
{
    wxUint16 nameLen, extraLen;
    wxUint32 compressedSize, size, crc;

    wxDataInputStream ds(stream);

    ds >> m_VersionNeeded >> m_Flags >> m_Method;
    SetDateTime(wxDateTime().SetFromDOS(ds.Read32()));
    ds >> crc >> compressedSize >> size >> nameLen >> extraLen;

    bool sumsValid = (m_Flags & wxZIP_SUMS_FOLLOW) == 0;

    if (sumsValid || crc)
        m_Crc = crc;
    if ((sumsValid || compressedSize) || m_Method == wxZIP_METHOD_STORE)
        m_CompressedSize = compressedSize;
    if ((sumsValid || size) || m_Method == wxZIP_METHOD_STORE)
        m_Size = size;

    SetName(ReadString(stream, nameLen, conv), wxPATH_UNIX);

    if (extraLen || GetLocalExtraLen()) {
        Unique(m_LocalExtra, extraLen);
        if (extraLen)
            stream.Read(m_LocalExtra->GetData(), extraLen);
    }

    return LOCAL_SIZE + nameLen + extraLen;
}

size_t wxZipEntry::WriteLocal(wxOutputStream& stream, wxMBConv& conv) const
{
    wxString unixName = GetName(wxPATH_UNIX);
    const wxWX2MBbuf name_buf = conv.cWX2MB(unixName);
    const char *name = name_buf;
    if (!name) name = "";
    wxUint16 nameLen = wx_truncate_cast(wxUint16, strlen(name));

    wxDataOutputStream ds(stream);

    ds << m_VersionNeeded << m_Flags << m_Method;
    ds.Write32(GetDateTime().GetAsDOS());

    ds.Write32(m_Crc);
    ds.Write32(m_CompressedSize != wxInvalidOffset ?
               wx_truncate_cast(wxUint32, m_CompressedSize) : 0);
    ds.Write32(m_Size != wxInvalidOffset ?
               wx_truncate_cast(wxUint32, m_Size) : 0);

    ds << nameLen;
    wxUint16 extraLen = wx_truncate_cast(wxUint16, GetLocalExtraLen());
    ds.Write16(extraLen);

    stream.Write(name, nameLen);
    if (extraLen)
        stream.Write(m_LocalExtra->GetData(), extraLen);

    return LOCAL_SIZE + nameLen + extraLen;
}

size_t wxZipEntry::ReadCentral(wxInputStream& stream, wxMBConv& conv)
{
    wxUint16 nameLen, extraLen, commentLen;

    wxDataInputStream ds(stream);

    ds >> m_VersionMadeBy >> m_SystemMadeBy;

    SetVersionNeeded(ds.Read16());
    SetFlags(ds.Read16());
    SetMethod(ds.Read16());
    SetDateTime(wxDateTime().SetFromDOS(ds.Read32()));
    SetCrc(ds.Read32());
    SetCompressedSize(ds.Read32());
    SetSize(ds.Read32());

    ds >> nameLen >> extraLen >> commentLen
       >> m_DiskStart >> m_InternalAttributes >> m_ExternalAttributes;
    SetOffset(ds.Read32());

    SetName(ReadString(stream, nameLen, conv), wxPATH_UNIX);

    if (extraLen || GetExtraLen()) {
        Unique(m_Extra, extraLen);
        if (extraLen)
            stream.Read(m_Extra->GetData(), extraLen);
    }

    if (commentLen)
        m_Comment = ReadString(stream, commentLen, conv);
    else
        m_Comment.clear();

    return CENTRAL_SIZE + nameLen + extraLen + commentLen;
}

size_t wxZipEntry::WriteCentral(wxOutputStream& stream, wxMBConv& conv) const
{
    wxString unixName = GetName(wxPATH_UNIX);
    const wxWX2MBbuf name_buf = conv.cWX2MB(unixName);
    const char *name = name_buf;
    if (!name) name = "";
    wxUint16 nameLen = wx_truncate_cast(wxUint16, strlen(name));

    const wxWX2MBbuf comment_buf = conv.cWX2MB(m_Comment);
    const char *comment = comment_buf;
    if (!comment) comment = "";
    wxUint16 commentLen = wx_truncate_cast(wxUint16, strlen(comment));

    wxUint16 extraLen = wx_truncate_cast(wxUint16, GetExtraLen());

    wxDataOutputStream ds(stream);

    ds << CENTRAL_MAGIC << m_VersionMadeBy << m_SystemMadeBy;

    ds.Write16(wx_truncate_cast(wxUint16, GetVersionNeeded()));
    ds.Write16(wx_truncate_cast(wxUint16, GetFlags()));
    ds.Write16(wx_truncate_cast(wxUint16, GetMethod()));
    ds.Write32(GetDateTime().GetAsDOS());
    ds.Write32(GetCrc());
    ds.Write32(wx_truncate_cast(wxUint32, GetCompressedSize()));
    ds.Write32(wx_truncate_cast(wxUint32, GetSize()));
    ds.Write16(nameLen);
    ds.Write16(extraLen);

    ds << commentLen << m_DiskStart << m_InternalAttributes
       << m_ExternalAttributes << wx_truncate_cast(wxUint32, GetOffset());

    stream.Write(name, nameLen);
    if (extraLen)
        stream.Write(GetExtra(), extraLen);
    stream.Write(comment, commentLen);

    return CENTRAL_SIZE + nameLen + extraLen + commentLen;
}

// Info-zip prefixes this record with a signature, but pkzip doesn't. So if
// the 1st value is the signature then it is probably an info-zip record,
// though there is a small chance that it is in fact a pkzip record which
// happens to have the signature as it's CRC.
//
size_t wxZipEntry::ReadDescriptor(wxInputStream& stream)
{
    wxDataInputStream ds(stream);

    m_Crc = ds.Read32();
    m_CompressedSize = ds.Read32();
    m_Size = ds.Read32();

    // if 1st value is the signature then this is probably an info-zip record
    if (m_Crc == SUMS_MAGIC)
    {
        char buf[8];
        stream.Read(buf, sizeof(buf));
        wxUint32 u1 = CrackUint32(buf);
        wxUint32 u2 = CrackUint32(buf + 4);

        // look for the signature of the following record to decide which
        if ((u1 == LOCAL_MAGIC || u1 == CENTRAL_MAGIC) &&
            (u2 != LOCAL_MAGIC && u2 != CENTRAL_MAGIC))
        {
            // it's a pkzip style record after all!
            stream.Ungetch(buf, sizeof(buf));
        }
        else
        {
            // it's an info-zip record as expected
            stream.Ungetch(buf + 4, sizeof(buf) - 4);
            m_Crc = wx_truncate_cast(wxUint32, m_CompressedSize);
            m_CompressedSize = m_Size;
            m_Size = u1;
            return SUMS_SIZE + 4;
        }
    }

    return SUMS_SIZE;
}

size_t wxZipEntry::WriteDescriptor(wxOutputStream& stream, wxUint32 crc,
                                   wxFileOffset compressedSize, wxFileOffset size)
{
    m_Crc = crc;
    m_CompressedSize = compressedSize;
    m_Size = size;

    wxDataOutputStream ds(stream);

    ds.Write32(crc);
    ds.Write32(wx_truncate_cast(wxUint32, compressedSize));
    ds.Write32(wx_truncate_cast(wxUint32, size));

    return SUMS_SIZE;
}


/////////////////////////////////////////////////////////////////////////////
// wxZipEndRec - holds the end of central directory record

class wxZipEndRec
{
public:
    wxZipEndRec();

    int GetDiskNumber() const                   { return m_DiskNumber; }
    int GetStartDisk() const                    { return m_StartDisk; }
    int GetEntriesHere() const                  { return m_EntriesHere; }
    int GetTotalEntries() const                 { return m_TotalEntries; }
    wxFileOffset GetSize() const                { return m_Size; }
    wxFileOffset GetOffset() const              { return m_Offset; }
    wxString GetComment() const                 { return m_Comment; }

    void SetDiskNumber(int num)
        { m_DiskNumber = wx_truncate_cast(wxUint16, num); }
    void SetStartDisk(int num)
        { m_StartDisk = wx_truncate_cast(wxUint16, num); }
    void SetEntriesHere(int num)
        { m_EntriesHere = wx_truncate_cast(wxUint16, num); }
    void SetTotalEntries(int num)
        { m_TotalEntries = wx_truncate_cast(wxUint16, num); }
    void SetSize(wxFileOffset size)
        { m_Size = wx_truncate_cast(wxUint32, size); }
    void SetOffset(wxFileOffset offset)
        { m_Offset = wx_truncate_cast(wxUint32, offset); }
    void SetComment(const wxString& comment)
        { m_Comment = comment; }

    bool Read(wxInputStream& stream, wxMBConv& conv);
    bool Write(wxOutputStream& stream, wxMBConv& conv) const;

private:
    wxUint16 m_DiskNumber;
    wxUint16 m_StartDisk;
    wxUint16 m_EntriesHere;
    wxUint16 m_TotalEntries;
    wxUint32 m_Size;
    wxUint32 m_Offset;
    wxString m_Comment;
};

wxZipEndRec::wxZipEndRec()
  : m_DiskNumber(0),
    m_StartDisk(0),
    m_EntriesHere(0),
    m_TotalEntries(0),
    m_Size(0),
    m_Offset(0)
{
}

bool wxZipEndRec::Write(wxOutputStream& stream, wxMBConv& conv) const
{
    const wxWX2MBbuf comment_buf = conv.cWX2MB(m_Comment);
    const char *comment = comment_buf;
    if (!comment) comment = "";
    wxUint16 commentLen = (wxUint16)strlen(comment);

    wxDataOutputStream ds(stream);

    ds << END_MAGIC << m_DiskNumber << m_StartDisk << m_EntriesHere
       << m_TotalEntries << m_Size << m_Offset << commentLen;

    stream.Write(comment, commentLen);

    return stream.IsOk();
}

bool wxZipEndRec::Read(wxInputStream& stream, wxMBConv& conv)
{
    wxDataInputStream ds(stream);
    wxUint16 commentLen;

    ds >> m_DiskNumber >> m_StartDisk >> m_EntriesHere
       >> m_TotalEntries >> m_Size >> m_Offset >> commentLen;

    if (commentLen)
        m_Comment = ReadString(stream, commentLen, conv);

    if (stream.IsOk())
        if (m_DiskNumber == 0 && m_StartDisk == 0 &&
                m_EntriesHere == m_TotalEntries)
            return true;
        else
            wxLogError(_("unsupported zip archive"));

    return false;
}


/////////////////////////////////////////////////////////////////////////////
// A weak link from an input stream to an output stream

class wxZipStreamLink
{
public:
    wxZipStreamLink(wxZipOutputStream *stream) : m_ref(1), m_stream(stream) { }

    wxZipStreamLink *AddRef() { m_ref++; return this; }
    wxZipOutputStream *GetOutputStream() const { return m_stream; }

    void Release(class wxZipInputStream *WXUNUSED(s))
        { if (--m_ref == 0) delete this; }
    void Release(class wxZipOutputStream *WXUNUSED(s))
        { m_stream = NULL; if (--m_ref == 0) delete this; }

private:
    ~wxZipStreamLink() { }

    int m_ref;
    wxZipOutputStream *m_stream;
};


/////////////////////////////////////////////////////////////////////////////
// Input stream

// leave the default wxZipEntryPtr free for users
wxDECLARE_SCOPED_PTR(wxZipEntry, wx__ZipEntryPtr)
wxDEFINE_SCOPED_PTR (wxZipEntry, wx__ZipEntryPtr)

// constructor
//
wxZipInputStream::wxZipInputStream(wxInputStream& stream,
                                   wxMBConv& conv /*=wxConvLocal*/)
  : wxArchiveInputStream(stream, conv)
{
    Init();
}

#if 1 //WXWIN_COMPATIBILITY_2_6

// Part of the compatibility constructor, which has been made inline to
// avoid a problem with it not being exported by mingw 3.2.3
//
void wxZipInputStream::Init(const wxString& file)
{
    // no error messages
    wxLogNull nolog;
    Init();
    m_allowSeeking = true;
    m_ffile = wx_static_cast(wxFFileInputStream*, m_parent_i_stream);
    wx__ZipEntryPtr entry;

    if (m_ffile->Ok()) {
        do {
            entry.reset(GetNextEntry());
        }
        while (entry.get() != NULL && entry->GetInternalName() != file);
    }

    if (entry.get() == NULL)
        m_lasterror = wxSTREAM_READ_ERROR;
}

wxInputStream& wxZipInputStream::OpenFile(const wxString& archive)
{
    wxLogNull nolog;
    return *new wxFFileInputStream(archive);
}

#endif // WXWIN_COMPATIBILITY_2_6

void wxZipInputStream::Init()
{
    m_store = new wxStoredInputStream(*m_parent_i_stream);
    m_inflate = NULL;
    m_rawin = NULL;
    m_raw = false;
    m_headerSize = 0;
    m_decomp = NULL;
    m_parentSeekable = false;
    m_weaklinks = new wxZipWeakLinks;
    m_streamlink = NULL;
    m_offsetAdjustment = 0;
    m_position = wxInvalidOffset;
    m_signature = 0;
    m_TotalEntries = 0;
    m_lasterror = m_parent_i_stream->GetLastError();
    m_ffile = NULL;
#if 1 //WXWIN_COMPATIBILITY_2_6
    m_allowSeeking = false;
#endif
}

wxZipInputStream::~wxZipInputStream()
{
    CloseDecompressor(m_decomp);

    delete m_store;
    delete m_inflate;
    delete m_rawin;
    delete m_ffile;

    m_weaklinks->Release(this);

    if (m_streamlink)
        m_streamlink->Release(this);
}

wxString wxZipInputStream::GetComment()
{
    if (m_position == wxInvalidOffset)
        if (!LoadEndRecord())
            return wxEmptyString;

    if (!m_parentSeekable && Eof() && m_signature) {
        m_lasterror = wxSTREAM_NO_ERROR;
        m_lasterror = ReadLocal(true);
    }

    return m_Comment;
}

int wxZipInputStream::GetTotalEntries()
{
    if (m_position == wxInvalidOffset)
        LoadEndRecord();
    return m_TotalEntries;
}

wxZipStreamLink *wxZipInputStream::MakeLink(wxZipOutputStream *out)
{
    wxZipStreamLink *link = NULL;

    if (!m_parentSeekable && (IsOpened() || !Eof())) {
        link = new wxZipStreamLink(out);
        if (m_streamlink)
            m_streamlink->Release(this);
        m_streamlink = link->AddRef();
    }

    return link;
}

bool wxZipInputStream::LoadEndRecord()
{
    wxCHECK(m_position == wxInvalidOffset, false);
    if (!IsOk())
        return false;

    m_position = 0;

    // First find the end-of-central-directory record.
    if (!FindEndRecord()) {
        // failed, so either this is a non-seekable stream (ok), or not a zip
        if (m_parentSeekable) {
            m_lasterror = wxSTREAM_READ_ERROR;
            wxLogError(_("invalid zip file"));
            return false;
        }
        else {
            wxLogNull nolog;
            wxFileOffset pos = m_parent_i_stream->TellI();
            // FIXME
            //if (pos != wxInvalidOffset)
            if (pos >= 0 && pos <= LONG_MAX)
                m_offsetAdjustment = m_position = pos;
            return true;
        }
    }

    wxZipEndRec endrec;

    // Read in the end record
    wxFileOffset endPos = m_parent_i_stream->TellI() - 4;
    if (!endrec.Read(*m_parent_i_stream, GetConv())) {
        if (!*m_parent_i_stream) {
            m_lasterror = wxSTREAM_READ_ERROR;
            return false;
        }
        // TODO: try this out
        wxLogWarning(_("assuming this is a multi-part zip concatenated"));
    }

    m_TotalEntries = endrec.GetTotalEntries();
    m_Comment = endrec.GetComment();

    // Now find the central-directory. we have the file offset of
    // the CD, so look there first.
    if (m_parent_i_stream->SeekI(endrec.GetOffset()) != wxInvalidOffset &&
            ReadSignature() == CENTRAL_MAGIC) {
        m_signature = CENTRAL_MAGIC;
        m_position = endrec.GetOffset();
        m_offsetAdjustment = 0;
        return true;
    }

    // If it's not there, then it could be that the zip has been appended
    // to a self extractor, so take the CD size (also in endrec), subtract
    // it from the file offset of the end-central-directory and look there.
    if (m_parent_i_stream->SeekI(endPos - endrec.GetSize())
            != wxInvalidOffset && ReadSignature() == CENTRAL_MAGIC) {
        m_signature = CENTRAL_MAGIC;
        m_position = endPos - endrec.GetSize();
        m_offsetAdjustment = m_position - endrec.GetOffset();
        return true;
    }

    wxLogError(_("can't find central directory in zip"));
    m_lasterror = wxSTREAM_READ_ERROR;
    return false;
}

// Find the end-of-central-directory record.
// If found the stream will be positioned just past the 4 signature bytes.
//
bool wxZipInputStream::FindEndRecord()
{
    if (!m_parent_i_stream->IsSeekable())
        return false;

    // usually it's 22 bytes in size and the last thing in the file
    {
        wxLogNull nolog;
        if (m_parent_i_stream->SeekI(-END_SIZE, wxFromEnd) == wxInvalidOffset)
            return false;
    }

    m_parentSeekable = true;
    m_signature = 0;
    char magic[4];
    if (m_parent_i_stream->Read(magic, 4).LastRead() != 4)
        return false;
    if ((m_signature = CrackUint32(magic)) == END_MAGIC)
        return true;

    // unfortunately, the record has a comment field that can be up to 65535
    // bytes in length, so if the signature not found then search backwards.
    wxFileOffset pos = m_parent_i_stream->TellI();
    const int BUFSIZE = 1024;
    wxCharBuffer buf(BUFSIZE);

    memcpy(buf.data(), magic, 3);
    wxFileOffset minpos = wxMax(pos - 65535L, 0);

    while (pos > minpos) {
        size_t len = wx_truncate_cast(size_t,
                        pos - wxMax(pos - (BUFSIZE - 3), minpos));
        memcpy(buf.data() + len, buf, 3);
        pos -= len;

        if (m_parent_i_stream->SeekI(pos, wxFromStart) == wxInvalidOffset ||
                m_parent_i_stream->Read(buf.data(), len).LastRead() != len)
            return false;

        char *p = buf.data() + len;

        while (p-- > buf.data()) {
            if ((m_signature = CrackUint32(p)) == END_MAGIC) {
                size_t remainder = buf.data() + len - p;
                if (remainder > 4)
                    m_parent_i_stream->Ungetch(p + 4, remainder - 4);
                return true;
            }
        }
    }

    return false;
}

wxZipEntry *wxZipInputStream::GetNextEntry()
{
    if (m_position == wxInvalidOffset)
        if (!LoadEndRecord())
            return NULL;

    m_lasterror = m_parentSeekable ? ReadCentral() : ReadLocal();
    if (!IsOk())
        return NULL;

    wx__ZipEntryPtr entry(new wxZipEntry(m_entry));
    entry->m_backlink = m_weaklinks->AddEntry(entry.get(), entry->GetKey());
    return entry.release();
}

wxStreamError wxZipInputStream::ReadCentral()
{
    if (!AtHeader())
        CloseEntry();

    if (m_signature == END_MAGIC)
        return wxSTREAM_EOF;

    if (m_signature != CENTRAL_MAGIC) {
        wxLogError(_("error reading zip central directory"));
        return wxSTREAM_READ_ERROR;
    }

    if (QuietSeek(*m_parent_i_stream, m_position + 4) == wxInvalidOffset)
        return wxSTREAM_READ_ERROR;

    m_position += m_entry.ReadCentral(*m_parent_i_stream, GetConv());
    if (m_parent_i_stream->GetLastError() == wxSTREAM_READ_ERROR) {
        m_signature = 0;
        return wxSTREAM_READ_ERROR;
    }

    m_signature = ReadSignature();

    if (m_offsetAdjustment)
        m_entry.SetOffset(m_entry.GetOffset() + m_offsetAdjustment);
    m_entry.SetKey(m_entry.GetOffset());

    return wxSTREAM_NO_ERROR;
}

wxStreamError wxZipInputStream::ReadLocal(bool readEndRec /*=false*/)
{
    if (!AtHeader())
        CloseEntry();

    if (!m_signature)
        m_signature = ReadSignature();

    if (m_signature == CENTRAL_MAGIC || m_signature == END_MAGIC) {
        if (m_streamlink && !m_streamlink->GetOutputStream()) {
            m_streamlink->Release(this);
            m_streamlink = NULL;
        }
    }

    while (m_signature == CENTRAL_MAGIC) {
        if (m_weaklinks->IsEmpty() && m_streamlink == NULL)
            return wxSTREAM_EOF;

        m_position += m_entry.ReadCentral(*m_parent_i_stream, GetConv());
        m_signature = 0;
        if (m_parent_i_stream->GetLastError() == wxSTREAM_READ_ERROR)
            return wxSTREAM_READ_ERROR;

        wxZipEntry *entry = m_weaklinks->GetEntry(m_entry.GetOffset());
        if (entry) {
            entry->SetSystemMadeBy(m_entry.GetSystemMadeBy());
            entry->SetVersionMadeBy(m_entry.GetVersionMadeBy());
            entry->SetComment(m_entry.GetComment());
            entry->SetDiskStart(m_entry.GetDiskStart());
            entry->SetInternalAttributes(m_entry.GetInternalAttributes());
            entry->SetExternalAttributes(m_entry.GetExternalAttributes());
            Copy(entry->m_Extra, m_entry.m_Extra);
            entry->Notify();
            m_weaklinks->RemoveEntry(entry->GetOffset());
        }

        m_signature = ReadSignature();
    }

    if (m_signature == END_MAGIC) {
        if (readEndRec || m_streamlink) {
            wxZipEndRec endrec;
            endrec.Read(*m_parent_i_stream, GetConv());
            m_Comment = endrec.GetComment();
            m_signature = 0;
            if (m_streamlink) {
                m_streamlink->GetOutputStream()->SetComment(endrec.GetComment());
                m_streamlink->Release(this);
                m_streamlink = NULL;
            }
        }
        return wxSTREAM_EOF;
    }

    if (m_signature != LOCAL_MAGIC) {
        wxLogError(_("error reading zip local header"));
        return wxSTREAM_READ_ERROR;
    }

    m_headerSize = m_entry.ReadLocal(*m_parent_i_stream, GetConv());
    m_signature = 0;
    m_entry.SetOffset(m_position);
    m_entry.SetKey(m_position);

    if (m_parent_i_stream->GetLastError() == wxSTREAM_READ_ERROR) {
        return wxSTREAM_READ_ERROR;
    } else {
        m_TotalEntries++;
        return wxSTREAM_NO_ERROR;
    }
}

wxUint32 wxZipInputStream::ReadSignature()
{
    char magic[4];
    m_parent_i_stream->Read(magic, 4);
    return m_parent_i_stream->LastRead() == 4 ? CrackUint32(magic) : 0;
}

bool wxZipInputStream::OpenEntry(wxArchiveEntry& entry)
{
    wxZipEntry *zipEntry = wxStaticCast(&entry, wxZipEntry);
    return zipEntry ? OpenEntry(*zipEntry) : false;
}

// Open an entry
//
bool wxZipInputStream::DoOpen(wxZipEntry *entry, bool raw)
{
    if (m_position == wxInvalidOffset)
        if (!LoadEndRecord())
            return false;
    if (m_lasterror == wxSTREAM_READ_ERROR)
        return false;
    if (IsOpened())
        CloseEntry();

    m_raw = raw;

    if (entry) {
        if (AfterHeader() && entry->GetKey() == m_entry.GetOffset())
            return true;
        // can only open the current entry on a non-seekable stream
        wxCHECK(m_parentSeekable, false);
    }

    m_lasterror = wxSTREAM_READ_ERROR;

    if (entry)
        m_entry = *entry;

    if (m_parentSeekable) {
        if (QuietSeek(*m_parent_i_stream, m_entry.GetOffset())
                == wxInvalidOffset)
            return false;
        if (ReadSignature() != LOCAL_MAGIC) {
            wxLogError(_("bad zipfile offset to entry"));
            return false;
        }
    }

    if (m_parentSeekable || AtHeader()) {
        m_headerSize = m_entry.ReadLocal(*m_parent_i_stream, GetConv());
        if (m_parentSeekable) {
            wxZipEntry *ref = m_weaklinks->GetEntry(m_entry.GetKey());
            if (ref) {
                Copy(ref->m_LocalExtra, m_entry.m_LocalExtra);
                ref->Notify();
                m_weaklinks->RemoveEntry(ref->GetKey());
            }
            if (entry && entry != ref) {
                Copy(entry->m_LocalExtra, m_entry.m_LocalExtra);
                entry->Notify();
            }
        }
    }

    m_lasterror = m_parent_i_stream->GetLastError();
    return IsOk();
}

bool wxZipInputStream::OpenDecompressor(bool raw /*=false*/)
{
    wxASSERT(AfterHeader());

    wxFileOffset compressedSize = m_entry.GetCompressedSize();

    if (raw)
        m_raw = true;

    if (m_raw) {
        if (compressedSize != wxInvalidOffset) {
            m_store->Open(compressedSize);
            m_decomp = m_store;
        } else {
            if (!m_rawin)
                m_rawin = new wxRawInputStream(*m_parent_i_stream);
            m_decomp = m_rawin->Open(OpenDecompressor(m_rawin->GetTee()));
        }
    } else {
        if (compressedSize != wxInvalidOffset &&
                (m_entry.GetMethod() != wxZIP_METHOD_DEFLATE ||
                 wxZlibInputStream::CanHandleGZip())) {
            m_store->Open(compressedSize);
            m_decomp = OpenDecompressor(*m_store);
        } else {
            m_decomp = OpenDecompressor(*m_parent_i_stream);
        }
    }

    m_crcAccumulator = crc32(0, Z_NULL, 0);
    m_lasterror = m_decomp ? m_decomp->GetLastError() : wxSTREAM_READ_ERROR;
    return IsOk();
}

// Can be overriden to add support for additional decompression methods
//
wxInputStream *wxZipInputStream::OpenDecompressor(wxInputStream& stream)
{
    switch (m_entry.GetMethod()) {
        case wxZIP_METHOD_STORE:
            if (m_entry.GetSize() == wxInvalidOffset) {
                wxLogError(_("stored file length not in Zip header"));
                break;
            }
            m_store->Open(m_entry.GetSize());
            return m_store;

        case wxZIP_METHOD_DEFLATE:
            if (!m_inflate)
                m_inflate = new wxZlibInputStream2(stream);
            else
                m_inflate->Open(stream);
            return m_inflate;

        default:
            wxLogError(_("unsupported Zip compression method"));
    }

    return NULL;
}

bool wxZipInputStream::CloseDecompressor(wxInputStream *decomp)
{
    if (decomp && decomp == m_rawin)
        return CloseDecompressor(m_rawin->GetFilterInputStream());
    if (decomp != m_store && decomp != m_inflate)
        delete decomp;
    return true;
}

// Closes the current entry and positions the underlying stream at the start
// of the next entry
//
bool wxZipInputStream::CloseEntry()
{
    if (AtHeader())
        return true;
    if (m_lasterror == wxSTREAM_READ_ERROR)
        return false;

    if (!m_parentSeekable) {
        if (!IsOpened() && !OpenDecompressor(true))
            return false;

        const int BUFSIZE = 8192;
        wxCharBuffer buf(BUFSIZE);
        while (IsOk())
            Read(buf.data(), BUFSIZE);

        m_position += m_headerSize + m_entry.GetCompressedSize();
    }

    if (m_lasterror == wxSTREAM_EOF)
        m_lasterror = wxSTREAM_NO_ERROR;

    CloseDecompressor(m_decomp);
    m_decomp = NULL;
    m_entry = wxZipEntry();
    m_headerSize = 0;
    m_raw = false;

    return IsOk();
}

size_t wxZipInputStream::OnSysRead(void *buffer, size_t size)
{
    if (!IsOpened())
        if ((AtHeader() && !DoOpen()) || !OpenDecompressor())
            m_lasterror = wxSTREAM_READ_ERROR;
    if (!IsOk() || !size)
        return 0;

    size_t count = m_decomp->Read(buffer, size).LastRead();
    if (!m_raw)
        m_crcAccumulator = crc32(m_crcAccumulator, (Byte*)buffer, count);
    m_lasterror = m_decomp->GetLastError();

    if (Eof()) {
        if ((m_entry.GetFlags() & wxZIP_SUMS_FOLLOW) != 0) {
            m_headerSize += m_entry.ReadDescriptor(*m_parent_i_stream);
            wxZipEntry *entry = m_weaklinks->GetEntry(m_entry.GetKey());

            if (entry) {
                entry->SetCrc(m_entry.GetCrc());
                entry->SetCompressedSize(m_entry.GetCompressedSize());
                entry->SetSize(m_entry.GetSize());
                entry->Notify();
            }
        }

        if (!m_raw) {
            m_lasterror = wxSTREAM_READ_ERROR;

            if (m_parent_i_stream->IsOk()) {
                if (m_entry.GetSize() != TellI())
                    wxLogError(_("reading zip stream (entry %s): bad length"),
                               m_entry.GetName().c_str());
                else if (m_crcAccumulator != m_entry.GetCrc())
                    wxLogError(_("reading zip stream (entry %s): bad crc"),
                               m_entry.GetName().c_str());
                else
                    m_lasterror = wxSTREAM_EOF;
            }
        }
    }

    return count;
}

#if 1 //WXWIN_COMPATIBILITY_2_6

// Borrowed from VS's zip stream (c) 1999 Vaclav Slavik
//
wxFileOffset wxZipInputStream::OnSysSeek(wxFileOffset seek, wxSeekMode mode)
{
    // seeking works when the stream is created with the compatibility
    // constructor
    if (!m_allowSeeking)
        return wxInvalidOffset;
    if (!IsOpened())
        if ((AtHeader() && !DoOpen()) || !OpenDecompressor())
            m_lasterror = wxSTREAM_READ_ERROR;
    if (!IsOk())
        return wxInvalidOffset;

    // NB: since ZIP files don't natively support seeking, we have to
    //     implement a brute force workaround -- reading all the data
    //     between current and the new position (or between beginning of
    //     the file and new position...)

    wxFileOffset nextpos;
    wxFileOffset pos = TellI();

    switch ( mode )
    {
        case wxFromCurrent : nextpos = seek + pos; break;
        case wxFromStart : nextpos = seek; break;
        case wxFromEnd : nextpos = GetLength() + seek; break;
        default : nextpos = pos; break; /* just to fool compiler, never happens */
    }

    wxFileOffset toskip wxDUMMY_INITIALIZE(0);
    if ( nextpos >= pos )
    {
        toskip = nextpos - pos;
    }
    else
    {
        wxZipEntry current(m_entry);
        if (!OpenEntry(current))
        {
            m_lasterror = wxSTREAM_READ_ERROR;
            return pos;
        }
        toskip = nextpos;
    }

    if ( toskip > 0 )
    {
        const int BUFSIZE = 4096;
        size_t sz;
        char buffer[BUFSIZE];
        while ( toskip > 0 )
        {
            sz = wx_truncate_cast(size_t, wxMin(toskip, BUFSIZE));
            Read(buffer, sz);
            toskip -= sz;
        }
    }

    pos = nextpos;
    return pos;
}

#endif // WXWIN_COMPATIBILITY_2_6


/////////////////////////////////////////////////////////////////////////////
// Output stream

#include "wx/listimpl.cpp"
WX_DEFINE_LIST(wx__ZipEntryList)

wxZipOutputStream::wxZipOutputStream(wxOutputStream& stream,
                                     int level      /*=-1*/,
                                     wxMBConv& conv /*=wxConvLocal*/)
  : wxArchiveOutputStream(stream, conv),
    m_store(new wxStoredOutputStream(stream)),
    m_deflate(NULL),
    m_backlink(NULL),
    m_initialData(new char[OUTPUT_LATENCY]),
    m_initialSize(0),
    m_pending(NULL),
    m_raw(false),
    m_headerOffset(0),
    m_headerSize(0),
    m_entrySize(0),
    m_comp(NULL),
    m_level(level),
    m_offsetAdjustment(wxInvalidOffset)
{
}

wxZipOutputStream::~wxZipOutputStream()
{
    Close();
    WX_CLEAR_LIST(wx__ZipEntryList, m_entries);
    delete m_store;
    delete m_deflate;
    delete m_pending;
    delete [] m_initialData;
    if (m_backlink)
        m_backlink->Release(this);
}

bool wxZipOutputStream::PutNextEntry(
    const wxString& name,
    const wxDateTime& dt /*=wxDateTime::Now()*/,
    wxFileOffset size    /*=wxInvalidOffset*/)
{
    return PutNextEntry(new wxZipEntry(name, dt, size));
}

bool wxZipOutputStream::PutNextDirEntry(
    const wxString& name,
    const wxDateTime& dt /*=wxDateTime::Now()*/)
{
    wxZipEntry *entry = new wxZipEntry(name, dt);
    entry->SetIsDir();
    return PutNextEntry(entry);
}

bool wxZipOutputStream::CopyEntry(wxZipEntry *entry,
                                  wxZipInputStream& inputStream)
{
    wx__ZipEntryPtr e(entry);

    return
        inputStream.DoOpen(e.get(), true) &&
        DoCreate(e.release(), true) &&
        Write(inputStream).IsOk() && inputStream.Eof();
}

bool wxZipOutputStream::PutNextEntry(wxArchiveEntry *entry)
{
    wxZipEntry *zipEntry = wxStaticCast(entry, wxZipEntry);
    if (!zipEntry)
        delete entry;
    return PutNextEntry(zipEntry);
}

bool wxZipOutputStream::CopyEntry(wxArchiveEntry *entry,
                                  wxArchiveInputStream& stream)
{
    wxZipEntry *zipEntry = wxStaticCast(entry, wxZipEntry);

    if (!zipEntry || !stream.OpenEntry(*zipEntry)) {
        delete entry;
        return false;
    }

    return CopyEntry(zipEntry, wx_static_cast(wxZipInputStream&, stream));
}

bool wxZipOutputStream::CopyArchiveMetaData(wxZipInputStream& inputStream)
{
    m_Comment = inputStream.GetComment();
    if (m_backlink)
        m_backlink->Release(this);
    m_backlink = inputStream.MakeLink(this);
    return true;
}

bool wxZipOutputStream::CopyArchiveMetaData(wxArchiveInputStream& stream)
{
    return CopyArchiveMetaData(wx_static_cast(wxZipInputStream&, stream));
}

void wxZipOutputStream::SetLevel(int level)
{
    if (level != m_level) {
        if (m_comp != m_deflate)
            delete m_deflate;
        m_deflate = NULL;
        m_level = level;
    }
}

bool wxZipOutputStream::DoCreate(wxZipEntry *entry, bool raw /*=false*/)
{
    CloseEntry();

    m_pending = entry;
    if (!m_pending)
        return false;

    // write the signature bytes right away
    wxDataOutputStream ds(*m_parent_o_stream);
    ds << LOCAL_MAGIC;

    // and if this is the first entry test for seekability
    if (m_headerOffset == 0 && m_parent_o_stream->IsSeekable()) {
#if wxUSE_LOG
        bool logging = wxLog::IsEnabled();
        wxLogNull nolog;
#endif // wxUSE_LOG
        wxFileOffset here = m_parent_o_stream->TellO();

        if (here != wxInvalidOffset && here >= 4) {
            if (m_parent_o_stream->SeekO(here - 4) == here - 4) {
                m_offsetAdjustment = here - 4;
#if wxUSE_LOG
                wxLog::EnableLogging(logging);
#endif // wxUSE_LOG
                m_parent_o_stream->SeekO(here);
            }
        }
    }

    m_pending->SetOffset(m_headerOffset);

    m_crcAccumulator = crc32(0, Z_NULL, 0);

    if (raw)
        m_raw = true;

    m_lasterror = wxSTREAM_NO_ERROR;
    return true;
}

// Can be overriden to add support for additional compression methods
//
wxOutputStream *wxZipOutputStream::OpenCompressor(
    wxOutputStream& stream,
    wxZipEntry& entry,
    const Buffer bufs[])
{
    if (entry.GetMethod() == wxZIP_METHOD_DEFAULT) {
        if (GetLevel() == 0
                && (IsParentSeekable()
                    || entry.GetCompressedSize() != wxInvalidOffset
                    || entry.GetSize() != wxInvalidOffset)) {
            entry.SetMethod(wxZIP_METHOD_STORE);
        } else {
            int size = 0;
            for (int i = 0; bufs[i].m_data; ++i)
                size += bufs[i].m_size;
            entry.SetMethod(size <= 6 ?
                            wxZIP_METHOD_STORE : wxZIP_METHOD_DEFLATE);
        }
    }

    switch (entry.GetMethod()) {
        case wxZIP_METHOD_STORE:
            if (entry.GetCompressedSize() == wxInvalidOffset)
                entry.SetCompressedSize(entry.GetSize());
            return m_store;

        case wxZIP_METHOD_DEFLATE:
        {
            int defbits = wxZIP_DEFLATE_NORMAL;
            switch (GetLevel()) {
                case 0: case 1:
                    defbits = wxZIP_DEFLATE_SUPERFAST;
                    break;
                case 2: case 3: case 4:
                    defbits = wxZIP_DEFLATE_FAST;
                    break;
                case 8: case 9:
                    defbits = wxZIP_DEFLATE_EXTRA;
                    break;
            }
            entry.SetFlags((entry.GetFlags() & ~wxZIP_DEFLATE_MASK) |
                            defbits | wxZIP_SUMS_FOLLOW);

            if (!m_deflate)
                m_deflate = new wxZlibOutputStream2(stream, GetLevel());
            else
                m_deflate->Open(stream);

            return m_deflate;
        }

        default:
            wxLogError(_("unsupported Zip compression method"));
    }

    return NULL;
}

bool wxZipOutputStream::CloseCompressor(wxOutputStream *comp)
{
    if (comp == m_deflate)
        m_deflate->Close();
    else if (comp != m_store)
        delete comp;
    return true;
}

// This is called when OUPUT_LATENCY bytes has been written to the
// wxZipOutputStream to actually create the zip entry.
//
void wxZipOutputStream::CreatePendingEntry(const void *buffer, size_t size)
{
    wxASSERT(IsOk() && m_pending && !m_comp);
    wx__ZipEntryPtr spPending(m_pending);
    m_pending = NULL;

    Buffer bufs[] = {
        { m_initialData, m_initialSize },
        { (const char*)buffer, size },
        { NULL, 0 }
    };

    if (m_raw)
        m_comp = m_store;
    else
        m_comp = OpenCompressor(*m_store, *spPending,
                                m_initialSize ? bufs : bufs + 1);

    if (IsParentSeekable()
        || (spPending->m_Crc
            && spPending->m_CompressedSize != wxInvalidOffset
            && spPending->m_Size != wxInvalidOffset))
        spPending->m_Flags &= ~wxZIP_SUMS_FOLLOW;
    else
        if (spPending->m_CompressedSize != wxInvalidOffset)
            spPending->m_Flags |= wxZIP_SUMS_FOLLOW;

    m_headerSize = spPending->WriteLocal(*m_parent_o_stream, GetConv());
    m_lasterror = m_parent_o_stream->GetLastError();

    if (IsOk()) {
        m_entries.push_back(spPending.release());
        OnSysWrite(m_initialData, m_initialSize);
    }

    m_initialSize = 0;
}

// This is called to write out the zip entry when Close has been called
// before OUTPUT_LATENCY bytes has been written to the wxZipOutputStream.
//
void wxZipOutputStream::CreatePendingEntry()
{
    wxASSERT(IsOk() && m_pending && !m_comp);
    wx__ZipEntryPtr spPending(m_pending);
    m_pending = NULL;
    m_lasterror = wxSTREAM_WRITE_ERROR;

    if (!m_raw) {
        // Initially compresses the data to memory, then fall back to 'store'
        // if the compressor makes the data larger rather than smaller.
        wxMemoryOutputStream mem;
        Buffer bufs[] = { { m_initialData, m_initialSize }, { NULL, 0 } };
        wxOutputStream *comp = OpenCompressor(mem, *spPending, bufs);

        if (!comp)
            return;
        if (comp != m_store) {
            bool ok = comp->Write(m_initialData, m_initialSize).IsOk();
            CloseCompressor(comp);
            if (!ok)
                return;
        }

        m_entrySize = m_initialSize;
        m_crcAccumulator = crc32(0, (Byte*)m_initialData, m_initialSize);

        if (mem.GetSize() > 0 && mem.GetSize() < m_initialSize) {
            m_initialSize = mem.GetSize();
            mem.CopyTo(m_initialData, m_initialSize);
        } else {
            spPending->SetMethod(wxZIP_METHOD_STORE);
        }

        spPending->SetSize(m_entrySize);
        spPending->SetCrc(m_crcAccumulator);
        spPending->SetCompressedSize(m_initialSize);
    }

    spPending->m_Flags &= ~wxZIP_SUMS_FOLLOW;
    m_headerSize = spPending->WriteLocal(*m_parent_o_stream, GetConv());

    if (m_parent_o_stream->IsOk()) {
        m_entries.push_back(spPending.release());
        m_comp = m_store;
        m_store->Write(m_initialData, m_initialSize);
    }

    m_initialSize = 0;
    m_lasterror = m_parent_o_stream->GetLastError();
}

// Write the 'central directory' and the 'end-central-directory' records.
//
bool wxZipOutputStream::Close()
{
    CloseEntry();

    if (m_lasterror == wxSTREAM_WRITE_ERROR || m_entries.size() == 0)
        return false;

    wxZipEndRec endrec;

    endrec.SetEntriesHere(m_entries.size());
    endrec.SetTotalEntries(m_entries.size());
    endrec.SetOffset(m_headerOffset);
    endrec.SetComment(m_Comment);

    wx__ZipEntryList::iterator it;
    wxFileOffset size = 0;

    for (it = m_entries.begin(); it != m_entries.end(); ++it) {
        size += (*it)->WriteCentral(*m_parent_o_stream, GetConv());
        delete *it;
    }
    m_entries.clear();

    endrec.SetSize(size);
    endrec.Write(*m_parent_o_stream, GetConv());

    m_lasterror = m_parent_o_stream->GetLastError();
    if (!IsOk())
        return false;
    m_lasterror = wxSTREAM_EOF;
    return true;
}

// Finish writing the current entry
//
bool wxZipOutputStream::CloseEntry()
{
    if (IsOk() && m_pending)
        CreatePendingEntry();
    if (!IsOk())
        return false;
    if (!m_comp)
        return true;

    CloseCompressor(m_comp);
    m_comp = NULL;

    wxFileOffset compressedSize = m_store->TellO();

    wxZipEntry& entry = *m_entries.back();

    // When writing raw the crc and size can't be checked
    if (m_raw) {
        m_crcAccumulator = entry.GetCrc();
        m_entrySize = entry.GetSize();
    }

    // Write the sums in the trailing 'data descriptor' if necessary
    if (entry.m_Flags & wxZIP_SUMS_FOLLOW) {
        wxASSERT(!IsParentSeekable());
        m_headerOffset +=
            entry.WriteDescriptor(*m_parent_o_stream, m_crcAccumulator,
                                  compressedSize, m_entrySize);
        m_lasterror = m_parent_o_stream->GetLastError();
    }

    // If the local header didn't have the correct crc and size written to
    // it then seek back and fix it
    else if (m_crcAccumulator != entry.GetCrc()
            || m_entrySize != entry.GetSize()
            || compressedSize != entry.GetCompressedSize())
    {
        if (IsParentSeekable()) {
            wxFileOffset here = m_parent_o_stream->TellO();
            wxFileOffset headerOffset = m_headerOffset + m_offsetAdjustment;
            m_parent_o_stream->SeekO(headerOffset + SUMS_OFFSET);
            entry.WriteDescriptor(*m_parent_o_stream, m_crcAccumulator,
                                  compressedSize, m_entrySize);
            m_parent_o_stream->SeekO(here);
            m_lasterror = m_parent_o_stream->GetLastError();
        } else {
            m_lasterror = wxSTREAM_WRITE_ERROR;
        }
    }

    m_headerOffset += m_headerSize + compressedSize;
    m_headerSize = 0;
    m_entrySize = 0;
    m_store->Close();
    m_raw = false;

    if (IsOk())
        m_lasterror = m_parent_o_stream->GetLastError();
    else
        wxLogError(_("error writing zip entry '%s': bad crc or length"),
                   entry.GetName().c_str());
    return IsOk();
}

void wxZipOutputStream::Sync()
{
    if (IsOk() && m_pending)
        CreatePendingEntry(NULL, 0);
    if (!m_comp)
        m_lasterror = wxSTREAM_WRITE_ERROR;
    if (IsOk()) {
        m_comp->Sync();
        m_lasterror = m_comp->GetLastError();
    }
}

size_t wxZipOutputStream::OnSysWrite(const void *buffer, size_t size)
{
    if (IsOk() && m_pending) {
        if (m_initialSize + size < OUTPUT_LATENCY) {
            memcpy(m_initialData + m_initialSize, buffer, size);
            m_initialSize += size;
            return size;
        } else {
            CreatePendingEntry(buffer, size);
        }
    }

    if (!m_comp)
        m_lasterror = wxSTREAM_WRITE_ERROR;
    if (!IsOk() || !size)
        return 0;

    if (m_comp->Write(buffer, size).LastWrite() != size)
        m_lasterror = wxSTREAM_WRITE_ERROR;
    m_crcAccumulator = crc32(m_crcAccumulator, (Byte*)buffer, size);
    m_entrySize += m_comp->LastWrite();

    return m_comp->LastWrite();
}

#endif // wxUSE_ZLIB && wxUSE_STREAMS && wxUSE_ZIPSTREAM
