]> git.saurik.com Git - wxWidgets.git/blobdiff - src/common/fs_arc.cpp
fixing overrelease and out-of-bounds write, fixes #13725
[wxWidgets.git] / src / common / fs_arc.cpp
index f96f1b8ee3111565e0a847f7cb06eeaa4eab3ff4..4b4c382cac7e1ebea9721e357f464e27997c24e4 100644 (file)
@@ -1,8 +1,8 @@
 /////////////////////////////////////////////////////////////////////////////
-// Name:        fs_zip.cpp
-// Purpose:     ZIP file system
-// Author:      Vaclav Slavik
-// Copyright:   (c) 1999 Vaclav Slavik
+// Name:        src/common/fs_arc.cpp
+// Purpose:     wxArchive file system
+// Author:      Vaclav Slavik, Mike Wetherell
+// Copyright:   (c) 1999 Vaclav Slavik, (c) 2006 Mike Wetherell
 // CVS-ID:      $Id$
 // Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
 #pragma hdrstop
 #endif
 
-#if wxUSE_FILESYSTEM && wxUSE_FS_ZIP && wxUSE_ZIPSTREAM && wxUSE_ZLIB
+#if wxUSE_FS_ARCHIVE
 
-#ifndef WXPRECOMP
+#include "wx/fs_arc.h"
+
+#ifndef WX_PRECOMP
     #include "wx/intl.h"
     #include "wx/log.h"
 #endif
 
-#include "wx/filesys.h"
-#include "wx/wfstream.h"
-#include "wx/zipstrm.h"
-#include "wx/fs_zip.h"
+#if WXWIN_COMPATIBILITY_2_6 && wxUSE_ZIPSTREAM
+    #include "wx/zipstrm.h"
+#else
+    #include "wx/archive.h"
+#endif
 
+#include "wx/private/fileback.h"
 
 //---------------------------------------------------------------------------
-// wxZipFSInputStream
+// wxArchiveFSCacheDataImpl
+//
+// Holds the catalog of an archive file, and if it is being read from a
+// non-seekable stream, a copy of its backing file.
+//
+// This class is actually the reference counted implementation for the
+// wxArchiveFSCacheData class below. It was done that way to allow sharing
+// between instances of wxFileSystem, though that's a feature not used in this
+// version.
 //---------------------------------------------------------------------------
-// Helper class for wxZipFSHandler
 
-class wxZipFSInputStream : public wxZipInputStream
+WX_DECLARE_STRING_HASH_MAP(wxArchiveEntry*, wxArchiveFSEntryHash);
+
+struct wxArchiveFSEntry
 {
-    public:
-       wxZipFSInputStream(wxFSFile *file)
-               : wxZipInputStream(*file->GetStream())
-       {
-            m_file = file;
-#if WXWIN_COMPATIBILITY_2_6
-            m_allowSeeking = true;
-#endif
-       }
+    wxArchiveEntry *entry;
+    wxArchiveFSEntry *next;
+};
+
+class wxArchiveFSCacheDataImpl
+{
+public:
+    wxArchiveFSCacheDataImpl(const wxArchiveClassFactory& factory,
+                             const wxBackingFile& backer);
+    wxArchiveFSCacheDataImpl(const wxArchiveClassFactory& factory,
+                             wxInputStream *stream);
+
+    ~wxArchiveFSCacheDataImpl();
+
+    void Release() { if (--m_refcount == 0) delete this; }
+    wxArchiveFSCacheDataImpl *AddRef() { m_refcount++; return this; }
+
+    wxArchiveEntry *Get(const wxString& name);
+    wxInputStream *NewStream() const;
+
+    wxArchiveFSEntry *GetNext(wxArchiveFSEntry *fse);
+
+private:
+    wxArchiveFSEntry *AddToCache(wxArchiveEntry *entry);
+    void CloseStreams();
+
+    int m_refcount;
+
+    wxArchiveFSEntryHash m_hash;
+    wxArchiveFSEntry *m_begin;
+    wxArchiveFSEntry **m_endptr;
+
+    wxBackingFile m_backer;
+    wxInputStream *m_stream;
+    wxArchiveInputStream *m_archive;
+};
+
+wxArchiveFSCacheDataImpl::wxArchiveFSCacheDataImpl(
+        const wxArchiveClassFactory& factory,
+        const wxBackingFile& backer)
+ :  m_refcount(1),
+    m_begin(NULL),
+    m_endptr(&m_begin),
+    m_backer(backer),
+    m_stream(new wxBackedInputStream(backer)),
+    m_archive(factory.NewStream(*m_stream))
+{
+}
+
+wxArchiveFSCacheDataImpl::wxArchiveFSCacheDataImpl(
+        const wxArchiveClassFactory& factory,
+        wxInputStream *stream)
+ :  m_refcount(1),
+    m_begin(NULL),
+    m_endptr(&m_begin),
+    m_stream(stream),
+    m_archive(factory.NewStream(*m_stream))
+{
+}
+
+wxArchiveFSCacheDataImpl::~wxArchiveFSCacheDataImpl()
+{
+    WX_CLEAR_HASH_MAP(wxArchiveFSEntryHash, m_hash);
+
+    wxArchiveFSEntry *entry = m_begin;
+
+    while (entry)
+    {
+        wxArchiveFSEntry *next = entry->next;
+        delete entry;
+        entry = next;
+    }
+
+    CloseStreams();
+}
+
+wxArchiveFSEntry *wxArchiveFSCacheDataImpl::AddToCache(wxArchiveEntry *entry)
+{
+    m_hash[entry->GetName(wxPATH_UNIX)] = entry;
+    wxArchiveFSEntry *fse = new wxArchiveFSEntry;
+    *m_endptr = fse;
+    (*m_endptr)->entry = entry;
+    (*m_endptr)->next = NULL;
+    m_endptr = &(*m_endptr)->next;
+    return fse;
+}
+
+void wxArchiveFSCacheDataImpl::CloseStreams()
+{
+    wxDELETE(m_archive);
+    wxDELETE(m_stream);
+}
+
+wxArchiveEntry *wxArchiveFSCacheDataImpl::Get(const wxString& name)
+{
+    wxArchiveFSEntryHash::iterator it = m_hash.find(name);
+
+    if (it != m_hash.end())
+        return it->second;
+
+    if (!m_archive)
+        return NULL;
+
+    wxArchiveEntry *entry;
+
+    while ((entry = m_archive->GetNextEntry()) != NULL)
+    {
+        AddToCache(entry);
+
+        if (entry->GetName(wxPATH_UNIX) == name)
+            return entry;
+    }
+
+    CloseStreams();
+
+    return NULL;
+}
+
+wxInputStream* wxArchiveFSCacheDataImpl::NewStream() const
+{
+    if (m_backer)
+        return new wxBackedInputStream(m_backer);
+    else
+        return NULL;
+}
+
+wxArchiveFSEntry *wxArchiveFSCacheDataImpl::GetNext(wxArchiveFSEntry *fse)
+{
+    wxArchiveFSEntry *next = fse ? fse->next : m_begin;
+
+    if (!next && m_archive)
+    {
+        wxArchiveEntry *entry = m_archive->GetNextEntry();
+
+        if (entry)
+            next = AddToCache(entry);
+        else
+            CloseStreams();
+    }
+
+    return next;
+}
+
+//---------------------------------------------------------------------------
+// wxArchiveFSCacheData
+//
+// This is the inteface for wxArchiveFSCacheDataImpl above. Holds the catalog
+// of an archive file, and if it is being read from a non-seekable stream, a
+// copy of its backing file.
+//---------------------------------------------------------------------------
+
+class wxArchiveFSCacheData
+{
+public:
+    wxArchiveFSCacheData() : m_impl(NULL) { }
+    wxArchiveFSCacheData(const wxArchiveClassFactory& factory,
+                         const wxBackingFile& backer);
+    wxArchiveFSCacheData(const wxArchiveClassFactory& factory,
+                         wxInputStream *stream);
+
+    wxArchiveFSCacheData(const wxArchiveFSCacheData& data);
+    wxArchiveFSCacheData& operator=(const wxArchiveFSCacheData& data);
+
+    ~wxArchiveFSCacheData() { if (m_impl) m_impl->Release(); }
 
-       virtual ~wxZipFSInputStream() { delete m_file; }
+    wxArchiveEntry *Get(const wxString& name) { return m_impl->Get(name); }
+    wxInputStream *NewStream() const { return m_impl->NewStream(); }
+    wxArchiveFSEntry *GetNext(wxArchiveFSEntry *fse)
+        { return m_impl->GetNext(fse); }
 
-    private:
-       wxFSFile *m_file;
+private:
+    wxArchiveFSCacheDataImpl *m_impl;
 };
 
+wxArchiveFSCacheData::wxArchiveFSCacheData(
+        const wxArchiveClassFactory& factory,
+        const wxBackingFile& backer)
+  : m_impl(new wxArchiveFSCacheDataImpl(factory, backer))
+{
+}
+
+wxArchiveFSCacheData::wxArchiveFSCacheData(
+        const wxArchiveClassFactory& factory,
+        wxInputStream *stream)
+  : m_impl(new wxArchiveFSCacheDataImpl(factory, stream))
+{
+}
+
+wxArchiveFSCacheData::wxArchiveFSCacheData(const wxArchiveFSCacheData& data)
+  : m_impl(data.m_impl ? data.m_impl->AddRef() : NULL)
+{
+}
+
+wxArchiveFSCacheData& wxArchiveFSCacheData::operator=(
+        const wxArchiveFSCacheData& data)
+{
+    if (data.m_impl != m_impl)
+    {
+        if (m_impl)
+            m_impl->Release();
+
+        m_impl = data.m_impl;
+
+        if (m_impl)
+            m_impl->AddRef();
+    }
+
+    return *this;
+}
+
+//---------------------------------------------------------------------------
+// wxArchiveFSCache
+//
+// wxArchiveFSCacheData caches a single archive, and this class holds a
+// collection of them to cache all the archives accessed by this instance
+// of wxFileSystem.
+//---------------------------------------------------------------------------
+
+WX_DECLARE_STRING_HASH_MAP(wxArchiveFSCacheData, wxArchiveFSCacheDataHash);
+
+class wxArchiveFSCache
+{
+public:
+    wxArchiveFSCache() { }
+    ~wxArchiveFSCache() { }
+
+    wxArchiveFSCacheData* Add(const wxString& name,
+                              const wxArchiveClassFactory& factory,
+                              wxInputStream *stream);
+
+    wxArchiveFSCacheData *Get(const wxString& name);
+
+private:
+    wxArchiveFSCacheDataHash m_hash;
+};
+
+wxArchiveFSCacheData* wxArchiveFSCache::Add(
+        const wxString& name,
+        const wxArchiveClassFactory& factory,
+        wxInputStream *stream)
+{
+    wxArchiveFSCacheData& data = m_hash[name];
+
+    if (stream->IsSeekable())
+        data = wxArchiveFSCacheData(factory, stream);
+    else
+        data = wxArchiveFSCacheData(factory, wxBackingFile(stream));
+
+    return &data;
+}
+
+wxArchiveFSCacheData *wxArchiveFSCache::Get(const wxString& name)
+{
+    wxArchiveFSCacheDataHash::iterator it;
+
+    if ((it = m_hash.find(name)) != m_hash.end())
+        return &it->second;
+
+    return NULL;
+}
+
 //----------------------------------------------------------------------------
-// wxZipFSHandler
+// wxArchiveFSHandler
 //----------------------------------------------------------------------------
 
-wxZipFSHandler::wxZipFSHandler() : wxFileSystemHandler()
+IMPLEMENT_DYNAMIC_CLASS(wxArchiveFSHandler, wxFileSystemHandler)
+
+wxArchiveFSHandler::wxArchiveFSHandler()
+ :  wxFileSystemHandler()
 {
     m_Archive = NULL;
+    m_FindEntry = NULL;
     m_ZipFile = m_Pattern = m_BaseDir = wxEmptyString;
     m_AllowDirs = m_AllowFiles = true;
     m_DirsFound = NULL;
+    m_cache = NULL;
 }
 
-
-
-wxZipFSHandler::~wxZipFSHandler()
+wxArchiveFSHandler::~wxArchiveFSHandler()
 {
     Cleanup();
+    delete m_cache;
 }
 
-
-void wxZipFSHandler::Cleanup()
+void wxArchiveFSHandler::Cleanup()
 {
-    wxDELETE(m_Archive);
     wxDELETE(m_DirsFound);
 }
-
 
-bool wxZipFSHandler::CanOpen(const wxString& location)
+bool wxArchiveFSHandler::CanOpen(const wxString& location)
 {
     wxString p = GetProtocol(location);
-    return (p == wxT("zip"));
+    return wxArchiveClassFactory::Find(p) != NULL;
 }
 
-
-wxFSFile* wxZipFSHandler::OpenFile(wxFileSystem& WXUNUSED(fs), const wxString& location)
+wxFSFile* wxArchiveFSHandler::OpenFile(
+        wxFileSystem& WXUNUSED(fs),
+        const wxString& location)
 {
     wxString right = GetRightLocation(location);
     wxString left = GetLeftLocation(location);
-    wxZipInputStream *s;
+    wxString protocol = GetProtocol(location);
+    wxString key = left + wxT("#") + protocol + wxT(":");
 
     if (right.Contains(wxT("./")))
     {
@@ -98,67 +358,96 @@ wxFSFile* wxZipFSHandler::OpenFile(wxFileSystem& WXUNUSED(fs), const wxString& l
         right = rightPart.GetFullPath(wxPATH_UNIX);
     }
 
-    if (right.GetChar(0) == wxT('/')) right = right.Mid(1);
+    if (!right.empty() && right.GetChar(0) == wxT('/')) right = right.Mid(1);
+
+    if (!m_cache)
+        m_cache = new wxArchiveFSCache;
 
-    // a new wxFileSystem object is needed here to avoid infinite recursion
-    wxFSFile *leftFile = wxFileSystem().OpenFile(left);
-    if (!leftFile)
-       return NULL;
+    const wxArchiveClassFactory *factory;
+    factory = wxArchiveClassFactory::Find(protocol);
+    if (!factory)
+        return NULL;
 
-    s = new wxZipFSInputStream(leftFile);
-    if (s && s->IsOk())
+    wxArchiveFSCacheData *cached = m_cache->Get(key);
+    if (!cached)
     {
-#if wxUSE_DATETIME
-       wxDateTime dtMod;
-#endif // wxUSE_DATETIME
+        wxFSFile *leftFile = m_fs.OpenFile(left);
+        if (!leftFile)
+            return NULL;
+        cached = m_cache->Add(key, *factory, leftFile->DetachStream());
+        delete leftFile;
+    }
 
-       bool found = false;
-       while (!found)
-       {
-           wxZipEntry *ent = s->GetNextEntry();
-           if (!ent)
-               break;
-
-           if (ent->GetInternalName() == right)
-           {
-               found = true;
-               dtMod = ent->GetDateTime();
-           }
-
-           delete ent;
-       }
-       if (found)
-       {
-           return new wxFSFile(s,
-                            left + wxT("#zip:") + right,
-                            GetMimeTypeFromExt(location),
-                            GetAnchor(location)
-#if wxUSE_DATETIME
-                            , dtMod
-#endif // wxUSE_DATETIME
-                            );
-       }
+    wxArchiveEntry *entry = cached->Get(right);
+    if (!entry)
+        return NULL;
+
+    wxInputStream *leftStream = cached->NewStream();
+    if (!leftStream)
+    {
+        wxFSFile *leftFile = m_fs.OpenFile(left);
+        if (!leftFile)
+            return NULL;
+        leftStream = leftFile->DetachStream();
+        delete leftFile;
     }
 
-    delete s;
-    return NULL;
-}
+    wxArchiveInputStream *s = factory->NewStream(leftStream);
+    if ( !s )
+        return NULL;
 
+    s->OpenEntry(*entry);
 
+    if (!s->IsOk())
+    {
+        delete s;
+        return NULL;
+    }
 
-wxString wxZipFSHandler::FindFirst(const wxString& spec, int flags)
+#if WXWIN_COMPATIBILITY_2_6 && wxUSE_ZIPSTREAM
+    if (wxDynamicCast(factory, wxZipClassFactory))
+        ((wxZipInputStream*)s)->m_allowSeeking = true;
+#endif // WXWIN_COMPATIBILITY_2_6
+
+    return new wxFSFile(s,
+                        key + right,
+                        wxEmptyString,
+                        GetAnchor(location)
+#if wxUSE_DATETIME
+                        , entry->GetDateTime()
+#endif // wxUSE_DATETIME
+                        );
+}
+
+wxString wxArchiveFSHandler::FindFirst(const wxString& spec, int flags)
 {
     wxString right = GetRightLocation(spec);
     wxString left = GetLeftLocation(spec);
+    wxString protocol = GetProtocol(spec);
+    wxString key = left + wxT("#") + protocol + wxT(":");
 
     if (!right.empty() && right.Last() == wxT('/')) right.RemoveLast();
 
-    if (m_Archive)
+    if (!m_cache)
+        m_cache = new wxArchiveFSCache;
+
+    const wxArchiveClassFactory *factory;
+    factory = wxArchiveClassFactory::Find(protocol);
+    if (!factory)
+        return wxEmptyString;
+
+    m_Archive = m_cache->Get(key);
+    if (!m_Archive)
     {
-        delete m_Archive;
-        m_Archive = NULL;
+        wxFSFile *leftFile = m_fs.OpenFile(left);
+        if (!leftFile)
+            return wxEmptyString;
+        m_Archive = m_cache->Add(key, *factory, leftFile->DetachStream());
+        delete leftFile;
     }
 
+    m_FindEntry = NULL;
+
     switch (flags)
     {
         case wxFILE:
@@ -169,11 +458,7 @@ wxString wxZipFSHandler::FindFirst(const wxString& spec, int flags)
             m_AllowDirs = m_AllowFiles = true; break;
     }
 
-    m_ZipFile = left;
-
-    wxFSFile *leftFile = wxFileSystem().OpenFile(left);
-    if (leftFile)
-        m_Archive = new wxZipFSInputStream(leftFile);
+    m_ZipFile = key;
 
     m_Pattern = right.AfterLast(wxT('/'));
     m_BaseDir = right.BeforeLast(wxT('/'));
@@ -185,7 +470,7 @@ wxString wxZipFSHandler::FindFirst(const wxString& spec, int flags)
         if (m_AllowDirs)
         {
             delete m_DirsFound;
-            m_DirsFound = new wxZipFilenameHashMap();
+            m_DirsFound = new wxArchiveFilenameHashMap();
             if (right.empty())  // allow "/" to match the archive root
                 return spec;
         }
@@ -194,32 +479,28 @@ wxString wxZipFSHandler::FindFirst(const wxString& spec, int flags)
     return wxEmptyString;
 }
 
-
-
-wxString wxZipFSHandler::FindNext()
+wxString wxArchiveFSHandler::FindNext()
 {
     if (!m_Archive) return wxEmptyString;
     return DoFind();
 }
 
-
-
-wxString wxZipFSHandler::DoFind()
+wxString wxArchiveFSHandler::DoFind()
 {
     wxString namestr, dir, filename;
     wxString match = wxEmptyString;
 
     while (match == wxEmptyString)
     {
-        wxZipEntry *entry = m_Archive->GetNextEntry();
-        if (!entry)
+        m_FindEntry = m_Archive->GetNext(m_FindEntry);
+
+        if (!m_FindEntry)
         {
-            delete m_Archive;
             m_Archive = NULL;
+            m_FindEntry = NULL;
             break;
         }
-        namestr = entry->GetName(wxPATH_UNIX);
-        delete entry;
+        namestr = m_FindEntry->entry->GetName(wxPATH_UNIX);
 
         if (m_AllowDirs)
         {
@@ -233,7 +514,7 @@ wxString wxZipFSHandler::DoFind()
                     dir = dir.BeforeLast(wxT('/'));
                     if (!filename.empty() && m_BaseDir == dir &&
                                 wxMatchWild(m_Pattern, filename, false))
-                        match = m_ZipFile + wxT("#zip:") + dir + wxT("/") + filename;
+                        match = m_ZipFile + dir + wxT("/") + filename;
                 }
                 else
                     break; // already tranversed
@@ -244,13 +525,10 @@ wxString wxZipFSHandler::DoFind()
         dir = namestr.BeforeLast(wxT('/'));
         if (m_AllowFiles && !filename.empty() && m_BaseDir == dir &&
                             wxMatchWild(m_Pattern, filename, false))
-            match = m_ZipFile + wxT("#zip:") + namestr;
+            match = m_ZipFile + namestr;
     }
 
     return match;
 }
 
-
-
-#endif
-      //wxUSE_FILESYSTEM && wxUSE_FS_ZIP && wxUSE_ZIPSTREAM
+#endif // wxUSE_FS_ARCHIVE