1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/common/zipstrm.cpp
3 // Purpose:     Streams for Zip files
4 // Author:      Mike Wetherell
5 // Copyright:   (c) Mike Wetherell
6 // Licence:     wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
8 
9 // For compilers that support precompilation, includes "wx.h".
10 #include "wx/wxprec.h"
11 
12 #ifdef __BORLANDC__
13     #pragma hdrstop
14 #endif
15 
16 #if wxUSE_ZIPSTREAM
17 
18 #include "wx/zipstrm.h"
19 
20 #ifndef WX_PRECOMP
21     #include "wx/hashmap.h"
22     #include "wx/intl.h"
23     #include "wx/log.h"
24     #include "wx/utils.h"
25 #endif
26 
27 #include "wx/datstrm.h"
28 #include "wx/zstream.h"
29 #include "wx/mstream.h"
30 #include "wx/scopedptr.h"
31 #include "wx/wfstream.h"
32 #include "zlib.h"
33 
34 // value for the 'version needed to extract' field (20 means 2.0)
35 enum {
36     VERSION_NEEDED_TO_EXTRACT = 20
37 };
38 
39 // signatures for the various records (PKxx)
40 enum {
41     CENTRAL_MAGIC = 0x02014b50,     // central directory record
42     LOCAL_MAGIC   = 0x04034b50,     // local header
43     END_MAGIC     = 0x06054b50,     // end of central directory record
44     SUMS_MAGIC    = 0x08074b50      // data descriptor (info-zip)
45 };
46 
47 // unix file attributes. zip stores them in the high 16 bits of the
48 // 'external attributes' field, hence the extra zeros.
49 enum {
50     wxZIP_S_IFMT  = 0xF0000000,
51     wxZIP_S_IFDIR = 0x40000000,
52     wxZIP_S_IFREG = 0x80000000
53 };
54 
55 // minimum sizes for the various records
56 enum {
57     CENTRAL_SIZE  = 46,
58     LOCAL_SIZE    = 30,
59     END_SIZE      = 22,
60     SUMS_SIZE     = 12
61 };
62 
63 // The number of bytes that must be written to an wxZipOutputStream before
64 // a zip entry is created. The purpose of this latency is so that
65 // OpenCompressor() can see a little data before deciding which compressor
66 // it should use.
67 enum {
68     OUTPUT_LATENCY = 4096
69 };
70 
71 // Some offsets into the local header
72 enum {
73     SUMS_OFFSET  = 14
74 };
75 
IMPLEMENT_DYNAMIC_CLASS(wxZipEntry,wxArchiveEntry)76 IMPLEMENT_DYNAMIC_CLASS(wxZipEntry, wxArchiveEntry)
77 IMPLEMENT_DYNAMIC_CLASS(wxZipClassFactory, wxArchiveClassFactory)
78 
79 
80 /////////////////////////////////////////////////////////////////////////////
81 // Helpers
82 
83 // read a string of a given length
84 //
85 static wxString ReadString(wxInputStream& stream, wxUint16 len, wxMBConv& conv)
86 {
87     if (len == 0)
88         return wxEmptyString;
89 
90 #if wxUSE_UNICODE
91     wxCharBuffer buf(len);
92     stream.Read(buf.data(), len);
93     wxString str(buf, conv);
94 #else
95     wxString str;
96     (void)conv;
97     {
98         wxStringBuffer buf(str, len);
99         stream.Read(buf, len);
100     }
101 #endif
102 
103     return str;
104 }
105 
106 // Decode a little endian wxUint32 number from a character array
107 //
CrackUint32(const char * m)108 static inline wxUint32 CrackUint32(const char *m)
109 {
110     const unsigned char *n = (const unsigned char*)m;
111     return (n[3] << 24) | (n[2] << 16) | (n[1] << 8) | n[0];
112 }
113 
114 // Decode a little endian wxUint16 number from a character array
115 //
CrackUint16(const char * m)116 static inline wxUint16 CrackUint16(const char *m)
117 {
118     const unsigned char *n = (const unsigned char*)m;
119     return (n[1] << 8) | n[0];
120 }
121 
122 // Temporarily lower the logging level in debug mode to avoid a warning
123 // from SeekI about seeking on a stream with data written back to it.
124 //
QuietSeek(wxInputStream & stream,wxFileOffset pos)125 static wxFileOffset QuietSeek(wxInputStream& stream, wxFileOffset pos)
126 {
127 #if wxUSE_LOG
128     wxLogLevel level = wxLog::GetLogLevel();
129     wxLog::SetLogLevel(wxLOG_Debug - 1);
130     wxFileOffset result = stream.SeekI(pos);
131     wxLog::SetLogLevel(level);
132     return result;
133 #else
134     return stream.SeekI(pos);
135 #endif
136 }
137 
138 
139 /////////////////////////////////////////////////////////////////////////////
140 // Class factory
141 
142 static wxZipClassFactory g_wxZipClassFactory;
143 
wxZipClassFactory()144 wxZipClassFactory::wxZipClassFactory()
145 {
146     if (this == &g_wxZipClassFactory)
147         PushFront();
148 }
149 
150 const wxChar * const *
GetProtocols(wxStreamProtocolType type) const151 wxZipClassFactory::GetProtocols(wxStreamProtocolType type) const
152 {
153     static const wxChar *protocols[] = { wxT("zip"), NULL };
154     static const wxChar *mimetypes[] = { wxT("application/zip"), NULL };
155     static const wxChar *fileexts[]  = { wxT(".zip"), wxT(".htb"), NULL };
156     static const wxChar *empty[]     = { NULL };
157 
158     switch (type) {
159         case wxSTREAM_PROTOCOL: return protocols;
160         case wxSTREAM_MIMETYPE: return mimetypes;
161         case wxSTREAM_FILEEXT:  return fileexts;
162         default:                return empty;
163     }
164 }
165 
166 
167 /////////////////////////////////////////////////////////////////////////////
168 // Read a zip header
169 
170 class wxZipHeader
171 {
172 public:
173     wxZipHeader(wxInputStream& stream, size_t size);
174 
175     inline wxUint8 Read8();
176     inline wxUint16 Read16();
177     inline wxUint32 Read32();
178 
GetData() const179     const char *GetData() const             { return m_data; }
GetSize() const180     size_t GetSize() const                  { return m_size; }
operator bool() const181     operator bool() const                   { return m_ok; }
182 
Seek(size_t pos)183     size_t Seek(size_t pos)                 { m_pos = pos; return m_pos; }
Skip(size_t size)184     size_t Skip(size_t size)                { m_pos += size; return m_pos; }
185 
operator >>(wxUint8 & n)186     wxZipHeader& operator>>(wxUint8& n)     { n = Read8();  return *this; }
operator >>(wxUint16 & n)187     wxZipHeader& operator>>(wxUint16& n)    { n = Read16(); return *this; }
operator >>(wxUint32 & n)188     wxZipHeader& operator>>(wxUint32& n)    { n = Read32(); return *this; }
189 
190 private:
191     char m_data[64];
192     size_t m_size;
193     size_t m_pos;
194     bool m_ok;
195 };
196 
wxZipHeader(wxInputStream & stream,size_t size)197 wxZipHeader::wxZipHeader(wxInputStream& stream, size_t size)
198   : m_size(0),
199     m_pos(0),
200     m_ok(false)
201 {
202     wxCHECK_RET(size <= sizeof(m_data), wxT("buffer too small"));
203     m_size = stream.Read(m_data, size).LastRead();
204     m_ok = m_size == size;
205 }
206 
Read8()207 inline wxUint8 wxZipHeader::Read8()
208 {
209     wxASSERT(m_pos < m_size);
210     return m_data[m_pos++];
211 }
212 
Read16()213 inline wxUint16 wxZipHeader::Read16()
214 {
215     wxASSERT(m_pos + 2 <= m_size);
216     wxUint16 n = CrackUint16(m_data + m_pos);
217     m_pos += 2;
218     return n;
219 }
220 
Read32()221 inline wxUint32 wxZipHeader::Read32()
222 {
223     wxASSERT(m_pos + 4 <= m_size);
224     wxUint32 n = CrackUint32(m_data + m_pos);
225     m_pos += 4;
226     return n;
227 }
228 
229 
230 /////////////////////////////////////////////////////////////////////////////
231 // Stored input stream
232 // Trival decompressor for files which are 'stored' in the zip file.
233 
234 class wxStoredInputStream : public wxFilterInputStream
235 {
236 public:
237     wxStoredInputStream(wxInputStream& stream);
238 
Open(wxFileOffset len)239     void Open(wxFileOffset len) { Close(); m_len = len; }
Close()240     void Close() { m_pos = 0; m_lasterror = wxSTREAM_NO_ERROR; }
241 
Peek()242     virtual char Peek() { return wxInputStream::Peek(); }
GetLength() const243     virtual wxFileOffset GetLength() const { return m_len; }
244 
245 protected:
246     virtual size_t OnSysRead(void *buffer, size_t size);
OnSysTell() const247     virtual wxFileOffset OnSysTell() const { return m_pos; }
248 
249 private:
250     wxFileOffset m_pos;
251     wxFileOffset m_len;
252 
253     wxDECLARE_NO_COPY_CLASS(wxStoredInputStream);
254 };
255 
wxStoredInputStream(wxInputStream & stream)256 wxStoredInputStream::wxStoredInputStream(wxInputStream& stream)
257   : wxFilterInputStream(stream),
258     m_pos(0),
259     m_len(0)
260 {
261 }
262 
OnSysRead(void * buffer,size_t size)263 size_t wxStoredInputStream::OnSysRead(void *buffer, size_t size)
264 {
265     size_t count = wx_truncate_cast(size_t,
266                 wxMin(size + wxFileOffset(0), m_len - m_pos + size_t(0)));
267     count = m_parent_i_stream->Read(buffer, count).LastRead();
268     m_pos += count;
269 
270     if (count < size)
271         m_lasterror = m_pos == m_len ? wxSTREAM_EOF : wxSTREAM_READ_ERROR;
272 
273     return count;
274 }
275 
276 
277 /////////////////////////////////////////////////////////////////////////////
278 // Stored output stream
279 // Trival compressor for files which are 'stored' in the zip file.
280 
281 class wxStoredOutputStream : public wxFilterOutputStream
282 {
283 public:
wxStoredOutputStream(wxOutputStream & stream)284     wxStoredOutputStream(wxOutputStream& stream) :
285         wxFilterOutputStream(stream), m_pos(0) { }
286 
Close()287     bool Close() {
288         m_pos = 0;
289         m_lasterror = wxSTREAM_NO_ERROR;
290         return true;
291     }
292 
293 protected:
294     virtual size_t OnSysWrite(const void *buffer, size_t size);
OnSysTell() const295     virtual wxFileOffset OnSysTell() const { return m_pos; }
296 
297 private:
298     wxFileOffset m_pos;
299     wxDECLARE_NO_COPY_CLASS(wxStoredOutputStream);
300 };
301 
OnSysWrite(const void * buffer,size_t size)302 size_t wxStoredOutputStream::OnSysWrite(const void *buffer, size_t size)
303 {
304     if (!IsOk() || !size)
305         return 0;
306     size_t count = m_parent_o_stream->Write(buffer, size).LastWrite();
307     if (count != size)
308         m_lasterror = wxSTREAM_WRITE_ERROR;
309     m_pos += count;
310     return count;
311 }
312 
313 
314 /////////////////////////////////////////////////////////////////////////////
315 // wxRawInputStream
316 //
317 // Used to handle the unusal case of raw copying an entry of unknown
318 // length. This can only happen when the zip being copied from is being
319 // read from a non-seekable stream, and also was original written to a
320 // non-seekable stream.
321 //
322 // In this case there's no option but to decompress the stream to find
323 // it's length, but we can still write the raw compressed data to avoid the
324 // compression overhead (which is the greater one).
325 //
326 // Usage is like this:
327 //  m_rawin = new wxRawInputStream(*m_parent_i_stream);
328 //  m_decomp = m_rawin->Open(OpenDecompressor(m_rawin->GetTee()));
329 //
330 // The wxRawInputStream owns a wxTeeInputStream object, the role of which
331 // is something like the unix 'tee' command; it is a transparent filter, but
332 // allows the data read to be read a second time via an extra method 'GetData'.
333 //
334 // The wxRawInputStream then draws data through the tee using a decompressor
335 // then instead of returning the decompressed data, retuns the raw data
336 // from wxTeeInputStream::GetData().
337 
338 class wxTeeInputStream : public wxFilterInputStream
339 {
340 public:
341     wxTeeInputStream(wxInputStream& stream);
342 
GetCount() const343     size_t GetCount() const { return m_end - m_start; }
344     size_t GetData(char *buffer, size_t size);
345 
346     void Open();
347     bool Final();
348 
349     wxInputStream& Read(void *buffer, size_t size);
350 
351 protected:
352     virtual size_t OnSysRead(void *buffer, size_t size);
OnSysTell() const353     virtual wxFileOffset OnSysTell() const { return m_pos; }
354 
355 private:
356     wxFileOffset m_pos;
357     wxMemoryBuffer m_buf;
358     size_t m_start;
359     size_t m_end;
360 
361     wxDECLARE_NO_COPY_CLASS(wxTeeInputStream);
362 };
363 
wxTeeInputStream(wxInputStream & stream)364 wxTeeInputStream::wxTeeInputStream(wxInputStream& stream)
365   : wxFilterInputStream(stream),
366     m_pos(0), m_buf(8192), m_start(0), m_end(0)
367 {
368 }
369 
Open()370 void wxTeeInputStream::Open()
371 {
372     m_pos = m_start = m_end = 0;
373     m_lasterror = wxSTREAM_NO_ERROR;
374 }
375 
Final()376 bool wxTeeInputStream::Final()
377 {
378     bool final = m_end == m_buf.GetDataLen();
379     m_end = m_buf.GetDataLen();
380     return final;
381 }
382 
Read(void * buffer,size_t size)383 wxInputStream& wxTeeInputStream::Read(void *buffer, size_t size)
384 {
385     size_t count = wxInputStream::Read(buffer, size).LastRead();
386     m_end = m_buf.GetDataLen();
387     m_buf.AppendData(buffer, count);
388     return *this;
389 }
390 
OnSysRead(void * buffer,size_t size)391 size_t wxTeeInputStream::OnSysRead(void *buffer, size_t size)
392 {
393     size_t count = m_parent_i_stream->Read(buffer, size).LastRead();
394     if (count < size)
395         m_lasterror = m_parent_i_stream->GetLastError();
396     return count;
397 }
398 
GetData(char * buffer,size_t size)399 size_t wxTeeInputStream::GetData(char *buffer, size_t size)
400 {
401     if (m_wbacksize) {
402         size_t len = m_buf.GetDataLen();
403         len = len > m_wbacksize ? len - m_wbacksize : 0;
404         m_buf.SetDataLen(len);
405         if (m_end > len) {
406             wxFAIL; // we've already returned data that's now being ungot
407             m_end = len;
408         }
409         m_parent_i_stream->Reset();
410         m_parent_i_stream->Ungetch(m_wback, m_wbacksize);
411         free(m_wback);
412         m_wback = NULL;
413         m_wbacksize = 0;
414         m_wbackcur = 0;
415     }
416 
417     if (size > GetCount())
418         size = GetCount();
419     if (size) {
420         memcpy(buffer, m_buf + m_start, size);
421         m_start += size;
422         wxASSERT(m_start <= m_end);
423     }
424 
425     if (m_start == m_end && m_start > 0 && m_buf.GetDataLen() > 0) {
426         size_t len = m_buf.GetDataLen();
427         char *buf = (char*)m_buf.GetWriteBuf(len);
428         len -= m_end;
429         memmove(buf, buf + m_end, len);
430         m_buf.UngetWriteBuf(len);
431         m_start = m_end = 0;
432     }
433 
434     return size;
435 }
436 
437 class wxRawInputStream : public wxFilterInputStream
438 {
439 public:
440     wxRawInputStream(wxInputStream& stream);
~wxRawInputStream()441     virtual ~wxRawInputStream() { delete m_tee; }
442 
443     wxInputStream* Open(wxInputStream *decomp);
GetTee() const444     wxInputStream& GetTee() const { return *m_tee; }
445 
446 protected:
447     virtual size_t OnSysRead(void *buffer, size_t size);
OnSysTell() const448     virtual wxFileOffset OnSysTell() const { return m_pos; }
449 
450 private:
451     wxFileOffset m_pos;
452     wxTeeInputStream *m_tee;
453 
454     enum { BUFSIZE = 8192 };
455     wxCharBuffer m_dummy;
456 
457     wxDECLARE_NO_COPY_CLASS(wxRawInputStream);
458 };
459 
wxRawInputStream(wxInputStream & stream)460 wxRawInputStream::wxRawInputStream(wxInputStream& stream)
461   : wxFilterInputStream(stream),
462     m_pos(0),
463     m_tee(new wxTeeInputStream(stream)),
464     m_dummy(BUFSIZE)
465 {
466 }
467 
Open(wxInputStream * decomp)468 wxInputStream *wxRawInputStream::Open(wxInputStream *decomp)
469 {
470     if (decomp) {
471         m_parent_i_stream = decomp;
472         m_pos = 0;
473         m_lasterror = wxSTREAM_NO_ERROR;
474         m_tee->Open();
475         return this;
476     } else {
477         return NULL;
478     }
479 }
480 
OnSysRead(void * buffer,size_t size)481 size_t wxRawInputStream::OnSysRead(void *buffer, size_t size)
482 {
483     char *buf = (char*)buffer;
484     size_t count = 0;
485 
486     while (count < size && IsOk())
487     {
488         while (m_parent_i_stream->IsOk() && m_tee->GetCount() == 0)
489             m_parent_i_stream->Read(m_dummy.data(), BUFSIZE);
490 
491         size_t n = m_tee->GetData(buf + count, size - count);
492         count += n;
493 
494         if (n == 0 && m_tee->Final())
495             m_lasterror = m_parent_i_stream->GetLastError();
496     }
497 
498     m_pos += count;
499     return count;
500 }
501 
502 
503 /////////////////////////////////////////////////////////////////////////////
504 // Zlib streams than can be reused without recreating.
505 
506 class wxZlibOutputStream2 : public wxZlibOutputStream
507 {
508 public:
wxZlibOutputStream2(wxOutputStream & stream,int level)509     wxZlibOutputStream2(wxOutputStream& stream, int level) :
510         wxZlibOutputStream(stream, level, wxZLIB_NO_HEADER) { }
511 
512     bool Open(wxOutputStream& stream);
Close()513     bool Close() { DoFlush(true); m_pos = wxInvalidOffset; return IsOk(); }
514 };
515 
Open(wxOutputStream & stream)516 bool wxZlibOutputStream2::Open(wxOutputStream& stream)
517 {
518     wxCHECK(m_pos == wxInvalidOffset, false);
519 
520     m_deflate->next_out = m_z_buffer;
521     m_deflate->avail_out = m_z_size;
522     m_pos = 0;
523     m_lasterror = wxSTREAM_NO_ERROR;
524     m_parent_o_stream = &stream;
525 
526     if (deflateReset(m_deflate) != Z_OK) {
527         wxLogError(_("can't re-initialize zlib deflate stream"));
528         m_lasterror = wxSTREAM_WRITE_ERROR;
529         return false;
530     }
531 
532     return true;
533 }
534 
535 class wxZlibInputStream2 : public wxZlibInputStream
536 {
537 public:
wxZlibInputStream2(wxInputStream & stream)538     wxZlibInputStream2(wxInputStream& stream) :
539         wxZlibInputStream(stream, wxZLIB_NO_HEADER) { }
540 
541     bool Open(wxInputStream& stream);
542 };
543 
Open(wxInputStream & stream)544 bool wxZlibInputStream2::Open(wxInputStream& stream)
545 {
546     m_inflate->avail_in = 0;
547     m_pos = 0;
548     m_lasterror = wxSTREAM_NO_ERROR;
549     m_parent_i_stream = &stream;
550 
551     if (inflateReset(m_inflate) != Z_OK) {
552         wxLogError(_("can't re-initialize zlib inflate stream"));
553         m_lasterror = wxSTREAM_READ_ERROR;
554         return false;
555     }
556 
557     return true;
558 }
559 
560 
561 /////////////////////////////////////////////////////////////////////////////
562 // Class to hold wxZipEntry's Extra and LocalExtra fields
563 
564 class wxZipMemory
565 {
566 public:
wxZipMemory()567     wxZipMemory() : m_data(NULL), m_size(0), m_capacity(0), m_ref(1) { }
568 
AddRef()569     wxZipMemory *AddRef() { m_ref++; return this; }
Release()570     void Release() { if (--m_ref == 0) delete this; }
571 
GetData() const572     char *GetData() const { return m_data; }
GetSize() const573     size_t GetSize() const { return m_size; }
GetCapacity() const574     size_t GetCapacity() const { return m_capacity; }
575 
576     wxZipMemory *Unique(size_t size);
577 
578 private:
~wxZipMemory()579     ~wxZipMemory() { delete [] m_data; }
580 
581     char *m_data;
582     size_t m_size;
583     size_t m_capacity;
584     int m_ref;
585 
586     wxSUPPRESS_GCC_PRIVATE_DTOR_WARNING(wxZipMemory)
587 };
588 
Unique(size_t size)589 wxZipMemory *wxZipMemory::Unique(size_t size)
590 {
591     wxZipMemory *zm;
592 
593     if (m_ref > 1) {
594         --m_ref;
595         zm = new wxZipMemory;
596     } else {
597         zm = this;
598     }
599 
600     if (zm->m_capacity < size) {
601         delete [] zm->m_data;
602         zm->m_data = new char[size];
603         zm->m_capacity = size;
604     }
605 
606     zm->m_size = size;
607     return zm;
608 }
609 
AddRef(wxZipMemory * zm)610 static inline wxZipMemory *AddRef(wxZipMemory *zm)
611 {
612     if (zm)
613         zm->AddRef();
614     return zm;
615 }
616 
Release(wxZipMemory * zm)617 static inline void Release(wxZipMemory *zm)
618 {
619     if (zm)
620         zm->Release();
621 }
622 
Copy(wxZipMemory * & dest,wxZipMemory * src)623 static void Copy(wxZipMemory*& dest, wxZipMemory *src)
624 {
625     Release(dest);
626     dest = AddRef(src);
627 }
628 
Unique(wxZipMemory * & zm,size_t size)629 static void Unique(wxZipMemory*& zm, size_t size)
630 {
631     if (!zm && size)
632         zm = new wxZipMemory;
633     if (zm)
634         zm = zm->Unique(size);
635 }
636 
637 
638 /////////////////////////////////////////////////////////////////////////////
639 // Collection of weak references to entries
640 
641 WX_DECLARE_HASH_MAP(long, wxZipEntry*, wxIntegerHash,
642                     wxIntegerEqual, wxOffsetZipEntryMap_);
643 
644 class wxZipWeakLinks
645 {
646 public:
wxZipWeakLinks()647     wxZipWeakLinks() : m_ref(1) { }
648 
Release(const wxZipInputStream * WXUNUSED (x))649     void Release(const wxZipInputStream* WXUNUSED(x))
650         { if (--m_ref == 0) delete this; }
Release(wxFileOffset key)651     void Release(wxFileOffset key)
652         { RemoveEntry(key); if (--m_ref == 0) delete this; }
653 
654     wxZipWeakLinks *AddEntry(wxZipEntry *entry, wxFileOffset key);
RemoveEntry(wxFileOffset key)655     void RemoveEntry(wxFileOffset key)
656         { m_entries.erase(wx_truncate_cast(key_type, key)); }
657     wxZipEntry *GetEntry(wxFileOffset key) const;
IsEmpty() const658     bool IsEmpty() const { return m_entries.empty(); }
659 
660 private:
~wxZipWeakLinks()661     ~wxZipWeakLinks() { wxASSERT(IsEmpty()); }
662 
663     typedef wxOffsetZipEntryMap_::key_type key_type;
664 
665     int m_ref;
666     wxOffsetZipEntryMap_ m_entries;
667 
668     wxSUPPRESS_GCC_PRIVATE_DTOR_WARNING(wxZipWeakLinks)
669 };
670 
AddEntry(wxZipEntry * entry,wxFileOffset key)671 wxZipWeakLinks *wxZipWeakLinks::AddEntry(wxZipEntry *entry, wxFileOffset key)
672 {
673     m_entries[wx_truncate_cast(key_type, key)] = entry;
674     m_ref++;
675     return this;
676 }
677 
GetEntry(wxFileOffset key) const678 wxZipEntry *wxZipWeakLinks::GetEntry(wxFileOffset key) const
679 {
680     wxOffsetZipEntryMap_::const_iterator it =
681         m_entries.find(wx_truncate_cast(key_type, key));
682     return it != m_entries.end() ?  it->second : NULL;
683 }
684 
685 
686 /////////////////////////////////////////////////////////////////////////////
687 // ZipEntry
688 
wxZipEntry(const wxString & name,const wxDateTime & dt,wxFileOffset size)689 wxZipEntry::wxZipEntry(
690     const wxString& name /*=wxEmptyString*/,
691     const wxDateTime& dt /*=wxDateTime::Now()*/,
692     wxFileOffset size    /*=wxInvalidOffset*/)
693   :
694     m_SystemMadeBy(wxZIP_SYSTEM_MSDOS),
695     m_VersionMadeBy(wxMAJOR_VERSION * 10 + wxMINOR_VERSION),
696     m_VersionNeeded(VERSION_NEEDED_TO_EXTRACT),
697     m_Flags(0),
698     m_Method(wxZIP_METHOD_DEFAULT),
699     m_DateTime(dt),
700     m_Crc(0),
701     m_CompressedSize(wxInvalidOffset),
702     m_Size(size),
703     m_Key(wxInvalidOffset),
704     m_Offset(wxInvalidOffset),
705     m_DiskStart(0),
706     m_InternalAttributes(0),
707     m_ExternalAttributes(0),
708     m_Extra(NULL),
709     m_LocalExtra(NULL),
710     m_zipnotifier(NULL),
711     m_backlink(NULL)
712 {
713     if (!name.empty())
714         SetName(name);
715 }
716 
~wxZipEntry()717 wxZipEntry::~wxZipEntry()
718 {
719     if (m_backlink)
720         m_backlink->Release(m_Key);
721     Release(m_Extra);
722     Release(m_LocalExtra);
723 }
724 
wxZipEntry(const wxZipEntry & e)725 wxZipEntry::wxZipEntry(const wxZipEntry& e)
726   : wxArchiveEntry(e),
727     m_SystemMadeBy(e.m_SystemMadeBy),
728     m_VersionMadeBy(e.m_VersionMadeBy),
729     m_VersionNeeded(e.m_VersionNeeded),
730     m_Flags(e.m_Flags),
731     m_Method(e.m_Method),
732     m_DateTime(e.m_DateTime),
733     m_Crc(e.m_Crc),
734     m_CompressedSize(e.m_CompressedSize),
735     m_Size(e.m_Size),
736     m_Name(e.m_Name),
737     m_Key(e.m_Key),
738     m_Offset(e.m_Offset),
739     m_Comment(e.m_Comment),
740     m_DiskStart(e.m_DiskStart),
741     m_InternalAttributes(e.m_InternalAttributes),
742     m_ExternalAttributes(e.m_ExternalAttributes),
743     m_Extra(AddRef(e.m_Extra)),
744     m_LocalExtra(AddRef(e.m_LocalExtra)),
745     m_zipnotifier(NULL),
746     m_backlink(NULL)
747 {
748 }
749 
operator =(const wxZipEntry & e)750 wxZipEntry& wxZipEntry::operator=(const wxZipEntry& e)
751 {
752     if (&e != this) {
753         m_SystemMadeBy = e.m_SystemMadeBy;
754         m_VersionMadeBy = e.m_VersionMadeBy;
755         m_VersionNeeded = e.m_VersionNeeded;
756         m_Flags = e.m_Flags;
757         m_Method = e.m_Method;
758         m_DateTime = e.m_DateTime;
759         m_Crc = e.m_Crc;
760         m_CompressedSize = e.m_CompressedSize;
761         m_Size = e.m_Size;
762         m_Name = e.m_Name;
763         m_Key = e.m_Key;
764         m_Offset = e.m_Offset;
765         m_Comment = e.m_Comment;
766         m_DiskStart = e.m_DiskStart;
767         m_InternalAttributes = e.m_InternalAttributes;
768         m_ExternalAttributes = e.m_ExternalAttributes;
769         Copy(m_Extra, e.m_Extra);
770         Copy(m_LocalExtra, e.m_LocalExtra);
771         m_zipnotifier = NULL;
772         if (m_backlink) {
773             m_backlink->Release(m_Key);
774             m_backlink = NULL;
775         }
776     }
777     return *this;
778 }
779 
GetName(wxPathFormat format) const780 wxString wxZipEntry::GetName(wxPathFormat format /*=wxPATH_NATIVE*/) const
781 {
782     bool isDir = IsDir() && !m_Name.empty();
783 
784     // optimisations for common (and easy) cases
785     switch (wxFileName::GetFormat(format)) {
786         case wxPATH_DOS:
787         {
788             wxString name(isDir ? m_Name + wxT("\\") : m_Name);
789             for (size_t i = 0; i < name.length(); i++)
790                 if (name[i] == wxT('/'))
791                     name[i] = wxT('\\');
792             return name;
793         }
794 
795         case wxPATH_UNIX:
796             return isDir ? m_Name + wxT("/") : m_Name;
797 
798         default:
799             ;
800     }
801 
802     wxFileName fn;
803 
804     if (isDir)
805         fn.AssignDir(m_Name, wxPATH_UNIX);
806     else
807         fn.Assign(m_Name, wxPATH_UNIX);
808 
809     return fn.GetFullPath(format);
810 }
811 
812 // Static - Internally tars and zips use forward slashes for the path
813 // separator, absolute paths aren't allowed, and directory names have a
814 // trailing slash.  This function converts a path into this internal format,
815 // but without a trailing slash for a directory.
816 //
GetInternalName(const wxString & name,wxPathFormat format,bool * pIsDir)817 wxString wxZipEntry::GetInternalName(const wxString& name,
818                                      wxPathFormat format /*=wxPATH_NATIVE*/,
819                                      bool *pIsDir        /*=NULL*/)
820 {
821     wxString internal;
822 
823     if (wxFileName::GetFormat(format) != wxPATH_UNIX)
824         internal = wxFileName(name, format).GetFullPath(wxPATH_UNIX);
825     else
826         internal = name;
827 
828     bool isDir = !internal.empty() && internal.Last() == '/';
829     if (pIsDir)
830         *pIsDir = isDir;
831     if (isDir)
832         internal.erase(internal.length() - 1);
833 
834     while (!internal.empty() && *internal.begin() == '/')
835         internal.erase(0, 1);
836     while (!internal.empty() && internal.compare(0, 2, wxT("./")) == 0)
837         internal.erase(0, 2);
838     if (internal == wxT(".") || internal == wxT(".."))
839         internal = wxEmptyString;
840 
841     return internal;
842 }
843 
SetSystemMadeBy(int system)844 void wxZipEntry::SetSystemMadeBy(int system)
845 {
846     int mode = GetMode();
847     bool wasUnix = IsMadeByUnix();
848 
849     m_SystemMadeBy = (wxUint8)system;
850 
851     if (!wasUnix && IsMadeByUnix()) {
852         SetIsDir(IsDir());
853         SetMode(mode);
854     } else if (wasUnix && !IsMadeByUnix()) {
855         m_ExternalAttributes &= 0xffff;
856     }
857 }
858 
SetIsDir(bool isDir)859 void wxZipEntry::SetIsDir(bool isDir /*=true*/)
860 {
861     if (isDir)
862         m_ExternalAttributes |= wxZIP_A_SUBDIR;
863     else
864         m_ExternalAttributes &= ~wxZIP_A_SUBDIR;
865 
866     if (IsMadeByUnix()) {
867         m_ExternalAttributes &= ~wxZIP_S_IFMT;
868         if (isDir)
869             m_ExternalAttributes |= wxZIP_S_IFDIR;
870         else
871             m_ExternalAttributes |= wxZIP_S_IFREG;
872     }
873 }
874 
875 // Return unix style permission bits
876 //
GetMode() const877 int wxZipEntry::GetMode() const
878 {
879     // return unix permissions if present
880     if (IsMadeByUnix())
881         return (m_ExternalAttributes >> 16) & 0777;
882 
883     // otherwise synthesize from the dos attribs
884     int mode = 0644;
885     if (m_ExternalAttributes & wxZIP_A_RDONLY)
886         mode &= ~0200;
887     if (m_ExternalAttributes & wxZIP_A_SUBDIR)
888         mode |= 0111;
889 
890     return mode;
891 }
892 
893 // Set unix permissions
894 //
SetMode(int mode)895 void wxZipEntry::SetMode(int mode)
896 {
897     // Set dos attrib bits to be compatible
898     if (mode & 0222)
899         m_ExternalAttributes &= ~wxZIP_A_RDONLY;
900     else
901         m_ExternalAttributes |= wxZIP_A_RDONLY;
902 
903     // set the actual unix permission bits if the system type allows
904     if (IsMadeByUnix()) {
905         m_ExternalAttributes &= ~(0777L << 16);
906         m_ExternalAttributes |= (mode & 0777L) << 16;
907     }
908 }
909 
GetExtra() const910 const char *wxZipEntry::GetExtra() const
911 {
912     return m_Extra ? m_Extra->GetData() : NULL;
913 }
914 
GetExtraLen() const915 size_t wxZipEntry::GetExtraLen() const
916 {
917     return m_Extra ? m_Extra->GetSize() : 0;
918 }
919 
SetExtra(const char * extra,size_t len)920 void wxZipEntry::SetExtra(const char *extra, size_t len)
921 {
922     Unique(m_Extra, len);
923     if (len)
924         memcpy(m_Extra->GetData(), extra, len);
925 }
926 
GetLocalExtra() const927 const char *wxZipEntry::GetLocalExtra() const
928 {
929     return m_LocalExtra ? m_LocalExtra->GetData() : NULL;
930 }
931 
GetLocalExtraLen() const932 size_t  wxZipEntry::GetLocalExtraLen() const
933 {
934     return m_LocalExtra ? m_LocalExtra->GetSize() : 0;
935 }
936 
SetLocalExtra(const char * extra,size_t len)937 void wxZipEntry::SetLocalExtra(const char *extra, size_t len)
938 {
939     Unique(m_LocalExtra, len);
940     if (len)
941         memcpy(m_LocalExtra->GetData(), extra, len);
942 }
943 
SetNotifier(wxZipNotifier & notifier)944 void wxZipEntry::SetNotifier(wxZipNotifier& notifier)
945 {
946     wxArchiveEntry::UnsetNotifier();
947     m_zipnotifier = &notifier;
948     m_zipnotifier->OnEntryUpdated(*this);
949 }
950 
Notify()951 void wxZipEntry::Notify()
952 {
953     if (m_zipnotifier)
954         m_zipnotifier->OnEntryUpdated(*this);
955     else if (GetNotifier())
956         GetNotifier()->OnEntryUpdated(*this);
957 }
958 
UnsetNotifier()959 void wxZipEntry::UnsetNotifier()
960 {
961     wxArchiveEntry::UnsetNotifier();
962     m_zipnotifier = NULL;
963 }
964 
ReadLocal(wxInputStream & stream,wxMBConv & conv)965 size_t wxZipEntry::ReadLocal(wxInputStream& stream, wxMBConv& conv)
966 {
967     wxUint16 nameLen, extraLen;
968     wxUint32 compressedSize, size, crc;
969 
970     wxZipHeader ds(stream, LOCAL_SIZE - 4);
971     if (!ds)
972         return 0;
973 
974     ds >> m_VersionNeeded >> m_Flags >> m_Method;
975     SetDateTime(wxDateTime().SetFromDOS(ds.Read32()));
976     ds >> crc >> compressedSize >> size >> nameLen >> extraLen;
977 
978     bool sumsValid = (m_Flags & wxZIP_SUMS_FOLLOW) == 0;
979 
980     if (sumsValid || crc)
981         m_Crc = crc;
982     if ((sumsValid || compressedSize) || m_Method == wxZIP_METHOD_STORE)
983         m_CompressedSize = compressedSize;
984     if ((sumsValid || size) || m_Method == wxZIP_METHOD_STORE)
985         m_Size = size;
986 
987     SetName(ReadString(stream, nameLen, conv), wxPATH_UNIX);
988     if (stream.LastRead() != nameLen + 0u)
989         return 0;
990 
991     if (extraLen || GetLocalExtraLen()) {
992         Unique(m_LocalExtra, extraLen);
993         if (extraLen) {
994             stream.Read(m_LocalExtra->GetData(), extraLen);
995             if (stream.LastRead() != extraLen + 0u)
996                 return 0;
997         }
998     }
999 
1000     return LOCAL_SIZE + nameLen + extraLen;
1001 }
1002 
WriteLocal(wxOutputStream & stream,wxMBConv & conv) const1003 size_t wxZipEntry::WriteLocal(wxOutputStream& stream, wxMBConv& conv) const
1004 {
1005     wxString unixName = GetName(wxPATH_UNIX);
1006     const wxWX2MBbuf name_buf = unixName.mb_str(conv);
1007     const char *name = name_buf;
1008     if (!name) name = "";
1009     wxUint16 nameLen = wx_truncate_cast(wxUint16, strlen(name));
1010 
1011     wxDataOutputStream ds(stream);
1012 
1013     ds << m_VersionNeeded << m_Flags << m_Method;
1014     ds.Write32(GetDateTime().GetAsDOS());
1015 
1016     ds.Write32(m_Crc);
1017     ds.Write32(m_CompressedSize != wxInvalidOffset ?
1018                wx_truncate_cast(wxUint32, m_CompressedSize) : 0);
1019     ds.Write32(m_Size != wxInvalidOffset ?
1020                wx_truncate_cast(wxUint32, m_Size) : 0);
1021 
1022     ds << nameLen;
1023     wxUint16 extraLen = wx_truncate_cast(wxUint16, GetLocalExtraLen());
1024     ds.Write16(extraLen);
1025 
1026     stream.Write(name, nameLen);
1027     if (extraLen)
1028         stream.Write(m_LocalExtra->GetData(), extraLen);
1029 
1030     return LOCAL_SIZE + nameLen + extraLen;
1031 }
1032 
ReadCentral(wxInputStream & stream,wxMBConv & conv)1033 size_t wxZipEntry::ReadCentral(wxInputStream& stream, wxMBConv& conv)
1034 {
1035     wxUint16 nameLen, extraLen, commentLen;
1036 
1037     wxZipHeader ds(stream, CENTRAL_SIZE - 4);
1038     if (!ds)
1039         return 0;
1040 
1041     ds >> m_VersionMadeBy >> m_SystemMadeBy;
1042 
1043     SetVersionNeeded(ds.Read16());
1044     SetFlags(ds.Read16());
1045     SetMethod(ds.Read16());
1046     SetDateTime(wxDateTime().SetFromDOS(ds.Read32()));
1047     SetCrc(ds.Read32());
1048     SetCompressedSize(ds.Read32());
1049     SetSize(ds.Read32());
1050 
1051     ds >> nameLen >> extraLen >> commentLen
1052        >> m_DiskStart >> m_InternalAttributes >> m_ExternalAttributes;
1053     SetOffset(ds.Read32());
1054 
1055     SetName(ReadString(stream, nameLen, conv), wxPATH_UNIX);
1056     if (stream.LastRead() != nameLen + 0u)
1057         return 0;
1058 
1059     if (extraLen || GetExtraLen()) {
1060         Unique(m_Extra, extraLen);
1061         if (extraLen) {
1062             stream.Read(m_Extra->GetData(), extraLen);
1063             if (stream.LastRead() != extraLen + 0u)
1064                 return 0;
1065         }
1066     }
1067 
1068     if (commentLen) {
1069         m_Comment = ReadString(stream, commentLen, conv);
1070         if (stream.LastRead() != commentLen + 0u)
1071             return 0;
1072     } else {
1073         m_Comment.clear();
1074     }
1075 
1076     return CENTRAL_SIZE + nameLen + extraLen + commentLen;
1077 }
1078 
WriteCentral(wxOutputStream & stream,wxMBConv & conv) const1079 size_t wxZipEntry::WriteCentral(wxOutputStream& stream, wxMBConv& conv) const
1080 {
1081     wxString unixName = GetName(wxPATH_UNIX);
1082     const wxWX2MBbuf name_buf = unixName.mb_str(conv);
1083     const char *name = name_buf;
1084     if (!name) name = "";
1085     wxUint16 nameLen = wx_truncate_cast(wxUint16, strlen(name));
1086 
1087     const wxWX2MBbuf comment_buf = m_Comment.mb_str(conv);
1088     const char *comment = comment_buf;
1089     if (!comment) comment = "";
1090     wxUint16 commentLen = wx_truncate_cast(wxUint16, strlen(comment));
1091 
1092     wxUint16 extraLen = wx_truncate_cast(wxUint16, GetExtraLen());
1093 
1094     wxDataOutputStream ds(stream);
1095 
1096     ds << CENTRAL_MAGIC << m_VersionMadeBy << m_SystemMadeBy;
1097 
1098     ds.Write16(wx_truncate_cast(wxUint16, GetVersionNeeded()));
1099     ds.Write16(wx_truncate_cast(wxUint16, GetFlags()));
1100     ds.Write16(wx_truncate_cast(wxUint16, GetMethod()));
1101     ds.Write32(GetDateTime().GetAsDOS());
1102     ds.Write32(GetCrc());
1103     ds.Write32(wx_truncate_cast(wxUint32, GetCompressedSize()));
1104     ds.Write32(wx_truncate_cast(wxUint32, GetSize()));
1105     ds.Write16(nameLen);
1106     ds.Write16(extraLen);
1107 
1108     ds << commentLen << m_DiskStart << m_InternalAttributes
1109        << m_ExternalAttributes << wx_truncate_cast(wxUint32, GetOffset());
1110 
1111     stream.Write(name, nameLen);
1112     if (extraLen)
1113         stream.Write(GetExtra(), extraLen);
1114     stream.Write(comment, commentLen);
1115 
1116     return CENTRAL_SIZE + nameLen + extraLen + commentLen;
1117 }
1118 
1119 // Info-zip prefixes this record with a signature, but pkzip doesn't. So if
1120 // the 1st value is the signature then it is probably an info-zip record,
1121 // though there is a small chance that it is in fact a pkzip record which
1122 // happens to have the signature as it's CRC.
1123 //
ReadDescriptor(wxInputStream & stream)1124 size_t wxZipEntry::ReadDescriptor(wxInputStream& stream)
1125 {
1126     wxZipHeader ds(stream, SUMS_SIZE);
1127     if (!ds)
1128         return 0;
1129 
1130     m_Crc = ds.Read32();
1131     m_CompressedSize = ds.Read32();
1132     m_Size = ds.Read32();
1133 
1134     // if 1st value is the signature then this is probably an info-zip record
1135     if (m_Crc == SUMS_MAGIC)
1136     {
1137         wxZipHeader buf(stream, 8);
1138         wxUint32 u1 = buf.GetSize() >= 4 ? buf.Read32() : (wxUint32)LOCAL_MAGIC;
1139         wxUint32 u2 = buf.GetSize() == 8 ? buf.Read32() : 0;
1140 
1141         // look for the signature of the following record to decide which
1142         if ((u1 == LOCAL_MAGIC || u1 == CENTRAL_MAGIC) &&
1143             (u2 != LOCAL_MAGIC && u2 != CENTRAL_MAGIC))
1144         {
1145             // it's a pkzip style record after all!
1146             if (buf.GetSize() > 0)
1147                 stream.Ungetch(buf.GetData(), buf.GetSize());
1148         }
1149         else
1150         {
1151             // it's an info-zip record as expected
1152             if (buf.GetSize() > 4)
1153                 stream.Ungetch(buf.GetData() + 4, buf.GetSize() - 4);
1154             m_Crc = wx_truncate_cast(wxUint32, m_CompressedSize);
1155             m_CompressedSize = m_Size;
1156             m_Size = u1;
1157             return SUMS_SIZE + 4;
1158         }
1159     }
1160 
1161     return SUMS_SIZE;
1162 }
1163 
WriteDescriptor(wxOutputStream & stream,wxUint32 crc,wxFileOffset compressedSize,wxFileOffset size)1164 size_t wxZipEntry::WriteDescriptor(wxOutputStream& stream, wxUint32 crc,
1165                                    wxFileOffset compressedSize, wxFileOffset size)
1166 {
1167     m_Crc = crc;
1168     m_CompressedSize = compressedSize;
1169     m_Size = size;
1170 
1171     wxDataOutputStream ds(stream);
1172 
1173     ds.Write32(crc);
1174     ds.Write32(wx_truncate_cast(wxUint32, compressedSize));
1175     ds.Write32(wx_truncate_cast(wxUint32, size));
1176 
1177     return SUMS_SIZE;
1178 }
1179 
1180 
1181 /////////////////////////////////////////////////////////////////////////////
1182 // wxZipEndRec - holds the end of central directory record
1183 
1184 class wxZipEndRec
1185 {
1186 public:
1187     wxZipEndRec();
1188 
GetDiskNumber() const1189     int GetDiskNumber() const                   { return m_DiskNumber; }
GetStartDisk() const1190     int GetStartDisk() const                    { return m_StartDisk; }
GetEntriesHere() const1191     int GetEntriesHere() const                  { return m_EntriesHere; }
GetTotalEntries() const1192     int GetTotalEntries() const                 { return m_TotalEntries; }
GetSize() const1193     wxFileOffset GetSize() const                { return m_Size; }
GetOffset() const1194     wxFileOffset GetOffset() const              { return m_Offset; }
GetComment() const1195     wxString GetComment() const                 { return m_Comment; }
1196 
SetDiskNumber(int num)1197     void SetDiskNumber(int num)
1198         { m_DiskNumber = wx_truncate_cast(wxUint16, num); }
SetStartDisk(int num)1199     void SetStartDisk(int num)
1200         { m_StartDisk = wx_truncate_cast(wxUint16, num); }
SetEntriesHere(int num)1201     void SetEntriesHere(int num)
1202         { m_EntriesHere = wx_truncate_cast(wxUint16, num); }
SetTotalEntries(int num)1203     void SetTotalEntries(int num)
1204         { m_TotalEntries = wx_truncate_cast(wxUint16, num); }
SetSize(wxFileOffset size)1205     void SetSize(wxFileOffset size)
1206         { m_Size = wx_truncate_cast(wxUint32, size); }
SetOffset(wxFileOffset offset)1207     void SetOffset(wxFileOffset offset)
1208         { m_Offset = wx_truncate_cast(wxUint32, offset); }
SetComment(const wxString & comment)1209     void SetComment(const wxString& comment)
1210         { m_Comment = comment; }
1211 
1212     bool Read(wxInputStream& stream, wxMBConv& conv);
1213     bool Write(wxOutputStream& stream, wxMBConv& conv) const;
1214 
1215 private:
1216     wxUint16 m_DiskNumber;
1217     wxUint16 m_StartDisk;
1218     wxUint16 m_EntriesHere;
1219     wxUint16 m_TotalEntries;
1220     wxUint32 m_Size;
1221     wxUint32 m_Offset;
1222     wxString m_Comment;
1223 };
1224 
wxZipEndRec()1225 wxZipEndRec::wxZipEndRec()
1226   : m_DiskNumber(0),
1227     m_StartDisk(0),
1228     m_EntriesHere(0),
1229     m_TotalEntries(0),
1230     m_Size(0),
1231     m_Offset(0)
1232 {
1233 }
1234 
Write(wxOutputStream & stream,wxMBConv & conv) const1235 bool wxZipEndRec::Write(wxOutputStream& stream, wxMBConv& conv) const
1236 {
1237     const wxWX2MBbuf comment_buf = m_Comment.mb_str(conv);
1238     const char *comment = comment_buf;
1239     if (!comment) comment = "";
1240     wxUint16 commentLen = (wxUint16)strlen(comment);
1241 
1242     wxDataOutputStream ds(stream);
1243 
1244     ds << END_MAGIC << m_DiskNumber << m_StartDisk << m_EntriesHere
1245        << m_TotalEntries << m_Size << m_Offset << commentLen;
1246 
1247     stream.Write(comment, commentLen);
1248 
1249     return stream.IsOk();
1250 }
1251 
Read(wxInputStream & stream,wxMBConv & conv)1252 bool wxZipEndRec::Read(wxInputStream& stream, wxMBConv& conv)
1253 {
1254     wxZipHeader ds(stream, END_SIZE - 4);
1255     if (!ds)
1256         return false;
1257 
1258     wxUint16 commentLen;
1259 
1260     ds >> m_DiskNumber >> m_StartDisk >> m_EntriesHere
1261        >> m_TotalEntries >> m_Size >> m_Offset >> commentLen;
1262 
1263     if (commentLen) {
1264         m_Comment = ReadString(stream, commentLen, conv);
1265         if (stream.LastRead() != commentLen + 0u)
1266             return false;
1267     }
1268 
1269     if (m_DiskNumber != 0 || m_StartDisk != 0 ||
1270             m_EntriesHere != m_TotalEntries)
1271     {
1272         wxLogWarning(_("assuming this is a multi-part zip concatenated"));
1273     }
1274 
1275     return true;
1276 }
1277 
1278 
1279 /////////////////////////////////////////////////////////////////////////////
1280 // A weak link from an input stream to an output stream
1281 
1282 class wxZipStreamLink
1283 {
1284 public:
wxZipStreamLink(wxZipOutputStream * stream)1285     wxZipStreamLink(wxZipOutputStream *stream) : m_ref(1), m_stream(stream) { }
1286 
AddRef()1287     wxZipStreamLink *AddRef() { m_ref++; return this; }
GetOutputStream() const1288     wxZipOutputStream *GetOutputStream() const { return m_stream; }
1289 
Release(class wxZipInputStream * WXUNUSED (s))1290     void Release(class wxZipInputStream *WXUNUSED(s))
1291         { if (--m_ref == 0) delete this; }
Release(class wxZipOutputStream * WXUNUSED (s))1292     void Release(class wxZipOutputStream *WXUNUSED(s))
1293         { m_stream = NULL; if (--m_ref == 0) delete this; }
1294 
1295 private:
~wxZipStreamLink()1296     ~wxZipStreamLink() { }
1297 
1298     int m_ref;
1299     wxZipOutputStream *m_stream;
1300 
1301     wxSUPPRESS_GCC_PRIVATE_DTOR_WARNING(wxZipStreamLink)
1302 };
1303 
1304 
1305 /////////////////////////////////////////////////////////////////////////////
1306 // Input stream
1307 
1308 // leave the default wxZipEntryPtr free for users
wxDECLARE_SCOPED_PTR(wxZipEntry,wxZipEntryPtr_)1309 wxDECLARE_SCOPED_PTR(wxZipEntry, wxZipEntryPtr_)
1310 wxDEFINE_SCOPED_PTR (wxZipEntry, wxZipEntryPtr_)
1311 
1312 // constructor
1313 //
1314 wxZipInputStream::wxZipInputStream(wxInputStream& stream,
1315                                    wxMBConv& conv /*=wxConvLocal*/)
1316   : wxArchiveInputStream(stream, conv)
1317 {
1318     Init();
1319 }
1320 
wxZipInputStream(wxInputStream * stream,wxMBConv & conv)1321 wxZipInputStream::wxZipInputStream(wxInputStream *stream,
1322                                    wxMBConv& conv /*=wxConvLocal*/)
1323   : wxArchiveInputStream(stream, conv)
1324 {
1325     Init();
1326 }
1327 
1328 #if WXWIN_COMPATIBILITY_2_6 && wxUSE_FFILE
1329 
1330 // Part of the compatibility constructor, which has been made inline to
1331 // avoid a problem with it not being exported by mingw 3.2.3
1332 //
Init(const wxString & file)1333 void wxZipInputStream::Init(const wxString& file)
1334 {
1335     // no error messages
1336     wxLogNull nolog;
1337     Init();
1338     m_allowSeeking = true;
1339     wxFFileInputStream *ffile;
1340     ffile = static_cast<wxFFileInputStream*>(m_parent_i_stream);
1341     wxZipEntryPtr_ entry;
1342 
1343     if (ffile->IsOk()) {
1344         do {
1345             entry.reset(GetNextEntry());
1346         }
1347         while (entry.get() != NULL && entry->GetInternalName() != file);
1348     }
1349 
1350     if (entry.get() == NULL)
1351         m_lasterror = wxSTREAM_READ_ERROR;
1352 }
1353 
OpenFile(const wxString & archive)1354 wxInputStream* wxZipInputStream::OpenFile(const wxString& archive)
1355 {
1356     wxLogNull nolog;
1357     return new wxFFileInputStream(archive);
1358 }
1359 
1360 #endif // WXWIN_COMPATIBILITY_2_6 && wxUSE_FFILE
1361 
Init()1362 void wxZipInputStream::Init()
1363 {
1364     m_store = new wxStoredInputStream(*m_parent_i_stream);
1365     m_inflate = NULL;
1366     m_rawin = NULL;
1367     m_raw = false;
1368     m_headerSize = 0;
1369     m_decomp = NULL;
1370     m_parentSeekable = false;
1371     m_weaklinks = new wxZipWeakLinks;
1372     m_streamlink = NULL;
1373     m_offsetAdjustment = 0;
1374     m_position = wxInvalidOffset;
1375     m_signature = 0;
1376     m_TotalEntries = 0;
1377     m_lasterror = m_parent_i_stream->GetLastError();
1378 #if WXWIN_COMPATIBILITY_2_6
1379     m_allowSeeking = false;
1380 #endif
1381 }
1382 
~wxZipInputStream()1383 wxZipInputStream::~wxZipInputStream()
1384 {
1385     CloseDecompressor(m_decomp);
1386 
1387     delete m_store;
1388     delete m_inflate;
1389     delete m_rawin;
1390 
1391     m_weaklinks->Release(this);
1392 
1393     if (m_streamlink)
1394         m_streamlink->Release(this);
1395 }
1396 
GetComment()1397 wxString wxZipInputStream::GetComment()
1398 {
1399     if (m_position == wxInvalidOffset)
1400         if (!LoadEndRecord())
1401             return wxEmptyString;
1402 
1403     if (!m_parentSeekable && Eof() && m_signature) {
1404         m_lasterror = wxSTREAM_NO_ERROR;
1405         m_lasterror = ReadLocal(true);
1406     }
1407 
1408     return m_Comment;
1409 }
1410 
GetTotalEntries()1411 int wxZipInputStream::GetTotalEntries()
1412 {
1413     if (m_position == wxInvalidOffset)
1414         LoadEndRecord();
1415     return m_TotalEntries;
1416 }
1417 
MakeLink(wxZipOutputStream * out)1418 wxZipStreamLink *wxZipInputStream::MakeLink(wxZipOutputStream *out)
1419 {
1420     wxZipStreamLink *link = NULL;
1421 
1422     if (!m_parentSeekable && (IsOpened() || !Eof())) {
1423         link = new wxZipStreamLink(out);
1424         if (m_streamlink)
1425             m_streamlink->Release(this);
1426         m_streamlink = link->AddRef();
1427     }
1428 
1429     return link;
1430 }
1431 
LoadEndRecord()1432 bool wxZipInputStream::LoadEndRecord()
1433 {
1434     wxCHECK(m_position == wxInvalidOffset, false);
1435     if (!IsOk())
1436         return false;
1437 
1438     m_position = 0;
1439 
1440     // First find the end-of-central-directory record.
1441     if (!FindEndRecord()) {
1442         // failed, so either this is a non-seekable stream (ok), or not a zip
1443         if (m_parentSeekable) {
1444             m_lasterror = wxSTREAM_READ_ERROR;
1445             wxLogError(_("invalid zip file"));
1446             return false;
1447         }
1448         else {
1449             wxLogNull nolog;
1450             wxFileOffset pos = m_parent_i_stream->TellI();
1451             if (pos != wxInvalidOffset)
1452                 m_offsetAdjustment = m_position = pos;
1453             return true;
1454         }
1455     }
1456 
1457     wxZipEndRec endrec;
1458 
1459     // Read in the end record
1460     const wxFileOffset curPos = m_parent_i_stream->TellI();
1461     if ( curPos < 4 )
1462     {
1463         // Either failed to get the position (if it's negative) or can't be a
1464         // valid ZIP file anyhow (this probably can't happen here, but it
1465         // doesn't harm to check).
1466         return false;
1467     }
1468 
1469     const size_t endPos = curPos - 4;
1470     if (!endrec.Read(*m_parent_i_stream, GetConv()))
1471         return false;
1472 
1473     m_TotalEntries = endrec.GetTotalEntries();
1474     m_Comment = endrec.GetComment();
1475 
1476     wxUint32 magic = m_TotalEntries ? CENTRAL_MAGIC : END_MAGIC;
1477 
1478     // Now find the central-directory. we have the file offset of
1479     // the CD, so look there first.
1480     if (m_parent_i_stream->SeekI(endrec.GetOffset()) != wxInvalidOffset &&
1481             ReadSignature() == magic) {
1482         m_signature = magic;
1483         m_position = endrec.GetOffset();
1484         m_offsetAdjustment = 0;
1485         return true;
1486     }
1487 
1488     // If it's not there, then it could be that the zip has been appended
1489     // to a self extractor, so take the CD size (also in endrec), subtract
1490     // it from the file offset of the end-central-directory and look there.
1491     const wxUint64 recSize = endrec.GetSize();
1492     if (recSize <= endPos &&
1493             m_parent_i_stream->SeekI(endPos - recSize) != wxInvalidOffset &&
1494                 ReadSignature() == magic) {
1495         m_signature = magic;
1496         m_position = endPos - recSize;
1497         m_offsetAdjustment = m_position - endrec.GetOffset();
1498         return true;
1499     }
1500 
1501     wxLogError(_("can't find central directory in zip"));
1502     m_lasterror = wxSTREAM_READ_ERROR;
1503     return false;
1504 }
1505 
1506 // Find the end-of-central-directory record.
1507 // If found the stream will be positioned just past the 4 signature bytes.
1508 //
FindEndRecord()1509 bool wxZipInputStream::FindEndRecord()
1510 {
1511     if (!m_parent_i_stream->IsSeekable())
1512         return false;
1513 
1514     // usually it's 22 bytes in size and the last thing in the file
1515     {
1516         wxLogNull nolog;
1517         if (m_parent_i_stream->SeekI(-END_SIZE, wxFromEnd) == wxInvalidOffset)
1518             return false;
1519     }
1520 
1521     m_parentSeekable = true;
1522     m_signature = 0;
1523     char magic[4];
1524     if (m_parent_i_stream->Read(magic, 4).LastRead() != 4)
1525         return false;
1526     if ((m_signature = CrackUint32(magic)) == END_MAGIC)
1527         return true;
1528 
1529     // unfortunately, the record has a comment field that can be up to 65535
1530     // bytes in length, so if the signature not found then search backwards.
1531     wxFileOffset pos = m_parent_i_stream->TellI();
1532     const int BUFSIZE = 1024;
1533     wxCharBuffer buf(BUFSIZE);
1534 
1535     memcpy(buf.data(), magic, 3);
1536     wxFileOffset minpos = wxMax(pos - 65535L, 0);
1537 
1538     while (pos > minpos) {
1539         size_t len = wx_truncate_cast(size_t,
1540                         pos - wxMax(pos - (BUFSIZE - 3), minpos));
1541         if ( len < 3 )
1542             break;
1543 
1544         memcpy(buf.data() + len, buf, 3);
1545         pos -= len;
1546 
1547         if (m_parent_i_stream->SeekI(pos, wxFromStart) == wxInvalidOffset ||
1548                 m_parent_i_stream->Read(buf.data(), len).LastRead() != len)
1549             return false;
1550 
1551         char *p = buf.data() + len;
1552 
1553         while (p-- > buf.data()) {
1554             if ((m_signature = CrackUint32(p)) == END_MAGIC) {
1555                 size_t remainder = buf.data() + len - p;
1556                 if (remainder > 4)
1557                     m_parent_i_stream->Ungetch(p + 4, remainder - 4);
1558                 return true;
1559             }
1560         }
1561     }
1562 
1563     return false;
1564 }
1565 
GetNextEntry()1566 wxZipEntry *wxZipInputStream::GetNextEntry()
1567 {
1568     if (m_position == wxInvalidOffset)
1569         if (!LoadEndRecord())
1570             return NULL;
1571 
1572     m_lasterror = m_parentSeekable ? ReadCentral() : ReadLocal();
1573     if (!IsOk())
1574         return NULL;
1575 
1576     wxZipEntryPtr_ entry(new wxZipEntry(m_entry));
1577     entry->m_backlink = m_weaklinks->AddEntry(entry.get(), entry->GetKey());
1578     return entry.release();
1579 }
1580 
ReadCentral()1581 wxStreamError wxZipInputStream::ReadCentral()
1582 {
1583     if (!AtHeader())
1584         CloseEntry();
1585 
1586     if (m_signature == END_MAGIC)
1587         return wxSTREAM_EOF;
1588 
1589     if (m_signature != CENTRAL_MAGIC) {
1590         wxLogError(_("error reading zip central directory"));
1591         return wxSTREAM_READ_ERROR;
1592     }
1593 
1594     if (QuietSeek(*m_parent_i_stream, m_position + 4) == wxInvalidOffset)
1595         return wxSTREAM_READ_ERROR;
1596 
1597     size_t size = m_entry.ReadCentral(*m_parent_i_stream, GetConv());
1598     if (!size) {
1599         m_signature = 0;
1600         return wxSTREAM_READ_ERROR;
1601     }
1602 
1603     m_position += size;
1604     m_signature = ReadSignature();
1605 
1606     if (m_offsetAdjustment)
1607         m_entry.SetOffset(m_entry.GetOffset() + m_offsetAdjustment);
1608     m_entry.SetKey(m_entry.GetOffset());
1609 
1610     return wxSTREAM_NO_ERROR;
1611 }
1612 
ReadLocal(bool readEndRec)1613 wxStreamError wxZipInputStream::ReadLocal(bool readEndRec /*=false*/)
1614 {
1615     if (!AtHeader())
1616         CloseEntry();
1617 
1618     if (!m_signature)
1619         m_signature = ReadSignature();
1620 
1621     if (m_signature == CENTRAL_MAGIC || m_signature == END_MAGIC) {
1622         if (m_streamlink && !m_streamlink->GetOutputStream()) {
1623             m_streamlink->Release(this);
1624             m_streamlink = NULL;
1625         }
1626     }
1627 
1628     while (m_signature == CENTRAL_MAGIC) {
1629         if (m_weaklinks->IsEmpty() && m_streamlink == NULL)
1630             return wxSTREAM_EOF;
1631 
1632         size_t size = m_entry.ReadCentral(*m_parent_i_stream, GetConv());
1633         m_position += size;
1634         m_signature = 0;
1635         if (!size)
1636             return wxSTREAM_READ_ERROR;
1637 
1638         wxZipEntry *entry = m_weaklinks->GetEntry(m_entry.GetOffset());
1639         if (entry) {
1640             entry->SetSystemMadeBy(m_entry.GetSystemMadeBy());
1641             entry->SetVersionMadeBy(m_entry.GetVersionMadeBy());
1642             entry->SetComment(m_entry.GetComment());
1643             entry->SetDiskStart(m_entry.GetDiskStart());
1644             entry->SetInternalAttributes(m_entry.GetInternalAttributes());
1645             entry->SetExternalAttributes(m_entry.GetExternalAttributes());
1646             Copy(entry->m_Extra, m_entry.m_Extra);
1647             entry->Notify();
1648             m_weaklinks->RemoveEntry(entry->GetOffset());
1649         }
1650 
1651         m_signature = ReadSignature();
1652     }
1653 
1654     if (m_signature == END_MAGIC) {
1655         if (readEndRec || m_streamlink) {
1656             wxZipEndRec endrec;
1657             endrec.Read(*m_parent_i_stream, GetConv());
1658             m_Comment = endrec.GetComment();
1659             m_signature = 0;
1660             if (m_streamlink) {
1661                 m_streamlink->GetOutputStream()->SetComment(endrec.GetComment());
1662                 m_streamlink->Release(this);
1663                 m_streamlink = NULL;
1664             }
1665         }
1666         return wxSTREAM_EOF;
1667     }
1668 
1669     if (m_signature == LOCAL_MAGIC) {
1670         m_headerSize = m_entry.ReadLocal(*m_parent_i_stream, GetConv());
1671         m_signature = 0;
1672         m_entry.SetOffset(m_position);
1673         m_entry.SetKey(m_position);
1674 
1675         if (m_headerSize) {
1676             m_TotalEntries++;
1677             return wxSTREAM_NO_ERROR;
1678         }
1679     }
1680 
1681     wxLogError(_("error reading zip local header"));
1682     return wxSTREAM_READ_ERROR;
1683 }
1684 
ReadSignature()1685 wxUint32 wxZipInputStream::ReadSignature()
1686 {
1687     char magic[4];
1688     m_parent_i_stream->Read(magic, 4);
1689     return m_parent_i_stream->LastRead() == 4 ? CrackUint32(magic) : 0;
1690 }
1691 
OpenEntry(wxArchiveEntry & entry)1692 bool wxZipInputStream::OpenEntry(wxArchiveEntry& entry)
1693 {
1694     wxZipEntry *zipEntry = wxStaticCast(&entry, wxZipEntry);
1695     return zipEntry ? OpenEntry(*zipEntry) : false;
1696 }
1697 
1698 // Open an entry
1699 //
DoOpen(wxZipEntry * entry,bool raw)1700 bool wxZipInputStream::DoOpen(wxZipEntry *entry, bool raw)
1701 {
1702     if (m_position == wxInvalidOffset)
1703         if (!LoadEndRecord())
1704             return false;
1705     if (m_lasterror == wxSTREAM_READ_ERROR)
1706         return false;
1707     if (IsOpened())
1708         CloseEntry();
1709 
1710     m_raw = raw;
1711 
1712     if (entry) {
1713         if (AfterHeader() && entry->GetKey() == m_entry.GetOffset())
1714             return true;
1715         // can only open the current entry on a non-seekable stream
1716         wxCHECK(m_parentSeekable, false);
1717     }
1718 
1719     m_lasterror = wxSTREAM_READ_ERROR;
1720 
1721     if (entry)
1722         m_entry = *entry;
1723 
1724     if (m_parentSeekable) {
1725         if (QuietSeek(*m_parent_i_stream, m_entry.GetOffset())
1726                 == wxInvalidOffset)
1727             return false;
1728         if (ReadSignature() != LOCAL_MAGIC) {
1729             wxLogError(_("bad zipfile offset to entry"));
1730             return false;
1731         }
1732     }
1733 
1734     if (m_parentSeekable || AtHeader()) {
1735         m_headerSize = m_entry.ReadLocal(*m_parent_i_stream, GetConv());
1736         if (m_headerSize && m_parentSeekable) {
1737             wxZipEntry *ref = m_weaklinks->GetEntry(m_entry.GetKey());
1738             if (ref) {
1739                 Copy(ref->m_LocalExtra, m_entry.m_LocalExtra);
1740                 ref->Notify();
1741                 m_weaklinks->RemoveEntry(ref->GetKey());
1742             }
1743             if (entry && entry != ref) {
1744                 Copy(entry->m_LocalExtra, m_entry.m_LocalExtra);
1745                 entry->Notify();
1746             }
1747         }
1748     }
1749 
1750     if (m_headerSize)
1751         m_lasterror = wxSTREAM_NO_ERROR;
1752     return IsOk();
1753 }
1754 
OpenDecompressor(bool raw)1755 bool wxZipInputStream::OpenDecompressor(bool raw /*=false*/)
1756 {
1757     wxASSERT(AfterHeader());
1758 
1759     wxFileOffset compressedSize = m_entry.GetCompressedSize();
1760 
1761     if (raw)
1762         m_raw = true;
1763 
1764     if (m_raw) {
1765         if (compressedSize != wxInvalidOffset) {
1766             m_store->Open(compressedSize);
1767             m_decomp = m_store;
1768         } else {
1769             if (!m_rawin)
1770                 m_rawin = new wxRawInputStream(*m_parent_i_stream);
1771             m_decomp = m_rawin->Open(OpenDecompressor(m_rawin->GetTee()));
1772         }
1773     } else {
1774         if (compressedSize != wxInvalidOffset &&
1775                 (m_entry.GetMethod() != wxZIP_METHOD_DEFLATE ||
1776                  wxZlibInputStream::CanHandleGZip())) {
1777             m_store->Open(compressedSize);
1778             m_decomp = OpenDecompressor(*m_store);
1779         } else {
1780             m_decomp = OpenDecompressor(*m_parent_i_stream);
1781         }
1782     }
1783 
1784     m_crcAccumulator = crc32(0, Z_NULL, 0);
1785     m_lasterror = m_decomp ? m_decomp->GetLastError() : wxSTREAM_READ_ERROR;
1786     return IsOk();
1787 }
1788 
1789 // Can be overridden to add support for additional decompression methods
1790 //
OpenDecompressor(wxInputStream & stream)1791 wxInputStream *wxZipInputStream::OpenDecompressor(wxInputStream& stream)
1792 {
1793     switch (m_entry.GetMethod()) {
1794         case wxZIP_METHOD_STORE:
1795             if (m_entry.GetSize() == wxInvalidOffset) {
1796                 wxLogError(_("stored file length not in Zip header"));
1797                 break;
1798             }
1799             m_store->Open(m_entry.GetSize());
1800             return m_store;
1801 
1802         case wxZIP_METHOD_DEFLATE:
1803             if (!m_inflate)
1804                 m_inflate = new wxZlibInputStream2(stream);
1805             else
1806                 m_inflate->Open(stream);
1807             return m_inflate;
1808 
1809         default:
1810             wxLogError(_("unsupported Zip compression method"));
1811     }
1812 
1813     return NULL;
1814 }
1815 
CloseDecompressor(wxInputStream * decomp)1816 bool wxZipInputStream::CloseDecompressor(wxInputStream *decomp)
1817 {
1818     if (decomp && decomp == m_rawin)
1819         return CloseDecompressor(m_rawin->GetFilterInputStream());
1820     if (decomp != m_store && decomp != m_inflate)
1821         delete decomp;
1822     return true;
1823 }
1824 
1825 // Closes the current entry and positions the underlying stream at the start
1826 // of the next entry
1827 //
CloseEntry()1828 bool wxZipInputStream::CloseEntry()
1829 {
1830     if (AtHeader())
1831         return true;
1832     if (m_lasterror == wxSTREAM_READ_ERROR)
1833         return false;
1834 
1835     if (!m_parentSeekable) {
1836         if (!IsOpened() && !OpenDecompressor(true))
1837             return false;
1838 
1839         const int BUFSIZE = 8192;
1840         wxCharBuffer buf(BUFSIZE);
1841         while (IsOk())
1842             Read(buf.data(), BUFSIZE);
1843 
1844         m_position += m_headerSize + m_entry.GetCompressedSize();
1845     }
1846 
1847     if (m_lasterror == wxSTREAM_EOF)
1848         m_lasterror = wxSTREAM_NO_ERROR;
1849 
1850     CloseDecompressor(m_decomp);
1851     m_decomp = NULL;
1852     m_entry = wxZipEntry();
1853     m_headerSize = 0;
1854     m_raw = false;
1855 
1856     return IsOk();
1857 }
1858 
OnSysRead(void * buffer,size_t size)1859 size_t wxZipInputStream::OnSysRead(void *buffer, size_t size)
1860 {
1861     if (!IsOpened())
1862         if ((AtHeader() && !DoOpen()) || !OpenDecompressor())
1863             m_lasterror = wxSTREAM_READ_ERROR;
1864     if (!IsOk() || !size)
1865         return 0;
1866 
1867     size_t count = m_decomp->Read(buffer, size).LastRead();
1868     if (!m_raw)
1869         m_crcAccumulator = crc32(m_crcAccumulator, (Byte*)buffer, count);
1870     if (count < size)
1871         m_lasterror = m_decomp->GetLastError();
1872 
1873     if (Eof()) {
1874         if ((m_entry.GetFlags() & wxZIP_SUMS_FOLLOW) != 0) {
1875             m_headerSize += m_entry.ReadDescriptor(*m_parent_i_stream);
1876             wxZipEntry *entry = m_weaklinks->GetEntry(m_entry.GetKey());
1877 
1878             if (entry) {
1879                 entry->SetCrc(m_entry.GetCrc());
1880                 entry->SetCompressedSize(m_entry.GetCompressedSize());
1881                 entry->SetSize(m_entry.GetSize());
1882                 entry->Notify();
1883             }
1884         }
1885 
1886         if (!m_raw) {
1887             m_lasterror = wxSTREAM_READ_ERROR;
1888 
1889             if (m_entry.GetSize() != TellI())
1890             {
1891                 wxLogError(_("reading zip stream (entry %s): bad length"),
1892                            m_entry.GetName().c_str());
1893             }
1894             else if (m_crcAccumulator != m_entry.GetCrc())
1895             {
1896                 wxLogError(_("reading zip stream (entry %s): bad crc"),
1897                            m_entry.GetName().c_str());
1898             }
1899             else
1900             {
1901                 m_lasterror = wxSTREAM_EOF;
1902             }
1903         }
1904     }
1905 
1906     return count;
1907 }
1908 
1909 #if WXWIN_COMPATIBILITY_2_6
1910 
1911 // Borrowed from VS's zip stream (c) 1999 Vaclav Slavik
1912 //
OnSysSeek(wxFileOffset seek,wxSeekMode mode)1913 wxFileOffset wxZipInputStream::OnSysSeek(wxFileOffset seek, wxSeekMode mode)
1914 {
1915     // seeking works when the stream is created with the compatibility
1916     // constructor
1917     if (!m_allowSeeking)
1918         return wxInvalidOffset;
1919     if (!IsOpened())
1920         if ((AtHeader() && !DoOpen()) || !OpenDecompressor())
1921             m_lasterror = wxSTREAM_READ_ERROR;
1922     if (!IsOk())
1923         return wxInvalidOffset;
1924 
1925     // NB: since ZIP files don't natively support seeking, we have to
1926     //     implement a brute force workaround -- reading all the data
1927     //     between current and the new position (or between beginning of
1928     //     the file and new position...)
1929 
1930     wxFileOffset nextpos;
1931     wxFileOffset pos = TellI();
1932 
1933     switch ( mode )
1934     {
1935         case wxFromCurrent : nextpos = seek + pos; break;
1936         case wxFromStart : nextpos = seek; break;
1937         case wxFromEnd : nextpos = GetLength() + seek; break;
1938         default : nextpos = pos; break; /* just to fool compiler, never happens */
1939     }
1940 
1941     wxFileOffset toskip wxDUMMY_INITIALIZE(0);
1942     if ( nextpos >= pos )
1943     {
1944         toskip = nextpos - pos;
1945     }
1946     else
1947     {
1948         wxZipEntry current(m_entry);
1949         if (!OpenEntry(current))
1950         {
1951             m_lasterror = wxSTREAM_READ_ERROR;
1952             return pos;
1953         }
1954         toskip = nextpos;
1955     }
1956 
1957     if ( toskip > 0 )
1958     {
1959         const int BUFSIZE = 4096;
1960         size_t sz;
1961         char buffer[BUFSIZE];
1962         while ( toskip > 0 )
1963         {
1964             sz = wx_truncate_cast(size_t, wxMin(toskip, BUFSIZE));
1965             Read(buffer, sz);
1966             toskip -= sz;
1967         }
1968     }
1969 
1970     pos = nextpos;
1971     return pos;
1972 }
1973 
1974 #endif // WXWIN_COMPATIBILITY_2_6
1975 
1976 
1977 /////////////////////////////////////////////////////////////////////////////
1978 // Output stream
1979 
1980 #include "wx/listimpl.cpp"
WX_DEFINE_LIST(wxZipEntryList_)1981 WX_DEFINE_LIST(wxZipEntryList_)
1982 
1983 wxZipOutputStream::wxZipOutputStream(wxOutputStream& stream,
1984                                      int level      /*=-1*/,
1985                                      wxMBConv& conv /*=wxConvLocal*/)
1986   : wxArchiveOutputStream(stream, conv)
1987 {
1988     Init(level);
1989 }
1990 
wxZipOutputStream(wxOutputStream * stream,int level,wxMBConv & conv)1991 wxZipOutputStream::wxZipOutputStream(wxOutputStream *stream,
1992                                      int level      /*=-1*/,
1993                                      wxMBConv& conv /*=wxConvLocal*/)
1994   : wxArchiveOutputStream(stream, conv)
1995 {
1996     Init(level);
1997 }
1998 
Init(int level)1999 void wxZipOutputStream::Init(int level)
2000 {
2001     m_store = new wxStoredOutputStream(*m_parent_o_stream);
2002     m_deflate = NULL;
2003     m_backlink = NULL;
2004     m_initialData = new char[OUTPUT_LATENCY];
2005     m_initialSize = 0;
2006     m_pending = NULL;
2007     m_raw = false;
2008     m_headerOffset = 0;
2009     m_headerSize = 0;
2010     m_entrySize = 0;
2011     m_comp = NULL;
2012     m_level = level;
2013     m_offsetAdjustment = wxInvalidOffset;
2014     m_endrecWritten = false;
2015 }
2016 
~wxZipOutputStream()2017 wxZipOutputStream::~wxZipOutputStream()
2018 {
2019     Close();
2020     WX_CLEAR_LIST(wxZipEntryList_, m_entries);
2021     delete m_store;
2022     delete m_deflate;
2023     delete m_pending;
2024     delete [] m_initialData;
2025     if (m_backlink)
2026         m_backlink->Release(this);
2027 }
2028 
PutNextEntry(const wxString & name,const wxDateTime & dt,wxFileOffset size)2029 bool wxZipOutputStream::PutNextEntry(
2030     const wxString& name,
2031     const wxDateTime& dt /*=wxDateTime::Now()*/,
2032     wxFileOffset size    /*=wxInvalidOffset*/)
2033 {
2034     return PutNextEntry(new wxZipEntry(name, dt, size));
2035 }
2036 
PutNextDirEntry(const wxString & name,const wxDateTime & dt)2037 bool wxZipOutputStream::PutNextDirEntry(
2038     const wxString& name,
2039     const wxDateTime& dt /*=wxDateTime::Now()*/)
2040 {
2041     wxZipEntry *entry = new wxZipEntry(name, dt);
2042     entry->SetIsDir();
2043     return PutNextEntry(entry);
2044 }
2045 
CopyEntry(wxZipEntry * entry,wxZipInputStream & inputStream)2046 bool wxZipOutputStream::CopyEntry(wxZipEntry *entry,
2047                                   wxZipInputStream& inputStream)
2048 {
2049     wxZipEntryPtr_ e(entry);
2050 
2051     return
2052         inputStream.DoOpen(e.get(), true) &&
2053         DoCreate(e.release(), true) &&
2054         Write(inputStream).IsOk() && inputStream.Eof();
2055 }
2056 
PutNextEntry(wxArchiveEntry * entry)2057 bool wxZipOutputStream::PutNextEntry(wxArchiveEntry *entry)
2058 {
2059     wxZipEntry *zipEntry = wxStaticCast(entry, wxZipEntry);
2060     if (!zipEntry)
2061         delete entry;
2062     return PutNextEntry(zipEntry);
2063 }
2064 
CopyEntry(wxArchiveEntry * entry,wxArchiveInputStream & stream)2065 bool wxZipOutputStream::CopyEntry(wxArchiveEntry *entry,
2066                                   wxArchiveInputStream& stream)
2067 {
2068     wxZipEntry *zipEntry = wxStaticCast(entry, wxZipEntry);
2069 
2070     if (!zipEntry || !stream.OpenEntry(*zipEntry)) {
2071         delete entry;
2072         return false;
2073     }
2074 
2075     return CopyEntry(zipEntry, static_cast<wxZipInputStream&>(stream));
2076 }
2077 
CopyArchiveMetaData(wxZipInputStream & inputStream)2078 bool wxZipOutputStream::CopyArchiveMetaData(wxZipInputStream& inputStream)
2079 {
2080     m_Comment = inputStream.GetComment();
2081     if (m_backlink)
2082         m_backlink->Release(this);
2083     m_backlink = inputStream.MakeLink(this);
2084     return true;
2085 }
2086 
CopyArchiveMetaData(wxArchiveInputStream & stream)2087 bool wxZipOutputStream::CopyArchiveMetaData(wxArchiveInputStream& stream)
2088 {
2089     return CopyArchiveMetaData(static_cast<wxZipInputStream&>(stream));
2090 }
2091 
SetLevel(int level)2092 void wxZipOutputStream::SetLevel(int level)
2093 {
2094     if (level != m_level) {
2095         if (m_comp != m_deflate)
2096             delete m_deflate;
2097         m_deflate = NULL;
2098         m_level = level;
2099     }
2100 }
2101 
DoCreate(wxZipEntry * entry,bool raw)2102 bool wxZipOutputStream::DoCreate(wxZipEntry *entry, bool raw /*=false*/)
2103 {
2104     CloseEntry();
2105 
2106     m_pending = entry;
2107     if (!m_pending)
2108         return false;
2109 
2110     // write the signature bytes right away
2111     wxDataOutputStream ds(*m_parent_o_stream);
2112     ds << LOCAL_MAGIC;
2113 
2114     // and if this is the first entry test for seekability
2115     if (m_headerOffset == 0 && m_parent_o_stream->IsSeekable()) {
2116 #if wxUSE_LOG
2117         bool logging = wxLog::IsEnabled();
2118         wxLogNull nolog;
2119 #endif // wxUSE_LOG
2120         wxFileOffset here = m_parent_o_stream->TellO();
2121 
2122         if (here != wxInvalidOffset && here >= 4) {
2123             if (m_parent_o_stream->SeekO(here - 4) == here - 4) {
2124                 m_offsetAdjustment = here - 4;
2125 #if wxUSE_LOG
2126                 wxLog::EnableLogging(logging);
2127 #endif // wxUSE_LOG
2128                 m_parent_o_stream->SeekO(here);
2129             }
2130         }
2131     }
2132 
2133     m_pending->SetOffset(m_headerOffset);
2134 
2135     m_crcAccumulator = crc32(0, Z_NULL, 0);
2136 
2137     if (raw)
2138         m_raw = true;
2139 
2140     m_lasterror = wxSTREAM_NO_ERROR;
2141     return true;
2142 }
2143 
2144 // Can be overridden to add support for additional compression methods
2145 //
OpenCompressor(wxOutputStream & stream,wxZipEntry & entry,const Buffer bufs[])2146 wxOutputStream *wxZipOutputStream::OpenCompressor(
2147     wxOutputStream& stream,
2148     wxZipEntry& entry,
2149     const Buffer bufs[])
2150 {
2151     if (entry.GetMethod() == wxZIP_METHOD_DEFAULT) {
2152         if (GetLevel() == 0
2153                 && (IsParentSeekable()
2154                     || entry.GetCompressedSize() != wxInvalidOffset
2155                     || entry.GetSize() != wxInvalidOffset)) {
2156             entry.SetMethod(wxZIP_METHOD_STORE);
2157         } else {
2158             int size = 0;
2159             for (int i = 0; bufs[i].m_data; ++i)
2160                 size += bufs[i].m_size;
2161             entry.SetMethod(size <= 6 ?
2162                             wxZIP_METHOD_STORE : wxZIP_METHOD_DEFLATE);
2163         }
2164     }
2165 
2166     switch (entry.GetMethod()) {
2167         case wxZIP_METHOD_STORE:
2168             if (entry.GetCompressedSize() == wxInvalidOffset)
2169                 entry.SetCompressedSize(entry.GetSize());
2170             return m_store;
2171 
2172         case wxZIP_METHOD_DEFLATE:
2173         {
2174             int defbits = wxZIP_DEFLATE_NORMAL;
2175             switch (GetLevel()) {
2176                 case 0: case 1:
2177                     defbits = wxZIP_DEFLATE_SUPERFAST;
2178                     break;
2179                 case 2: case 3: case 4:
2180                     defbits = wxZIP_DEFLATE_FAST;
2181                     break;
2182                 case 8: case 9:
2183                     defbits = wxZIP_DEFLATE_EXTRA;
2184                     break;
2185             }
2186             entry.SetFlags((entry.GetFlags() & ~wxZIP_DEFLATE_MASK) |
2187                             defbits | wxZIP_SUMS_FOLLOW);
2188 
2189             if (!m_deflate)
2190                 m_deflate = new wxZlibOutputStream2(stream, GetLevel());
2191             else
2192                 m_deflate->Open(stream);
2193 
2194             return m_deflate;
2195         }
2196 
2197         default:
2198             wxLogError(_("unsupported Zip compression method"));
2199     }
2200 
2201     return NULL;
2202 }
2203 
CloseCompressor(wxOutputStream * comp)2204 bool wxZipOutputStream::CloseCompressor(wxOutputStream *comp)
2205 {
2206     if (comp == m_deflate)
2207         m_deflate->Close();
2208     else if (comp != m_store)
2209         delete comp;
2210     return true;
2211 }
2212 
2213 // This is called when OUPUT_LATENCY bytes has been written to the
2214 // wxZipOutputStream to actually create the zip entry.
2215 //
CreatePendingEntry(const void * buffer,size_t size)2216 void wxZipOutputStream::CreatePendingEntry(const void *buffer, size_t size)
2217 {
2218     wxASSERT(IsOk() && m_pending && !m_comp);
2219     wxZipEntryPtr_ spPending(m_pending);
2220     m_pending = NULL;
2221 
2222     Buffer bufs[] = {
2223         { m_initialData, m_initialSize },
2224         { (const char*)buffer, size },
2225         { NULL, 0 }
2226     };
2227 
2228     if (m_raw)
2229         m_comp = m_store;
2230     else
2231         m_comp = OpenCompressor(*m_store, *spPending,
2232                                 m_initialSize ? bufs : bufs + 1);
2233 
2234     if (IsParentSeekable()
2235         || (spPending->m_Crc
2236             && spPending->m_CompressedSize != wxInvalidOffset
2237             && spPending->m_Size != wxInvalidOffset))
2238         spPending->m_Flags &= ~wxZIP_SUMS_FOLLOW;
2239     else
2240         if (spPending->m_CompressedSize != wxInvalidOffset)
2241             spPending->m_Flags |= wxZIP_SUMS_FOLLOW;
2242 
2243     m_headerSize = spPending->WriteLocal(*m_parent_o_stream, GetConv());
2244     m_lasterror = m_parent_o_stream->GetLastError();
2245 
2246     if (IsOk()) {
2247         m_entries.push_back(spPending.release());
2248         OnSysWrite(m_initialData, m_initialSize);
2249     }
2250 
2251     m_initialSize = 0;
2252 }
2253 
2254 // This is called to write out the zip entry when Close has been called
2255 // before OUTPUT_LATENCY bytes has been written to the wxZipOutputStream.
2256 //
CreatePendingEntry()2257 void wxZipOutputStream::CreatePendingEntry()
2258 {
2259     wxASSERT(IsOk() && m_pending && !m_comp);
2260     wxZipEntryPtr_ spPending(m_pending);
2261     m_pending = NULL;
2262     m_lasterror = wxSTREAM_WRITE_ERROR;
2263 
2264     if (!m_raw) {
2265         // Initially compresses the data to memory, then fall back to 'store'
2266         // if the compressor makes the data larger rather than smaller.
2267         wxMemoryOutputStream mem;
2268         Buffer bufs[] = { { m_initialData, m_initialSize }, { NULL, 0 } };
2269         wxOutputStream *comp = OpenCompressor(mem, *spPending, bufs);
2270 
2271         if (!comp)
2272             return;
2273         if (comp != m_store) {
2274             bool ok = comp->Write(m_initialData, m_initialSize).IsOk();
2275             CloseCompressor(comp);
2276             if (!ok)
2277                 return;
2278         }
2279 
2280         m_entrySize = m_initialSize;
2281         m_crcAccumulator = crc32(0, (Byte*)m_initialData, m_initialSize);
2282 
2283         if (mem.GetSize() > 0 && mem.GetSize() < m_initialSize) {
2284             m_initialSize = mem.GetSize();
2285             mem.CopyTo(m_initialData, m_initialSize);
2286         } else {
2287             spPending->SetMethod(wxZIP_METHOD_STORE);
2288         }
2289 
2290         spPending->SetSize(m_entrySize);
2291         spPending->SetCrc(m_crcAccumulator);
2292         spPending->SetCompressedSize(m_initialSize);
2293     }
2294 
2295     spPending->m_Flags &= ~wxZIP_SUMS_FOLLOW;
2296     m_headerSize = spPending->WriteLocal(*m_parent_o_stream, GetConv());
2297 
2298     if (m_parent_o_stream->IsOk()) {
2299         m_entries.push_back(spPending.release());
2300         m_comp = m_store;
2301         m_store->Write(m_initialData, m_initialSize);
2302     }
2303 
2304     m_initialSize = 0;
2305     m_lasterror = m_parent_o_stream->GetLastError();
2306 }
2307 
2308 // Write the 'central directory' and the 'end-central-directory' records.
2309 //
Close()2310 bool wxZipOutputStream::Close()
2311 {
2312     CloseEntry();
2313 
2314     if (m_lasterror == wxSTREAM_WRITE_ERROR
2315         || (m_entries.size() == 0 && m_endrecWritten))
2316     {
2317         wxFilterOutputStream::Close();
2318         return false;
2319     }
2320 
2321     wxZipEndRec endrec;
2322 
2323     endrec.SetEntriesHere(m_entries.size());
2324     endrec.SetTotalEntries(m_entries.size());
2325     endrec.SetOffset(m_headerOffset);
2326     endrec.SetComment(m_Comment);
2327 
2328     wxZipEntryList_::iterator it;
2329     wxFileOffset size = 0;
2330 
2331     for (it = m_entries.begin(); it != m_entries.end(); ++it) {
2332         size += (*it)->WriteCentral(*m_parent_o_stream, GetConv());
2333         delete *it;
2334     }
2335     m_entries.clear();
2336 
2337     endrec.SetSize(size);
2338     endrec.Write(*m_parent_o_stream, GetConv());
2339 
2340     m_lasterror = m_parent_o_stream->GetLastError();
2341     m_endrecWritten = true;
2342 
2343     if (!wxFilterOutputStream::Close() || !IsOk())
2344         return false;
2345     m_lasterror = wxSTREAM_EOF;
2346     return true;
2347 }
2348 
2349 // Finish writing the current entry
2350 //
CloseEntry()2351 bool wxZipOutputStream::CloseEntry()
2352 {
2353     if (IsOk() && m_pending)
2354         CreatePendingEntry();
2355     if (!IsOk())
2356         return false;
2357     if (!m_comp)
2358         return true;
2359 
2360     CloseCompressor(m_comp);
2361     m_comp = NULL;
2362 
2363     wxFileOffset compressedSize = m_store->TellO();
2364 
2365     wxZipEntry& entry = *m_entries.back();
2366 
2367     // When writing raw the crc and size can't be checked
2368     if (m_raw) {
2369         m_crcAccumulator = entry.GetCrc();
2370         m_entrySize = entry.GetSize();
2371     }
2372 
2373     // Write the sums in the trailing 'data descriptor' if necessary
2374     if (entry.m_Flags & wxZIP_SUMS_FOLLOW) {
2375         wxASSERT(!IsParentSeekable());
2376         m_headerOffset +=
2377             entry.WriteDescriptor(*m_parent_o_stream, m_crcAccumulator,
2378                                   compressedSize, m_entrySize);
2379         m_lasterror = m_parent_o_stream->GetLastError();
2380     }
2381 
2382     // If the local header didn't have the correct crc and size written to
2383     // it then seek back and fix it
2384     else if (m_crcAccumulator != entry.GetCrc()
2385             || m_entrySize != entry.GetSize()
2386             || compressedSize != entry.GetCompressedSize())
2387     {
2388         if (IsParentSeekable()) {
2389             wxFileOffset here = m_parent_o_stream->TellO();
2390             wxFileOffset headerOffset = m_headerOffset + m_offsetAdjustment;
2391             m_parent_o_stream->SeekO(headerOffset + SUMS_OFFSET);
2392             entry.WriteDescriptor(*m_parent_o_stream, m_crcAccumulator,
2393                                   compressedSize, m_entrySize);
2394             m_parent_o_stream->SeekO(here);
2395             m_lasterror = m_parent_o_stream->GetLastError();
2396         } else {
2397             m_lasterror = wxSTREAM_WRITE_ERROR;
2398         }
2399     }
2400 
2401     m_headerOffset += m_headerSize + compressedSize;
2402     m_headerSize = 0;
2403     m_entrySize = 0;
2404     m_store->Close();
2405     m_raw = false;
2406 
2407     if (IsOk())
2408         m_lasterror = m_parent_o_stream->GetLastError();
2409     else
2410         wxLogError(_("error writing zip entry '%s': bad crc or length"),
2411                    entry.GetName().c_str());
2412     return IsOk();
2413 }
2414 
Sync()2415 void wxZipOutputStream::Sync()
2416 {
2417     if (IsOk() && m_pending)
2418         CreatePendingEntry(NULL, 0);
2419     if (!m_comp)
2420         m_lasterror = wxSTREAM_WRITE_ERROR;
2421     if (IsOk()) {
2422         m_comp->Sync();
2423         m_lasterror = m_comp->GetLastError();
2424     }
2425 }
2426 
OnSysWrite(const void * buffer,size_t size)2427 size_t wxZipOutputStream::OnSysWrite(const void *buffer, size_t size)
2428 {
2429     if (IsOk() && m_pending) {
2430         if (m_initialSize + size < OUTPUT_LATENCY) {
2431             memcpy(m_initialData + m_initialSize, buffer, size);
2432             m_initialSize += size;
2433             return size;
2434         } else {
2435             CreatePendingEntry(buffer, size);
2436         }
2437     }
2438 
2439     if (!m_comp)
2440         m_lasterror = wxSTREAM_WRITE_ERROR;
2441     if (!IsOk() || !size)
2442         return 0;
2443 
2444     if (m_comp->Write(buffer, size).LastWrite() != size)
2445         m_lasterror = wxSTREAM_WRITE_ERROR;
2446     m_crcAccumulator = crc32(m_crcAccumulator, (Byte*)buffer, size);
2447     m_entrySize += m_comp->LastWrite();
2448 
2449     return m_comp->LastWrite();
2450 }
2451 
2452 #endif // wxUSE_ZIPSTREAM
2453