From 00375592f92f68c4ca3f44d8e839bcfd47adc4e1 Mon Sep 17 00:00:00 2001
From: Vadim Zeitlin <vadim@wxwidgets.org>
Date: Wed, 10 Nov 2004 23:58:38 +0000
Subject: [PATCH] added ZIP classes by M.J.Wetherell (patch 1030239)

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@30436 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
---
 build/bakefiles/common.bkl    |    1 -
 build/bakefiles/files.bkl     |    2 +-
 docs/changes.txt              |    1 +
 docs/latex/wx/arc.tex         |  433 +++++++
 docs/latex/wx/archive.tex     |  648 ++++++++++
 docs/latex/wx/classes.tex     |    1 +
 docs/latex/wx/topics.tex      |    1 +
 docs/latex/wx/zipstrm.tex     |  696 +++++++++-
 include/wx/archive.h          |  353 ++++++
 include/wx/fs_zip.h           |    3 +-
 include/wx/zipstrm.h          |  521 +++++++-
 src/common/archive.cpp        |   74 ++
 src/common/fs_zip.cpp         |   59 +-
 src/common/zipstrm.cpp        | 2229 +++++++++++++++++++++++++++++++--
 tests/archive/archivetest.cpp | 1649 ++++++++++++++++++++++++
 tests/test.bkl                |    1 +
 16 files changed, 6518 insertions(+), 154 deletions(-)
 create mode 100644 docs/latex/wx/arc.tex
 create mode 100644 docs/latex/wx/archive.tex
 create mode 100644 include/wx/archive.h
 create mode 100644 src/common/archive.cpp
 create mode 100644 tests/archive/archivetest.cpp

diff --git a/build/bakefiles/common.bkl b/build/bakefiles/common.bkl
index b3f93fa901..d0efe6ec53 100644
--- a/build/bakefiles/common.bkl
+++ b/build/bakefiles/common.bkl
@@ -473,7 +473,6 @@ $(TAB)copy "$(DOLLAR)(InputPath)" $(SETUPHDIR)\wx\setup.h
             <precomp-headers>on</precomp-headers>
             <precomp-headers-file>wxprec_$(id)</precomp-headers-file>
             <precomp-headers-exclude>
-                src/common/unzip.c
                 src/common/extended.c
                 src/msw/gsocket.cpp
                 src/msw/gsockmsw.cpp
diff --git a/build/bakefiles/files.bkl b/build/bakefiles/files.bkl
index 33079e69fe..38638abd71 100644
--- a/build/bakefiles/files.bkl
+++ b/build/bakefiles/files.bkl
@@ -214,7 +214,7 @@ IMPORTANT: please read docs/tech/tn0016.txt before modifying this file!
     src/common/textfile.cpp
     src/common/tokenzr.cpp
     src/common/txtstrm.cpp
-    src/common/unzip.c
+    src/common/archive.cpp
     src/common/uri.cpp
     src/common/variant.cpp
     src/common/wfstream.cpp
diff --git a/docs/changes.txt b/docs/changes.txt
index ef2582f98f..7a503dfa36 100644
--- a/docs/changes.txt
+++ b/docs/changes.txt
@@ -217,6 +217,7 @@ OTHER CHANGES
 
 All:
 
+- new classes for reading and writing ZIP files (M.J.Wetherell)
 - Norwegian (Bokmål) translation added (Hans F. Nordhaug)
 - wxDynamicLibrary::HasSymbol() added
 - added wxTextInputStream::operator>>(wchar_t) for compilers which support this
diff --git a/docs/latex/wx/arc.tex b/docs/latex/wx/arc.tex
new file mode 100644
index 0000000000..a7f3fdc7ce
--- /dev/null
+++ b/docs/latex/wx/arc.tex
@@ -0,0 +1,433 @@
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Name:        arc.tex
+%% Purpose:     Overview of the archive classes
+%% Author:      M.J.Wetherell
+%% RCS-ID:      $Id$
+%% Copyright:   2004 M.J.Wetherell
+%% License:     wxWidgets license
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\section{Archive formats such as zip}\label{wxarc}
+
+The archive classes handle archive formats such as zip, tar, rar and cab.
+Currently only the wxZip classes are included.
+
+For each archive type, there are the following classes (using zip here
+as an example):
+
+\begin{twocollist}\twocolwidtha{4cm}
+\twocolitem{\helpref{wxZipInputStream}{wxzipinputstream}}{Input stream}
+\twocolitem{\helpref{wxZipOutputStream}{wxzipoutputstream}}{Output stream}
+\twocolitem{\helpref{wxZipEntry}{wxzipentry}}{Holds the meta-data for an
+entry (e.g. filename, timestamp, etc.)}
+\end{twocollist}
+
+There are also abstract wxArchive classes that can be used to write code
+that can handle any of the archive types,
+see '\helpref{Generic archive programming}{wxarcgeneric}'.
+Also see \helpref{wxFileSystem}{fs} for a higher level interface that
+can handle archive files in a generic way.
+
+The classes are designed to handle archives on both seekable streams such
+as disk files, or non-seekable streams such as pipes and sockets
+(see '\helpref{Archives on non-seekable streams}{wxarcnoseek}').
+
+\wxheading{See also}
+
+\helpref{wxFileSystem}{fs}
+
+
+\subsection{Creating an archive}\label{wxarccreate}
+
+\helpref{Archive formats such as zip}{wxarc}
+
+Call \helpref{PutNextEntry()}{wxarchiveoutputstreamputnextentry} to
+create each new entry in the archive, then write the entry's data.
+Another call to PutNextEntry() closes the current entry and begins the next.
+
+For example:
+
+\begin{verbatim}
+    wxFFileOutputStream out(_T("test.zip"));
+    wxZipOutputStream zip(out);
+    wxTextOutputStream txt(zip);
+
+    zip.PutNextEntry(_T("entry1.txt"));
+    txt << _T("Some text for entry1\n");
+
+    zip.PutNextEntry(_T("entry2.txt"));
+    txt << _T("Some text for entry2\n");
+
+\end{verbatim}
+
+
+\subsection{Extracting an archive}\label{wxarcextract}
+
+\helpref{Archive formats such as zip}{wxarc}
+
+\helpref{GetNextEntry()}{wxarchiveinputstreamgetnextentry} returns an
+entry object containing the meta-data for the next entry in the archive
+(and gives away ownership). Reading from the input stream then returns
+the entry's data. Eof() becomes true after an attempt has been made to
+read past the end of the entry's data.
+
+When there are no more entries, GetNextEntry() returns NULL and sets Eof().
+
+\begin{verbatim}
+    wxDEFINE_SCOPED_PTR_TYPE(wxZipEntry);
+    wxZipEntryPtr entry;
+
+    wxFFileInputStream in(_T("test.zip"));
+    wxZipInputStream zip(in);
+    wxTextInputStream txt(zip);
+    wxString data;
+
+    while (entry.reset(zip.GetNextEntry()), entry.get() != NULL)
+    {
+        wxString name = entry->GetName();    // access meta-data
+        txt >> data;                         // access data
+    }
+
+\end{verbatim}
+
+
+\subsection{Modifying an archive}\label{wxarcmodify}
+
+\helpref{Archive formats such as zip}{wxarc}
+
+To modify an existing archive, write a new copy of the archive to a new file,
+making any necessary changes along the way and transferring any unchanged
+entries using \helpref{CopyEntry()}{wxarchiveoutputstreamcopyentry}.
+For archive types which compress entry data, CopyEntry() is likely to be
+much more efficient than transferring the data using Read() and Write()
+since it will copy them without decompressing and recompressing them.
+
+In general modifications are not possible without rewriting the archive,
+though it may be possible in some limited cases. Even then, rewriting
+the archive is usually a better choice since a failure can be handled
+without losing the whole archive.
+
+For example to delete all entries matching the pattern "*.txt":
+
+\begin{verbatim}
+    wxFFileInputStream in(_T("in.zip"));
+    wxFFileOutputStream out(_T("out.zip"));
+
+    wxZipInputStream inzip(in);
+    wxZipOutputStream outzip(out);
+    wxZipEntryPtr entry;
+
+    // transfer any meta-data for the archive as a whole (the zip comment
+    // in the case of zip)
+    outzip.CopyArchiveMetaData(inzip);
+
+    // call CopyEntry for each entry except those matching the pattern
+    while (entry.reset(inzip.GetNextEntry()), entry.get() != NULL)
+        if (!entry->GetName().Matches(_T("*.txt")))
+            if (!outzip.CopyEntry(entry.release(), inzip))
+                break;
+
+    bool success = inzip.Eof() && outzip.Close();
+
+\end{verbatim}
+
+
+\subsection{Looking up an archive entry by name}\label{wxarcbyname}
+
+\helpref{Archive formats such as zip}{wxarc}
+
+Also see \helpref{wxFileSystem}{fs} for a higher level interface that is
+more convenient for accessing archive entries by name.
+
+To open just one entry in an archive, the most efficient way is
+to simply search for it linearly by calling
+ \helpref{GetNextEntry()}{wxarchiveinputstreamgetnextentry} until the
+required entry is found. This works both for archives on seekable and
+non-seekable streams.
+
+The format of filenames in the archive is likely to be different
+from the local filename format. For example zips and tars use
+unix style names, with forward slashes as the path separator,
+and absolute paths are not allowed. So if on Windows the file
+"C:$\backslash$MYDIR$\backslash$MYFILE.TXT" is stored, then when reading
+the entry back \helpref{GetName()}{wxarchiveentryname} will return
+"MYDIR$\backslash$MYFILE.TXT". The conversion into the internal format
+and back has lost some information.
+
+So to avoid ambiguity when searching for an entry matching a local name,
+it is better to convert the local name to the archive's internal format
+and search for that:
+
+\begin{verbatim}
+    wxDEFINE_SCOPED_PTR_TYPE(wxZipEntry);
+    wxZipEntryPtr entry;
+
+    // convert the local name we are looking for into the internal format
+    wxString name = wxZipEntry::GetInternalName(localname);
+
+    // open the zip
+    wxFFileInputStream in(_T("test.zip"));
+    wxZipInputStream zip(in);
+
+    // call GetNextEntry() until the required internal name is found
+    do {
+        entry.reset(zip.GetNextEntry());
+    }
+    while (entry.get() != NULL && entry->GetInternalName() != name);
+
+    if (entry.get() != NULL) {
+        // read the entry's data...
+    }
+
+\end{verbatim}
+
+To access several entries randomly, it is most efficient to transfer the
+entire catalogue of entries to a container such as a std::map or a
+ \helpref{wxHashMap}{wxhashmap} then entries looked up by name can be
+opened using the \helpref{OpenEntry()}{wxarchiveinputstreamopenentry} method.
+
+\begin{verbatim}
+    WX_DECLARE_STRING_HASH_MAP(wxZipEntry*, ZipCatalog);
+    ZipCatalog::iterator it;
+    wxZipEntry *entry;
+    ZipCatalog cat;
+
+    // open the zip
+    wxFFileInputStream in(_T("test.zip"));
+    wxZipInputStream zip(in);
+
+    // load the zip catalog
+    while ((entry = zip.GetNextEntry()) != NULL) {
+        wxZipEntry*& current = cat[entry->GetInternalName()];
+        // some archive formats can have multiple entries with the same name
+        // (e.g. tar) though it is an error in the case of zip
+        delete current;
+        current = entry;
+    }
+
+    // open an entry by name
+    if ((it = cat.find(wxZipEntry::GetInternalName(localname))) != cat.end()) {
+        zip.OpenEntry(*it->second);
+        // ... now read entry's data
+    }
+
+\end{verbatim}
+
+To open more than one entry simultaneously you need more than one
+underlying stream on the same archive:
+
+\begin{verbatim}
+    // opening another entry without closing the first requires another
+    // input stream for the same file
+    wxFFileInputStream in2(_T("test.zip"));
+    wxZipInputStream zip2(in2);
+    if ((it = cat.find(wxZipEntry::GetInternalName(local2))) != cat.end())
+        zip2.OpenEntry(*it->second);
+
+\end{verbatim}
+
+
+\subsection{Generic archive programming}\label{wxarcgeneric}
+
+\helpref{Archive formats such as zip}{wxarc}
+
+Also see \helpref{wxFileSystem}{fs} for a higher level interface that
+can handle archive files in a generic way.
+
+The specific archive classes, such as the wxZip classes, inherit from
+the following abstract classes which can be used to write code that can
+handle any of the archive types:
+
+\begin{twocollist}\twocolwidtha{5cm}
+\twocolitem{\helpref{wxArchiveInputStream}{wxarchiveinputstream}}{Input stream}
+\twocolitem{\helpref{wxArchiveOutputStream}{wxarchiveoutputstream}}{Output stream}
+\twocolitem{\helpref{wxArchiveEntry}{wxarchiveentry}}{Holds the meta-data for an
+entry (e.g. filename)}
+\end{twocollist}
+
+In order to able to write generic code it's necessary to be able to create
+instances of the classes without knowing which archive type is being used.
+So there is a class factory for each archive type, derived from
+ \helpref{wxArchiveClassFactory}{wxarchiveclassfactory}, which can create
+the other classes.
+
+For example, given {\it wxArchiveClassFactory* factory}:
+
+\begin{verbatim}
+    // create streams without knowing their type
+    wxArchiveInputStreamPtr  inarc(factory->NewStream(in));
+    wxArchiveOutputStreamPtr outarc(factory->NewStream(out));
+
+    // create an empty entry object
+    wxArchiveEntryPtr        entry(factory->NewEntry());
+
+\end{verbatim}
+
+The class factory itself can either be created explicitly:
+
+\begin{verbatim}
+    wxArchiveClassFactory *factory = new wxZipClassFactory;
+
+\end{verbatim}
+
+or using wxWidgets' \helpref{RTTI}{runtimeclassoverview}:
+
+\begin{verbatim}
+wxArchiveClassFactory *MakeFactory(const wxString& type)
+{
+    wxString name = _T("wx") + type.Left(1).Upper() +
+                    type.Mid(1).Lower() + _T("ClassFactory");
+
+    wxObject *pObj = wxCreateDynamicObject(name);
+    wxArchiveClassFactory *pcf = wxDynamicCast(pObj, wxArchiveClassFactory);
+
+    if (!pcf) {
+        wxLogError(_T("can't handle '%s' archives"), type.c_str());
+        delete pObj;
+    }
+
+    return pcf;
+}
+
+\end{verbatim}
+
+
+\subsection{Archives on non-seekable streams}\label{wxarcnoseek}
+
+\helpref{Archive formats such as zip}{wxarc}
+
+In general, handling archives on non-seekable streams is done in the same
+way as for seekable streams, with a few caveats.
+
+The main limitation is that accessing entries randomly using
+ \helpref{OpenEntry()}{wxarchiveinputstreamopenentry} 
+is not possible, the entries can only be accessed sequentially in the order 
+they are stored within the archive.
+
+For each archive type, there will also be other limitations which will
+depend on the order the entries' meta-data is stored within the archive.
+These are not too difficult to deal with, and are outlined below.
+
+\wxheading{PutNextEntry and the entry size}
+
+When writing archives, some archive formats store the entry size before
+the entry's data (tar has this limitation, zip doesn't). In this case
+the entry's size must be passed to
+ \helpref{PutNextEntry()}{wxarchiveoutputstreamputnextentry} or an error
+occurs.
+
+This is only an issue on non-seekable streams, since otherwise the archive
+output stream can seek back and fix up the header once the size of the
+entry is known.
+
+For generic programming, one way to handle this is to supply the size
+whenever it is known, and rely on the error message from the output
+stream when the operation is not supported.
+
+\wxheading{GetNextEntry and the weak reference mechanism}
+
+Some archive formats do not store all an entry's meta-data before the
+entry's data (zip is an example). In this case, when reading from a
+non-seekable stream, \helpref{GetNextEntry()}{wxarchiveinputstreamgetnextentry} 
+can only return a partially populated \helpref{wxArchiveEntry}{wxarchiveentry}
+object - not all the fields are set.
+
+The input stream then keeps a weak reference to the entry object and
+updates it when more meta-data becomes available. A weak reference being
+one that does not prevent you from deleting the wxArchiveEntry object - the
+input stream only attempts to update it if it is still around.
+
+The documentation for each archive entry type gives the details
+of what meta-data becomes available and when. For generic programming,
+when the worst case must be assumed, you can rely on all the fields
+of wxArchiveEntry being fully populated when GetNextEntry() returns,
+with the the following exceptions:
+
+\begin{twocollist}\twocolwidtha{3cm}
+\twocolitem{\helpref{GetSize()}{wxarchiveentrysize}}{Guaranteed to be
+available after the entry has been read to \helpref{Eof()}{wxinputstreameof},
+or \helpref{CloseEntry()}{wxarchiveinputstreamcloseentry} has been called}
+\twocolitem{\helpref{IsReadOnly()}{wxarchiveentryisreadonly}}{Guaranteed to
+be available after the end of the archive has been reached, i.e. after
+GetNextEntry() returns NULL and Eof() is true}
+\end{twocollist}
+
+This mechanism allows \helpref{CopyEntry()}{wxarchiveoutputstreamcopyentry}
+to always fully preserve entries' meta-data. No matter what order order
+the meta-data occurs within the archive, the input stream will always
+have read it before the output stream must write it.
+
+\wxheading{wxArchiveNotifier}
+
+Notifier objects can be used to get a notification whenever an input
+stream updates a \helpref{wxArchiveEntry}{wxarchiveentry} object's data
+via the weak reference mechanism.
+
+Consider the following code which renames an entry in an archive.
+This is the usual way to modify an entry's meta-data, simply set the
+required field before writing it with
+ \helpref{CopyEntry()}{wxarchiveoutputstreamcopyentry}:
+
+\begin{verbatim}
+    wxArchiveInputStreamPtr  arc(factory->NewStream(in));
+    wxArchiveOutputStreamPtr outarc(factory->NewStream(out));
+    wxArchiveEntryPtr        entry;
+
+    outarc->CopyArchiveMetaData(*arc);
+
+    while (entry.reset(arc->GetNextEntry()), entry.get() != NULL) {
+        if (entry->GetName() == from)
+            entry->SetName(to);
+        if (!outarc->CopyEntry(entry.release(), *arc))
+            break;
+    }
+
+    bool success = arc->Eof() && outarc->Close();
+
+\end{verbatim}
+
+However, for non-seekable streams, this technique cannot be used for
+fields such as \helpref{IsReadOnly()}{wxarchiveentryisreadonly},
+which are not necessarily set when
+ \helpref{GetNextEntry()}{wxarchiveinputstreamgetnextentry} returns. In
+this case a \helpref{wxArchiveNotifier}{wxarchivenotifier} can be used:
+
+\begin{verbatim}
+class MyNotifier : public wxArchiveNotifier
+{
+public:
+    void OnEntryUpdated(wxArchiveEntry& entry) { entry.SetIsReadOnly(false); }
+};
+
+\end{verbatim}
+
+The meta-data changes are done in your notifier's
+ \helpref{OnEntryUpdated()}{wxarchivenotifieronentryupdated} method,
+then \helpref{SetNotifier()}{wxarchiveentrynotifier} is called before
+CopyEntry():
+
+\begin{verbatim}
+    wxArchiveInputStreamPtr  arc(factory->NewStream(in));
+    wxArchiveOutputStreamPtr outarc(factory->NewStream(out));
+    wxArchiveEntryPtr        entry;
+    MyNotifier               notifier;
+
+    outarc->CopyArchiveMetaData(*arc);
+
+    while (entry.reset(arc->GetNextEntry()), entry.get() != NULL) {
+        entry->SetNotifier(notifier);
+        if (!outarc->CopyEntry(entry.release(), *arc))
+            break;
+    }
+
+    bool success = arc->Eof() && outarc->Close();
+
+\end{verbatim}
+
+SetNotifier() calls OnEntryUpdated() immediately, then the input
+stream calls it again whenever it sets more fields in the entry. Since
+OnEntryUpdated() will be called at least once, this technique always
+works even when it is not strictly necessary to use it. For example,
+changing the entry name can be done this way too and it works on seekable
+streams as well as non-seekable.
+
diff --git a/docs/latex/wx/archive.tex b/docs/latex/wx/archive.tex
new file mode 100644
index 0000000000..304306fb05
--- /dev/null
+++ b/docs/latex/wx/archive.tex
@@ -0,0 +1,648 @@
+%
+% automatically generated by HelpGen $Revision$ from
+% wx/archive.h at 16/Sep/04 12:19:29
+%
+
+\section{\class{wxArchiveClassFactory}}\label{wxarchiveclassfactory}
+
+An abstract base class which serves as a common interface to
+archive class factories such as \helpref{wxZipClassFactory}{wxzipclassfactory}.
+
+For each supported archive type (such as zip) there is a class factory
+derived from wxArchiveClassFactory, which allows archive objects to be
+created in a generic way, without knowing the particular type of archive
+being used.
+
+\wxheading{Derived from}
+
+\helpref{wxObject}{wxobject}
+
+\wxheading{Include files}
+
+<wx/archive.h>
+
+\wxheading{See also}
+
+\helpref{Archive formats such as zip}{wxarc}\\
+\helpref{Generic archive programming}{wxarcgeneric}\\
+\helpref{wxArchiveEntry}{wxarchiveentry}\\
+\helpref{wxArchiveInputStream}{wxarchiveinputstream}\\
+\helpref{wxArchiveOutputStream}{wxarchiveoutputstream}
+
+\latexignore{\rtfignore{\wxheading{Members}}}
+
+
+\membersection{wxArchiveClassFactory::Get/SetConv}\label{wxarchiveclassfactoryconv}
+
+\constfunc{wxMBConv\&}{GetConv}{\void}
+
+\func{void}{SetConv}{\param{wxMBConv\& }{conv}}
+
+The \helpref{wxMBConv}{wxmbconv} object that the created streams
+will use when translating meta-data. The initial default, set by the
+constructor, is wxConvLocal.
+
+
+\membersection{wxArchiveClassFactory::GetInternalName}\label{wxarchiveclassfactorygetinternalname}
+
+\constfunc{wxString}{GetInternalName}{\param{const wxString\& }{name}, \param{wxPathFormat }{format = wxPATH\_NATIVE}}
+
+Calls the static GetInternalName() function for the archive entry type,
+for example
+ \helpref{wxZipEntry::GetInternalName()}{wxzipentrygetinternalname}.
+
+
+\membersection{wxArchiveClassFactory::NewEntry}\label{wxarchiveclassfactorynewentry}
+
+\constfunc{wxArchiveEntry*}{NewEntry}{\void}
+
+Create a new \helpref{wxArchiveEntry}{wxarchiveentry} object of the
+appropriate type.
+
+
+\membersection{wxArchiveClassFactory::NewStream}\label{wxarchiveclassfactorynewstream}
+
+\constfunc{wxArchiveInputStream*}{NewStream}{\param{wxInputStream\& }{stream}}
+
+\constfunc{wxArchiveOutputStream*}{NewStream}{\param{wxOutputStream\& }{stream}}
+
+Create a new \helpref{wxArchiveInputStream}{wxarchiveinputstream}
+or \helpref{wxArchiveOutputStream}{wxarchiveoutputstream} of the
+appropriate type.
+
+
+%
+% automatically generated by HelpGen $Revision$ from
+% wx/archive.h at 16/Sep/04 12:19:29
+%
+
+\section{\class{wxArchiveEntry}}\label{wxarchiveentry}
+
+An abstract base class which serves as a common interface to
+archive entry classes such as \helpref{wxZipEntry}{wxzipentry}.
+These hold the meta-data (filename, timestamp, etc.), for entries
+in archive files such as zips and tars.
+
+\wxheading{Derived from}
+
+\helpref{wxObject}{wxobject}
+
+\wxheading{Include files}
+
+<wx/archive.h>
+
+\wxheading{See also}
+
+\helpref{Archive formats such as zip}{wxarc}\\
+\helpref{Generic archive programming}{wxarcgeneric}\\
+\helpref{wxArchiveInputStream}{wxarchiveinputstream}\\
+\helpref{wxArchiveOutputStream}{wxarchiveoutputstream}\\
+\helpref{wxArchiveNotifier}{wxarchivenotifier}
+
+\wxheading{Non-seekable streams}
+
+This information applies only when reading archives from non-seekable
+streams. When the stream is
+seekable \helpref{GetNextEntry()}{wxarchiveinputstreamgetnextentry}
+returns a fully populated \helpref{wxArchiveEntry}{wxarchiveentry}.
+See '\helpref{Archives on non-seekable streams}{wxarcnoseek}' for
+more information.
+
+For generic programming, when the worst case must be assumed, you can
+rely on all the fields of wxArchiveEntry being fully populated when
+GetNextEntry() returns, with the the following exceptions:
+
+\begin{twocollist}\twocolwidtha{3cm}
+\twocolitem{\helpref{GetSize()}{wxarchiveentrysize}}{Guaranteed to be
+available after the entry has been read to \helpref{Eof()}{wxinputstreameof},
+or \helpref{CloseEntry()}{wxarchiveinputstreamcloseentry} has been called}
+\twocolitem{\helpref{IsReadOnly()}{wxarchiveentryisreadonly}}{Guaranteed to
+be available after the end of the archive has been reached, i.e. after
+GetNextEntry() returns NULL and Eof() is true}
+\end{twocollist}
+
+
+\latexignore{\rtfignore{\wxheading{Members}}}
+
+
+\membersection{wxArchiveEntry::Clone}\label{wxarchiveentryclone}
+
+\constfunc{wxArchiveEntry*}{Clone}{\void}
+
+Returns a copy of this entry object.
+
+
+\membersection{wxArchiveEntry::Get/SetDateTime}\label{wxarchiveentrydatetime}
+
+\constfunc{wxDateTime}{GetDateTime}{\void}
+
+\func{void}{SetDateTime}{\param{const wxDateTime\& }{dt}}
+
+The entry's timestamp.
+
+
+\membersection{wxArchiveEntry::GetInternalFormat}\label{wxarchiveentrygetinternalformat}
+
+\constfunc{wxPathFormat}{GetInternalFormat}{\void}
+
+Returns the path format used internally within the archive to store
+filenames.
+
+
+\membersection{wxArchiveEntry::GetInternalName}\label{wxarchiveentrygetinternalname}
+
+\constfunc{wxString}{GetInternalName}{\void}
+
+Returns the entry's filename in the internal format used within the
+archive. The name can include directory components, i.e. it can be a
+full path.
+
+The names of directory entries are returned without any trailing path
+separator. This gives a canonical name that can be used in comparisons.
+
+\wxheading{See also}
+
+\helpref{Looking up an archive entry by name}{wxarcbyname}
+
+
+\membersection{wxArchiveEntry::Get/SetName}\label{wxarchiveentryname}
+
+\constfunc{wxString}{GetName}{\param{wxPathFormat }{format = wxPATH\_NATIVE}}
+
+\func{void}{SetName}{\param{const wxString\& }{name}, \param{wxPathFormat }{format = wxPATH\_NATIVE}}
+
+The entry's name, by default in the native format. The name can include
+directory components, i.e. it can be a full path.
+
+If this is a directory entry, (i.e. if \helpref{IsDir()}{wxarchiveentryisdir}
+is true) then GetName() returns the name with a trailing path separator.
+
+Similarly, setting a name with a trailing path separator sets IsDir().
+
+
+\membersection{wxArchiveEntry::GetOffset}\label{wxarchiveentrygetoffset}
+
+\constfunc{off\_t}{GetOffset}{\void}
+
+Returns a numeric value unique to the entry within the archive.
+
+
+\membersection{wxArchiveEntry::Get/SetSize}\label{wxarchiveentrysize}
+
+\constfunc{off\_t}{GetSize}{\void}
+
+\func{void}{SetSize}{\param{off\_t }{size}}
+
+The size of the entry's data in bytes.
+
+
+\membersection{wxArchiveEntry::IsDir/SetIsDir}\label{wxarchiveentryisdir}
+
+\constfunc{bool}{IsDir}{\void}
+
+\func{void}{SetIsDir}{\param{bool }{isDir = true}}
+
+True if this is a directory entry.
+
+Directory entries are entries with no data, which are used to store
+the meta-data of directories. They also make it possible for completely
+empty directories to be stored.
+
+The names of entries within an archive can be complete paths, and
+unarchivers typically create whatever directories are necessary as they
+restore files, even if the archive contains no explicit directory entries.
+
+
+\membersection{wxArchiveEntry::IsReadOnly/SetIsReadOnly}\label{wxarchiveentryisreadonly}
+
+\constfunc{bool}{IsReadOnly}{\void}
+
+\func{void}{SetIsReadOnly}{\param{bool }{isReadOnly = true}}
+
+True if the entry is a read-only file.
+
+
+\membersection{wxArchiveEntry::Set/UnsetNotifier}\label{wxarchiveentrynotifier}
+
+\func{void}{SetNotifier}{\param{wxArchiveNotifier\& }{notifier}}
+
+\func{void}{UnsetNotifier}{\void}
+
+Sets the \helpref{notifier}{wxarchivenotifier} for this entry.
+Whenever the \helpref{wxArchiveInputStream}{wxarchiveinputstream} updates
+this entry, it will then invoke the associated
+notifier's \helpref{OnEntryUpdated}{wxarchivenotifieronentryupdated}
+method.
+
+Setting a notifier is not usually necessary. It is used to handle
+certain cases when modifying an archive in a pipeline (i.e. between
+non-seekable streams).
+
+\wxheading{See also}
+
+\helpref{Archives on non-seekable streams}{wxarcnoseek}\\
+\helpref{wxArchiveNotifier}{wxarchivenotifier}
+
+
+%
+% automatically generated by HelpGen $Revision$ from
+% wx/archive.h at 16/Sep/04 12:19:29
+%
+
+\section{\class{wxArchiveInputStream}}\label{wxarchiveinputstream}
+
+An abstract base class which serves as a common interface to
+archive input streams such as \helpref{wxZipInputStream}{wxzipinputstream}.
+
+\helpref{GetNextEntry()}{wxarchiveinputstreamgetnextentry} returns an
+ \helpref{wxArchiveEntry}{wxarchiveentry} object containing the meta-data
+for the next entry in the archive (and gives away ownership). Reading from
+the wxArchiveInputStream then returns the entry's data. Eof() becomes true
+after an attempt has been made to read past the end of the entry's data.
+When there are no more entries, GetNextEntry() returns NULL and sets Eof().
+
+\wxheading{Derived from}
+
+\helpref{wxFilterInputStream}{wxfilterinputstream}
+
+\wxheading{Include files}
+
+<wx/archive.h>
+
+\wxheading{Data structures}
+{\small \begin{verbatim}
+typedef wxArchiveEntry entry\_type
+\end{verbatim}}
+
+\wxheading{See also}
+
+\helpref{Archive formats such as zip}{wxarc}\\
+\helpref{wxArchiveEntry}{wxarchiveentry}\\
+\helpref{wxArchiveOutputStream}{wxarchiveoutputstream}
+
+\latexignore{\rtfignore{\wxheading{Members}}}
+
+
+\membersection{wxArchiveInputStream::CloseEntry}\label{wxarchiveinputstreamcloseentry}
+
+\func{bool}{CloseEntry}{\void}
+
+Closes the current entry. On a non-seekable stream reads to the end of
+the current entry first.
+
+
+\membersection{wxArchiveInputStream::GetNextEntry}\label{wxarchiveinputstreamgetnextentry}
+
+\func{wxArchiveEntry*}{GetNextEntry}{\void}
+
+Closes the current entry if one is open, then reads the meta-data for
+the next entry and returns it in a \helpref{wxArchiveEntry}{wxarchiveentry}
+object, giving away ownership. Reading this wxArchiveInputStream then
+returns the entry's data.
+
+
+\membersection{wxArchiveInputStream::OpenEntry}\label{wxarchiveinputstreamopenentry}
+
+\func{bool}{OpenEntry}{\param{wxArchiveEntry\& }{entry}}
+
+Closes the current entry if one is open, then opens the entry specified
+by the \helpref{wxArchiveEntry}{wxarchiveentry} object.
+
+{\it entry} must be from the same archive file that this
+wxArchiveInputStream is reading, and it must be reading it from a
+seekable stream.
+
+\wxheading{See also}
+
+\helpref{Looking up an archive entry by name}{wxarcbyname}
+
+
+%
+% automatically generated by HelpGen $Revision$ from
+% wx/archive.h at 16/Sep/04 12:19:29
+%
+
+\section{\class{wxArchiveIterator}}\label{wxarchiveiterator}
+
+An input iterator template class that can be used to transfer an archive's
+catalogue to a container. It is only available if wxUSE\_STL is set to 1
+in setup.h, and the uses for it outlined below require a compiler which
+supports member templates.
+
+\begin{verbatim}
+template <class Arc, class T = typename Arc::entry_type*>
+class wxArchiveIterator
+{
+    // this constructor creates an 'end of sequence' object
+    wxArchiveIterator();
+
+    // template parameter 'Arc' should be the type of an archive input stream
+    wxArchiveIterator(Arc& arc) {
+
+    /* ... */
+};
+
+\end{verbatim}
+
+The first template parameter should be the type of archive input stream
+(e.g. \helpref{wxArchiveInputStream}{wxarchiveinputstream}) and the
+second can either be a pointer to an entry
+(e.g. \helpref{wxArchiveEntry}{wxarchiveentry}*), or a string/pointer pair
+(e.g. std::pair<wxString, wxArchiveEntry*>).
+
+The {\tt <wx/archive.h>} header defines the following typedefs:
+
+\begin{verbatim}
+    typedef wxArchiveIterator<wxArchiveInputStream> wxArchiveIter;
+
+    typedef wxArchiveIterator<wxArchiveInputStream,
+             std::pair<wxString, wxArchiveEntry*> > wxArchivePairIter;
+
+\end{verbatim}
+
+The header for any implementation of this interface should define similar
+typedefs for its types, for example in {\tt <wx/zipstrm.h>} there is:
+
+\begin{verbatim}
+    typedef wxArchiveIterator<wxZipInputStream> wxZipIter;
+
+    typedef wxArchiveIterator<wxZipInputStream,
+             std::pair<wxString, wxZipEntry*> > wxZipPairIter;
+
+\end{verbatim}
+
+Transferring the catalogue of an archive {\it arc} to a vector {\it cat},
+can then be done something like this:
+
+\begin{verbatim}
+    std::vector<wxArchiveEntry*> cat((wxArchiveIter)arc, wxArchiveIter());
+
+\end{verbatim}
+
+When the iterator is dereferenced, it gives away ownership of an entry
+object. So in the above example, when you have finished with {\it cat}
+you must delete the pointers it contains.
+
+If you have smart pointers with normal copy semantics (i.e. not auto\_ptr
+or \helpref{wxScopedPtr}{wxscopedptr}), then you can create an iterator
+which uses them instead.  For example, with a smart pointer class for
+zip entries {\it ZipEntryPtr}:
+
+\begin{verbatim}
+    typedef std::vector<ZipEntryPtr> ZipCatalog;
+    typedef wxArchiveIterator<wxZipInputStream, ZipEntryPtr> ZipIter;
+    ZipCatalog cat((ZipIter)zip, ZipIter());
+
+\end{verbatim}
+
+Iterators that return std::pair objects can be used to
+populate a std::multimap, to allow entries to be looked
+up by name. The string is initialised using the wxArchiveEntry object's
+ \helpref{GetInternalName()}{wxarchiveentrygetinternalname} function.
+
+\begin{verbatim}
+    typedef std::multimap<wxString, wxZipEntry*> ZipCatalog;
+    ZipCatalog cat((wxZipPairIter)zip, wxZipPairIter());
+
+\end{verbatim}
+ 
+Note that this iterator also gives away ownership of an entry 
+object each time it is dereferenced. So in the above example, when
+you have finished with {\it cat} you must delete the pointers it contains.
+
+Or if you have them, a pair containing a smart pointer can be used
+(again {\it ZipEntryPtr}), no worries about ownership:
+
+\begin{verbatim}
+    typedef std::multimap<wxString, ZipEntryPtr> ZipCatalog;
+    typedef wxArchiveIterator<wxZipInputStream,
+                std::pair<wxString, ZipEntryPtr> > ZipPairIter;
+    ZipCatalog cat((ZipPairIter)zip, ZipPairIter());
+
+\end{verbatim}
+
+\wxheading{Derived from}
+
+No base class
+
+\wxheading{Include files}
+
+<wx/archive.h>
+
+\wxheading{See also}
+
+\helpref{wxArchiveEntry}{wxarchiveentry}\\
+\helpref{wxArchiveInputStream}{wxarchiveinputstream}\\
+\helpref{wxArchiveOutputStream}{wxarchiveoutputstream}
+
+\wxheading{Data structures}
+{\small \begin{verbatim}
+typedef std::input\_iterator\_tag iterator\_category
+typedef T value\_type
+typedef ptrdiff\_t difference\_type
+typedef T* pointer
+typedef T\& reference
+\end{verbatim}}
+
+\latexignore{\rtfignore{\wxheading{Members}}}
+
+
+\membersection{wxArchiveIterator::wxArchiveIterator}\label{wxarchiveiteratorwxarchiveiterator}
+
+\func{}{wxArchiveIterator}{\void}
+
+Construct an 'end of sequence' instance.
+
+\func{}{wxArchiveIterator}{\param{Arc\& }{arc}}
+
+Construct iterator that returns all the entries in the archive input
+stream {\it arc}.
+
+
+\membersection{wxArchiveIterator::operator*}\label{wxarchiveiteratoroperatorunknown}
+
+\constfunc{const T\&}{operator*}{\void}
+
+Returns an entry object from the archive input stream, giving away
+ownership.
+
+
+\membersection{wxArchiveIterator::operator++}\label{wxarchiveiteratoroperatorunknown}
+
+\func{wxArchiveIterator\&}{operator++}{\void}
+
+\func{wxArchiveIterator\&}{operator++}{\param{int}{}}
+
+Position the input iterator at the next entry in the archive input stream.
+
+
+%
+% automatically generated by HelpGen $Revision$ from
+% wx/archive.h at 16/Sep/04 12:19:29
+%
+
+\section{\class{wxArchiveNotifier}}\label{wxarchivenotifier}
+
+If you need to know when a
+ \helpref{wxArchiveInputStream}{wxarchiveinputstream} updates a
+ \helpref{wxArchiveEntry}{wxarchiveentry} object, you can create
+a notifier by deriving from this abstract base class, overriding
+ \helpref{OnEntryUpdated()}{wxarchivenotifieronentryupdated}.  An instance
+of your notifier class can then be assigned to the wxArchiveEntry object
+using \helpref{wxArchiveEntry::SetNotifier()}{wxarchiveentrynotifier}.
+Your OnEntryUpdated() method will then be invoked whenever the input
+stream updates the entry.
+
+Setting a notifier is not usually necessary. It is used to handle
+certain cases when modifying an archive in a pipeline (i.e. between
+non-seekable streams).
+See \helpref{Archives on non-seekable streams}{wxarcnoseek}.
+
+\wxheading{Derived from}
+
+No base class
+
+\wxheading{Include files}
+
+<wx/archive.h>
+
+\wxheading{See also}
+
+\helpref{Archives on non-seekable streams}{wxarcnoseek}\\
+\helpref{wxArchiveEntry}{wxarchiveentry}\\
+\helpref{wxArchiveInputStream}{wxarchiveinputstream}\\
+\helpref{wxArchiveOutputStream}{wxarchiveoutputstream}
+
+\latexignore{\rtfignore{\wxheading{Members}}}
+
+
+\membersection{wxArchiveNotifier::OnEntryUpdated}\label{wxarchivenotifieronentryupdated}
+
+\func{void}{OnEntryUpdated}{\param{class wxArchiveEntry\& }{entry}}
+
+This method must be overridden in your derived class.
+
+
+%
+% automatically generated by HelpGen $Revision$ from
+% wx/archive.h at 16/Sep/04 12:19:29
+%
+
+\section{\class{wxArchiveOutputStream}}\label{wxarchiveoutputstream}
+
+An abstract base class which serves as a common interface to
+archive output streams such as \helpref{wxZipOutputStream}{wxzipoutputstream}.
+
+\helpref{PutNextEntry()}{wxarchiveoutputstreamputnextentry} is used
+to create a new entry in the output archive, then the entry's data is
+written to the wxArchiveOutputStream.  Another call to PutNextEntry()
+closes the current entry and begins the next.
+
+\wxheading{Derived from}
+
+\helpref{wxFilterOutputStream}{wxfilteroutputstream}
+
+\wxheading{Include files}
+
+<wx/archive.h>
+
+\wxheading{See also}
+
+\helpref{Archive formats such as zip}{wxarc}\\
+\helpref{wxArchiveEntry}{wxarchiveentry}\\
+\helpref{wxArchiveInputStream}{wxarchiveinputstream}
+
+\latexignore{\rtfignore{\wxheading{Members}}}
+
+
+\membersection{wxArchiveOutputStream::\destruct{wxArchiveOutputStream}}\label{wxarchiveoutputstreamdtor}
+
+\func{}{\destruct{wxArchiveOutputStream}}{\void}
+
+Calls \helpref{Close()}{wxarchiveoutputstreamclose} if it has not already
+been called.
+
+
+\membersection{wxArchiveOutputStream::Close}\label{wxarchiveoutputstreamclose}
+
+\func{bool}{Close}{\void}
+
+Closes the archive, returning true if it was successfully written.
+Called by the destructor if not called explicitly.
+
+
+\membersection{wxArchiveOutputStream::CloseEntry}\label{wxarchiveoutputstreamcloseentry}
+
+\func{bool}{CloseEntry}{\void}
+
+Close the current entry. It is called implicitly whenever another new
+entry is created with \helpref{CopyEntry()}{wxarchiveoutputstreamcopyentry}
+or \helpref{PutNextEntry()}{wxarchiveoutputstreamputnextentry}, or
+when the archive is closed.
+
+
+\membersection{wxArchiveOutputStream::CopyArchiveMetaData}\label{wxarchiveoutputstreamcopyarchivemetadata}
+
+\func{bool}{CopyArchiveMetaData}{\param{wxArchiveInputStream\& }{stream}}
+
+Some archive formats have additional meta-data that applies to the archive
+as a whole.  For example in the case of zip there is a comment, which
+is stored at the end of the zip file.  CopyArchiveMetaData() can be used
+to transfer such information when writing a modified copy of an archive.
+
+Since the position of the meta-data can vary between the various archive
+formats, it is best to call CopyArchiveMetaData() before transferring
+the entries.  The \helpref{wxArchiveOutputStream}{wxarchiveoutputstream}
+will then hold on to the meta-data and write it at the correct point in
+the output file.
+
+When the input archive is being read from a non-seekable stream, the
+meta-data may not be available when CopyArchiveMetaData() is called,
+in which case the two streams set up a link and transfer the data
+when it becomes available.
+
+
+\membersection{wxArchiveOutputStream::CopyEntry}\label{wxarchiveoutputstreamcopyentry}
+
+\func{bool}{CopyEntry}{\param{wxArchiveEntry* }{entry}, \param{wxArchiveInputStream\& }{stream}}
+
+Takes ownership of {\it entry} and uses it to create a new entry in the
+archive. {\it entry} is then opened in the input stream {\it stream}
+and its contents copied to this stream.
+
+For archive types which compress entry data, CopyEntry() is likely to be
+much more efficient than transferring the data using Read() and Write()
+since it will copy them without decompressing and recompressing them.
+
+{\it entry} must be from the same archive file that {\it stream} is
+accessing. For non-seekable streams, {\it entry} must also be the last
+thing read from {\it stream}.
+
+
+\membersection{wxArchiveOutputStream::PutNextDirEntry}\label{wxarchiveoutputstreamputnextdirentry}
+
+\func{bool}{PutNextDirEntry}{\param{const wxString\& }{name}, \param{const wxDateTime\& }{dt = wxDateTime::Now()}}
+
+Create a new directory entry
+(see \helpref{wxArchiveEntry::IsDir()}{wxarchiveentryisdir})
+with the given name and timestamp.
+
+\helpref{PutNextEntry()}{wxarchiveoutputstreamputnextentry} can
+also be used to create directory entries, by supplying a name with
+a trailing path separator.
+
+
+\membersection{wxArchiveOutputStream::PutNextEntry}\label{wxarchiveoutputstreamputnextentry}
+
+\func{bool}{PutNextEntry}{\param{wxArchiveEntry* }{entry}}
+
+Takes ownership of {\it entry} and uses it to create a new entry in
+the archive. The entry's data can then be written by writing to this
+wxArchiveOutputStream.
+
+\func{bool}{PutNextEntry}{\param{const wxString\& }{name}, \param{const wxDateTime\& }{dt = wxDateTime::Now()}, \param{off\_t }{size = wxInvalidOffset}}
+
+Create a new entry with the given name, timestamp and size. The entry's
+data can then be written by writing to this wxArchiveOutputStream.
+
+
diff --git a/docs/latex/wx/classes.tex b/docs/latex/wx/classes.tex
index 0880b91ce4..08957a27f5 100644
--- a/docs/latex/wx/classes.tex
+++ b/docs/latex/wx/classes.tex
@@ -9,6 +9,7 @@
 \input accessible.tex
 \input activevt.tex
 \input app.tex
+\input archive.tex
 \input array.tex
 \input arrstrng.tex
 \input artprov.tex
diff --git a/docs/latex/wx/topics.tex b/docs/latex/wx/topics.tex
index fccb15c4cc..b9946e27d6 100644
--- a/docs/latex/wx/topics.tex
+++ b/docs/latex/wx/topics.tex
@@ -59,4 +59,5 @@ This chapter contains a selection of topic overviews, first things first:
 \input tenvvars.tex
 \input wxPython.tex
 \input re_syntax.tex
+\input arc.tex
 
diff --git a/docs/latex/wx/zipstrm.tex b/docs/latex/wx/zipstrm.tex
index f0a740f97e..65e0f3f673 100644
--- a/docs/latex/wx/zipstrm.tex
+++ b/docs/latex/wx/zipstrm.tex
@@ -1,40 +1,706 @@
 %
-% automatically generated by HelpGen from
-% zipstream.h at 02/May/99 19:54:25
+% automatically generated by HelpGen $Revision$ from
+% wx/zipstrm.h at 16/Sep/04 12:19:29
 %
 
-\section{\class{wxZipInputStream}}\label{wxzipinputstream}
+\section{\class{wxZipClassFactory}}\label{wxzipclassfactory}
 
-This class is input stream from ZIP archive. The archive
-must be local file (accessible via FILE*).
-It has all features including GetSize and seeking.
+Class factory for the zip archive format. See the base class
+for details.
 
-\wxheading{Note}
+\wxheading{Derived from}
 
-If you need to enumerate files in ZIP archive, you can use 
-\helpref{wxFileSystem}{wxfilesystem} together with wxZipFSHandler (see 
-\helpref{the overview}{fs}).
+\helpref{wxArchiveClassFactory}{wxarchiveclassfactory}
 
+\wxheading{Include files}
+
+<wx/zipstrm.h>
+
+\wxheading{See also}
+
+\helpref{Archive formats such as zip}{wxarc}\\
+\helpref{Generic archive programming}{wxarcgeneric}
+\helpref{wxZipEntry}{wxzipentry}\\
+\helpref{wxZipInputStream}{wxzipinputstream}\\
+\helpref{wxZipOutputStream}{wxzipoutputstream}
+
+
+%
+% automatically generated by HelpGen $Revision$ from
+% wx/zipstrm.h at 16/Sep/04 12:19:29
+%
+
+\section{\class{wxZipEntry}}\label{wxzipentry}
+
+Holds the meta-data for an entry in a zip.
 
 \wxheading{Derived from}
 
-\helpref{wxInputStream}{wxinputstream}
+\helpref{wxArchiveEntry}{wxarchiveentry}
 
 \wxheading{Include files}
 
 <wx/zipstrm.h>
 
+\wxheading{Data structures}
+
+Constants for \helpref{Get/SetMethod}{wxzipentrymethod}:
+
+\begin{verbatim}
+// Compression Method, only 0 (store) and 8 (deflate) are supported here
+//
+enum wxZipMethod
+{
+    wxZIP_METHOD_STORE,
+    wxZIP_METHOD_SHRINK,
+    wxZIP_METHOD_REDUCE1,
+    wxZIP_METHOD_REDUCE2,
+    wxZIP_METHOD_REDUCE3,
+    wxZIP_METHOD_REDUCE4,
+    wxZIP_METHOD_IMPLODE,
+    wxZIP_METHOD_TOKENIZE,
+    wxZIP_METHOD_DEFLATE,
+    wxZIP_METHOD_DEFLATE64,
+    wxZIP_METHOD_BZIP2 = 12,
+    wxZIP_METHOD_DEFAULT = 0xffff
+};
+
+\end{verbatim}
+
+Constants for \helpref{Get/SetSystemMadeBy}{wxzipentrysystemmadeby}:
+
+\begin{verbatim}
+// Originating File-System.
+// 
+// These are Pkware's values. Note that Info-zip disagree on some of them,
+// most notably NTFS.
+//
+enum wxZipSystem
+{
+    wxZIP_SYSTEM_MSDOS,
+    wxZIP_SYSTEM_AMIGA,
+    wxZIP_SYSTEM_OPENVMS,
+    wxZIP_SYSTEM_UNIX,
+    wxZIP_SYSTEM_VM_CMS,
+    wxZIP_SYSTEM_ATARI_ST,
+    wxZIP_SYSTEM_OS2_HPFS,
+    wxZIP_SYSTEM_MACINTOSH,
+    wxZIP_SYSTEM_Z_SYSTEM,
+    wxZIP_SYSTEM_CPM,
+    wxZIP_SYSTEM_WINDOWS_NTFS,
+    wxZIP_SYSTEM_MVS,
+    wxZIP_SYSTEM_VSE,
+    wxZIP_SYSTEM_ACORN_RISC,
+    wxZIP_SYSTEM_VFAT,
+    wxZIP_SYSTEM_ALTERNATE_MVS,
+    wxZIP_SYSTEM_BEOS,
+    wxZIP_SYSTEM_TANDEM,
+    wxZIP_SYSTEM_OS_400
+};
+
+\end{verbatim}
+
+Constants for \helpref{Get/SetExternalAttributes}{wxzipentryexternalattributes}:
+
+\begin{verbatim}
+// Dos/Win file attributes
+//
+enum wxZipAttributes
+{
+    wxZIP_A_RDONLY = 0x01,
+    wxZIP_A_HIDDEN = 0x02,
+    wxZIP_A_SYSTEM = 0x04,
+    wxZIP_A_SUBDIR = 0x10,
+    wxZIP_A_ARCH   = 0x20,
+
+    wxZIP_A_MASK   = 0x37
+};
+
+\end{verbatim}
+
+Constants for \helpref{Get/SetFlags}{wxzipentrygetflags}:
+
+\begin{verbatim}
+// Values for the flags field in the zip headers
+//
+enum wxZipFlags
+{
+    wxZIP_ENCRYPTED         = 0x0001,
+    wxZIP_DEFLATE_NORMAL    = 0x0000,   // normal compression
+    wxZIP_DEFLATE_EXTRA     = 0x0002,   // extra compression
+    wxZIP_DEFLATE_FAST      = 0x0004,   // fast compression
+    wxZIP_DEFLATE_SUPERFAST = 0x0006,   // superfast compression
+    wxZIP_DEFLATE_MASK      = 0x0006,
+    wxZIP_SUMS_FOLLOW       = 0x0008,   // crc and sizes come after the data
+    wxZIP_ENHANCED          = 0x0010,
+    wxZIP_PATCH             = 0x0020,
+    wxZIP_STRONG_ENC        = 0x0040,
+    wxZIP_UNUSED            = 0x0F80,
+    wxZIP_RESERVED          = 0xF000
+};
+
+\end{verbatim}
+
+\wxheading{See also}
+
+\helpref{Archive formats such as zip}{wxarc}\\
+\helpref{wxZipInputStream}{wxzipinputstream}\\
+\helpref{wxZipOutputStream}{wxzipoutputstream}\\
+\helpref{wxZipNotifier}{wxzipnotifier}
+
+\wxheading{Field availability}
+
+When reading a zip from a stream that is seekable,
+ \helpref{GetNextEntry()}{wxzipinputstreamgetnextentry} returns
+a fully populated wxZipEntry object except for
+ \helpref{wxZipEntry::GetLocalExtra()}{wxzipentrylocalextra}. GetLocalExtra()
+becomes available when the entry is opened, either by calling
+ \helpref{wxZipInputStream::OpenEntry}{wxzipinputstreamopenentry} or by
+making an attempt to read the entry's data.
+
+For zips on \helpref{non-seekable}{wxarcnoseek} streams, the following
+fields are always available when GetNextEntry() returns:
+
+\helpref{GetDateTime}{wxarchiveentrydatetime}\\
+\helpref{GetInternalFormat}{wxarchiveentrygetinternalformat}\\
+\helpref{GetInternalName}{wxzipentrygetinternalname}\\
+\helpref{GetFlags}{wxzipentrygetflags}\\
+\helpref{GetLocalExtra}{wxzipentrylocalextra}\\
+\helpref{GetMethod}{wxzipentrymethod}\\
+\helpref{GetName}{wxarchiveentryname}\\
+\helpref{GetOffset}{wxarchiveentrygetoffset}\\
+\helpref{IsDir}{wxarchiveentryisdir}
+
+The following fields are also usually available when GetNextEntry()
+returns, however, if the zip was also written to a non-seekable stream
+the zipper is permitted to store them after the entry's data. In that
+case they become available when the entry's data has been read to Eof(),
+or \helpref{CloseEntry()}{wxarchiveinputstreamcloseentry} has been called.
+{\tt (GetFlags() \& wxZIP\_SUMS\_FOLLOW) != 0} indicates that one or
+more of these come after the data:
+
+\helpref{GetCompressedSize}{wxzipentrygetcompressedsize}\\
+\helpref{GetCrc}{wxzipentrygetcrc}\\
+\helpref{GetSize}{wxarchiveentrysize}
+
+The following are stored at the end of the zip, and become available
+when the end of the zip has been reached, i.e. after GetNextEntry()
+returns NULL and Eof() is true:
+
+\helpref{GetComment}{wxzipentrycomment}\\
+\helpref{GetExternalAttributes}{wxzipentryexternalattributes}\\
+\helpref{GetExtra}{wxzipentryextra}\\
+\helpref{GetMode}{wxzipentrymode}\\
+\helpref{GetSystemMadeBy}{wxzipentrysystemmadeby}\\
+\helpref{IsReadOnly}{wxarchiveentryisreadonly}\\
+\helpref{IsMadeByUnix}{wxzipentryismadebyunix}\\
+\helpref{IsText}{wxzipentryistext}
+
+
 \latexignore{\rtfignore{\wxheading{Members}}}
 
+
+\membersection{wxZipEntry::wxZipEntry}\label{wxzipentrywxzipentry}
+
+\func{}{wxZipEntry}{\param{const wxString\& }{name = wxEmptyString}, \param{const wxDateTime\& }{dt = wxDateTime::Now()}, \param{off\_t }{size = wxInvalidOffset}}
+
+Constructor.
+
+\func{}{wxZipEntry}{\param{const wxZipEntry\& }{entry}}
+
+Copy constructor.
+
+
+\membersection{wxZipEntry::Clone}\label{wxzipentryclone}
+
+\constfunc{wxZipEntry*}{Clone}{\void}
+
+Make a copy of this entry.
+
+
+\membersection{wxZipEntry::Get/SetComment}\label{wxzipentrycomment}
+
+\constfunc{wxString}{GetComment}{\void}
+
+\func{void}{SetComment}{\param{const wxString\& }{comment}}
+
+A short comment for this entry.
+
+
+\membersection{wxZipEntry::GetCompressedSize}\label{wxzipentrygetcompressedsize}
+
+\constfunc{off\_t}{GetCompressedSize}{\void}
+
+The compressed size of this entry in bytes.
+
+
+\membersection{wxZipEntry::GetCrc}\label{wxzipentrygetcrc}
+
+\constfunc{wxUint32}{GetCrc}{\void}
+
+CRC32 for this entry's data.
+
+
+\membersection{wxZipEntry::Get/SetExternalAttributes}\label{wxzipentryexternalattributes}
+
+\constfunc{wxUint32}{GetExternalAttributes}{\void}
+
+\func{void}{SetExternalAttributes}{\param{wxUint32 }{attr}}
+
+The low 8 bits are always the DOS/Windows file attributes for this entry.
+The values of these attributes are given in the
+enumeration {\tt wxZipAttributes}.
+
+The remaining bits can store platform specific permission bits or
+attributes, and their meaning depends on the value
+of \helpref{SetSystemMadeBy()}{wxzipentrysystemmadeby}.
+If \helpref{IsMadeByUnix()}{wxzipentryismadebyunix} is true then the
+high 16 bits are unix mode bits.
+
+The following other accessors access these bits:
+
+\helpref{IsReadOnly/SetIsReadOnly}{wxarchiveentryisreadonly}\\
+\helpref{IsDir/SetIsDir}{wxarchiveentryisdir}\\
+\helpref{Get/SetMode}{wxzipentrymode}
+
+
+\membersection{wxZipEntry::Get/SetExtra}\label{wxzipentryextra}
+
+\constfunc{const char*}{GetExtra}{\void}
+
+\constfunc{size\_t}{GetExtraLen}{\void}
+
+\func{void}{SetExtra}{\param{const char* }{extra}, \param{size\_t }{len}}
+
+The extra field from the entry's central directory record.
+
+The extra field is used to store platform or application specific
+data. See Pkware's document 'appnote.txt' for information on its format.
+
+
+\membersection{wxZipEntry::GetFlags}\label{wxzipentrygetflags}
+
+\constfunc{int}{GetFlags}{\void}
+
+Returns a combination of the bits flags in the enumeration {\tt wxZipFlags}.
+
+
+\membersection{wxZipEntry::GetInternalName}\label{wxzipentrygetinternalname}
+
+\constfunc{wxString}{GetInternalName}{\void}
+
+Returns the entry's filename in the internal format used within the
+archive. The name can include directory components, i.e. it can be a
+full path.
+
+The names of directory entries are returned without any trailing path
+separator. This gives a canonical name that can be used in comparisons.
+
+\func{wxString}{GetInternalName}{\param{const wxString\& }{name}, \param{wxPathFormat }{format = wxPATH\_NATIVE}, \param{bool* }{pIsDir = NULL}}
+
+A static member that translates a filename into the internal format used
+within the archive. If the third parameter is provided, the bool pointed
+to is set to indicate whether the name looks like a directory name
+(i.e. has a trailing path separator).
+
+\wxheading{See also}
+
+\helpref{Looking up an archive entry by name}{wxarcbyname}
+
+
+\membersection{wxZipEntry::Get/SetLocalExtra}\label{wxzipentrylocalextra}
+
+\constfunc{const char*}{GetLocalExtra}{\void}
+
+\constfunc{size\_t}{GetLocalExtraLen}{\void}
+
+\func{void}{SetLocalExtra}{\param{const char* }{extra}, \param{size\_t }{len}}
+
+The extra field from the entry's local record.
+
+The extra field is used to store platform or application specific
+data. See Pkware's document 'appnote.txt' for information on its format.
+
+
+\membersection{wxZipEntry::Get/SetMethod}\label{wxzipentrymethod}
+
+\constfunc{int}{GetMethod}{\void}
+
+\func{void}{SetMethod}{\param{int }{method}}
+
+The compression method. The enumeration {\tt wxZipMethod} lists the
+possible values.
+
+The default constructor sets this to wxZIP\_METHOD\_DEFAULT,
+which allows \helpref{wxZipOutputStream}{wxzipoutputstream} to
+choose the method when writing the entry.
+
+
+\membersection{wxZipEntry::Get/SetMode}\label{wxzipentrymode}
+
+\constfunc{int}{GetMode}{\void}
+
+If \helpref{IsMadeByUnix()}{wxzipentryismadebyunix} is true then
+returns the unix permission bits stored in
+ \helpref{GetExternalAttributes()}{wxzipentryexternalattributes}.
+Otherwise synthesises them from the DOS attributes.
+
+\func{void}{SetMode}{\param{int }{mode}}
+
+Sets the DOS attributes
+in \helpref{GetExternalAttributes()}{wxzipentryexternalattributes}
+to be consistent with the {\tt mode} given.
+
+If \helpref{IsMadeByUnix()}{wxzipentryismadebyunix} is true then also
+stores {\tt mode} in GetExternalAttributes().
+
+Note that the default constructor
+sets \helpref{GetSystemMadeBy()}{wxzipentrysystemmadeby} to 
+wxZIP\_SYSTEM\_MSDOS by default. So to be able to store unix
+permissions when creating zips, call SetSystemMadeBy(wxZIP\_SYSTEM\_UNIX).
+
+
+\membersection{wxZipEntry::SetNotifier}\label{wxzipentrynotifier}
+
+\func{void}{SetNotifier}{\param{wxZipNotifier\& }{notifier}}
+
+\func{void}{UnsetNotifier}{\void}
+
+Sets the \helpref{notifier}{wxzipnotifier} for this entry.
+Whenever the \helpref{wxZipInputStream}{wxzipinputstream} updates
+this entry, it will then invoke the associated
+notifier's \helpref{OnEntryUpdated}{wxzipnotifieronentryupdated}
+method.
+
+Setting a notifier is not usually necessary. It is used to handle
+certain cases when modifying an zip in a pipeline (i.e. between
+non-seekable streams).
+
+\wxheading{See also}
+
+\helpref{Archives on non-seekable streams}{wxarcnoseek}\\
+\helpref{wxZipNotifier}{wxzipnotifier}
+
+
+\membersection{wxZipEntry::Get/SetSystemMadeBy}\label{wxzipentrysystemmadeby}
+
+\constfunc{int}{GetSystemMadeBy}{\void}
+
+\func{void}{SetSystemMadeBy}{\param{int }{system}}
+
+The originating file-system. The default constructor sets this to
+wxZIP\_SYSTEM\_MSDOS. Set it to wxZIP\_SYSTEM\_UNIX in order to be
+able to store unix permissions using \helpref{SetMode()}{wxzipentrymode}.
+
+
+\membersection{wxZipEntry::IsMadeByUnix}\label{wxzipentryismadebyunix}
+
+\constfunc{bool}{IsMadeByUnix}{\void}
+
+Returns true if \helpref{GetSystemMadeBy()}{wxzipentrysystemmadeby}
+is a flavour of unix.
+
+
+\membersection{wxZipEntry::IsText/SetIsText}\label{wxzipentryistext}
+
+\constfunc{bool}{IsText}{\void}
+
+\func{void}{SetIsText}{\param{bool }{isText = true}}
+
+Indicates that this entry's data is text in an 8-bit encoding.
+
+
+\membersection{wxZipEntry::operator=}\label{wxzipentryoperatorassign}
+
+\func{wxZipEntry\& operator}{operator=}{\param{const wxZipEntry\& }{entry}}
+
+Assignment operator.
+
+
+%
+% automatically generated by HelpGen $Revision$ from
+% wx/zipstrm.h at 16/Sep/04 12:19:29
+%
+
+\section{\class{wxZipInputStream}}\label{wxzipinputstream}
+
+Input stream for reading zip files.
+
+\helpref{GetNextEntry()}{wxzipinputstreamgetnextentry} returns an
+ \helpref{wxZipEntry}{wxzipentry} object containing the meta-data
+for the next entry in the zip (and gives away ownership). Reading from
+the wxZipInputStream then returns the entry's data. Eof() becomes true
+after an attempt has been made to read past the end of the entry's data.
+When there are no more entries, GetNextEntry() returns NULL and sets Eof().
+
+\wxheading{Derived from}
+
+\helpref{wxArchiveInputStream}{wxarchiveinputstream}
+
+\wxheading{Include files}
+
+<wx/zipstrm.h>
+
+\wxheading{Data structures}
+{\small \begin{verbatim}
+typedef wxZipEntry entry\_type
+\end{verbatim}}
+
+\wxheading{See also}
+
+\helpref{Archive formats such as zip}{wxarc}\\
+\helpref{wxZipEntry}{wxzipentry}\\
+\helpref{wxZipOutputStream}{wxzipoutputstream}
+
+\latexignore{\rtfignore{\wxheading{Members}}}
+
+
 \membersection{wxZipInputStream::wxZipInputStream}\label{wxzipinputstreamwxzipinputstream}
 
+\func{}{wxZipInputStream}{\param{wxInputStream\& }{stream}, \param{wxMBConv\& }{conv = wxConvLocal}}
+
+Constructor. In a Unicode build the second parameter {\tt conv} is
+used to translate the filename and comment fields into Unicode. It has
+no effect on the stream's data.
+
 \func{}{wxZipInputStream}{\param{const wxString\& }{archive}, \param{const wxString\& }{file}}
 
-Constructor.
+Compatibility constructor.
+
+
+\membersection{wxZipInputStream::CloseEntry}\label{wxzipinputstreamcloseentry}
+
+\func{bool}{CloseEntry}{\void}
+
+Closes the current entry. On a non-seekable stream reads to the end of
+the current entry first.
+
+
+\membersection{wxZipInputStream::GetComment}\label{wxzipinputstreamgetcomment}
+
+\func{wxString}{GetComment}{\void}
+
+Returns the zip comment.
+
+This is stored a the end of the zip, therefore when reading a zip
+from a non-seekable stream, it returns the empty string until the
+end of the zip has been reached, i.e. when GetNextEntry() returns
+NULL.
+
+
+\membersection{wxZipInputStream::GetNextEntry}\label{wxzipinputstreamgetnextentry}
+
+\func{wxZipEntry*}{GetNextEntry}{\void}
+
+Closes the current entry if one is open, then reads the meta-data for
+the next entry and returns it in a \helpref{wxZipEntry}{wxzipentry}
+object, giving away ownership. The stream is then open and can be read.
+
+
+\membersection{wxZipInputStream::GetTotalEntries}\label{wxzipinputstreamgettotalentries}
+
+\func{int}{GetTotalEntries}{\void}
+
+For a zip on a seekable stream returns the total number of entries in
+the zip. For zips on non-seekable streams returns the number of entries
+returned so far by \helpref{GetNextEntry()}{wxzipinputstreamgetnextentry}.
+
+
+\membersection{wxZipInputStream::OpenEntry}\label{wxzipinputstreamopenentry}
+
+\func{bool}{OpenEntry}{\param{wxZipEntry\& }{entry}}
+
+Closes the current entry if one is open, then opens the entry specified
+by the {\it entry} object.
+
+{\it entry} should be from the same zip file, and the zip should
+be on a seekable stream.
+
+\wxheading{See also}
+
+\helpref{Looking up an archive entry by name}{wxarcbyname}
+
+
+%
+% automatically generated by HelpGen $Revision$ from
+% wx/zipstrm.h at 16/Sep/04 12:19:29
+%
+
+\section{\class{wxZipNotifier}}\label{wxzipnotifier}
+
+If you need to know when a \helpref{wxZipInputStream}{wxzipinputstream}
+updates a \helpref{wxZipEntry}{wxzipentry},
+you can create a notifier by deriving from this abstract base class,
+overriding \helpref{OnEntryUpdated()}{wxzipnotifieronentryupdated}.
+An instance of your notifier class can then be assigned to wxZipEntry
+objects, using \helpref{wxZipEntry::SetNotifier()}{wxzipentrynotifier}.
+
+Setting a notifier is not usually necessary. It is used to handle
+certain cases when modifying an zip in a pipeline (i.e. between
+non-seekable streams).
+See '\helpref{Archives on non-seekable streams}{wxarcnoseek}'.
+
+\wxheading{Derived from}
+
+No base class
+
+\wxheading{Include files}
+
+<wx/zipstrm.h>
+
+\wxheading{See also}
+
+\helpref{Archives on non-seekable streams}{wxarcnoseek}\\
+\helpref{wxZipEntry}{wxzipentry}\\
+\helpref{wxZipInputStream}{wxzipinputstream}\\
+\helpref{wxZipOutputStream}{wxzipoutputstream}
+
+\latexignore{\rtfignore{\wxheading{Members}}}
+
+
+\membersection{wxZipNotifier::OnEntryUpdated}\label{wxzipnotifieronentryupdated}
+
+\func{void}{OnEntryUpdated}{\param{wxZipEntry\& }{entry}}
+
+Override this to receive notifications when
+an \helpref{wxZipEntry}{wxzipentry} object changes.
+
+
+%
+% automatically generated by HelpGen $Revision$ from
+% wx/zipstrm.h at 16/Sep/04 12:19:29
+%
+
+\section{\class{wxZipOutputStream}}\label{wxzipoutputstream}
+
+Output stream for writing zip files.
+
+\helpref{PutNextEntry()}{wxzipoutputstreamputnextentry} is used to create
+a new entry in the output zip, then the entry's data is written to the
+wxZipOutputStream.  Another call to PutNextEntry() closes the current
+entry and begins the next.
+
+\wxheading{Derived from}
+
+\helpref{wxArchiveOutputStream}{wxarchiveoutputstream}
+
+\wxheading{Include files}
+
+<wx/zipstrm.h>
+
+\wxheading{See also}
+
+\helpref{Archive formats such as zip}{wxarc}\\
+\helpref{wxZipEntry}{wxzipentry}\\
+\helpref{wxZipInputStream}{wxzipinputstream}
+
+\latexignore{\rtfignore{\wxheading{Members}}}
+
+
+\membersection{wxZipOutputStream::wxZipOutputStream}\label{wxzipoutputstreamwxzipoutputstream}
+
+\func{}{wxZipOutputStream}{\param{wxOutputStream\& }{stream}, \param{int }{level = -1}, \param{wxMBConv\& }{conv = wxConvLocal}}
+
+Constructor. {\tt level} is the compression level to use.
+It can be a value between 0 and 9 or -1 to use the default value
+which currently is equivalent to 6.
+
+In a Unicode build the third parameter {\tt conv} is used to translate
+the filename and comment fields to Unicode. It has no effect on the
+stream's data.
+
+
+\membersection{wxZipOutputStream::\destruct{wxZipOutputStream}}\label{wxzipoutputstreamdtor}
+
+\func{}{\destruct{wxZipOutputStream}}{\void}
+
+The destructor calls \helpref{Close()}{wxzipoutputstreamclose} to finish
+writing the zip if it has not been called already.
+
+
+\membersection{wxZipOutputStream::Close}\label{wxzipoutputstreamclose}
+
+\func{bool}{Close}{\void}
+
+Finishes writing the zip, returning true if successfully.
+Called by the destructor if not called explicitly.
+
+
+\membersection{wxZipOutputStream::CloseEntry}\label{wxzipoutputstreamcloseentry}
+
+\func{bool}{CloseEntry}{\void}
+
+Close the current entry. It is called implicitly whenever another new
+entry is created with \helpref{CopyEntry()}{wxzipoutputstreamcopyentry}
+or \helpref{PutNextEntry()}{wxzipoutputstreamputnextentry}, or
+when the zip is closed.
+
+
+\membersection{wxZipOutputStream::CopyArchiveMetaData}\label{wxzipoutputstreamcopyarchivemetadata}
+
+\func{bool}{CopyArchiveMetaData}{\param{wxZipInputStream\& }{inputStream}}
+
+Transfers the zip comment from the \helpref{wxZipInputStream}{wxzipinputstream}
+to this output stream.
+
+
+\membersection{wxZipOutputStream::CopyEntry}\label{wxzipoutputstreamcopyentry}
+
+\func{bool}{CopyEntry}{\param{wxZipEntry* }{entry}, \param{wxZipInputStream\& }{inputStream}}
+
+Takes ownership of {\tt entry} and uses it to create a new entry
+in the zip. {\tt entry} is then opened in {\tt inputStream} and its contents
+copied to this stream.
+
+CopyEntry() is much more efficient than transferring the data using
+Read() and Write() since it will copy them without decompressing and
+recompressing them.
+
+For zips on seekable streams, {\tt entry} must be from the same zip file
+as {\tt stream}. For non-seekable streams, {\tt entry} must also be the
+last thing read from {\tt inputStream}.
+
+
+\membersection{wxZipOutputStream::Get/SetLevel}\label{wxzipoutputstreamlevel}
+
+\constfunc{int}{GetLevel}{\void}
+
+\func{void}{SetLevel}{\param{int }{level}}
+
+Set the compression level that will be used the next time an entry is
+created. It can be a value between 0 and 9 or -1 to use the default value
+which currently is equivalent to 6.
+
+
+\membersection{wxZipOutputStream::PutNextDirEntry}\label{wxzipoutputstreamputnextdirentry}
+
+\func{bool}{PutNextDirEntry}{\param{const wxString\& }{name}, \param{const wxDateTime\& }{dt = wxDateTime::Now()}}
+
+Create a new directory entry
+(see \helpref{wxArchiveEntry::IsDir()}{wxarchiveentryisdir})
+with the given name and timestamp.
+
+\helpref{PutNextEntry()}{wxzipoutputstreamputnextentry} can
+also be used to create directory entries, by supplying a name with
+a trailing path separator.
+
+
+\membersection{wxZipOutputStream::PutNextEntry}\label{wxzipoutputstreamputnextentry}
+
+\func{bool}{PutNextEntry}{\param{wxZipEntry* }{entry}}
+
+Takes ownership of {\tt entry} and uses it to create a new entry
+in the zip. 
+
+\func{bool}{PutNextEntry}{\param{const wxString\& }{name}, \param{const wxDateTime\& }{dt = wxDateTime::Now()}, \param{off\_t }{size = wxInvalidOffset}}
+
+Create a new entry with the given name, timestamp and size.
+
 
-\wxheading{Parameters}
+\membersection{wxZipOutputStream::SetComment}\label{wxzipoutputstreamsetcomment}
 
-\docparam{archive}{name of ZIP file}
+\func{void}{SetComment}{\param{const wxString\& }{comment}}
 
-\docparam{file}{name of file stored in the archive}
+Sets a comment for the zip as a whole. It is written at the end of the
+zip.
 
diff --git a/include/wx/archive.h b/include/wx/archive.h
new file mode 100644
index 0000000000..fb5a647383
--- /dev/null
+++ b/include/wx/archive.h
@@ -0,0 +1,353 @@
+/////////////////////////////////////////////////////////////////////////////
+// Name:        archive.h
+// Purpose:     Streams for archive formats
+// Author:      Mike Wetherell
+// RCS-ID:      $Id$
+// Copyright:   (c) 2004 Mike Wetherell
+// Licence:     wxWindows licence
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef _WX_ARCHIVE_H__
+#define _WX_ARCHIVE_H__
+
+#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
+#pragma interface "archive.h"
+#endif
+
+#include "wx/defs.h"
+
+#if wxUSE_ZLIB && wxUSE_STREAMS && wxUSE_ZIPSTREAM
+
+#include "wx/stream.h"
+#include "wx/filename.h"
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxArchiveNotifier
+
+class WXDLLIMPEXP_BASE wxArchiveNotifier
+{
+public:
+    virtual ~wxArchiveNotifier() { }
+
+    virtual void OnEntryUpdated(class wxArchiveEntry& entry) = 0;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxArchiveEntry
+//
+// Holds an entry's meta data, such as filename and timestamp.
+
+class WXDLLIMPEXP_BASE wxArchiveEntry : public wxObject
+{
+public:
+    virtual ~wxArchiveEntry() { }
+
+    virtual wxDateTime   GetDateTime() const = 0;
+    virtual wxFileOffset GetSize() const = 0;
+    virtual wxFileOffset GetOffset() const = 0;
+    virtual bool         IsDir() const = 0;
+    virtual bool         IsReadOnly() const = 0;
+    virtual wxString     GetInternalName() const = 0;
+    virtual wxPathFormat GetInternalFormat() const = 0;
+    virtual wxString     GetName(wxPathFormat format = wxPATH_NATIVE) const = 0;
+
+    virtual void SetDateTime(const wxDateTime& dt) = 0;
+    virtual void SetSize(wxFileOffset size) = 0;
+    virtual void SetIsDir(bool isDir = true) = 0;
+    virtual void SetIsReadOnly(bool isReadOnly = true) = 0;
+    virtual void SetName(const wxString& name,
+                         wxPathFormat format = wxPATH_NATIVE) = 0;
+    
+    wxArchiveEntry *Clone() const { return DoClone(); }
+
+    void SetNotifier(wxArchiveNotifier& notifier);
+    virtual void UnsetNotifier() { m_notifier = NULL; }
+
+protected:
+    wxArchiveEntry() : m_notifier(NULL) { }
+
+    virtual void SetOffset(wxFileOffset offset) = 0;
+    virtual wxArchiveEntry* DoClone() const = 0;
+
+    wxArchiveNotifier *GetNotifier() const { return m_notifier; }
+    wxArchiveEntry& operator=(const wxArchiveEntry& entry);
+
+private:
+    wxArchiveNotifier *m_notifier;
+
+    DECLARE_ABSTRACT_CLASS(wxArchiveEntry)
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxArchiveInputStream
+//
+// GetNextEntry() returns an wxArchiveEntry object containing the meta-data
+// for the next entry in the archive (and gives away ownership). Reading from
+// the wxArchiveInputStream then returns the entry's data. Eof() becomes true
+// after an attempt has been made to read past the end of the entry's data.
+//
+// When there are no more entries, GetNextEntry() returns NULL and sets Eof().
+
+class WXDLLIMPEXP_BASE wxArchiveInputStream : public wxFilterInputStream
+{
+public:
+    typedef wxArchiveEntry entry_type;
+
+    virtual ~wxArchiveInputStream() { }
+    
+    virtual bool OpenEntry(wxArchiveEntry& entry) = 0;
+    virtual bool CloseEntry() = 0;
+
+    wxArchiveEntry *GetNextEntry()  { return DoGetNextEntry(); }
+
+    virtual char Peek()             { return wxInputStream::Peek(); }
+    
+protected:
+    wxArchiveInputStream(wxInputStream& stream, wxMBConv& conv);
+
+    virtual wxArchiveEntry *DoGetNextEntry() = 0;
+
+    wxMBConv& GetConv() const       { return m_conv; }
+
+private:
+    wxMBConv& m_conv;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxArchiveOutputStream
+//
+// PutNextEntry is used to create a new entry in the output archive, then
+// the entry's data is written to the wxArchiveOutputStream.
+//
+// Only one entry can be open for output at a time; another call to
+// PutNextEntry closes the current entry and begins the next.
+// 
+// The overload 'bool PutNextEntry(wxArchiveEntry *entry)' takes ownership
+// of the entry object.
+
+class WXDLLIMPEXP_BASE wxArchiveOutputStream : public wxFilterOutputStream
+{
+public:
+    virtual ~wxArchiveOutputStream() { }
+
+    virtual bool PutNextEntry(wxArchiveEntry *entry) = 0;
+
+    virtual bool PutNextEntry(const wxString& name,
+                              const wxDateTime& dt = wxDateTime::Now(),
+                              wxFileOffset size = wxInvalidOffset) = 0;
+
+    virtual bool PutNextDirEntry(const wxString& name,
+                                 const wxDateTime& dt = wxDateTime::Now()) = 0;
+
+    virtual bool CopyEntry(wxArchiveEntry *entry,
+                           wxArchiveInputStream& stream) = 0;
+
+    virtual bool CopyArchiveMetaData(wxArchiveInputStream& stream) = 0;
+
+    virtual bool CloseEntry() = 0;
+    virtual bool Close() = 0;
+
+protected:
+    wxArchiveOutputStream(wxOutputStream& stream, wxMBConv& conv);
+
+    wxMBConv& GetConv() const { return m_conv; }
+
+private:
+    wxMBConv& m_conv;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxArchiveClassFactory
+//
+// A wxArchiveClassFactory instance for a particular archive type allows
+// the creation of the other classes that may be needed.
+
+class WXDLLIMPEXP_BASE wxArchiveClassFactory : public wxObject
+{
+public:
+    virtual ~wxArchiveClassFactory() { }
+
+    wxArchiveEntry *NewEntry() const
+        { return DoNewEntry(); }
+    wxArchiveInputStream *NewStream(wxInputStream& stream) const
+        { return DoNewStream(stream); }
+    wxArchiveOutputStream *NewStream(wxOutputStream& stream) const
+        { return DoNewStream(stream); }
+
+    virtual wxString GetInternalName(
+        const wxString& name,
+        wxPathFormat format = wxPATH_NATIVE) const = 0;
+
+    void SetConv(wxMBConv& conv) { m_pConv = &conv; }
+    wxMBConv& GetConv() const { return *m_pConv; }
+
+protected:
+    virtual wxArchiveEntry        *DoNewEntry() const = 0;
+    virtual wxArchiveInputStream  *DoNewStream(wxInputStream& stream) const = 0;
+    virtual wxArchiveOutputStream *DoNewStream(wxOutputStream& stream) const = 0;
+
+    wxArchiveClassFactory() : m_pConv(&wxConvLocal) { }
+    wxArchiveClassFactory& operator=(const wxArchiveClassFactory& WXUNUSED(f))
+        { return *this; }
+
+private:
+    wxMBConv *m_pConv;
+
+    DECLARE_ABSTRACT_CLASS(wxArchiveClassFactory)
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxArchiveIterator
+//
+// An input iterator that can be used to transfer an archive's catalog to
+// a container.
+
+#if wxUSE_STL || defined WX_TEST_ARCHIVE_ITERATOR
+#include <iterator>
+#include <utility>
+
+template <class X, class Y>
+void WXDLLIMPEXP_BASE _wxSetArchiveIteratorValue(
+    X& val, Y entry, void *WXUNUSED(d))
+{
+    val = X(entry);
+}
+template <class X, class Y, class Z>
+void WXDLLIMPEXP_BASE _wxSetArchiveIteratorValue(
+    std::pair<X, Y>& val, Z entry, Z WXUNUSED(d))
+{
+    val = std::make_pair(X(entry->GetInternalName()), Y(entry));
+}
+
+#if defined _MSC_VER && _MSC_VER < 1300
+template <class Arc, class T = Arc::entry_type*>
+#else
+template <class Arc, class T = typename Arc::entry_type*>
+#endif
+class WXDLLIMPEXP_BASE wxArchiveIterator
+{
+public:
+    typedef std::input_iterator_tag iterator_category;
+    typedef T value_type;
+    typedef ptrdiff_t difference_type;
+    typedef T* pointer;
+    typedef T& reference;
+
+    wxArchiveIterator() : m_rep(NULL) { }
+
+    wxArchiveIterator(Arc& arc) {
+        typename Arc::entry_type* entry = arc.GetNextEntry();
+        m_rep = entry ? new Rep(arc, entry) : NULL;
+    }
+
+    wxArchiveIterator(const wxArchiveIterator& it) : m_rep(it.m_rep) {
+        if (m_rep)
+            m_rep->AddRef();
+    }
+    
+    ~wxArchiveIterator() {
+        if (m_rep)
+            m_rep->UnRef();
+    }
+
+    const T& operator *() const {
+        return m_rep->GetValue();
+    }
+
+    const T* operator ->() const {
+        return &**this;
+    }
+
+    wxArchiveIterator& operator =(const wxArchiveIterator& it) {
+        if (it.m_rep)
+            it.m_rep.AddRef();
+        if (m_rep)
+            m_rep.UnRef();
+        m_rep = it.m_rep;
+        return *this;
+    }
+
+    wxArchiveIterator& operator ++() {
+        m_rep = m_rep->Next();
+        return *this;
+    }
+
+    wxArchiveIterator operator ++(int) {
+        wxArchiveIterator it(*this);
+        ++(*this);
+        return it;
+    }
+
+    friend bool operator ==(const wxArchiveIterator& i,
+                            const wxArchiveIterator& j) {
+        return i.m_rep == j.m_rep;
+    }
+
+    friend bool operator !=(const wxArchiveIterator& i,
+                            const wxArchiveIterator& j) {
+        return !(i == j);
+    }
+
+private:
+    class Rep {
+        Arc& m_arc;
+        typename Arc::entry_type* m_entry;
+        T m_value;
+        int m_ref;
+        
+    public:
+        Rep(Arc& arc, typename Arc::entry_type* entry)
+            : m_arc(arc), m_entry(entry), m_value(), m_ref(1) { }
+        ~Rep()
+            { delete m_entry; }
+        
+        void AddRef() {
+            m_ref++;
+        }
+
+        void UnRef() {
+            if (--m_ref == 0)
+                delete this;
+        }
+
+        Rep *Next() {
+            typename Arc::entry_type* entry = m_arc.GetNextEntry();
+            if (!entry) {
+                UnRef();
+                return NULL;
+            }
+            if (m_ref > 1) {
+                m_ref--; 
+                return new Rep(m_arc, entry);
+            }
+            delete m_entry;
+            m_entry = entry;
+            m_value = T();
+            return this;
+        }
+
+        const T& GetValue() {
+            if (m_entry) {
+                _wxSetArchiveIteratorValue(m_value, m_entry, m_entry);
+                m_entry = NULL;
+            }
+            return m_value;
+        }
+    } *m_rep;
+};
+
+typedef wxArchiveIterator<wxArchiveInputStream> wxArchiveIter;
+typedef wxArchiveIterator<wxArchiveInputStream,
+        std::pair<wxString, wxArchiveEntry*> >  wxArchivePairIter;
+
+#endif // wxUSE_STL || defined WX_TEST_ARCHIVE_ITERATOR
+
+#endif // wxUSE_STREAMS
+
+#endif // _WX_ARCHIVE_H__
diff --git a/include/wx/fs_zip.h b/include/wx/fs_zip.h
index 6dc75eb25c..d449cb072a 100644
--- a/include/wx/fs_zip.h
+++ b/include/wx/fs_zip.h
@@ -37,12 +37,13 @@ class WXDLLIMPEXP_BASE wxZipFSHandler : public wxFileSystemHandler
 
     private:
         // these vars are used by FindFirst/Next:
-        void *m_Archive;
+        class wxZipInputStream *m_Archive;
         wxString m_Pattern, m_BaseDir, m_ZipFile;
         bool m_AllowDirs, m_AllowFiles;
         wxLongToLongHashMap *m_DirsFound;
 
         wxString DoFind();
+        void CloseArchive(class wxZipInputStream *archive);
 
     DECLARE_NO_COPY_CLASS(wxZipFSHandler)
 };
diff --git a/include/wx/zipstrm.h b/include/wx/zipstrm.h
index 632963b888..7b83d0bc0b 100644
--- a/include/wx/zipstrm.h
+++ b/include/wx/zipstrm.h
@@ -1,13 +1,14 @@
 /////////////////////////////////////////////////////////////////////////////
-// Name:        zipstream.h
-// Purpose:     wxZipInputStream for reading files from ZIP archive
-// Author:      Vaclav Slavik
-// Copyright:   (c) 1999 Vaclav Slavik
+// Name:        zipstrm.h
+// Purpose:     Streams for Zip files
+// Author:      Mike Wetherell
+// RCS-ID:      $Id$
+// Copyright:   (c) Mike Wetherell
 // Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
 
-#ifndef __ZIPSTREAM_H__
-#define __ZIPSTREAM_H__
+#ifndef _WX_WXZIPSTREAM_H__
+#define _WX_WXZIPSTREAM_H__
 
 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
 #pragma interface "zipstrm.h"
@@ -15,47 +16,507 @@
 
 #include "wx/defs.h"
 
-#if wxUSE_STREAMS && wxUSE_ZIPSTREAM && wxUSE_ZLIB
+#if wxUSE_ZLIB && wxUSE_STREAMS && wxUSE_ZIPSTREAM
 
-#include "wx/stream.h"
+#include "wx/archive.h"
+#include "wx/hashmap.h"
+#include "wx/filename.h"
 
-//--------------------------------------------------------------------------------
-// wxZipInputStream
-//                  This class is input stream from ZIP archive. The archive
-//                  must be local file (accessible via FILE*)
-//--------------------------------------------------------------------------------
 
+/////////////////////////////////////////////////////////////////////////////
+// constants
+
+// Compression Method, only 0 (store) and 8 (deflate) are supported here
+//
+enum wxZipMethod
+{
+    wxZIP_METHOD_STORE,
+    wxZIP_METHOD_SHRINK,
+    wxZIP_METHOD_REDUCE1,
+    wxZIP_METHOD_REDUCE2,
+    wxZIP_METHOD_REDUCE3,
+    wxZIP_METHOD_REDUCE4,
+    wxZIP_METHOD_IMPLODE,
+    wxZIP_METHOD_TOKENIZE,
+    wxZIP_METHOD_DEFLATE,
+    wxZIP_METHOD_DEFLATE64,
+    wxZIP_METHOD_BZIP2 = 12,
+    wxZIP_METHOD_DEFAULT = 0xffff
+};
+
+// Originating File-System.
+// 
+// These are Pkware's values. Note that Info-zip disagree on some of them,
+// most notably NTFS.
+//
+enum wxZipSystem
+{
+    wxZIP_SYSTEM_MSDOS,
+    wxZIP_SYSTEM_AMIGA,
+    wxZIP_SYSTEM_OPENVMS,
+    wxZIP_SYSTEM_UNIX,
+    wxZIP_SYSTEM_VM_CMS,
+    wxZIP_SYSTEM_ATARI_ST,
+    wxZIP_SYSTEM_OS2_HPFS,
+    wxZIP_SYSTEM_MACINTOSH,
+    wxZIP_SYSTEM_Z_SYSTEM,
+    wxZIP_SYSTEM_CPM,
+    wxZIP_SYSTEM_WINDOWS_NTFS,
+    wxZIP_SYSTEM_MVS,
+    wxZIP_SYSTEM_VSE,
+    wxZIP_SYSTEM_ACORN_RISC,
+    wxZIP_SYSTEM_VFAT,
+    wxZIP_SYSTEM_ALTERNATE_MVS,
+    wxZIP_SYSTEM_BEOS,
+    wxZIP_SYSTEM_TANDEM,
+    wxZIP_SYSTEM_OS_400
+};
+
+// Dos/Win file attributes
+//
+enum wxZipAttributes
+{
+    wxZIP_A_RDONLY = 0x01,
+    wxZIP_A_HIDDEN = 0x02,
+    wxZIP_A_SYSTEM = 0x04,
+    wxZIP_A_SUBDIR = 0x10,
+    wxZIP_A_ARCH   = 0x20,
+
+    wxZIP_A_MASK   = 0x37
+};
+
+// Values for the flags field in the zip headers
+//
+enum wxZipFlags
+{
+    wxZIP_ENCRYPTED         = 0x0001,
+    wxZIP_DEFLATE_NORMAL    = 0x0000,   // normal compression
+    wxZIP_DEFLATE_EXTRA     = 0x0002,   // extra compression
+    wxZIP_DEFLATE_FAST      = 0x0004,   // fast compression
+    wxZIP_DEFLATE_SUPERFAST = 0x0006,   // superfast compression
+    wxZIP_DEFLATE_MASK      = 0x0006,
+    wxZIP_SUMS_FOLLOW       = 0x0008,   // crc and sizes come after the data
+    wxZIP_ENHANCED          = 0x0010,
+    wxZIP_PATCH             = 0x0020,
+    wxZIP_STRONG_ENC        = 0x0040,
+    wxZIP_UNUSED            = 0x0F80,
+    wxZIP_RESERVED          = 0xF000
+};
+
+// Forward decls
+//
+class WXDLLIMPEXP_BASE wxZipEntry;
+class WXDLLIMPEXP_BASE wxZipInputStream;
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxZipNotifier
 
-class WXDLLIMPEXP_BASE wxZipInputStream : public wxInputStream
+class WXDLLIMPEXP_BASE wxZipNotifier
 {
 public:
-    wxZipInputStream(const wxString& archive, const wxString& file);
-            // archive is name of .zip archive, file is name of file to be extracted.
-            // Remember that archive must be local file accesible via fopen, fread functions!
-    ~wxZipInputStream();
+    virtual ~wxZipNotifier() { }
+
+    virtual void OnEntryUpdated(wxZipEntry& entry) = 0;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Zip Entry - holds the meta data for a file in the zip
+
+class WXDLLIMPEXP_BASE wxZipEntry : public wxArchiveEntry
+{
+public:
+    wxZipEntry(const wxString& name = wxEmptyString,
+               const wxDateTime& dt = wxDateTime::Now(),
+               wxFileOffset size = wxInvalidOffset);
+    virtual ~wxZipEntry();
+
+    wxZipEntry(const wxZipEntry& entry);
+    wxZipEntry& operator=(const wxZipEntry& entry);
+
+    // Get accessors
+    wxDateTime   GetDateTime() const            { return m_DateTime; }
+    wxFileOffset GetSize() const                { return m_Size; }
+    wxFileOffset GetOffset() const              { return m_Offset; }
+    wxString     GetInternalName() const        { return m_Name; }
+    int          GetMethod() const              { return m_Method; }
+    int          GetFlags() const               { return m_Flags; }
+    wxUint32     GetCrc() const                 { return m_Crc; }
+    wxFileOffset GetCompressedSize() const      { return m_CompressedSize; }
+    int          GetSystemMadeBy() const        { return m_SystemMadeBy; }
+    wxString     GetComment() const             { return m_Comment; }
+    wxUint32     GetExternalAttributes() const  { return m_ExternalAttributes; }
+    wxPathFormat GetInternalFormat() const      { return wxPATH_UNIX; }
+    int          GetMode() const;
+    const char  *GetLocalExtra() const;
+    size_t       GetLocalExtraLen() const;
+    const char  *GetExtra() const;
+    size_t       GetExtraLen() const;
+    wxString     GetName(wxPathFormat format = wxPATH_NATIVE) const;
+
+    // is accessors
+    inline bool IsDir() const;
+    inline bool IsText() const;
+    inline bool IsReadOnly() const;
+    inline bool IsMadeByUnix() const;
+
+    // set accessors
+    void SetDateTime(const wxDateTime& dt)      { m_DateTime = dt; }
+    void SetSize(wxFileOffset size)             { m_Size = size; }
+    void SetMethod(int method)                  { m_Method = method; }
+    void SetComment(const wxString& comment)    { m_Comment = comment; }
+    void SetExternalAttributes(wxUint32 attr )  { m_ExternalAttributes = attr; }
+    void SetSystemMadeBy(int system);
+    void SetMode(int mode);
+    void SetExtra(const char *extra, size_t len);
+    void SetLocalExtra(const char *extra, size_t len);
+
+    inline void SetName(const wxString& name,
+                        wxPathFormat format = wxPATH_NATIVE);
+
+    static wxString GetInternalName(const wxString& name,
+                                    wxPathFormat format = wxPATH_NATIVE,
+                                    bool *pIsDir = NULL);
+
+    // set is accessors
+    void SetIsDir(bool isDir = true);
+    inline void SetIsReadOnly(bool isReadOnly = true);
+    inline void SetIsText(bool isText = true);
 
-    virtual wxFileOffset GetLength() const {return m_Size;}
-    virtual bool Eof() const;
+    wxZipEntry *Clone() const                   { return ZipClone(); }
+
+    void SetNotifier(wxZipNotifier& notifier);
+    void UnsetNotifier();
 
 protected:
-    virtual size_t OnSysRead(void *buffer, size_t bufsize);
-    virtual wxFileOffset OnSysSeek(wxFileOffset seek, wxSeekMode mode);
-    virtual wxFileOffset OnSysTell() const {return m_Pos;}
+    // Internal attributes
+    enum { TEXT_ATTR = 1 };
+
+    // protected Get accessors
+    int GetVersionNeeded() const                { return m_VersionNeeded; }
+    wxFileOffset GetKey() const                 { return m_Key; }
+    int GetVersionMadeBy() const                { return m_VersionMadeBy; }
+    int GetDiskStart() const                    { return m_DiskStart; }
+    int GetInternalAttributes() const           { return m_InternalAttributes; }
+
+    void SetVersionNeeded(int version)          { m_VersionNeeded = version; }
+    void SetOffset(wxFileOffset offset)         { m_Offset = offset; }
+    void SetFlags(int flags)                    { m_Flags = flags; }
+    void SetVersionMadeBy(int version)          { m_VersionMadeBy = version; }
+    void SetCrc(wxUint32 crc)                   { m_Crc = crc; }
+    void SetCompressedSize(wxFileOffset size)   { m_CompressedSize = size; }
+    void SetKey(wxFileOffset offset)            { m_Key = offset; }
+    void SetDiskStart(int start)                { m_DiskStart = start; }
+    void SetInternalAttributes(int attr)        { m_InternalAttributes = attr; }
+
+    virtual wxZipEntry *ZipClone() const        { return new wxZipEntry(*this); }
+
+    void Notify();
 
 private:
+    wxArchiveEntry* DoClone() const             { return ZipClone(); }
+
+    size_t ReadLocal(wxInputStream& stream, wxMBConv& conv);
+    size_t WriteLocal(wxOutputStream& stream, wxMBConv& conv) const;
+
+    size_t ReadCentral(wxInputStream& stream, wxMBConv& conv);
+    size_t WriteCentral(wxOutputStream& stream, wxMBConv& conv) const;
+
+    size_t ReadDescriptor(wxInputStream& stream);
+    size_t WriteDescriptor(wxOutputStream& stream, wxUint32 crc,
+                           wxFileOffset compressedSize, wxFileOffset size);
+
+    wxUint8      m_SystemMadeBy;       // one of enum wxZipSystem
+    wxUint8      m_VersionMadeBy;      // major * 10 + minor
+
+    wxUint16     m_VersionNeeded;      // ver needed to extract (20 i.e. v2.0)
+    wxUint16     m_Flags;
+    wxUint16     m_Method;             // compression method (one of wxZipMethod)
+    wxDateTime   m_DateTime;
+    wxUint32     m_Crc;
+    wxFileOffset m_CompressedSize;
     wxFileOffset m_Size;
-    wxFileOffset m_Pos;
+    wxString     m_Name;               // in internal format
+    wxFileOffset m_Key;                // the original offset for copied entries
+    wxFileOffset m_Offset;             // file offset of the entry
+
+    wxString     m_Comment;
+    wxUint16     m_DiskStart;          // for multidisk archives, not unsupported
+    wxUint16     m_InternalAttributes; // bit 0 set for text files
+    wxUint32     m_ExternalAttributes; // system specific depends on SystemMadeBy
+
+    class wxZipMemory *m_Extra;
+    class wxZipMemory *m_LocalExtra;
+
+    wxZipNotifier *m_zipnotifier;
+    class wxZipWeakLinks *m_backlink;
 
-    // this void* is handle of archive . I'm sorry it is void and not proper
-    // type but I don't want to make unzip.h header public.
-    void *m_Archive;
+    friend class wxZipInputStream;
+    friend class wxZipOutputStream;
+
+    DECLARE_DYNAMIC_CLASS(wxZipEntry)
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxZipOutputStream 
+
+WX_DECLARE_LIST_WITH_DECL(wxZipEntry, _wxZipEntryList, class WXDLLIMPEXP_BASE);
+
+class WXDLLIMPEXP_BASE wxZipOutputStream : public wxArchiveOutputStream
+{
+public:
+    wxZipOutputStream(wxOutputStream& stream,
+                      int level = -1,
+                      wxMBConv& conv = wxConvLocal);
+    virtual ~wxZipOutputStream();
+
+    bool PutNextEntry(wxZipEntry *entry)        { return DoCreate(entry); }
+
+    bool PutNextEntry(const wxString& name,
+                      const wxDateTime& dt = wxDateTime::Now(),
+                      wxFileOffset size = wxInvalidOffset);
+
+    bool PutNextDirEntry(const wxString& name,
+                         const wxDateTime& dt = wxDateTime::Now());
+
+    bool CopyEntry(wxZipEntry *entry, wxZipInputStream& inputStream);
+    bool CopyArchiveMetaData(wxZipInputStream& inputStream);
+
+    void Sync();
+    bool CloseEntry();
+    bool Close();
+
+    void SetComment(const wxString& comment)    { m_Comment = comment; }
+
+    int  GetLevel() const                       { return m_level; }
+    void SetLevel(int level);
+    
+protected:
+    virtual size_t OnSysWrite(const void *buffer, size_t size);
+    virtual wxFileOffset OnSysTell() const      { return m_entrySize; }
+
+    struct Buffer { const char *m_data; size_t m_size; };
+    virtual wxOutputStream *OpenCompressor(wxOutputStream& stream,
+                                           wxZipEntry& entry,
+                                           const Buffer bufs[]);
+    virtual bool CloseCompressor(wxOutputStream *comp);
+
+    bool IsParentSeekable() const               { return m_offsetAdjustment
+                                                        != wxInvalidOffset; }
+
+private:
+    bool PutNextEntry(wxArchiveEntry *entry);
+    bool CopyEntry(wxArchiveEntry *entry, wxArchiveInputStream& stream);
+    bool CopyArchiveMetaData(wxArchiveInputStream& stream);
+
+    bool IsOpened() const                       { return m_comp || m_pending; }
+
+    bool DoCreate(wxZipEntry *entry, bool raw = false);
+    void CreatePendingEntry(const void *buffer, size_t size);
+    void CreatePendingEntry();
+
+    class wxStoredOutputStream *m_store;
+    class wxZlibOutputStream2 *m_deflate;
+    class wxZipStreamLink *m_backlink;
+    _wxZipEntryList m_entries;
+    char *m_initialData;
+    size_t m_initialSize;
+    wxZipEntry *m_pending;
+    bool m_raw;
+    wxFileOffset m_headerOffset;
+    size_t m_headerSize;
+    wxFileOffset m_entrySize;
+    wxUint32 m_crcAccumulator;
+    wxOutputStream *m_comp;
+    int m_level;
+    wxFileOffset m_offsetAdjustment;
+    wxString m_Comment;
+
+    DECLARE_NO_COPY_CLASS(wxZipOutputStream)
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxZipInputStream 
+
+class WXDLLIMPEXP_BASE wxZipInputStream : public wxArchiveInputStream
+{
+public:
+    typedef wxZipEntry entry_type;
+
+    wxZipInputStream(wxInputStream& stream, wxMBConv& conv = wxConvLocal);
+    wxZipInputStream(const wxString& archive, const wxString& file);
+    virtual ~wxZipInputStream();
+
+    bool OpenEntry(wxZipEntry& entry)   { return DoOpen(&entry); }
+    bool CloseEntry();
+
+    wxZipEntry *GetNextEntry();
+
+    wxString GetComment();
+    int GetTotalEntries();
+
+    virtual wxFileOffset GetLength() const { return m_entry.GetSize(); }
+
+protected:
+    size_t OnSysRead(void *buffer, size_t size);
+    wxFileOffset OnSysTell() const { return m_decomp ? m_decomp->TellI() : 0; }
+    wxFileOffset OnSysSeek(wxFileOffset seek, wxSeekMode mode);
+
+    virtual wxInputStream *OpenDecompressor(wxInputStream& stream);
+    virtual bool CloseDecompressor(wxInputStream *decomp);
+
+private:
+    void Init();
+    wxInputStream& OpenFile(const wxString& archive);
+
+    wxArchiveEntry *DoGetNextEntry()    { return GetNextEntry(); }
+
+    bool OpenEntry(wxArchiveEntry& entry);
+
+    wxStreamError ReadLocal(bool readEndRec = false);
+    wxStreamError ReadCentral();
+
+    wxUint32 ReadSignature();
+    bool FindEndRecord();
+    bool LoadEndRecord();
+    
+    bool AtHeader() const       { return m_headerSize == 0; }
+    bool AfterHeader() const    { return m_headerSize > 0 && !m_decomp; }
+    bool IsOpened() const       { return m_decomp != NULL; }
+
+    wxZipStreamLink *MakeLink(wxZipOutputStream *out);
+
+    bool DoOpen(wxZipEntry *entry = NULL, bool raw = false);
+    bool OpenDecompressor(bool raw = false);
+
+    class wxStoredInputStream *m_store;
+    class wxZlibInputStream2 *m_inflate;
+    class wxRawInputStream *m_rawin;
+    class wxFFileInputStream *m_ffile;
+    wxZipEntry m_entry;
+    bool m_raw;
+    size_t m_headerSize;
+    wxUint32 m_crcAccumulator;
+    wxInputStream *m_decomp;
+    bool m_parentSeekable;
+    class wxZipWeakLinks *m_weaklinks;
+    class wxZipStreamLink *m_streamlink;
+    wxFileOffset m_offsetAdjustment;
+    wxFileOffset m_position;
+    wxUint32 m_signature;
+    size_t m_TotalEntries;
+    wxString m_Comment;
+
+    friend bool wxZipOutputStream::CopyEntry(
+                    wxZipEntry *entry, wxZipInputStream& inputStream);
+    friend bool wxZipOutputStream::CopyArchiveMetaData(
+                    wxZipInputStream& inputStream);
 
     DECLARE_NO_COPY_CLASS(wxZipInputStream)
 };
 
 
-#endif
-   // wxUSE_STREAMS && wxUSE_ZIPSTREAM && wxUSE_ZLIB
+/////////////////////////////////////////////////////////////////////////////
+// wxZipClassFactory
 
+class WXDLLIMPEXP_BASE wxZipClassFactory : public wxArchiveClassFactory
+{
+public:
+    wxZipEntry *NewEntry() const
+        { return new wxZipEntry; }
+    wxZipInputStream *NewStream(wxInputStream& stream) const
+        { return new wxZipInputStream(stream, GetConv()); }
+    wxZipOutputStream *NewStream(wxOutputStream& stream) const
+        { return new wxZipOutputStream(stream, -1, GetConv()); }
+
+    wxString GetInternalName(const wxString& name,
+                             wxPathFormat format = wxPATH_NATIVE) const
+        { return wxZipEntry::GetInternalName(name, format); }
+
+protected:
+    wxArchiveEntry *DoNewEntry() const
+        { return NewEntry(); }
+    wxArchiveInputStream *DoNewStream(wxInputStream& stream) const
+        { return NewStream(stream); }
+    wxArchiveOutputStream *DoNewStream(wxOutputStream& stream) const
+        { return NewStream(stream); }
+
+private:
+    DECLARE_DYNAMIC_CLASS(wxZipClassFactory)
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Iterators
+
+#if wxUSE_STL || defined WX_TEST_ARCHIVE_ITERATOR
+typedef wxArchiveIterator<wxZipInputStream> wxZipIter;
+typedef wxArchiveIterator<wxZipInputStream,
+         std::pair<wxString, wxZipEntry*> >  wxZipPairIter;
 #endif
-   // __ZIPSTREAM_H__
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxZipEntry inlines
+
+bool wxZipEntry::IsText() const
+{
+    return (m_InternalAttributes & TEXT_ATTR) != 0;
+}
+
+bool wxZipEntry::IsDir() const
+{
+    return (m_ExternalAttributes & wxZIP_A_SUBDIR) != 0;
+}
+
+bool wxZipEntry::IsReadOnly() const
+{
+    return (m_ExternalAttributes & wxZIP_A_RDONLY) != 0;
+}
+
+bool wxZipEntry::IsMadeByUnix() const
+{
+    const int pattern =
+        (1 << wxZIP_SYSTEM_OPENVMS) |
+        (1 << wxZIP_SYSTEM_UNIX) |
+        (1 << wxZIP_SYSTEM_ATARI_ST) |
+        (1 << wxZIP_SYSTEM_ACORN_RISC) |
+        (1 << wxZIP_SYSTEM_BEOS) | (1 << wxZIP_SYSTEM_TANDEM);
+
+    // note: some unix zippers put madeby = dos
+    return (m_SystemMadeBy == wxZIP_SYSTEM_MSDOS
+            && (m_ExternalAttributes & ~0xFFFF))
+        || ((pattern >> m_SystemMadeBy) & 1);
+}
+
+void wxZipEntry::SetIsText(bool isText)
+{
+    if (isText)
+        m_InternalAttributes |= TEXT_ATTR;
+    else
+        m_InternalAttributes &= ~TEXT_ATTR;
+}
+
+void wxZipEntry::SetIsReadOnly(bool isReadOnly)
+{
+    if (isReadOnly)
+        SetMode(GetMode() & ~0222);
+    else
+        SetMode(GetMode() | 0200);
+}
+
+void wxZipEntry::SetName(const wxString& name,
+                         wxPathFormat format /*=wxPATH_NATIVE*/)
+{
+    bool isDir;
+    m_Name = GetInternalName(name, format, &isDir);
+    SetIsDir(isDir);
+}
+
+
+#endif // wxUSE_ZLIB && wxUSE_STREAMS && wxUSE_ZIPSTREAM
+
+#endif // _WX_WXZIPSTREAM_H__
diff --git a/src/common/archive.cpp b/src/common/archive.cpp
new file mode 100644
index 0000000000..6420d94852
--- /dev/null
+++ b/src/common/archive.cpp
@@ -0,0 +1,74 @@
+/////////////////////////////////////////////////////////////////////////////
+// Name:        archive.cpp
+// Purpose:     Streams for archive formats
+// Author:      Mike Wetherell
+// RCS-ID:      $Id$
+// Copyright:   (c) Mike Wetherell
+// Licence:     wxWindows licence
+/////////////////////////////////////////////////////////////////////////////
+
+#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
+  #pragma implementation "archive.h"
+#endif
+
+// For compilers that support precompilation, includes "wx.h".
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+  #pragma hdrstop
+#endif
+
+#ifndef WX_PRECOMP
+  #include "wx/defs.h"
+#endif
+
+#if wxUSE_ZLIB && wxUSE_STREAMS && wxUSE_ZIPSTREAM
+
+#include "wx/archive.h"
+#include "wx/html/forcelnk.h"
+
+IMPLEMENT_ABSTRACT_CLASS(wxArchiveEntry, wxObject)
+IMPLEMENT_ABSTRACT_CLASS(wxArchiveClassFactory, wxObject)
+
+FORCE_LINK(zipstrm)
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxArchiveInputStream
+
+wxArchiveInputStream::wxArchiveInputStream(wxInputStream& stream,
+                                           wxMBConv& conv)
+  : wxFilterInputStream(stream),
+    m_conv(conv)
+{
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxArchiveOutputStream
+
+wxArchiveOutputStream::wxArchiveOutputStream(wxOutputStream& stream,
+                                             wxMBConv& conv)
+  : wxFilterOutputStream(stream),
+    m_conv(conv)
+{
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxArchiveEntry
+
+void wxArchiveEntry::SetNotifier(wxArchiveNotifier& notifier)
+{
+    UnsetNotifier();
+    m_notifier = &notifier;
+    m_notifier->OnEntryUpdated(*this);
+}
+
+wxArchiveEntry& wxArchiveEntry::operator=(const wxArchiveEntry& entry)
+{
+    m_notifier = entry.m_notifier;
+    return *this;
+}
+
+#endif
diff --git a/src/common/fs_zip.cpp b/src/common/fs_zip.cpp
index 65fbf89cc2..458db30ee3 100644
--- a/src/common/fs_zip.cpp
+++ b/src/common/fs_zip.cpp
@@ -27,15 +27,10 @@
 #endif
 
 #include "wx/filesys.h"
+#include "wx/wfstream.h"
 #include "wx/zipstrm.h"
 #include "wx/fs_zip.h"
 
-/* Not the right solution (paths in makefiles) but... */
-#ifdef __BORLANDC__
-#include "../common/unzip.h"
-#else
-#include "unzip.h"
-#endif
 
 //----------------------------------------------------------------------------
 // wxZipFSHandler
@@ -56,13 +51,22 @@ wxZipFSHandler::wxZipFSHandler() : wxFileSystemHandler()
 wxZipFSHandler::~wxZipFSHandler()
 {
     if (m_Archive)
-        unzClose((unzFile)m_Archive);
+        CloseArchive(m_Archive);
     if (m_DirsFound)
         delete m_DirsFound;
 }
 
 
 
+void wxZipFSHandler::CloseArchive(wxZipInputStream *archive)
+{
+    wxInputStream *stream = archive->GetFilterInputStream();
+    delete archive;
+    delete stream;
+}
+
+
+
 bool wxZipFSHandler::CanOpen(const wxString& location)
 {
     wxString p = GetProtocol(location);
@@ -125,7 +129,7 @@ wxString wxZipFSHandler::FindFirst(const wxString& spec, int flags)
 
     if (m_Archive)
     {
-        unzClose((unzFile)m_Archive);
+        CloseArchive(m_Archive);
         m_Archive = NULL;
     }
 
@@ -147,26 +151,18 @@ wxString wxZipFSHandler::FindFirst(const wxString& spec, int flags)
 
     m_ZipFile = left;
     wxString nativename = wxFileSystem::URLToFileName(m_ZipFile).GetFullPath();
-    m_Archive = (void*) unzOpen(nativename.mb_str(wxConvFile));
+    m_Archive = new wxZipInputStream(*new wxFFileInputStream(nativename));
     m_Pattern = right.AfterLast(wxT('/'));
     m_BaseDir = right.BeforeLast(wxT('/'));
 
     if (m_Archive)
     {
-        if (unzGoToFirstFile((unzFile)m_Archive) != UNZ_OK)
-        {
-            unzClose((unzFile)m_Archive);
-            m_Archive = NULL;
-        }
-        else
+        if (m_AllowDirs)
         {
-            if (m_AllowDirs)
-            {
-                delete m_DirsFound;
-                m_DirsFound = new wxLongToLongHashMap();
-            }
-            return DoFind();
+            delete m_DirsFound;
+            m_DirsFound = new wxLongToLongHashMap();
         }
+        return DoFind();
     }
     return wxEmptyString;
 }
@@ -183,16 +179,20 @@ wxString wxZipFSHandler::FindNext()
 
 wxString wxZipFSHandler::DoFind()
 {
-    static char namebuf[1024]; // char, not wxChar!
-    char *c;
     wxString namestr, dir, filename;
     wxString match = wxEmptyString;
 
     while (match == wxEmptyString)
     {
-        unzGetCurrentFileInfo((unzFile)m_Archive, NULL, namebuf, 1024, NULL, 0, NULL, 0);
-        for (c = namebuf; *c; c++) if (*c == '\\') *c = '/';
-        namestr = wxString::FromAscii(namebuf); // TODO what encoding does ZIP use?
+        wxZipEntry *entry = m_Archive->GetNextEntry();
+        if (!entry)
+        {
+            CloseArchive(m_Archive);
+            m_Archive = NULL;
+            break;
+        }
+        namestr = entry->GetName(wxPATH_UNIX);
+        delete entry;
 
         if (m_AllowDirs)
         {
@@ -221,13 +221,6 @@ wxString wxZipFSHandler::DoFind()
         if (m_AllowFiles && !filename.IsEmpty() && m_BaseDir == dir &&
                             wxMatchWild(m_Pattern, filename, false))
             match = m_ZipFile + wxT("#zip:") + namestr;
-
-        if (unzGoToNextFile((unzFile)m_Archive) != UNZ_OK)
-        {
-            unzClose((unzFile)m_Archive);
-            m_Archive = NULL;
-            break;
-        }
     }
 
     return match;
diff --git a/src/common/zipstrm.cpp b/src/common/zipstrm.cpp
index 93048447e3..1f291dbc43 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
+// 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,124 +18,1756 @@
   #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 size_t GetSize() 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, m_len - m_pos + (size_t)0);
+    count = m_parent_i_stream->Read(buffer, count).LastRead();
+    m_pos += count;
+
+    if (m_pos == m_len)
+        m_lasterror = wxSTREAM_EOF;
+    else if (!*m_parent_i_stream)
+        m_lasterror = wxSTREAM_READ_ERROR;
+
+    return count;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Stored output stream
+// Trival compressor for files which are 'stored' in the zip file.
+
+class wxStoredOutputStream : public wxFilterOutputStream
+{
+public:
+    wxStoredOutputStream(wxOutputStream& stream) :
+        wxFilterOutputStream(stream), m_pos(0) { }
+
+    bool Close() {
+        m_pos = 0;
+        m_lasterror = wxSTREAM_NO_ERROR;
+        return true;
+    }
+
+protected:
+    virtual size_t OnSysWrite(const void *buffer, size_t size);
+    virtual wxFileOffset OnSysTell() const { return m_pos; }
+
+private:
+    wxFileOffset m_pos;
+    DECLARE_NO_COPY_CLASS(wxStoredOutputStream)
+};
+
+size_t wxStoredOutputStream::OnSysWrite(const void *buffer, size_t size)
+{
+    if (!IsOk() || !size)
+        return 0;
+    size_t count = m_parent_o_stream->Write(buffer, size).LastWrite();
+    if (count != size)
+        m_lasterror = wxSTREAM_WRITE_ERROR;
+    m_pos += count;
+    return count;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wxRawInputStream
+//
+// Used to handle the unusal case of raw copying an entry of unknown
+// length. This can only happen when the zip being copied from is being
+// read from a non-seekable stream, and also was original written to a
+// non-seekable stream.
+//
+// In this case there's no option but to decompress the stream to find
+// it's length, but we can still write the raw compressed data to avoid the
+// compression overhead (which is the greater one).
+//
+// Usage is like this:
+//  m_rawin = new wxRawInputStream(*m_parent_i_stream);
+//  m_decomp = m_rawin->Open(OpenDecompressor(m_rawin->GetTee()));
+//
+// The wxRawInputStream owns a wxTeeInputStream object, the role of which
+// is something like the unix 'tee' command; it is a transparent filter, but
+// allows the data read to be read a second time via an extra method 'GetData'.
+//
+// The wxRawInputStream then draws data through the tee using a decompressor
+// then instead of returning the decompressed data, retuns the raw data
+// from wxTeeInputStream::GetData().
+
+class wxTeeInputStream : public wxFilterInputStream
+{
+public:
+    wxTeeInputStream(wxInputStream& stream);
+
+    size_t GetCount() const { return m_end - m_start; }
+    size_t GetData(char *buffer, size_t size);
+
+    void Open();
+    bool Final();
+
+    wxInputStream& Read(void *buffer, size_t size);
+
+protected:
+    virtual size_t OnSysRead(void *buffer, size_t size);
+    virtual wxFileOffset OnSysTell() const { return m_pos; }
+
+private:
+    wxFileOffset m_pos;
+    wxMemoryBuffer m_buf;
+    size_t m_start;
+    size_t m_end;
+
+    DECLARE_NO_COPY_CLASS(wxTeeInputStream)
+};
+
+wxTeeInputStream::wxTeeInputStream(wxInputStream& stream)
+  : wxFilterInputStream(stream),
+    m_pos(0), m_buf(8192), m_start(0), m_end(0)
+{
+}
+
+void wxTeeInputStream::Open()
+{
+    m_pos = m_start = m_end = 0;
+    m_lasterror = wxSTREAM_NO_ERROR;
+}
+
+bool wxTeeInputStream::Final()
+{
+    bool final = m_end == m_buf.GetDataLen();
+    m_end = m_buf.GetDataLen();
+    return final;
+}
+
+wxInputStream& wxTeeInputStream::Read(void *buffer, size_t size)
+{
+    size_t count = wxInputStream::Read(buffer, size).LastRead();
+    m_end = m_buf.GetDataLen();
+    m_buf.AppendData(buffer, count);
+    return *this;
+}
+
+size_t wxTeeInputStream::OnSysRead(void *buffer, size_t size)
+{
+    size_t count = m_parent_i_stream->Read(buffer, size).LastRead();
+    m_lasterror = m_parent_i_stream->GetLastError();
+    return count;
+}
+
+size_t wxTeeInputStream::GetData(char *buffer, size_t size)
+{
+    if (m_wbacksize) {
+        size_t len = m_buf.GetDataLen();
+        len = len > m_wbacksize ? len - m_wbacksize : 0;
+        m_buf.SetDataLen(len);
+        if (m_end > len) {
+            wxFAIL; // we've already returned data that's now being ungot
+            m_end = len;
+        }
+        m_parent_i_stream->Ungetch(m_wback, m_wbacksize);
+        free(m_wback);
+        m_wback = NULL;
+        m_wbacksize = 0;
+        m_wbackcur = 0;
+    }
+
+    if (size > GetCount())
+        size = GetCount();
+    if (size) {
+        memcpy(buffer, m_buf + m_start, size);
+        m_start += size;
+        wxASSERT(m_start <= m_end);
+    }
+
+    if (m_start == m_end && m_start > 0 && m_buf.GetDataLen() > 0) {
+        size_t len = m_buf.GetDataLen();
+        char *buf = (char*)m_buf.GetWriteBuf(len);
+        len -= m_end;
+        memmove(buf, buf + m_end, len);
+        m_buf.UngetWriteBuf(len);
+        m_start = m_end = 0;
+    }
+
+    return size;
+}
+
+class wxRawInputStream : public wxFilterInputStream
+{
+public:
+    wxRawInputStream(wxInputStream& stream);
+    virtual ~wxRawInputStream() { delete m_tee; }
+
+    wxInputStream* Open(wxInputStream *decomp);
+    wxInputStream& GetTee() const { return *m_tee; }
+
+protected:
+    virtual size_t OnSysRead(void *buffer, size_t size);
+    virtual wxFileOffset OnSysTell() const { return m_pos; }
+
+private:
+    wxFileOffset m_pos;
+    wxTeeInputStream *m_tee;
+
+    enum { BUFSIZE = 8192 };
+    wxCharBuffer m_dummy;
+
+    DECLARE_NO_COPY_CLASS(wxRawInputStream)
+};
+
+wxRawInputStream::wxRawInputStream(wxInputStream& stream)
+  : wxFilterInputStream(stream),
+    m_pos(0),
+    m_tee(new wxTeeInputStream(stream)),
+    m_dummy(BUFSIZE)
+{
+}
+
+wxInputStream *wxRawInputStream::Open(wxInputStream *decomp)
+{
+    if (decomp) {
+        m_parent_i_stream = decomp;
+        m_pos = 0;
+        m_lasterror = wxSTREAM_NO_ERROR;
+        m_tee->Open();
+        return this;
+    } else {
+        return NULL;
+    }
+}
+
+size_t wxRawInputStream::OnSysRead(void *buffer, size_t size)
+{
+    char *buf = (char*)buffer;
+    size_t count = 0;
+
+    while (count < size && IsOk())
+    {
+        while (m_parent_i_stream->IsOk() && m_tee->GetCount() == 0)
+            m_parent_i_stream->Read(m_dummy.data(), BUFSIZE);
+
+        size_t n = m_tee->GetData(buf + count, size - count);
+        count += n;
+
+        if (n == 0 && m_tee->Final())
+            m_lasterror = m_parent_i_stream->GetLastError();
+    }
+
+    m_pos += count;
+    return count;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Zlib streams than can be reused without recreating.
+
+class wxZlibOutputStream2 : public wxZlibOutputStream
+{
+public:
+    wxZlibOutputStream2(wxOutputStream& stream, int level) :
+        wxZlibOutputStream(stream, level, wxZLIB_NO_HEADER) { }
+
+    bool Open(wxOutputStream& stream);
+    bool Close() { DoFlush(true); m_pos = wxInvalidOffset; return IsOk(); }
+};
+
+bool wxZlibOutputStream2::Open(wxOutputStream& stream)
+{
+    wxCHECK(m_pos == wxInvalidOffset, false);
+
+    m_deflate->next_out = m_z_buffer;
+    m_deflate->avail_out = m_z_size;
+    m_pos = 0;
+    m_lasterror = wxSTREAM_NO_ERROR;
+    m_parent_o_stream = &stream;
+
+    if (deflateReset(m_deflate) != Z_OK) {
+        wxLogError(_("can't re-initialize zlib deflate stream"));
+        m_lasterror = wxSTREAM_WRITE_ERROR;
+        return false;
+    }
+
+    return true;
+}
+
+class wxZlibInputStream2 : public wxZlibInputStream
+{
+public:
+    wxZlibInputStream2(wxInputStream& stream) :
+        wxZlibInputStream(stream, wxZLIB_NO_HEADER) { }
+
+    bool Open(wxInputStream& stream);
+};
+
+bool wxZlibInputStream2::Open(wxInputStream& stream)
+{
+    m_inflate->avail_in = 0;
+    m_pos = 0;
+    m_lasterror = wxSTREAM_NO_ERROR;
+    m_parent_i_stream = &stream;
+
+    if (inflateReset(m_inflate) != Z_OK) {
+        wxLogError(_("can't re-initialize zlib inflate stream"));
+        m_lasterror = wxSTREAM_READ_ERROR;
+        return false;
+    }
+
+    return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Class to hold wxZipEntry's Extra and LocalExtra fields
+
+class wxZipMemory
+{
+public:
+    wxZipMemory() : m_data(NULL), m_size(0), m_capacity(0), m_ref(1) { }
+
+    wxZipMemory *AddRef() { m_ref++; return this; }
+    void Release() { if (--m_ref == 0) delete this; }
+
+    char *GetData() const { return m_data; }
+    size_t GetSize() const { return m_size; }
+    size_t GetCapacity() const { return m_capacity; }
+
+    wxZipMemory *Unique(size_t size);
+
+private:
+    ~wxZipMemory() { delete m_data; }
+
+    char *m_data;
+    size_t m_size;
+    size_t m_capacity;
+    int m_ref;
+};
+
+wxZipMemory *wxZipMemory::Unique(size_t size)
+{
+    wxZipMemory *zm;
+
+    if (m_ref > 1) {
+        --m_ref;
+        zm = new wxZipMemory;
+    } else {
+        zm = this;
+    }
+
+    if (zm->m_capacity < size) {
+        delete zm->m_data;
+        zm->m_data = new char[size];
+        zm->m_capacity = size;
+    }
+
+    zm->m_size = size;
+    return zm;
+}
+
+static inline wxZipMemory *AddRef(wxZipMemory *zm)
+{
+    if (zm)
+        zm->AddRef();
+    return zm;
+}
+
+static inline void Release(wxZipMemory *zm)
+{
+    if (zm)
+        zm->Release();
+}
+
+static void Copy(wxZipMemory*& dest, wxZipMemory *src)
+{
+    Release(dest);
+    dest = AddRef(src);
+}
+
+static void Unique(wxZipMemory*& zm, size_t size)
+{
+    if (!zm && size)
+        zm = new wxZipMemory;
+    if (zm)
+        zm = zm->Unique(size);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Collection of weak references to entries
+
+WX_DECLARE_HASH_MAP(long, wxZipEntry*, wxIntegerHash,
+                    wxIntegerEqual, _wxOffsetZipEntryMap);
+
+class wxZipWeakLinks
+{
+public:
+    wxZipWeakLinks() : m_ref(1) { }
+
+    void Release(const wxZipInputStream* WXUNUSED(x))
+        { if (--m_ref == 0) delete this; }
+    void Release(wxFileOffset key)
+        { RemoveEntry(key); if (--m_ref == 0) delete this; }
+
+    wxZipWeakLinks *AddEntry(wxZipEntry *entry, wxFileOffset key);
+    void RemoveEntry(wxFileOffset key) { m_entries.erase(key); }
+    wxZipEntry *GetEntry(wxFileOffset key) const;
+    bool IsEmpty() const { return m_entries.empty(); }
+
+private:
+    ~wxZipWeakLinks() { wxASSERT(IsEmpty()); }
+
+    int m_ref;
+    _wxOffsetZipEntryMap m_entries;
+};
+
+wxZipWeakLinks *wxZipWeakLinks::AddEntry(wxZipEntry *entry, wxFileOffset key)
+{
+    m_entries[key] = entry;
+    m_ref++;
+    return this;
+}
+
+wxZipEntry *wxZipWeakLinks::GetEntry(wxFileOffset key) const
+{
+    _wxOffsetZipEntryMap::const_iterator it = m_entries.find(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)
+  : 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(e.m_zipnotifier),
+    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 = e.m_zipnotifier;
+        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 = system;
+
+    if (!wasUnix && IsMadeByUnix()) {
+        SetIsDir(IsDir());
+        SetMode(mode);
+    } else if (wasUnix && !IsMadeByUnix()) {
+        m_ExternalAttributes &= 0xffff;
+    }
+}
+
+void wxZipEntry::SetIsDir(bool isDir /*=true*/)
+{
+    if (isDir)
+        m_ExternalAttributes |= wxZIP_A_SUBDIR;
+    else
+        m_ExternalAttributes &= ~wxZIP_A_SUBDIR;
+
+    if (IsMadeByUnix()) {
+        m_ExternalAttributes &= ~wxZIP_S_IFMT;
+        if (isDir)
+            m_ExternalAttributes |= wxZIP_S_IFDIR;
+        else
+            m_ExternalAttributes |= wxZIP_S_IFREG;
+    }
+}
+
+// Return unix style permission bits
+//
+int wxZipEntry::GetMode() const
+{
+    // return unix permissions if present
+    if (IsMadeByUnix())
+        return (m_ExternalAttributes >> 16) & 0777;
+
+    // otherwise synthesize from the dos attribs
+    int mode = 0644;
+    if (m_ExternalAttributes & wxZIP_A_RDONLY)
+        mode &= ~0200;
+    if (m_ExternalAttributes & wxZIP_A_SUBDIR)
+        mode |= 0111;
+
+    return mode;
+}
+
+// Set unix permissions
+//
+void wxZipEntry::SetMode(int mode)
+{
+    // Set dos attrib bits to be compatible
+    if (mode & 0222)
+        m_ExternalAttributes &= ~wxZIP_A_RDONLY;
+    else
+        m_ExternalAttributes |= wxZIP_A_RDONLY;
+
+    // set the actual unix permission bits if the system type allows
+    if (IsMadeByUnix()) {
+        m_ExternalAttributes &= ~(0777L << 16);
+        m_ExternalAttributes |= (mode & 0777L) << 16;
+    }
+}
+
+const char *wxZipEntry::GetExtra() const
+{
+    return m_Extra ? m_Extra->GetData() : NULL;
+}
+
+size_t wxZipEntry::GetExtraLen() const
+{
+    return m_Extra ? m_Extra->GetSize() : 0;
+}
+
+void wxZipEntry::SetExtra(const char *extra, size_t len)
+{
+    Unique(m_Extra, len);
+    if (len)
+        memcpy(m_Extra->GetData(), extra, len);
+}
+
+const char *wxZipEntry::GetLocalExtra() const
+{
+    return m_LocalExtra ? m_LocalExtra->GetData() : NULL;
+}
+
+size_t  wxZipEntry::GetLocalExtraLen() const
+{
+    return m_LocalExtra ? m_LocalExtra->GetSize() : 0;
+}
+
+void wxZipEntry::SetLocalExtra(const char *extra, size_t len)
+{
+    Unique(m_LocalExtra, len);
+    if (len)
+        memcpy(m_LocalExtra->GetData(), extra, len);
+}
+
+void wxZipEntry::SetNotifier(wxZipNotifier& notifier)
+{
+    wxArchiveEntry::UnsetNotifier();
+    m_zipnotifier = &notifier;
+    m_zipnotifier->OnEntryUpdated(*this);
+}
+
+void wxZipEntry::Notify()
+{
+    if (m_zipnotifier)
+        m_zipnotifier->OnEntryUpdated(*this);
+    else if (GetNotifier())
+        GetNotifier()->OnEntryUpdated(*this);
+}
+
+void wxZipEntry::UnsetNotifier()
+{
+    wxArchiveEntry::UnsetNotifier();
+    m_zipnotifier = NULL;
+}
+
+size_t wxZipEntry::ReadLocal(wxInputStream& stream, wxMBConv& conv)
+{
+    wxUint16 nameLen, extraLen;
+    wxUint32 compressedSize, size, crc;
+
+    wxDataInputStream ds(stream);
+
+    ds >> m_VersionNeeded >> m_Flags >> m_Method;
+    SetDateTime(wxDateTime().SetFromDOS(ds.Read32()));
+    ds >> crc >> compressedSize >> size >> nameLen >> extraLen;
+
+    bool sumsValid = (m_Flags & wxZIP_SUMS_FOLLOW) == 0;
+
+    if (sumsValid || crc)
+        m_Crc = crc;
+    if ((sumsValid || compressedSize) || m_Method == wxZIP_METHOD_STORE)
+        m_CompressedSize = compressedSize;
+    if ((sumsValid || size) || m_Method == wxZIP_METHOD_STORE)
+        m_Size = size;
+
+    SetName(ReadString(stream, nameLen, conv), wxPATH_UNIX);
+
+    if (extraLen || GetLocalExtraLen()) {
+        Unique(m_LocalExtra, extraLen);
+        if (extraLen)
+            stream.Read(m_LocalExtra->GetData(), extraLen);
+    }
+
+    return LOCAL_SIZE + nameLen + extraLen;
+}
+
+size_t wxZipEntry::WriteLocal(wxOutputStream& stream, wxMBConv& conv) const
+{
+    wxString unixName = GetName(wxPATH_UNIX);
+    const wxWX2MBbuf name_buf = conv.cWX2MB(unixName);
+    const char *name = name_buf;
+    if (!name) name = "";
+    wxUint16 nameLen = 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 ? m_CompressedSize : 0);
+    ds.Write32(m_Size != wxInvalidOffset ? m_Size : 0);
+
+    ds << nameLen;
+    wxUint16 extraLen = 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 = strlen(name);
+
+    const wxWX2MBbuf comment_buf = conv.cWX2MB(m_Comment);
+    const char *comment = comment_buf;
+    if (!comment) comment = "";
+    wxUint16 commentLen = strlen(comment);
+
+    wxUint16 extraLen = GetExtraLen();
+
+    wxDataOutputStream ds(stream);
+
+    ds << CENTRAL_MAGIC << m_VersionMadeBy << m_SystemMadeBy;
+
+    ds.Write16(GetVersionNeeded());
+    ds.Write16(GetFlags());
+    ds.Write16(GetMethod());
+    ds.Write32(GetDateTime().GetAsDOS());
+    ds.Write32(GetCrc());
+    ds.Write32(GetCompressedSize());
+    ds.Write32(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 = 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(compressedSize);
+    ds.Write32(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 = num; }
+    void SetStartDisk(int num)                  { m_StartDisk = num; }
+    void SetEntriesHere(int num)                { m_EntriesHere = num; }
+    void SetTotalEntries(int num)               { m_TotalEntries = 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 = strlen(comment);
+
+    wxDataOutputStream ds(stream);
+
+    ds << END_MAGIC << m_DiskNumber << m_StartDisk << m_EntriesHere
+       << m_TotalEntries << m_Size << m_Offset << commentLen;
+
+    stream.Write(comment, commentLen);
+
+    return stream.IsOk();
+}
+
+bool wxZipEndRec::Read(wxInputStream& stream, wxMBConv& conv)
+{
+    wxDataInputStream ds(stream);
+    wxUint16 commentLen;
+
+    ds >> m_DiskNumber >> m_StartDisk >> m_EntriesHere
+       >> m_TotalEntries >> m_Size >> m_Offset >> commentLen;
+
+    if (commentLen)
+        m_Comment = ReadString(stream, commentLen, conv);
+
+    if (stream.IsOk())
+        if (m_DiskNumber == 0 && m_StartDisk == 0 &&
+                m_EntriesHere == m_TotalEntries)
+            return true;
+        else
+            wxLogError(_("unsupported zip archive"));
+
+    return false;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// A weak link from an input stream to an output stream
+
+class wxZipStreamLink
+{
+public:
+    wxZipStreamLink(wxZipOutputStream *stream) : m_ref(1), m_stream(stream) { }
+
+    wxZipStreamLink *AddRef() { m_ref++; return this; }
+    wxZipOutputStream *GetOutputStream() const { return m_stream; }
+
+    void Release(class wxZipInputStream *WXUNUSED(s))
+        { if (--m_ref == 0) delete this; }
+    void Release(class wxZipOutputStream *WXUNUSED(s))
+        { m_stream = NULL; if (--m_ref == 0) delete this; }
+
+private:
+    ~wxZipStreamLink() { }
+
+    int m_ref;
+    wxZipOutputStream *m_stream;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Input stream
+
+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;
+}
 
-#include "wx/log.h"
-#include "wx/intl.h"
-#include "wx/stream.h"
-#include "wx/wfstream.h"
-#include "wx/zipstrm.h"
-#include "wx/utils.h"
+bool wxZipInputStream::LoadEndRecord()
+{
+    wxCHECK(m_position == wxInvalidOffset, false);
+    if (!IsOk())
+        return false;
 
-/* Not the right solution (paths in makefiles) but... */
-#ifdef __BORLANDC__
-#include "../common/unzip.h"
-#else
-#include "unzip.h"
-#endif
+    m_position = 0;
+
+    // First find the end-of-central-directory record.
+    if (!FindEndRecord()) {
+        // failed, so either this is a non-seekable stream (ok), or not a zip
+        if (m_parentSeekable) {
+            m_lasterror = wxSTREAM_READ_ERROR;
+            wxLogError(_("invalid zip file"));
+            return false;
+        }
+        else {
+            wxLogNull nolog;
+            wxFileOffset pos = m_parent_i_stream->TellI();
+            // FIXME
+            //if (pos != wxInvalidOffset)
+            if (pos >= 0 && pos <= LONG_MAX)
+                m_offsetAdjustment = m_position = pos;
+            return true;
+        }
+    }
+
+    wxZipEndRec endrec;
+
+    // Read in the end record
+    wxFileOffset endPos = m_parent_i_stream->TellI() - 4;
+    if (!endrec.Read(*m_parent_i_stream, GetConv())) {
+        if (!*m_parent_i_stream) {
+            m_lasterror = wxSTREAM_READ_ERROR;
+            return false;
+        }
+        // TODO: try this out
+        wxLogWarning(_("assuming this is a multi-part zip concatenated"));
+    }
+
+    m_TotalEntries = endrec.GetTotalEntries();
+    m_Comment = endrec.GetComment();
 
+    // Now find the central-directory. we have the file offset of
+    // the CD, so look there first. 
+    if (m_parent_i_stream->SeekI(endrec.GetOffset()) != wxInvalidOffset &&
+            ReadSignature() == CENTRAL_MAGIC) {
+        m_signature = CENTRAL_MAGIC;
+        m_position = endrec.GetOffset();
+        m_offsetAdjustment = 0;
+        return true;
+    }
+ 
+    // If it's not there, then it could be that the zip has been appended
+    // to a self extractor, so take the CD size (also in endrec), subtract
+    // it from the file offset of the end-central-directory and look there.
+    if (m_parent_i_stream->SeekI(endPos - endrec.GetSize())
+            != wxInvalidOffset && ReadSignature() == CENTRAL_MAGIC) {
+        m_signature = CENTRAL_MAGIC;
+        m_position = endPos - endrec.GetSize();
+        m_offsetAdjustment = m_position - endrec.GetOffset();
+        return true;
+    }
+
+    wxLogError(_("can't find central directory in zip"));
+    m_lasterror = wxSTREAM_READ_ERROR;
+    return false;
+}
 
-wxZipInputStream::wxZipInputStream(const wxString& archive, const wxString& file) : wxInputStream()
+// 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(wxConvFile));
-    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 = 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;
+            }
+        }
     }
-    // TODO what encoding does ZIP use?
-    if (unzLocateFile((unzFile)m_Archive, file.ToAscii(), 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 = 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();
 
+    if (m_signature == CENTRAL_MAGIC || m_signature == END_MAGIC) {
+        if (m_streamlink && !m_streamlink->GetOutputStream()) {
+            m_streamlink->Release(this);
+            m_streamlink = NULL;
+        }
+    }
 
-wxZipInputStream::~wxZipInputStream()
+    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 <= 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;
 
-    return m_Pos >= m_Size;
+        default:
+            wxLogError(_("unsupported Zip compression method"));
+    }
+
+    return NULL;
 }
 
+bool wxZipInputStream::CloseDecompressor(wxInputStream *decomp)
+{
+    if (decomp && decomp == m_rawin)
+        return CloseDecompressor(m_rawin->GetFilterInputStream());
+    if (decomp != m_store && decomp != m_inflate)
+        delete decomp;
+    return true;
+}
 
-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 <= m_Size,
-                  _T("wxZipInputStream: invalid current position") );
+    if (AtHeader())
+        return true;
+    if (m_lasterror == wxSTREAM_READ_ERROR)
+        return false;
 
-    if ( m_Pos >= 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 + (size_t)0)
-        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;
+}
 
+// Borrowed from VS's zip stream (c) 1999 Vaclav Slavik
+//
 wxFileOffset wxZipInputStream::OnSysSeek(wxFileOffset seek, wxSeekMode mode)
 {
+    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
     //     the file and new position...)
 
     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 = GetSize() - 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 = 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;
     }
@@ -147,14 +1780,464 @@ wxFileOffset wxZipInputStream::OnSysSeek(wxFileOffset 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 = 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
diff --git a/tests/archive/archivetest.cpp b/tests/archive/archivetest.cpp
new file mode 100644
index 0000000000..e836c3a16e
--- /dev/null
+++ b/tests/archive/archivetest.cpp
@@ -0,0 +1,1649 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        tests/archive/archive.cpp
+// Purpose:     Test the archive classes
+// Author:      Mike Wetherell
+// RCS-ID:      $Id$
+// Copyright:   (c) 2004 Mike Wetherell
+// Licence:     wxWindows licence
+///////////////////////////////////////////////////////////////////////////////
+
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+#   pragma hdrstop
+#endif
+
+#ifndef WX_PRECOMP
+#   include "wx/wx.h"
+#endif
+
+#if wxUSE_STREAMS
+
+#define WX_TEST_ARCHIVE_ITERATOR
+
+#include "wx/zipstrm.h"
+#include "wx/mstream.h"
+#include "wx/wfstream.h"
+#include "wx/dir.h"
+#include "wx/cppunit.h"
+#include <string>
+#include <list>
+#include <sys/stat.h>
+
+// Check whether member templates can be used
+//
+#if defined __GNUC__ && \
+    (__GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95))
+#   define WXARC_MEMBER_TEMPLATES
+#endif
+#if defined _MSC_VER && _MSC_VER >= 1310
+#   define WXARC_MEMBER_TEMPLATES
+#endif
+#if defined __BORLANDC__ && __BORLANDC__ >= 0x530
+#   define WXARC_MEMBER_TEMPLATES
+#endif
+#if defined __DMC__ && __DMC__ >= 0x832
+#   define WXARC_MEMBER_TEMPLATES
+#endif
+#if defined __MWERKS__ && __MWERKS__ >= 0x2200
+#   define WXARC_MEMBER_TEMPLATES
+#endif
+#if defined __HP_aCC && __HP_aCC > 33300
+#   define WXARC_MEMBER_TEMPLATES
+#endif
+#if defined __SUNPRO_CC && __SUNPRO_CC > 0x500
+#   define WXARC_MEMBER_TEMPLATES
+#endif
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Bit flags for options for the tests
+
+enum Options
+{
+    PipeIn      = 0x01,     // input streams are non-seekable
+    PipeOut     = 0x02,     // output streams are non-seekable
+    Stub        = 0x04,     // the archive should be appended to a stub
+    AllOptions  = 0x07
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// These structs are passed as the template parameter of the test case to
+// specify a set of classes to use in the test. This allows either the generic
+// wxArchiveXXX interface to be exercised or the specific interface for a
+// particular archive format e.g. wxZipXXX.
+
+struct ArchiveClasses
+{
+    typedef wxArchiveEntry EntryT;
+    typedef wxArchiveInputStream InputStreamT;
+    typedef wxArchiveOutputStream OutputStreamT;
+    typedef wxArchiveClassFactory ClassFactoryT;
+    typedef wxArchiveNotifier NotifierT;
+    typedef wxArchiveIter IterT;
+    typedef wxArchivePairIter PairIterT;
+};
+
+struct ZipClasses
+{
+    typedef wxZipEntry EntryT;
+    typedef wxZipInputStream InputStreamT;
+    typedef wxZipOutputStream OutputStreamT;
+    typedef wxZipClassFactory ClassFactoryT;
+    typedef wxZipNotifier NotifierT;
+    typedef wxZipIter IterT;
+    typedef wxZipPairIter PairIterT;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// A class to hold a test entry
+
+class TestEntry
+{
+public:
+    TestEntry(const wxDateTime& dt, int len, const char *data);
+    ~TestEntry() { delete [] m_data; }
+
+    wxDateTime GetDateTime() const  { return m_dt; }
+    wxFileOffset GetLength() const  { return m_len; }
+    size_t GetSize() const          { return m_len; }
+    const char *GetData() const     { return m_data; }
+    wxString GetComment() const     { return m_comment; }
+    bool IsText() const             { return m_isText; }
+
+    void SetComment(const wxString& comment) { m_comment = comment; }
+    void SetDateTime(const wxDateTime& dt)   { m_dt = dt; }
+
+private:
+    wxDateTime m_dt;
+    size_t m_len;
+    const char *m_data;
+    wxString m_comment;
+    bool m_isText;
+};
+
+TestEntry::TestEntry(const wxDateTime& dt, int len, const char *data)
+  : m_dt(dt),
+    m_len(len),
+    m_isText(len > 0)
+{
+    char *d = new char[len];
+    memcpy(d, data, len);
+    m_data = d;
+
+    for (int i = 0; i < len && m_isText; i++)
+        m_isText = (signed char)m_data[i] > 0;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// TestOutputStream and TestInputStream are memory streams which can be
+// seekable or non-seekable.
+
+class TestOutputStream : public wxOutputStream
+{
+public:
+    TestOutputStream(int options);
+
+    ~TestOutputStream() { delete [] m_data; }
+
+    int GetOptions() const { return m_options; }
+    size_t GetSize() const { return m_size; }
+
+    // gives away the data, this stream is then empty, and can be reused
+    void GetData(const char*& data, size_t& size);
+
+    enum { STUB_SIZE = 2048, INITIAL_SIZE = 0x18000, SEEK_LIMIT = 0x100000 };
+
+private:
+    void Init();
+
+    wxFileOffset OnSysSeek(wxFileOffset pos, wxSeekMode mode);
+    wxFileOffset OnSysTell() const;
+    size_t OnSysWrite(const void *buffer, size_t size);
+
+    int m_options;
+    size_t m_pos;
+    size_t m_capacity;
+    size_t m_size;
+    char *m_data;
+};
+
+TestOutputStream::TestOutputStream(int options)
+  : m_options(options)
+{
+    Init();
+}
+
+void TestOutputStream::Init()
+{
+    m_data = NULL;
+    m_size = 0;
+    m_capacity = 0;
+    m_pos = 0;
+
+    if (m_options & Stub) {
+        wxCharBuffer buf(STUB_SIZE);
+        memset(buf.data(), 0, STUB_SIZE);
+        Write(buf, STUB_SIZE);
+    }
+}
+
+wxFileOffset TestOutputStream::OnSysSeek(wxFileOffset pos, wxSeekMode mode)
+{
+    if ((m_options & PipeOut) == 0) {
+        switch (mode) {
+            case wxFromStart:   break;
+            case wxFromCurrent: pos += m_pos; break;
+            case wxFromEnd:     pos += m_size; break;
+        }
+        if (pos < 0 || pos > SEEK_LIMIT)
+            return wxInvalidOffset;
+        m_pos = pos;
+        return m_pos;
+    }
+    return wxInvalidOffset;
+}
+
+wxFileOffset TestOutputStream::OnSysTell() const
+{
+    return (m_options & PipeOut) == 0 ? m_pos : wxInvalidOffset;
+}
+
+size_t TestOutputStream::OnSysWrite(const void *buffer, size_t size)
+{
+    if (!IsOk() || !size)
+        return 0;
+    m_lasterror = wxSTREAM_WRITE_ERROR;
+
+    size_t newsize = m_pos + size;
+    wxCHECK(newsize > m_pos, 0);
+
+    if (m_capacity < newsize) {
+        size_t capacity = m_capacity ? m_capacity : INITIAL_SIZE;
+
+        while (capacity < newsize) {
+            capacity <<= 1;
+            wxCHECK(capacity > m_capacity, 0);
+        }
+
+        char *buf = new char[capacity];
+        if (m_data)
+            memcpy(buf, m_data, m_capacity);
+        delete [] m_data;
+        m_data = buf;
+        m_capacity = capacity;
+    }
+
+    memcpy(m_data + m_pos, buffer, size);
+    m_pos += size;
+    if (m_pos > m_size)
+        m_size = m_pos;
+    m_lasterror = wxSTREAM_NO_ERROR;
+
+    return size;
+}
+
+void TestOutputStream::GetData(const char*& data, size_t& size)
+{
+    data = m_data;
+    size = m_size;
+
+    if (m_options & Stub) {
+        char *d = m_data;
+        size += STUB_SIZE;
+
+        if (size > m_capacity) {
+            d = new char[size];
+            memcpy(d + STUB_SIZE, m_data, m_size);
+            delete [] m_data;
+        }
+        else {
+            memmove(d + STUB_SIZE, d, m_size);
+        }
+
+        memset(d, 0, STUB_SIZE);
+        data = d;
+    }
+
+    Init();
+    Reset();
+}
+
+class TestInputStream : public wxInputStream
+{
+public:
+    // ctor takes the data from the output stream, which is then empty
+    TestInputStream(TestOutputStream& out) : m_data(NULL) { SetData(out); }
+    // this ctor 'dups'
+    TestInputStream(const TestInputStream& in);
+    ~TestInputStream() { delete [] m_data; }
+
+    void Rewind();
+    size_t GetSize() const { return m_size; }
+    void SetData(TestOutputStream& out);
+
+private:
+    wxFileOffset OnSysSeek(wxFileOffset pos, wxSeekMode mode);
+    wxFileOffset OnSysTell() const;
+    size_t OnSysRead(void *buffer, size_t size);
+
+    int m_options;
+    size_t m_pos;
+    size_t m_size;
+    const char *m_data;
+};
+
+TestInputStream::TestInputStream(const TestInputStream& in)
+  : m_options(in.m_options),
+    m_pos(in.m_pos),
+    m_size(in.m_size)
+{
+    char *p = new char[m_size];
+    memcpy(p, in.m_data, m_size);
+    m_data = p;
+}
+
+void TestInputStream::Rewind()
+{
+    if ((m_options & Stub) && (m_options & PipeIn))
+        m_pos = TestOutputStream::STUB_SIZE * 2;
+    else
+        m_pos = 0;
+
+    if (m_wbacksize) {
+        free(m_wback);
+        m_wback = NULL;
+        m_wbacksize = 0;
+        m_wbackcur = 0;
+    }
+}
+
+void TestInputStream::SetData(TestOutputStream& out)
+{
+    delete [] m_data;
+    m_options = out.GetOptions();
+    out.GetData(m_data, m_size);
+    Rewind();
+    Reset();
+}
+
+wxFileOffset TestInputStream::OnSysSeek(wxFileOffset pos, wxSeekMode mode)
+{
+    if ((m_options & PipeIn) == 0) {
+        switch (mode) {
+            case wxFromStart:   break;
+            case wxFromCurrent: pos += m_pos; break;
+            case wxFromEnd:     pos += m_size; break;
+        }
+        if (pos < 0 || pos > TestOutputStream::SEEK_LIMIT)
+            return wxInvalidOffset;
+        m_pos = pos;
+        return m_pos;
+    }
+    return wxInvalidOffset;
+}
+
+wxFileOffset TestInputStream::OnSysTell() const
+{
+    return (m_options & PipeIn) == 0 ? m_pos : wxInvalidOffset;
+}
+
+size_t TestInputStream::OnSysRead(void *buffer, size_t size)
+{
+    if (!IsOk() || !size)
+        return 0;
+    if (m_size <= m_pos) {
+        m_lasterror = wxSTREAM_EOF;
+        return 0;
+    }
+
+    if (m_size - m_pos < size)
+        size = m_size - m_pos;
+    memcpy(buffer, m_data + m_pos, size);
+    m_pos += size;
+    return size;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// minimal non-intrusive reference counting pointer for testing the iterators
+
+template <class T> class Ptr
+{
+public:
+    explicit Ptr(T* p = NULL) : m_p(p), m_count(new int) { *m_count = 1; }
+    Ptr(const Ptr& sp) : m_p(sp.m_p), m_count(sp.m_count) { ++*m_count; }
+    ~Ptr() { Free(); }
+
+    Ptr& operator =(const Ptr& sp) {
+        if (&sp != this) {
+            Free();
+            m_p = sp.m_p;
+            m_count = sp.m_count;
+            ++*m_count;
+        }
+        return *this;
+    }
+
+    T* get() const { return m_p; }
+    T* operator->() const { return m_p; }
+    T& operator*() const { return *m_p; }
+
+private:
+    void Free() {
+        if (--*m_count == 0) {
+            delete m_p;
+            delete m_count;
+        }
+    }
+
+    T *m_p;
+    int *m_count;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Clean-up for temp directory
+
+class TempDir
+{
+public:
+    TempDir();
+    ~TempDir();
+    wxString GetName() const { return m_tmp; }
+
+private:
+    void RemoveDir(wxString& path);
+    wxString m_tmp;
+    wxString m_original;
+};
+
+TempDir::TempDir()
+{
+    wxString tmp = wxFileName::CreateTempFileName(_T("arctest-"));
+    if (tmp != wxEmptyString) {
+        wxRemoveFile(tmp);
+        m_original = wxGetCwd();
+        CPPUNIT_ASSERT(wxMkdir(tmp, 0700));
+        m_tmp = tmp;
+        CPPUNIT_ASSERT(wxSetWorkingDirectory(tmp));
+    }
+}
+
+TempDir::~TempDir()
+{
+    if (m_tmp != wxEmptyString) {
+        wxSetWorkingDirectory(m_original);
+        RemoveDir(m_tmp);
+    }
+}
+
+void TempDir::RemoveDir(wxString& path)
+{
+    wxCHECK_RET(!m_tmp.empty() && path.substr(0, m_tmp.length()) == m_tmp,
+                _T("remove '") + path + _T("' fails safety check"));
+
+    const wxChar *files[] = {
+        _T("text/empty"),
+        _T("text/small"),
+        _T("bin/bin1000"),
+        _T("bin/bin4095"),
+        _T("bin/bin4096"),
+        _T("bin/bin4097"),
+        _T("bin/bin16384"),
+        _T("zero/zero5"),
+        _T("zero/zero1024"),
+        _T("zero/zero32768"),
+        _T("zero/zero16385"),
+        _T("zero/newname"),
+        _T("newfile"),
+    };
+
+    const wxChar *dirs[] = {
+        _T("text/"), _T("bin/"), _T("zero/"), _T("empty/")
+    };
+
+    wxString tmp = m_tmp + wxFileName::GetPathSeparator();
+    size_t i;
+
+    for (i = 0; i < WXSIZEOF(files); i++)
+        wxRemoveFile(tmp + wxFileName(files[i], wxPATH_UNIX).GetFullPath());
+
+    for (i = 0; i < WXSIZEOF(dirs); i++)
+        wxRmdir(tmp + wxFileName(dirs[i], wxPATH_UNIX).GetFullPath());
+
+    if (!wxRmdir(m_tmp))
+        wxLogSysError(_T("can't remove temporary dir '%s'"), m_tmp.c_str());
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// wxFFile streams for piping to/from an external program
+
+#if defined __UNIX__ || defined __MINGW32__
+#   define WXARC_popen popen
+#   define WXARC_pclose pclose
+#elif defined _MSC_VER || defined __BORLANDC__
+#   define WXARC_popen _popen
+#   define WXARC_pclose _pclose
+#else
+#   define WXARC_NO_POPEN
+#   define WXARC_popen(cmd, type) NULL
+#   define WXARC_pclose(fp)
+#endif
+
+#ifdef __WXMSW__
+#   define WXARC_b "b"
+#else
+#   define WXARC_b
+#endif
+
+class PFileInputStream : public wxFFileInputStream
+{
+public:
+    PFileInputStream(const wxString& cmd) :
+        wxFFileInputStream(WXARC_popen(cmd.mb_str(), "r" WXARC_b)) { }
+    ~PFileInputStream()
+        { WXARC_pclose(m_file->fp()); m_file->Detach(); }
+};
+
+class PFileOutputStream : public wxFFileOutputStream
+{
+public:
+    PFileOutputStream(const wxString& cmd) :
+        wxFFileOutputStream(WXARC_popen(cmd.mb_str(), "w" WXARC_b)) { }
+    ~PFileOutputStream()
+        { WXARC_pclose(m_file->fp()); m_file->Detach(); }
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// The test case
+
+template <class Classes>
+class ArchiveTestCase : public CppUnit::TestCase
+{
+public:
+    ArchiveTestCase(const wxString& name,
+                    int id,
+                    typename Classes::ClassFactoryT *factory,
+                    int options,
+                    const wxString& archiver = wxEmptyString,
+                    const wxString& unarchiver = wxEmptyString);
+
+    ~ArchiveTestCase();
+
+protected:
+    // the classes to test
+    typedef typename Classes::EntryT EntryT;
+    typedef typename Classes::InputStreamT InputStreamT;
+    typedef typename Classes::OutputStreamT OutputStreamT;
+    typedef typename Classes::ClassFactoryT ClassFactoryT;
+    typedef typename Classes::NotifierT NotifierT;
+    typedef typename Classes::IterT IterT;
+    typedef typename Classes::PairIterT PairIterT;
+
+    // the entry point for the test
+    void runTest();
+
+    // create the test data
+    void CreateTestData();
+    TestEntry& Add(const char *name, const char *data, int len = -1);
+    TestEntry& Add(const char *name, int len = 0, int value = EOF);
+
+    // 'archive up' the test data
+    void CreateArchive(wxOutputStream& out);
+    void CreateArchive(wxOutputStream& out, const wxString& archiver);
+
+    // perform various modifications on the archive
+    void ModifyArchive(wxInputStream& in, wxOutputStream& out);
+
+    // extract the archive and verify its contents
+    void ExtractArchive(wxInputStream& in);
+    void ExtractArchive(wxInputStream& in, const wxString& unarchiver);
+    void VerifyDir(wxString& path, size_t rootlen = 0);
+
+    // tests for the iterators
+    void TestIterator(wxInputStream& in);
+    void TestPairIterator(wxInputStream& in);
+    void TestSmartIterator(wxInputStream& in);
+    void TestSmartPairIterator(wxInputStream& in);
+
+    // try reading two entries at the same time
+    void ReadSimultaneous(TestInputStream& in);
+
+    // overridables
+    virtual void OnCreateArchive(OutputStreamT& WXUNUSED(arc)) { }
+    virtual void OnSetNotifier(EntryT& entry);
+
+    virtual void OnArchiveExtracted(InputStreamT& WXUNUSED(arc),
+                                    int WXUNUSED(expectedTotal)) { }
+
+    virtual void OnCreateEntry(     OutputStreamT& WXUNUSED(arc),
+                                    TestEntry& WXUNUSED(testEntry),
+                                    EntryT *entry = NULL) { (void)entry; }
+
+    virtual void OnEntryExtracted(  EntryT& WXUNUSED(entry),
+                                    const TestEntry& WXUNUSED(testEntry),
+                                    InputStreamT *arc = NULL) { (void)arc; }
+
+    typedef std::map<wxString, TestEntry*> TestEntries;
+    TestEntries m_testEntries;              // test data
+    std::auto_ptr<ClassFactoryT> m_factory; // factory to make classes
+    int m_options;                          // test options
+    wxDateTime m_timeStamp;                 // timestamp to give test entries
+    int m_id;                               // select between the possibilites
+    wxString m_archiver;                    // external archiver
+    wxString m_unarchiver;                  // external unarchiver
+};
+
+template <class Classes>
+ArchiveTestCase<Classes>::ArchiveTestCase(const wxString& name,
+                                          int id,
+                                          ClassFactoryT *factory,
+                                          int options,
+                                          const wxString& archiver,
+                                          const wxString& unarchiver)
+  : CppUnit::TestCase(std::string(name.mb_str())),
+    m_factory(factory),
+    m_options(options),
+    m_timeStamp(1, wxDateTime::Mar, 2005, 12, 0),
+    m_id(id),
+    m_archiver(archiver),
+    m_unarchiver(unarchiver)
+{
+}
+
+template <class Classes>
+ArchiveTestCase<Classes>::~ArchiveTestCase()
+{
+    TestEntries::iterator it;
+    for (it = m_testEntries.begin(); it != m_testEntries.end(); ++it)
+        delete it->second;
+}
+
+template <class Classes>
+void ArchiveTestCase<Classes>::runTest()
+{
+    TestOutputStream out(m_options);
+
+    CreateTestData();
+
+    if (m_archiver.empty())
+        CreateArchive(out);
+    else
+        CreateArchive(out, m_archiver);
+
+    // check archive could be created
+    CPPUNIT_ASSERT(out.GetSize() > 0);
+
+    TestInputStream in(out);
+
+    TestIterator(in);
+    in.Rewind();
+    TestPairIterator(in);
+    in.Rewind();
+    TestSmartIterator(in);
+    in.Rewind();
+    TestSmartPairIterator(in);
+    in.Rewind();
+
+    if ((m_options & PipeIn) == 0) {
+        ReadSimultaneous(in);
+        in.Rewind();
+    }
+
+    ModifyArchive(in, out);
+    in.SetData(out);
+
+    if (m_unarchiver.empty())
+        ExtractArchive(in);
+    else
+        ExtractArchive(in, m_unarchiver);
+    
+    // check that all the test entries were found in the archive
+    CPPUNIT_ASSERT(m_testEntries.empty());
+}
+
+template <class Classes>
+void ArchiveTestCase<Classes>::CreateTestData()
+{
+    Add("text/");
+    Add("text/empty", "");
+    Add("text/small", "Small text file for testing\n"
+                      "archive streams in wxWidgets\n");
+
+    Add("bin/");
+    Add("bin/bin1000", 1000);
+    Add("bin/bin4095", 4095);
+    Add("bin/bin4096", 4096);
+    Add("bin/bin4097", 4097);
+    Add("bin/bin16384", 16384);
+
+    Add("zero/");
+    Add("zero/zero5", 5, 0);
+    Add("zero/zero1024", 1024, 109);
+    Add("zero/zero32768", 32768, 106);
+    Add("zero/zero16385", 16385, 119);
+
+    Add("empty/");
+}
+
+template <class Classes>
+TestEntry& ArchiveTestCase<Classes>::Add(const char *name,
+                                         const char *data,
+                                         int len /*=-1*/)
+{
+    if (len == -1)
+        len = strlen(data);
+    TestEntry*& entry = m_testEntries[wxString(name, *wxConvCurrent)];
+    wxASSERT(entry == NULL);
+    entry = new TestEntry(m_timeStamp, len, data);
+    m_timeStamp += wxTimeSpan(0, 1, 30);
+    return *entry;
+}
+
+template <class Classes>
+TestEntry& ArchiveTestCase<Classes>::Add(const char *name,
+                                         int len /*=0*/,
+                                         int value /*=EOF*/)
+{
+    wxCharBuffer buf(len);
+    for (int i = 0; i < len; i++)
+        buf.data()[i] = value == EOF ? rand() : value;
+    return Add(name, buf, len);
+}
+
+// Create an archive using the wx archive classes, write it to 'out'
+//
+template <class Classes>
+void ArchiveTestCase<Classes>::CreateArchive(wxOutputStream& out)
+{
+    std::auto_ptr<OutputStreamT> arc(m_factory->NewStream(out));
+    TestEntries::iterator it;
+
+    OnCreateArchive(*arc);
+
+    // We want to try creating entries in various different ways, 'choices'
+    // is just a number used to select between all the various possibilities.
+    int choices = m_id;
+
+    for (it = m_testEntries.begin(); it != m_testEntries.end(); ++it) {
+        choices += 5;
+        TestEntry& testEntry = *it->second;
+        wxString name = it->first;
+
+        // It should be possible to create a directory entry just by supplying
+        // a name that looks like a directory, or alternatively any old name
+        // can be identified as a directory using SetIsDir or PutNextDirEntry
+        bool setIsDir = name.Last() == _T('/') && (choices & 1);
+        if (setIsDir)
+            name.erase(name.length() - 1);
+
+        // provide some context for the error message so that we know which
+        // iteration of the loop we were on
+        std::string error_entry((_T(" '") + name + _T("'")).mb_str());
+        std::string error_context(" failed for entry" + error_entry);
+
+        if ((choices & 2) || testEntry.IsText()) {
+            // try PutNextEntry(EntryT *pEntry)
+            std::auto_ptr<EntryT> entry(m_factory->NewEntry());
+            entry->SetName(name, wxPATH_UNIX);
+            if (setIsDir)
+                entry->SetIsDir();
+            entry->SetDateTime(testEntry.GetDateTime());
+            entry->SetSize(testEntry.GetLength());
+            OnCreateEntry(*arc, testEntry, entry.get());
+            CPPUNIT_ASSERT_MESSAGE("PutNextEntry" + error_context,
+                                   arc->PutNextEntry(entry.release()));
+        }
+        else {
+            // try the convenience methods
+            OnCreateEntry(*arc, testEntry);
+            if (setIsDir)
+                CPPUNIT_ASSERT_MESSAGE("PutNextDirEntry" + error_context,
+                    arc->PutNextDirEntry(name, testEntry.GetDateTime()));
+            else
+                CPPUNIT_ASSERT_MESSAGE("PutNextEntry" + error_context,
+                    arc->PutNextEntry(name, testEntry.GetDateTime(),
+                                      testEntry.GetLength()));
+        }
+
+        if (name.Last() != _T('/')) {
+            // for non-dirs write the data
+            arc->Write(testEntry.GetData(), testEntry.GetSize());
+            CPPUNIT_ASSERT_MESSAGE("LastWrite check" + error_context,
+                arc->LastWrite() == testEntry.GetSize());
+            // should work with or without explicit CloseEntry
+            if (choices & 3)
+                CPPUNIT_ASSERT_MESSAGE("CloseEntry" + error_context,
+                    arc->CloseEntry());
+        }
+
+        CPPUNIT_ASSERT_MESSAGE("IsOk" + error_context, arc->IsOk());
+    }
+
+    // should work with or without explicit Close
+    if (m_id % 2)
+        CPPUNIT_ASSERT(arc->Close());
+}
+
+// Create an archive using an external archive program
+//
+template <class Classes>
+void ArchiveTestCase<Classes>::CreateArchive(wxOutputStream& out,
+                                             const wxString& archiver)
+{
+    // for an external archiver the test data need to be written to
+    // temp files
+    TempDir tmpdir;
+
+    // write the files
+    TestEntries::iterator i;
+    for (i = m_testEntries.begin(); i != m_testEntries.end(); ++i) {
+        wxFileName fn(i->first, wxPATH_UNIX);
+        TestEntry& entry = *i->second;
+
+        if (fn.IsDir()) {
+            fn.Mkdir(0777, wxPATH_MKDIR_FULL);
+        } else {
+            wxFileName::Mkdir(fn.GetPath(), 0777, wxPATH_MKDIR_FULL);
+            wxFFileOutputStream fileout(fn.GetFullPath());
+            fileout.Write(entry.GetData(), entry.GetSize());
+        }
+    }
+
+    for (i = m_testEntries.begin(); i != m_testEntries.end(); ++i) {
+        wxFileName fn(i->first, wxPATH_UNIX);
+        TestEntry& entry = *i->second;
+        wxDateTime dt = entry.GetDateTime();
+#ifdef __WXMSW__
+        if (fn.IsDir())
+            entry.SetDateTime(wxDateTime());
+        else
+#endif
+            fn.SetTimes(NULL, &dt, NULL);
+    }
+
+    if ((m_options & PipeOut) == 0) {
+        wxFileName fn(tmpdir.GetName());
+        fn.SetExt(_T("arc"));
+        wxString tmparc = fn.GetFullPath();
+
+        // call the archiver to create an archive file
+        system(wxString::Format(archiver, tmparc.c_str()).mb_str());
+
+        // then load the archive file
+        {
+            wxFFileInputStream in(tmparc);
+            if (in.Ok())
+                out.Write(in);
+        }
+
+        wxRemoveFile(tmparc);
+    }
+    else {
+        // for the non-seekable test, have the archiver output to "-"
+        // and read the archive via a pipe
+        PFileInputStream in(wxString::Format(archiver, _T("-")));
+        if (in.Ok())
+            out.Write(in);
+    }
+}
+
+// Do a standard set of modification on an archive, delete an entry,
+// rename an entry and add an entry
+//
+template <class Classes>
+void ArchiveTestCase<Classes>::ModifyArchive(wxInputStream& in,
+                                             wxOutputStream& out)
+{
+    std::auto_ptr<InputStreamT> arcIn(m_factory->NewStream(in));
+    std::auto_ptr<OutputStreamT> arcOut(m_factory->NewStream(out));
+    std::auto_ptr<EntryT> entry;
+
+    const wxString deleteName = _T("bin/bin1000");
+    const wxString renameFrom = _T("zero/zero1024");
+    const wxString renameTo   = _T("zero/newname");
+    const wxString newName    = _T("newfile");
+    const char *newData       = "New file added as a test\n";
+
+    arcOut->CopyArchiveMetaData(*arcIn);
+
+    while (entry.reset(arcIn->GetNextEntry()), entry.get() != NULL) {
+        OnSetNotifier(*entry);
+        wxString name = entry->GetName(wxPATH_UNIX);
+
+        // provide some context for the error message so that we know which
+        // iteration of the loop we were on
+        std::string error_entry((_T(" '") + name + _T("'")).mb_str());
+        std::string error_context(" failed for entry" + error_entry);
+
+        if (name == deleteName) {
+            TestEntries::iterator it = m_testEntries.find(name);
+            CPPUNIT_ASSERT_MESSAGE(
+                "deletion failed (already deleted?) for" + error_entry,
+                it != m_testEntries.end());
+            TestEntry *p = it->second;
+            m_testEntries.erase(it);
+            delete p;
+        }
+        else {
+            if (name == renameFrom) {
+                entry->SetName(renameTo);
+                TestEntries::iterator it = m_testEntries.find(renameFrom);
+                CPPUNIT_ASSERT_MESSAGE(
+                    "rename failed (already renamed?) for" + error_entry,
+                    it != m_testEntries.end());
+                TestEntry *p = it->second;
+                m_testEntries.erase(it);
+                m_testEntries[renameTo] = p;
+            }
+
+            CPPUNIT_ASSERT_MESSAGE("CopyEntry" + error_context,
+                arcOut->CopyEntry(entry.release(), *arcIn));
+        }
+    }
+
+    // check that the deletion and rename were done
+    CPPUNIT_ASSERT(m_testEntries.count(deleteName) == 0);
+    CPPUNIT_ASSERT(m_testEntries.count(renameFrom) == 0);
+    CPPUNIT_ASSERT(m_testEntries.count(renameTo) == 1);
+
+    // check that the end of the input archive was reached without error
+    CPPUNIT_ASSERT(arcIn->Eof());
+
+    // try adding a new entry
+    TestEntry& testEntry = Add(newName.mb_str(), newData);
+    entry.reset(m_factory->NewEntry());
+    entry->SetName(newName);
+    entry->SetDateTime(testEntry.GetDateTime());
+    entry->SetSize(testEntry.GetLength());
+    OnCreateEntry(*arcOut, testEntry, entry.get());
+    OnSetNotifier(*entry);
+    CPPUNIT_ASSERT(arcOut->PutNextEntry(entry.release()));
+    CPPUNIT_ASSERT(arcOut->Write(newData, strlen(newData)).IsOk());
+
+    // should work with or without explicit Close
+    if (m_id % 2)
+        CPPUNIT_ASSERT(arcOut->Close());
+}
+
+// Extract an archive using the wx archive classes
+//
+template <class Classes>
+void ArchiveTestCase<Classes>::ExtractArchive(wxInputStream& in)
+{
+    typedef Ptr<EntryT> EntryPtr;
+    typedef std::list<EntryPtr> Entries;
+    typedef typename Entries::iterator EntryIter;
+
+    std::auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
+    int expectedTotal = m_testEntries.size();
+    EntryPtr entry;
+    Entries entries;
+
+    if ((m_options & PipeIn) == 0)
+        OnArchiveExtracted(*arc, expectedTotal);
+
+    while (entry = EntryPtr(arc->GetNextEntry()), entry.get() != NULL) {
+        wxString name = entry->GetName(wxPATH_UNIX);
+
+        // provide some context for the error message so that we know which
+        // iteration of the loop we were on
+        std::string error_entry((_T(" '") + name + _T("'")).mb_str());
+        std::string error_context(" failed for entry" + error_entry);
+
+        TestEntries::iterator it = m_testEntries.find(name);
+        CPPUNIT_ASSERT_MESSAGE(
+            "archive contains an entry that shouldn't be there" + error_entry,
+            it != m_testEntries.end());
+
+        const TestEntry& testEntry = *it->second;
+
+        wxDateTime dt = testEntry.GetDateTime();
+        if (dt.IsValid())
+            CPPUNIT_ASSERT_MESSAGE("timestamp check" + error_context,
+                                   dt == entry->GetDateTime());
+
+        // non-seekable entries are allowed to have GetSize == wxInvalidOffset
+        // until the end of the entry's data has been read past
+        CPPUNIT_ASSERT_MESSAGE("entry size check" + error_context,
+            testEntry.GetLength() == entry->GetSize() ||
+            ((m_options & PipeIn) != 0 && entry->GetSize() == wxInvalidOffset));
+        CPPUNIT_ASSERT_MESSAGE(
+            "arc->GetSize() == entry->GetSize()" + error_context,
+            arc->GetSize() == (size_t)entry->GetSize());
+
+        if (name.Last() != _T('/'))
+        {
+            CPPUNIT_ASSERT_MESSAGE("!IsDir" + error_context,
+                !entry->IsDir());
+            wxCharBuffer buf(testEntry.GetSize() + 1);
+            CPPUNIT_ASSERT_MESSAGE("Read until Eof" + error_context,
+                arc->Read(buf.data(), testEntry.GetSize() + 1).Eof());
+            CPPUNIT_ASSERT_MESSAGE("LastRead check" + error_context,
+                arc->LastRead() == testEntry.GetSize());
+            CPPUNIT_ASSERT_MESSAGE("data compare" + error_context,
+                !memcmp(buf.data(), testEntry.GetData(), testEntry.GetSize()));
+        } else {
+            CPPUNIT_ASSERT_MESSAGE("IsDir" + error_context, entry->IsDir());
+        }
+
+        // GetSize() must return the right result in all cases after all the
+        // data has been read
+        CPPUNIT_ASSERT_MESSAGE("entry size check" + error_context,
+            testEntry.GetLength() == entry->GetSize());
+        CPPUNIT_ASSERT_MESSAGE(
+            "arc->GetSize() == entry->GetSize()" + error_context,
+            arc->GetSize() == (size_t)entry->GetSize());
+
+        if ((m_options & PipeIn) == 0) {
+            OnEntryExtracted(*entry, testEntry, arc.get());
+            delete it->second;
+            m_testEntries.erase(it);
+        } else {
+            entries.push_back(entry);
+        }
+    }
+
+    // check that the end of the input archive was reached without error
+    CPPUNIT_ASSERT(arc->Eof());
+
+    // for non-seekable streams these data are only guaranteed to be
+    // available once the end of the archive has been reached
+    if (m_options & PipeIn) {
+        for (EntryIter i = entries.begin(); i != entries.end(); ++i) {
+            wxString name = (*i)->GetName(wxPATH_UNIX);
+            TestEntries::iterator j = m_testEntries.find(name);
+            OnEntryExtracted(**i, *j->second);
+            delete j->second;
+            m_testEntries.erase(j);
+        }
+        OnArchiveExtracted(*arc, expectedTotal);
+    }
+}
+
+// Extract an archive using an external unarchive program
+//
+template <class Classes>
+void ArchiveTestCase<Classes>::ExtractArchive(wxInputStream& in,
+                                              const wxString& unarchiver)
+{
+    // for an external unarchiver, unarchive to a tempdir
+    TempDir tmpdir;
+
+    if ((m_options & PipeIn) == 0) {
+        wxFileName fn(tmpdir.GetName());
+        fn.SetExt(_T("arc"));
+        wxString tmparc = fn.GetFullPath();
+        
+        if (m_options & Stub)
+            in.SeekI(TestOutputStream::STUB_SIZE * 2);
+
+        // write the archive to a temporary file
+        {
+            wxFFileOutputStream out(tmparc);
+            if (out.Ok())
+                out.Write(in);
+        }
+
+        // call unarchiver
+        system(wxString::Format(unarchiver, tmparc.c_str()).mb_str());
+        wxRemoveFile(tmparc);
+    }
+    else {
+        // for the non-seekable test, have the archiver extract "-" and
+        // feed it the archive via a pipe
+        PFileOutputStream out(wxString::Format(unarchiver, _T("-")));
+        if (out.Ok())
+            out.Write(in);
+    }
+
+    wxString dir = tmpdir.GetName();
+    VerifyDir(dir);
+}
+
+// Verifies the files produced by an external unarchiver are as expected
+//
+template <class Classes>
+void ArchiveTestCase<Classes>::VerifyDir(wxString& path, size_t rootlen /*=0*/)
+{
+    wxDir dir;
+    path += wxFileName::GetPathSeparator();
+    int pos = path.length();
+    wxString name;
+
+    if (!rootlen)
+        rootlen = pos;
+
+    if (dir.Open(path) && dir.GetFirst(&name)) {
+        do {
+            path.replace(pos, wxString::npos, name);
+            name = m_factory->GetInternalName(
+                    path.substr(rootlen, wxString::npos));
+
+            bool isDir = wxDirExists(path);
+            if (isDir)
+                name += _T("/");
+
+            // provide some context for the error message so that we know which
+            // iteration of the loop we were on
+            std::string error_entry((_T(" '") + name + _T("'")).mb_str());
+            std::string error_context(" failed for entry" + error_entry);
+
+            TestEntries::iterator it = m_testEntries.find(name);
+            CPPUNIT_ASSERT_MESSAGE(
+                "archive contains an entry that shouldn't be there"
+                    + error_entry,
+                it != m_testEntries.end());
+
+            const TestEntry& testEntry = *it->second;
+            size_t size = 0;
+
+#ifndef __WXMSW__
+            CPPUNIT_ASSERT_MESSAGE("timestamp check" + error_context,
+                                   testEntry.GetDateTime() ==
+                                   wxFileName(path).GetModificationTime());
+#endif
+            if (!isDir) {
+                wxFFileInputStream in(path);
+                CPPUNIT_ASSERT_MESSAGE(
+                    "entry not found in archive" + error_entry, in.Ok());
+
+                size = in.GetSize();
+                wxCharBuffer buf(size);
+                CPPUNIT_ASSERT_MESSAGE("Read" + error_context,
+                    in.Read(buf.data(), size).LastRead() == size);
+                CPPUNIT_ASSERT_MESSAGE("size check" + error_context,
+                    testEntry.GetSize() == size);
+                CPPUNIT_ASSERT_MESSAGE("data compare" + error_context,
+                    memcmp(buf.data(), testEntry.GetData(), size) == 0);
+            }
+            else {
+                VerifyDir(path, rootlen);
+            }
+
+            delete it->second;
+            m_testEntries.erase(it);
+        }
+        while (dir.GetNext(&name));
+    }
+}
+
+// test the simple iterators that give away ownership of an entry
+//
+template <class Classes>
+void ArchiveTestCase<Classes>::TestIterator(wxInputStream& in)
+{
+    typedef std::list<EntryT*> ArchiveCatalog;
+    typedef typename ArchiveCatalog::iterator CatalogIter;
+
+    std::auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
+    size_t count = 0;
+
+#ifdef WXARC_MEMBER_TEMPLATES
+    ArchiveCatalog cat((IterT)*arc, IterT());
+#else
+    ArchiveCatalog cat;
+    for (IterT i(*arc); i != IterT(); ++i)
+        cat.push_back(*i);
+#endif
+
+    for (CatalogIter it = cat.begin(); it != cat.end(); ++it) {
+        std::auto_ptr<EntryT> entry(*it);
+        count += m_testEntries.count(entry->GetName(wxPATH_UNIX));
+    }
+
+    CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
+    CPPUNIT_ASSERT(count == cat.size());
+}
+
+// test the pair iterators that can be used to load a std::map or wxHashMap
+// these also give away ownership of entries
+//
+template <class Classes>
+void ArchiveTestCase<Classes>::TestPairIterator(wxInputStream& in)
+{
+    typedef std::map<wxString, EntryT*> ArchiveCatalog;
+    typedef typename ArchiveCatalog::iterator CatalogIter;
+
+    std::auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
+    size_t count = 0;
+
+#ifdef WXARC_MEMBER_TEMPLATES
+    ArchiveCatalog cat((PairIterT)*arc, PairIterT());
+#else
+    ArchiveCatalog cat;
+    for (PairIterT i(*arc); i != PairIterT(); ++i)
+        cat.push_back(*i);
+#endif
+
+    for (CatalogIter it = cat.begin(); it != cat.end(); ++it) {
+        std::auto_ptr<EntryT> entry(it->second);
+        count += m_testEntries.count(entry->GetName(wxPATH_UNIX));
+    }
+
+    CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
+    CPPUNIT_ASSERT(count == cat.size());
+}
+
+// simple iterators using smart pointers, no need to worry about ownership
+//
+template <class Classes>
+void ArchiveTestCase<Classes>::TestSmartIterator(wxInputStream& in)
+{
+    typedef std::list<Ptr<EntryT> > ArchiveCatalog;
+    typedef typename ArchiveCatalog::iterator CatalogIter;
+    typedef wxArchiveIterator<InputStreamT, Ptr<EntryT> > Iter;
+
+    std::auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
+
+#ifdef WXARC_MEMBER_TEMPLATES
+    ArchiveCatalog cat((Iter)*arc, Iter());
+#else
+    ArchiveCatalog cat;
+    for (Iter i(*arc); i != Iter(); ++i)
+        cat.push_back(*i);
+#endif
+
+    CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
+
+    for (CatalogIter it = cat.begin(); it != cat.end(); ++it)
+        CPPUNIT_ASSERT(m_testEntries.count((*it)->GetName(wxPATH_UNIX)));
+}
+
+// pair iterator using smart pointers
+//
+template <class Classes>
+void ArchiveTestCase<Classes>::TestSmartPairIterator(wxInputStream& in)
+{
+    typedef std::map<wxString, Ptr<EntryT> > ArchiveCatalog;
+    typedef typename ArchiveCatalog::iterator CatalogIter;
+    typedef wxArchiveIterator<InputStreamT,
+                std::pair<wxString, Ptr<EntryT> > > PairIter;
+
+    std::auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
+
+#ifdef WXARC_MEMBER_TEMPLATES
+    ArchiveCatalog cat((PairIter)*arc, PairIter());
+#else
+    ArchiveCatalog cat;
+    for (PairIter i(*arc); i != PairIter(); ++i)
+        cat.push_back(*i);
+#endif
+
+    CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
+
+    for (CatalogIter it = cat.begin(); it != cat.end(); ++it)
+        CPPUNIT_ASSERT(m_testEntries.count(it->second->GetName(wxPATH_UNIX)));
+}
+
+// try reading two entries at the same time
+//
+template <class Classes>
+void ArchiveTestCase<Classes>::ReadSimultaneous(TestInputStream& in)
+{
+    typedef std::map<wxString, Ptr<EntryT> > ArchiveCatalog;
+    typedef wxArchiveIterator<InputStreamT,
+                std::pair<wxString, Ptr<EntryT> > > PairIter;
+
+    // create two archive input streams
+    TestInputStream in2(in);
+    std::auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
+    std::auto_ptr<InputStreamT> arc2(m_factory->NewStream(in2));
+
+    // load the catalog
+#ifdef WXARC_MEMBER_TEMPLATES
+    ArchiveCatalog cat((PairIter)*arc, PairIter());
+#else
+    ArchiveCatalog cat;
+    for (PairIter i(*arc); i != PairIter(); ++i)
+        cat.push_back(*i);
+#endif
+
+    // the names of two entries to read
+    const wxChar *name = _T("text/small");
+    const wxChar *name2 = _T("bin/bin1000");
+
+    // open them
+    typename ArchiveCatalog::iterator j;
+    CPPUNIT_ASSERT((j = cat.find(name)) != cat.end());
+    CPPUNIT_ASSERT(arc->OpenEntry(*j->second));
+    CPPUNIT_ASSERT((j = cat.find(name2)) != cat.end());
+    CPPUNIT_ASSERT(arc2->OpenEntry(*j->second));
+
+    // get pointers to the expected data
+    TestEntries::iterator k;
+    CPPUNIT_ASSERT((k = m_testEntries.find(name)) != m_testEntries.end());
+    TestEntry *entry = k->second;
+    CPPUNIT_ASSERT((k = m_testEntries.find(name2)) != m_testEntries.end());
+    TestEntry *entry2 = k->second;
+
+    size_t count = 0, count2 = 0;
+    size_t size = entry->GetSize(), size2 = entry2->GetSize();
+    const char *data = entry->GetData(), *data2 = entry2->GetData();
+
+    // read and check the two entries in parallel, character by character
+    while (arc->IsOk() || arc2->IsOk()) {
+        char ch = arc->GetC();
+        if (arc->LastRead() == 1) {
+            CPPUNIT_ASSERT(count < size);
+            CPPUNIT_ASSERT(ch == data[count++]);
+        }
+        char ch2 = arc2->GetC();
+        if (arc2->LastRead() == 1) {
+            CPPUNIT_ASSERT(count2 < size2);
+            CPPUNIT_ASSERT(ch2 == data2[count2++]);
+        }
+    }
+
+    CPPUNIT_ASSERT(arc->Eof());
+    CPPUNIT_ASSERT(arc2->Eof());
+    CPPUNIT_ASSERT(count == size);
+    CPPUNIT_ASSERT(count2 == size2);
+}
+
+// Nothing useful can be done with a generic notifier yet, so just test one
+// can be set
+//
+template <class NotifierT, class EntryT>
+class ArchiveNotifier : public NotifierT
+{
+public:
+    void OnEntryUpdated(EntryT& WXUNUSED(entry)) { }
+};
+
+template <class Classes>
+void ArchiveTestCase<Classes>::OnSetNotifier(EntryT& entry)
+{
+    static ArchiveNotifier<NotifierT, EntryT> notifier;
+    entry.SetNotifier(notifier);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// ArchiveTestCase<ZipClasses> could be used directly, but instead this
+// derived class is used so that zip specific features can be tested.
+
+class ZipTestCase : public ArchiveTestCase<ZipClasses>
+{
+public:
+    ZipTestCase(const wxString& name,
+                int id,
+                int options,
+                const wxString& archiver = wxEmptyString,
+                const wxString& unarchiver = wxEmptyString)
+    :
+        ArchiveTestCase<ZipClasses>(name, id, new wxZipClassFactory,
+                                    options, archiver, unarchiver),
+        m_count(0)
+    { }
+
+protected:
+    void OnCreateArchive(wxZipOutputStream& zip);
+    
+    void OnArchiveExtracted(wxZipInputStream& zip, int expectedTotal);
+    
+    void OnCreateEntry(wxZipOutputStream& zip,
+                       TestEntry& testEntry,
+                       wxZipEntry *entry);
+    
+    void OnEntryExtracted(wxZipEntry& entry,
+                          const TestEntry& testEntry,
+                          wxZipInputStream *arc);
+
+    void OnSetNotifier(EntryT& entry);
+
+    int m_count;
+    wxString m_comment;
+};
+
+void ZipTestCase::OnCreateArchive(wxZipOutputStream& zip)
+{
+    m_comment << _T("Comment for test ") << m_id;
+    zip.SetComment(m_comment);
+}
+
+void ZipTestCase::OnArchiveExtracted(wxZipInputStream& zip, int expectedTotal)
+{
+    CPPUNIT_ASSERT(zip.GetComment() == m_comment);
+    CPPUNIT_ASSERT(zip.GetTotalEntries() == expectedTotal);
+}
+
+void ZipTestCase::OnCreateEntry(wxZipOutputStream& zip,
+                                TestEntry& testEntry,
+                                wxZipEntry *entry)
+{
+    zip.SetLevel((m_id + m_count) % 10);
+
+    if (entry) {
+        switch ((m_id + m_count) % 5) {
+            case 0:
+            {
+                wxString comment = _T("Comment for ") + entry->GetName();
+                entry->SetComment(comment);
+                // lowercase the expected result, and the notifier should do
+                // the same for the zip entries when ModifyArchive() runs
+                testEntry.SetComment(comment.Lower());
+                break;
+            }
+            case 2:
+                entry->SetMethod(wxZIP_METHOD_STORE);
+                break;
+            case 4:
+                entry->SetMethod(wxZIP_METHOD_DEFLATE);
+                break;
+        }
+        entry->SetIsText(testEntry.IsText());
+    }
+
+    m_count++;
+}
+
+void ZipTestCase::OnEntryExtracted(wxZipEntry& entry,
+                                   const TestEntry& testEntry,
+                                   wxZipInputStream *arc)
+{
+    // provide some context for the error message so that we know which
+    // iteration of the loop we were on
+    std::string error_entry((_T(" '") + entry.GetName() + _T("'")).mb_str());
+    std::string error_context(" failed for entry" + error_entry);
+
+    CPPUNIT_ASSERT_MESSAGE("GetComment" + error_context,
+        entry.GetComment() == testEntry.GetComment());
+
+    // for seekable streams, GetNextEntry() doesn't read the local header so
+    // call OpenEntry() to do it
+    if (arc && (m_options & PipeIn) == 0 && entry.IsDir())
+        arc->OpenEntry(entry);
+
+    CPPUNIT_ASSERT_MESSAGE("IsText" + error_context,
+                           entry.IsText() == testEntry.IsText());
+
+    CPPUNIT_ASSERT_MESSAGE("Extra/LocalExtra mismatch for entry" + error_entry,
+        (entry.GetExtraLen() != 0 && entry.GetLocalExtraLen() != 0) ||
+        (entry.GetExtraLen() == 0 && entry.GetLocalExtraLen() == 0));
+}
+
+// check the notifier mechanism by using it to fold the entry comments to
+// lowercase
+//
+class ZipNotifier : public wxZipNotifier
+{
+public:
+    void OnEntryUpdated(wxZipEntry& entry);
+};
+
+void ZipNotifier::OnEntryUpdated(wxZipEntry& entry)
+{
+    entry.SetComment(entry.GetComment().Lower());
+}
+
+void ZipTestCase::OnSetNotifier(EntryT& entry)
+{
+    static ZipNotifier notifier;
+    entry.SetNotifier(notifier);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// 'zip - -' produces local headers without the size field set. This is a
+// case not covered by all the other tests, so this class tests it as a
+// special case
+
+class ZipPipeTestCase : public CppUnit::TestCase
+{
+public:
+    ZipPipeTestCase(const wxString& name, int options) :
+        CppUnit::TestCase(std::string(name.mb_str())), m_options(options) { }
+
+protected:
+    void runTest();
+    int m_options;
+};
+
+void ZipPipeTestCase::runTest()
+{
+    TestOutputStream out(m_options);
+
+    wxString testdata = _T("test data to pipe through zip");
+    wxString cmd = _T("echo ") + testdata + _T(" | zip -q - -");
+
+    {
+        PFileInputStream in(cmd);
+        if (in.Ok())
+            out.Write(in);
+    }
+
+    TestInputStream in(out);
+    wxZipInputStream zip(in);
+
+    std::auto_ptr<wxZipEntry> entry(zip.GetNextEntry());
+    CPPUNIT_ASSERT(entry.get() != NULL);
+
+    if ((m_options & PipeIn) == 0)
+        CPPUNIT_ASSERT(entry->GetSize() != wxInvalidOffset);
+
+    char buf[64];
+    size_t len = zip.Read(buf, sizeof(buf) - 1).LastRead();
+
+    while (len > 0 && buf[len - 1] <= 32)
+        --len;
+    buf[len] = 0;
+
+    CPPUNIT_ASSERT(zip.Eof());
+    CPPUNIT_ASSERT(wxString(buf, *wxConvCurrent) == testdata);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// The suite
+
+class ArchiveTestSuite : public CppUnit::TestSuite
+{
+public:
+    ArchiveTestSuite();
+    static CppUnit::Test *suite()
+        { return (new ArchiveTestSuite)->makeSuite(); }
+
+private:
+    int m_id;
+    wxPathList m_path;
+
+    ArchiveTestSuite *makeSuite();
+    void AddCmd(wxArrayString& cmdlist, const wxString& cmd);
+    bool IsInPath(const wxString& cmd);
+
+    wxString Description(const wxString& type,
+                         int options,
+                         bool genericInterface = false,
+                         const wxString& archiver = wxEmptyString,
+                         const wxString& unarchiver = wxEmptyString);
+};
+
+ArchiveTestSuite::ArchiveTestSuite()
+  : CppUnit::TestSuite("ArchiveTestSuite"),
+    m_id(0)
+{
+    m_path.AddEnvList(_T("PATH"));
+}
+
+// add the command for an external archiver to the list, testing for it in
+// the path first
+//
+void ArchiveTestSuite::AddCmd(wxArrayString& cmdlist, const wxString& cmd)
+{
+    if (cmdlist.empty())
+        cmdlist.push_back(_T(""));
+    if (IsInPath(cmd))
+        cmdlist.push_back(cmd);
+}
+
+bool ArchiveTestSuite::IsInPath(const wxString& cmd)
+{
+    wxString c = cmd.BeforeFirst(_T(' '));
+#ifdef __WXMSW__
+    c += _T(".exe");
+#endif
+    return !m_path.FindValidPath(c).empty();
+}
+
+// make the test suite
+//
+ArchiveTestSuite *ArchiveTestSuite::makeSuite()
+{
+    typedef wxArrayString::iterator Iter;
+    wxArrayString zippers;
+    wxArrayString unzippers;
+
+    AddCmd(zippers, _T("zip -qr %s *"));
+    AddCmd(unzippers, _T("unzip -q %s"));
+
+    for (int genInterface = 0; genInterface < 2; genInterface++)
+        for (Iter i = unzippers.begin(); i != unzippers.end(); ++i)
+            for (Iter j = zippers.begin(); j != zippers.end(); ++j)
+                for (int options = 0; options <= AllOptions; options++)
+                {
+                    // unzip doesn't support piping in the zip
+                    if ((options & PipeIn) && !i->empty())
+                        continue;
+#ifdef WXARC_NO_POPEN 
+                    // if no popen then can use piped output of zip
+                    if ((options & PipeOut) && !j->empty())
+                        continue;
+#endif
+                    wxString name = Description(_T("wxZip"), options, 
+                                                genInterface != 0, *j, *i);
+
+                    if (genInterface)
+                        addTest(new ArchiveTestCase<ArchiveClasses>(
+                                    name, m_id,
+                                    new wxZipClassFactory,
+                                    options, *j, *i));
+                    else
+                        addTest(new ZipTestCase(name, m_id, options, *j, *i));
+
+                    m_id++;
+                }
+
+#ifndef WXARC_NO_POPEN 
+    // if have popen then can check the piped output of 'zip - -'
+    if (IsInPath(_T("zip")))
+        for (int options = 0; options <= PipeIn; options += PipeIn) {
+            wxString name = Description(_T("ZipPipeTestCase"), options);
+            addTest(new ZipPipeTestCase(name, options));
+            m_id++;
+        }
+#endif
+
+    return this;
+}
+
+// make a display string for the option bits
+//
+wxString ArchiveTestSuite::Description(const wxString& type,
+                                       int options,
+                                       bool genericInterface,
+                                       const wxString& archiver,
+                                       const wxString& unarchiver)
+{
+    wxString descr;
+    descr << m_id << _T(" ");
+    
+    if (genericInterface)
+        descr << _T("wxArchive (") << type << _T(")");
+    else
+        descr << type;
+
+    if (!archiver.empty())
+        descr << _T(" ") << archiver.BeforeFirst(_T(' '));
+    if (!unarchiver.empty())
+        descr << _T(" ") << unarchiver.BeforeFirst(_T(' '));
+    
+    wxString optstr;
+
+    if ((options & PipeIn) != 0)
+        optstr += _T("|PipeIn");
+    if ((options & PipeOut) != 0)
+        optstr += _T("|PipeOut");
+    if ((options & Stub) != 0)
+        optstr += _T("|Stub");
+    if (!optstr.empty())
+        optstr = _T(" (") + optstr.substr(1) + _T(")");
+
+    descr << optstr;
+
+    return descr;
+}
+
+// register in the unnamed registry so that these tests are run by default
+CPPUNIT_TEST_SUITE_REGISTRATION(ArchiveTestSuite);
+
+// also include in it's own registry so that these tests can be run alone
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ArchiveTestSuite, "ArchiveTestSuite");
+
+#endif // wxUSE_STREAMS
diff --git a/tests/test.bkl b/tests/test.bkl
index 9d9dec6178..6b03777e68 100644
--- a/tests/test.bkl
+++ b/tests/test.bkl
@@ -9,6 +9,7 @@
                    template_append="wx_append_base">
         <sources>
             test.cpp
+            archive/archivetest.cpp
             arrays/arrays.cpp
             datetime/datetimetest.cpp
             fileconf/fileconftest.cpp
-- 
2.50.0

