X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/999836aacade840a5d9a0cbb5fad7b1d4de62c93..4ac725a3b3cc829e39c91173aaeb61132d4a231c:/src/common/zipstrm.cpp?ds=inline diff --git a/src/common/zipstrm.cpp b/src/common/zipstrm.cpp index d3053e30ff..0242e35799 100644 --- a/src/common/zipstrm.cpp +++ b/src/common/zipstrm.cpp @@ -1,13 +1,14 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: zipstream.cpp -// Purpose: input stream for ZIP archive access -// Author: Vaclav Slavik -// Copyright: (c) 1999 Vaclav Slavik -// Licence: wxWindows Licence +// Name: 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" + #pragma implementation "zipstrm.h" #endif // For compilers that support precompilation, includes "wx.h". @@ -17,127 +18,1763 @@ #pragma hdrstop #endif -#if wxUSE_STREAMS && wxUSE_ZIPSTREAM && wxUSE_ZLIB +#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 "wx/html/forcelnk.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) + + +///////////////////////////////////////////////////////////////////////////// +// 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 = wxMin(size, (size_t)(m_len - m_pos)); + 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->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() { delete m_data; } + + 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: + + 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, _wxOffsetZipEntryMap); + +class wxZipWeakLinks +{ +public: + wxZipWeakLinks() : m_ref(1) { } + ~wxZipWeakLinks() { wxASSERT(IsEmpty()); } + + 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((_wxOffsetZipEntryMap::key_type)key); } + wxZipEntry *GetEntry(wxFileOffset key) const; + bool IsEmpty() const { return m_entries.empty(); } + +private: + + int m_ref; + _wxOffsetZipEntryMap m_entries; +}; + +wxZipWeakLinks *wxZipWeakLinks::AddEntry(wxZipEntry *entry, wxFileOffset key) +{ + m_entries[(_wxOffsetZipEntryMap::key_type)key] = entry; + m_ref++; + return this; +} + +wxZipEntry *wxZipWeakLinks::GetEntry(wxFileOffset key) const +{ + _wxOffsetZipEntryMap::const_iterator it = + m_entries.find((_wxOffsetZipEntryMap::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(); + + 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 = ¬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; + + 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 = (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 ? (wxUint32)m_CompressedSize : 0); + ds.Write32(m_Size != wxInvalidOffset ? (wxUint32)m_Size : 0); + + ds << nameLen; + wxUint16 extraLen = (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 = (wxUint16)strlen(name); + + const wxWX2MBbuf comment_buf = conv.cWX2MB(m_Comment); + const char *comment = comment_buf; + if (!comment) comment = ""; + wxUint16 commentLen = (wxUint16)strlen(comment); + + wxUint16 extraLen = (wxUint16)GetExtraLen(); + + wxDataOutputStream ds(stream); + + ds << CENTRAL_MAGIC << m_VersionMadeBy << m_SystemMadeBy; + + ds.Write16((wxUint16)GetVersionNeeded()); + ds.Write16((wxUint16)GetFlags()); + ds.Write16((wxUint16)GetMethod()); + ds.Write32(GetDateTime().GetAsDOS()); + ds.Write32(GetCrc()); + ds.Write32((wxUint32)GetCompressedSize()); + ds.Write32((wxUint32)GetSize()); + ds.Write16(nameLen); + ds.Write16(extraLen); + + ds << commentLen << m_DiskStart << m_InternalAttributes + << m_ExternalAttributes << (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 = (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((wxUint32)compressedSize); + ds.Write32((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 = (wxUint16)num; } + void SetStartDisk(int num) { m_StartDisk = (wxUint16)num; } + void SetEntriesHere(int num) { m_EntriesHere = (wxUint16)num; } + void SetTotalEntries(int num) { m_TotalEntries = (wxUint16)num; } + void SetSize(wxFileOffset size) { m_Size = (wxUint32)size; } + void SetOffset(wxFileOffset offset) { m_Offset = (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() { } + + 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: + + int m_ref; + wxZipOutputStream *m_stream; +}; + + +///////////////////////////////////////////////////////////////////////////// +// Input stream + +wxDECLARE_SCOPED_PTR(wxZipEntry, _wxZipEntryPtr) +wxDEFINE_SCOPED_PTR (wxZipEntry, _wxZipEntryPtr) + +// constructor +// +wxZipInputStream::wxZipInputStream(wxInputStream& stream, + wxMBConv& conv /*=wxConvLocal*/) + : wxArchiveInputStream(stream, conv) +{ + m_ffile = NULL; + Init(); +} + +// Compatibility constructor +// +wxZipInputStream::wxZipInputStream(const wxString& archive, + const wxString& file) + : wxArchiveInputStream(OpenFile(archive), wxConvLocal) +{ + // no error messages + wxLogNull nolog; + Init(); + _wxZipEntryPtr 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; + m_ffile = new wxFFileInputStream(archive); + return *m_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(); +} + +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; -#include "wx/log.h" -#include "wx/intl.h" -#include "wx/stream.h" -#include "wx/wfstream.h" -#include "wx/zipstrm.h" -#include "wx/utils.h" + // 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; + } + } -/* Not the right solution (paths in makefiles) but... */ -#ifdef __BORLANDC__ -#include "../common/unzip.h" -#else -#include "unzip.h" -#endif + 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(); -wxZipInputStream::wxZipInputStream(const wxString& archive, const wxString& file) : wxInputStream() + // 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() { - unz_file_info zinfo; + // 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_Pos = 0; - m_Size = 0; - m_Archive = (void*) unzOpen(archive.mb_str()); - if (m_Archive == NULL) - { - m_lasterror = wxSTREAM_READ_ERROR; - return; + 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 = (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; + } + } } - if (unzLocateFile((unzFile)m_Archive, file.mb_str(), 0) != UNZ_OK) - { - m_lasterror = wxSTREAM_READ_ERROR; - return; + + return false; +} + +wxZipEntry *wxZipInputStream::GetNextEntry() +{ + if (m_position == wxInvalidOffset) + if (!LoadEndRecord()) + return NULL; + + 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; } - unzGetCurrentFileInfo((unzFile)m_Archive, &zinfo, (char*) NULL, 0, (void*) NULL, 0, (char*) NULL, 0); + if (QuietSeek(*m_parent_i_stream, m_position + 4) == wxInvalidOffset) + return wxSTREAM_READ_ERROR; - if (unzOpenCurrentFile((unzFile)m_Archive) != UNZ_OK) - { - m_lasterror = wxSTREAM_READ_ERROR; - return; + 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_Size = (size_t)zinfo.uncompressed_size; + + 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(); -wxZipInputStream::~wxZipInputStream() + 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() { - if (m_Archive) - { - if (m_Size != 0) - unzCloseCurrentFile((unzFile)m_Archive); - unzClose((unzFile)m_Archive); + 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; + wxCHECK(!IsOpened(), false); + + 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(); } -bool wxZipInputStream::Eof() const +// Can be overriden 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); + 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; +} -off_t wxZipInputStream::OnSysSeek(off_t seek, wxSeekMode mode) +// 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 + if (!m_ffile || AtHeader()) + 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() - 1 + seek; break; + default : nextpos = pos; break; /* just to fool compiler, never happens */ } size_t toskip; - if ( nextpos > m_Pos ) + if ( nextpos >= pos ) { - toskip = nextpos - m_Pos; + toskip = (size_t)(nextpos - pos); } else { - unzCloseCurrentFile((unzFile)m_Archive); - if (unzOpenCurrentFile((unzFile)m_Archive) != UNZ_OK) + wxZipEntry current(m_entry); + CloseEntry(); + if (!OpenEntry(current)) { m_lasterror = wxSTREAM_READ_ERROR; - return m_Pos; + return pos; } - toskip = nextpos; + toskip = (size_t)nextpos; } - + if ( toskip > 0 ) { const size_t BUFSIZE = 4096; @@ -146,14 +1783,465 @@ off_t wxZipInputStream::OnSysSeek(off_t seek, wxSeekMode mode) while ( toskip > 0 ) { sz = wxMin(toskip, BUFSIZE); - unzReadCurrentFile((unzFile)m_Archive, buffer, sz); + Read(buffer, sz); toskip -= sz; } } - m_Pos = nextpos; - return m_Pos; + pos = nextpos; + return pos; } -#endif - // wxUSE_STREAMS && wxUSE_ZIPSTREAM && wxUSE_ZLIB + +///////////////////////////////////////////////////////////////////////////// +// Output stream + +#include "wx/listimpl.cpp" +WX_DEFINE_LIST(_wxZipEntryList); + +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(_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, 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) { + bool logging = wxLog::IsEnabled(); + wxLogNull nolog; + 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; + wxLog::EnableLogging(logging); + 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); + _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) + 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(); + 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