X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/9d1f22e7c2002395cc609c1b51fe8351cfef2186..a66bf817a6ba833ad47cefc9fa8948ef8045a59d:/src/common/zipstrm.cpp?ds=sidebyside diff --git a/src/common/zipstrm.cpp b/src/common/zipstrm.cpp index 9401ef1c1a..4902738a07 100644 --- a/src/common/zipstrm.cpp +++ b/src/common/zipstrm.cpp @@ -1,160 +1,2439 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: zipstream.cpp -// Purpose: input stream for ZIP archive access -// Author: Vaclav Slavik -// Copyright: (c) 1999 Vaclav Slavik -// Licence: wxWindows Licence +// Name: src/common/zipstrm.cpp +// Purpose: Streams for Zip files +// Author: Mike Wetherell +// RCS-ID: $Id$ +// Copyright: (c) Mike Wetherell +// Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// -#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) -#pragma implementation "zipstrm.h" +// For compilers that support precompilation, includes "wx.h". +#include "wx/wxprec.h" + +#ifdef __BORLANDC__ + #pragma hdrstop +#endif + +#if wxUSE_ZIPSTREAM + +#include "wx/zipstrm.h" + +#ifndef WX_PRECOMP + #include "wx/hashmap.h" + #include "wx/intl.h" + #include "wx/log.h" + #include "wx/utils.h" +#endif + +#include "wx/datstrm.h" +#include "wx/zstream.h" +#include "wx/mstream.h" +#include "wx/scopedptr.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) + + +///////////////////////////////////////////////////////////////////////////// +// Helpers + +// read a string of a given length +// +static wxString ReadString(wxInputStream& stream, wxUint16 len, wxMBConv& conv) +{ + if (len == 0) + return wxEmptyString; + +#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 -// For compilers that support precompilation, includes "wx.h". -#include "wx/wxprec.h" + 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]; +} + +// Decode a little endian wxUint16 number from a character array +// +static inline wxUint16 CrackUint16(const char *m) +{ + const unsigned char *n = (const unsigned char*)m; + return (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) +{ +#if wxUSE_LOG + 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 +} + + +///////////////////////////////////////////////////////////////////////////// +// Class factory + +static wxZipClassFactory g_wxZipClassFactory; + +wxZipClassFactory::wxZipClassFactory() +{ + if (this == &g_wxZipClassFactory) + PushFront(); +} + +const wxChar * const * +wxZipClassFactory::GetProtocols(wxStreamProtocolType type) const +{ + static const wxChar *protocols[] = { wxT("zip"), NULL }; + static const wxChar *mimetypes[] = { wxT("application/zip"), NULL }; + static const wxChar *fileexts[] = { wxT(".zip"), wxT(".htb"), NULL }; + static const wxChar *empty[] = { NULL }; + + switch (type) { + case wxSTREAM_PROTOCOL: return protocols; + case wxSTREAM_MIMETYPE: return mimetypes; + case wxSTREAM_FILEEXT: return fileexts; + default: return empty; + } +} + + +///////////////////////////////////////////////////////////////////////////// +// Read a zip header + +class wxZipHeader +{ +public: + wxZipHeader(wxInputStream& stream, size_t size); + + inline wxUint8 Read8(); + inline wxUint16 Read16(); + inline wxUint32 Read32(); + + const char *GetData() const { return m_data; } + size_t GetSize() const { return m_size; } + operator bool() const { return m_ok; } + + size_t Seek(size_t pos) { m_pos = pos; return m_pos; } + size_t Skip(size_t size) { m_pos += size; return m_pos; } + + wxZipHeader& operator>>(wxUint8& n) { n = Read8(); return *this; } + wxZipHeader& operator>>(wxUint16& n) { n = Read16(); return *this; } + wxZipHeader& operator>>(wxUint32& n) { n = Read32(); return *this; } + +private: + char m_data[64]; + size_t m_size; + size_t m_pos; + bool m_ok; +}; + +wxZipHeader::wxZipHeader(wxInputStream& stream, size_t size) + : m_size(0), + m_pos(0), + m_ok(false) +{ + wxCHECK_RET(size <= sizeof(m_data), wxT("buffer too small")); + m_size = stream.Read(m_data, size).LastRead(); + m_ok = m_size == size; +} + +inline wxUint8 wxZipHeader::Read8() +{ + wxASSERT(m_pos < m_size); + return m_data[m_pos++]; +} + +inline wxUint16 wxZipHeader::Read16() +{ + wxASSERT(m_pos + 2 <= m_size); + wxUint16 n = CrackUint16(m_data + m_pos); + m_pos += 2; + return n; +} + +inline wxUint32 wxZipHeader::Read32() +{ + wxASSERT(m_pos + 4 <= m_size); + wxUint32 n = CrackUint32(m_data + m_pos); + m_pos += 4; + return n; +} + + +///////////////////////////////////////////////////////////////////////////// +// 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; + + wxDECLARE_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 (count < size) + m_lasterror = m_pos == m_len ? wxSTREAM_EOF : 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; + wxDECLARE_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; + + wxDECLARE_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(); + if (count < size) + 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; + + wxDECLARE_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; + + wxSUPPRESS_GCC_PRIVATE_DTOR_WARNING(wxZipMemory) +}; + +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, wxOffsetZipEntryMap_); + +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: + ~wxZipWeakLinks() { wxASSERT(IsEmpty()); } + + typedef wxOffsetZipEntryMap_::key_type key_type; + + int m_ref; + wxOffsetZipEntryMap_ m_entries; + + wxSUPPRESS_GCC_PRIVATE_DTOR_WARNING(wxZipWeakLinks) +}; + +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 +{ + wxOffsetZipEntryMap_::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 + wxT("\\") : m_Name); + for (size_t i = 0; i < name.length(); i++) + if (name[i] == wxT('/')) + name[i] = wxT('\\'); + return name; + } + + case wxPATH_UNIX: + return isDir ? m_Name + wxT("/") : 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, wxT("./")) == 0) + internal.erase(0, 2); + if (internal == wxT(".") || internal == wxT("..")) + 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 = ¬ifier; + 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; + + wxZipHeader ds(stream, LOCAL_SIZE - 4); + if (!ds) + return 0; + + 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 (stream.LastRead() != nameLen + 0u) + return 0; + + if (extraLen || GetLocalExtraLen()) { + Unique(m_LocalExtra, extraLen); + if (extraLen) { + stream.Read(m_LocalExtra->GetData(), extraLen); + if (stream.LastRead() != extraLen + 0u) + return 0; + } + } + + return LOCAL_SIZE + nameLen + extraLen; +} + +size_t wxZipEntry::WriteLocal(wxOutputStream& stream, wxMBConv& conv) const +{ + wxString unixName = GetName(wxPATH_UNIX); + const wxWX2MBbuf name_buf = unixName.mb_str(conv); + 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; + + wxZipHeader ds(stream, CENTRAL_SIZE - 4); + if (!ds) + return 0; + + 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 (stream.LastRead() != nameLen + 0u) + return 0; + + if (extraLen || GetExtraLen()) { + Unique(m_Extra, extraLen); + if (extraLen) { + stream.Read(m_Extra->GetData(), extraLen); + if (stream.LastRead() != extraLen + 0u) + return 0; + } + } + + if (commentLen) { + m_Comment = ReadString(stream, commentLen, conv); + if (stream.LastRead() != commentLen + 0u) + return 0; + } 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 = unixName.mb_str(conv); + const char *name = name_buf; + if (!name) name = ""; + wxUint16 nameLen = wx_truncate_cast(wxUint16, strlen(name)); + + const wxWX2MBbuf comment_buf = m_Comment.mb_str(conv); + 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) +{ + wxZipHeader ds(stream, SUMS_SIZE); + if (!ds) + return 0; + + 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) + { + wxZipHeader buf(stream, 8); + wxUint32 u1 = buf.GetSize() >= 4 ? buf.Read32() : (wxUint32)LOCAL_MAGIC; + wxUint32 u2 = buf.GetSize() == 8 ? buf.Read32() : 0; + + // 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! + if (buf.GetSize() > 0) + stream.Ungetch(buf.GetData(), buf.GetSize()); + } + else + { + // it's an info-zip record as expected + if (buf.GetSize() > 4) + stream.Ungetch(buf.GetData() + 4, buf.GetSize() - 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 = m_Comment.mb_str(conv); + 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) +{ + wxZipHeader ds(stream, END_SIZE - 4); + if (!ds) + return false; + + 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.LastRead() != commentLen + 0u) + return false; + } + + if (m_DiskNumber != 0 || m_StartDisk != 0 || + m_EntriesHere != m_TotalEntries) + { + wxLogWarning(_("assuming this is a multi-part zip concatenated")); + } + + return true; +} + + +///////////////////////////////////////////////////////////////////////////// +// 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; + + wxSUPPRESS_GCC_PRIVATE_DTOR_WARNING(wxZipStreamLink) +}; + + +///////////////////////////////////////////////////////////////////////////// +// Input stream + +// leave the default wxZipEntryPtr free for users +wxDECLARE_SCOPED_PTR(wxZipEntry, wxZipEntryPtr_) +wxDEFINE_SCOPED_PTR (wxZipEntry, wxZipEntryPtr_) + +// constructor +// +wxZipInputStream::wxZipInputStream(wxInputStream& stream, + wxMBConv& conv /*=wxConvLocal*/) + : wxArchiveInputStream(stream, conv) +{ + Init(); +} + +wxZipInputStream::wxZipInputStream(wxInputStream *stream, + wxMBConv& conv /*=wxConvLocal*/) + : wxArchiveInputStream(stream, conv) +{ + Init(); +} + +#if WXWIN_COMPATIBILITY_2_6 && wxUSE_FFILE + +// 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; + wxFFileInputStream *ffile; + ffile = static_cast(m_parent_i_stream); + wxZipEntryPtr_ entry; + + if (ffile->IsOk()) { + 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 && wxUSE_FFILE + +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(); +#if WXWIN_COMPATIBILITY_2_6 + m_allowSeeking = false; +#endif +} + +wxZipInputStream::~wxZipInputStream() +{ + CloseDecompressor(m_decomp); + + delete m_store; + delete m_inflate; + delete m_rawin; + + 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(); + if (pos != wxInvalidOffset) + 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())) + return false; + + m_TotalEntries = endrec.GetTotalEntries(); + m_Comment = endrec.GetComment(); + + wxUint32 magic = m_TotalEntries ? CENTRAL_MAGIC : END_MAGIC; + + // 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() == magic) { + m_signature = 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() == magic) { + m_signature = 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; -#ifdef __BORLANDC__ - #pragma hdrstop -#endif + // 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; + } -#if wxUSE_STREAMS && wxUSE_ZIPSTREAM && wxUSE_ZLIB + 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; -#include "wx/log.h" -#include "wx/intl.h" -#include "wx/stream.h" -#include "wx/wfstream.h" -#include "wx/zipstrm.h" -#include "wx/utils.h" + // 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); -/* Not the right solution (paths in makefiles) but... */ -#ifdef __BORLANDC__ -#include "../common/unzip.h" -#else -#include "unzip.h" -#endif + 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; +} -wxZipInputStream::wxZipInputStream(const wxString& archive, const wxString& file) : wxInputStream() +wxZipEntry *wxZipInputStream::GetNextEntry() { - unz_file_info zinfo; + if (m_position == wxInvalidOffset) + if (!LoadEndRecord()) + return NULL; - m_Pos = 0; - m_Size = 0; - m_Archive = (void*) unzOpen(archive.fn_str()); - if (m_Archive == NULL) - { - m_lasterror = wxSTREAM_READ_ERROR; - return; + m_lasterror = m_parentSeekable ? ReadCentral() : ReadLocal(); + if (!IsOk()) + return NULL; + + wxZipEntryPtr_ 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; } - // TODO what encoding does ZIP use? - if (unzLocateFile((unzFile)m_Archive, file.ToAscii(), 0) != UNZ_OK) - { - m_lasterror = wxSTREAM_READ_ERROR; - return; + + if (QuietSeek(*m_parent_i_stream, m_position + 4) == wxInvalidOffset) + return wxSTREAM_READ_ERROR; + + size_t size = m_entry.ReadCentral(*m_parent_i_stream, GetConv()); + if (!size) { + m_signature = 0; + return wxSTREAM_READ_ERROR; } - unzGetCurrentFileInfo((unzFile)m_Archive, &zinfo, (char*) NULL, 0, (void*) NULL, 0, (char*) NULL, 0); + m_position += size; + m_signature = ReadSignature(); - if (unzOpenCurrentFile((unzFile)m_Archive) != UNZ_OK) - { - m_lasterror = wxSTREAM_READ_ERROR; - return; + 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; + + size_t size = m_entry.ReadCentral(*m_parent_i_stream, GetConv()); + m_position += size; + m_signature = 0; + if (!size) + 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) { + 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_headerSize) { + m_TotalEntries++; + return wxSTREAM_NO_ERROR; + } } - m_Size = (size_t)zinfo.uncompressed_size; + + wxLogError(_("error reading zip local header")); + return wxSTREAM_READ_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; +} -wxZipInputStream::~wxZipInputStream() +// Open an entry +// +bool wxZipInputStream::DoOpen(wxZipEntry *entry, bool raw) { - if (m_Archive) - { - if (m_Size != 0) - unzCloseCurrentFile((unzFile)m_Archive); - unzClose((unzFile)m_Archive); + 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_headerSize && 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(); + } + } + } + + if (m_headerSize) + m_lasterror = wxSTREAM_NO_ERROR; + 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(); } -bool wxZipInputStream::Eof() const +// Can be overridden to add support for additional decompression methods +// +wxInputStream *wxZipInputStream::OpenDecompressor(wxInputStream& stream) { - wxASSERT_MSG( m_Pos <= (off_t)m_Size, - _T("wxZipInputStream: invalid current position") ); + 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 m_Pos >= (off_t)m_Size; + 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; +} -size_t wxZipInputStream::OnSysRead(void *buffer, size_t bufsize) +// Closes the current entry and positions the underlying stream at the start +// of the next entry +// +bool wxZipInputStream::CloseEntry() { - wxASSERT_MSG( m_Pos <= (off_t)m_Size, - _T("wxZipInputStream: invalid current position") ); + if (AtHeader()) + return true; + if (m_lasterror == wxSTREAM_READ_ERROR) + return false; - if ( m_Pos >= (off_t)m_Size ) - { - m_lasterror = wxSTREAM_EOF; - return 0; + 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_Pos + bufsize > m_Size) - bufsize = m_Size - m_Pos; + if (m_lasterror == wxSTREAM_EOF) + m_lasterror = wxSTREAM_NO_ERROR; - unzReadCurrentFile((unzFile)m_Archive, buffer, bufsize); - m_Pos += bufsize; + CloseDecompressor(m_decomp); + m_decomp = NULL; + m_entry = wxZipEntry(); + m_headerSize = 0; + m_raw = false; - return bufsize; + 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); + if (count < size) + 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_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; +} -off_t wxZipInputStream::OnSysSeek(off_t seek, wxSeekMode mode) +#if WXWIN_COMPATIBILITY_2_6 + +// Borrowed from VS's zip stream (c) 1999 Vaclav Slavik +// +wxFileOffset wxZipInputStream::OnSysSeek(wxFileOffset seek, wxSeekMode mode) { - // NB: since ZIP files don't natively support seeking, we have to + // 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 + // between current and the new position (or between beginning of // the file and new position...) - off_t nextpos; + wxFileOffset nextpos; + wxFileOffset pos = TellI(); switch ( mode ) { - case wxFromCurrent : nextpos = seek + m_Pos; break; + case wxFromCurrent : nextpos = seek + pos; break; case wxFromStart : nextpos = seek; break; - case wxFromEnd : nextpos = m_Size - 1 + seek; break; - default : nextpos = m_Pos; break; /* just to fool compiler, never happens */ + case wxFromEnd : nextpos = GetLength() + seek; break; + default : nextpos = pos; break; /* just to fool compiler, never happens */ } - size_t toskip; - if ( nextpos > m_Pos ) + wxFileOffset toskip wxDUMMY_INITIALIZE(0); + if ( nextpos >= pos ) { - toskip = nextpos - m_Pos; + toskip = nextpos - pos; } else { - unzCloseCurrentFile((unzFile)m_Archive); - if (unzOpenCurrentFile((unzFile)m_Archive) != UNZ_OK) + wxZipEntry current(m_entry); + if (!OpenEntry(current)) { m_lasterror = wxSTREAM_READ_ERROR; - return m_Pos; + return pos; } toskip = nextpos; } - + if ( toskip > 0 ) { - const size_t BUFSIZE = 4096; + const int BUFSIZE = 4096; size_t sz; char buffer[BUFSIZE]; while ( toskip > 0 ) { - sz = wxMin(toskip, BUFSIZE); - unzReadCurrentFile((unzFile)m_Archive, buffer, sz); + sz = wx_truncate_cast(size_t, wxMin(toskip, BUFSIZE)); + Read(buffer, sz); toskip -= sz; } } - m_Pos = nextpos; - return m_Pos; + pos = nextpos; + return pos; } -#endif - // wxUSE_STREAMS && wxUSE_ZIPSTREAM && wxUSE_ZLIB +#endif // WXWIN_COMPATIBILITY_2_6 + + +///////////////////////////////////////////////////////////////////////////// +// Output stream + +#include "wx/listimpl.cpp" +WX_DEFINE_LIST(wxZipEntryList_) + +wxZipOutputStream::wxZipOutputStream(wxOutputStream& stream, + int level /*=-1*/, + wxMBConv& conv /*=wxConvLocal*/) + : wxArchiveOutputStream(stream, conv) +{ + Init(level); +} + +wxZipOutputStream::wxZipOutputStream(wxOutputStream *stream, + int level /*=-1*/, + wxMBConv& conv /*=wxConvLocal*/) + : wxArchiveOutputStream(stream, conv) +{ + Init(level); +} + +void wxZipOutputStream::Init(int level) +{ + m_store = new wxStoredOutputStream(*m_parent_o_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; + m_endrecWritten = false; +} + +wxZipOutputStream::~wxZipOutputStream() +{ + Close(); + WX_CLEAR_LIST(wxZipEntryList_, 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) +{ + wxZipEntryPtr_ 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, static_cast(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(static_cast(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 overridden 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); + wxZipEntryPtr_ 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); + wxZipEntryPtr_ 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 && m_endrecWritten)) + { + wxFilterOutputStream::Close(); + return false; + } + + wxZipEndRec endrec; + + endrec.SetEntriesHere(m_entries.size()); + endrec.SetTotalEntries(m_entries.size()); + endrec.SetOffset(m_headerOffset); + endrec.SetComment(m_Comment); + + wxZipEntryList_::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(); + m_endrecWritten = true; + + if (!wxFilterOutputStream::Close() || !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_ZIPSTREAM