]> git.saurik.com Git - wxWidgets.git/blobdiff - src/common/zipstrm.cpp
Don't handle "Return" key as "TAB" even when the default button is disabled.
[wxWidgets.git] / src / common / zipstrm.cpp
index eacd2b1d9c4d6de8dca192dd1d1c8ccb29a0a5df..4902738a07aaf7bed96e3e1c1a17a27bc3b8c998 100644 (file)
@@ -1,5 +1,5 @@
 /////////////////////////////////////////////////////////////////////////////
-// Name:        zipstrm.cpp
+// Name:        src/common/zipstrm.cpp
 // Purpose:     Streams for Zip files
 // Author:      Mike Wetherell
 // RCS-ID:      $Id$
@@ -7,32 +7,28 @@
 // Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
 
-#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
-  #pragma implementation "zipstrm.h"
-#endif
-
 // For compilers that support precompilation, includes "wx.h".
 #include "wx/wxprec.h"
 
 #ifdef __BORLANDC__
-  #pragma hdrstop
+    #pragma hdrstop
 #endif
 
+#if wxUSE_ZIPSTREAM
+
+#include "wx/zipstrm.h"
+
 #ifndef WX_PRECOMP
-  #include "wx/defs.h"
+    #include "wx/hashmap.h"
+    #include "wx/intl.h"
+    #include "wx/log.h"
+    #include "wx/utils.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/scopedptr.h"
 #include "wx/wfstream.h"
 #include "zlib.h"
 
@@ -81,13 +77,6 @@ enum {
 IMPLEMENT_DYNAMIC_CLASS(wxZipEntry, wxArchiveEntry)
 IMPLEMENT_DYNAMIC_CLASS(wxZipClassFactory, wxArchiveClassFactory)
 
-//FORCE_LINK_ME(zipstrm)
-int _wx_link_dummy_func_zipstrm();
-int _wx_link_dummy_func_zipstrm()
-{
-    return 1;
-}
-
 
 /////////////////////////////////////////////////////////////////////////////
 // Helpers
@@ -96,6 +85,9 @@ int _wx_link_dummy_func_zipstrm()
 //
 static wxString ReadString(wxInputStream& stream, wxUint16 len, wxMBConv& conv)
 {
+    if (len == 0)
+        return wxEmptyString;
+
 #if wxUSE_UNICODE
     wxCharBuffer buf(len);
     stream.Read(buf.data(), len);
@@ -120,12 +112,20 @@ static inline wxUint32 CrackUint32(const char *m)
     return (n[3] << 24) | (n[2] << 16) | (n[1] << 8) | n[0];
 }
 
+// Decode a little endian wxUint16 number from a character array
+//
+static inline wxUint16 CrackUint16(const char *m)
+{
+    const unsigned char *n = (const unsigned char*)m;
+    return (n[1] << 8) | n[0];
+}
+
 // Temporarily lower the logging level in debug mode to avoid a warning
 // from SeekI about seeking on a stream with data written back to it.
 //
 static wxFileOffset QuietSeek(wxInputStream& stream, wxFileOffset pos)
 {
-#ifdef __WXDEBUG__
+#if wxUSE_LOG
     wxLogLevel level = wxLog::GetLogLevel();
     wxLog::SetLogLevel(wxLOG_Debug - 1);
     wxFileOffset result = stream.SeekI(pos);
@@ -137,6 +137,97 @@ static wxFileOffset QuietSeek(wxInputStream& stream, wxFileOffset pos)
 }
 
 
+/////////////////////////////////////////////////////////////////////////////
+// Class factory
+
+static wxZipClassFactory g_wxZipClassFactory;
+
+wxZipClassFactory::wxZipClassFactory()
+{
+    if (this == &g_wxZipClassFactory)
+        PushFront();
+}
+
+const wxChar * const *
+wxZipClassFactory::GetProtocols(wxStreamProtocolType type) const
+{
+    static const wxChar *protocols[] = { wxT("zip"), NULL };
+    static const wxChar *mimetypes[] = { wxT("application/zip"), NULL };
+    static const wxChar *fileexts[]  = { wxT(".zip"), wxT(".htb"), NULL };
+    static const wxChar *empty[]     = { NULL };
+
+    switch (type) {
+        case wxSTREAM_PROTOCOL: return protocols;
+        case wxSTREAM_MIMETYPE: return mimetypes;
+        case wxSTREAM_FILEEXT:  return fileexts;
+        default:                return empty;
+    }
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Read a zip header
+
+class wxZipHeader
+{
+public:
+    wxZipHeader(wxInputStream& stream, size_t size);
+
+    inline wxUint8 Read8();
+    inline wxUint16 Read16();
+    inline wxUint32 Read32();
+
+    const char *GetData() const             { return m_data; }
+    size_t GetSize() const                  { return m_size; }
+    operator bool() const                   { return m_ok; }
+
+    size_t Seek(size_t pos)                 { m_pos = pos; return m_pos; }
+    size_t Skip(size_t size)                { m_pos += size; return m_pos; }
+
+    wxZipHeader& operator>>(wxUint8& n)     { n = Read8();  return *this; }
+    wxZipHeader& operator>>(wxUint16& n)    { n = Read16(); return *this; }
+    wxZipHeader& operator>>(wxUint32& n)    { n = Read32(); return *this; }
+
+private:
+    char m_data[64];
+    size_t m_size;
+    size_t m_pos;
+    bool m_ok;
+};
+
+wxZipHeader::wxZipHeader(wxInputStream& stream, size_t size)
+  : m_size(0),
+    m_pos(0),
+    m_ok(false)
+{
+    wxCHECK_RET(size <= sizeof(m_data), wxT("buffer too small"));
+    m_size = stream.Read(m_data, size).LastRead();
+    m_ok = m_size == size;
+}
+
+inline wxUint8 wxZipHeader::Read8()
+{
+    wxASSERT(m_pos < m_size);
+    return m_data[m_pos++];
+}
+
+inline wxUint16 wxZipHeader::Read16()
+{
+    wxASSERT(m_pos + 2 <= m_size);
+    wxUint16 n = CrackUint16(m_data + m_pos);
+    m_pos += 2;
+    return n;
+}
+
+inline wxUint32 wxZipHeader::Read32()
+{
+    wxASSERT(m_pos + 4 <= m_size);
+    wxUint32 n = CrackUint32(m_data + m_pos);
+    m_pos += 4;
+    return n;
+}
+
+
 /////////////////////////////////////////////////////////////////////////////
 // Stored input stream
 // Trival decompressor for files which are 'stored' in the zip file.
@@ -160,7 +251,7 @@ private:
     wxFileOffset m_pos;
     wxFileOffset m_len;
 
-    DECLARE_NO_COPY_CLASS(wxStoredInputStream)
+    wxDECLARE_NO_COPY_CLASS(wxStoredInputStream);
 };
 
 wxStoredInputStream::wxStoredInputStream(wxInputStream& stream)
@@ -172,14 +263,13 @@ wxStoredInputStream::wxStoredInputStream(wxInputStream& stream)
 
 size_t wxStoredInputStream::OnSysRead(void *buffer, size_t size)
 {
-    size_t count = wxMin(size, (size_t)(m_len - m_pos));
+    size_t count = wx_truncate_cast(size_t,
+                wxMin(size + wxFileOffset(0), m_len - m_pos + size_t(0)));
     count = m_parent_i_stream->Read(buffer, count).LastRead();
     m_pos += count;
 
-    if (m_pos == m_len)
-        m_lasterror = wxSTREAM_EOF;
-    else if (!*m_parent_i_stream)
-        m_lasterror = wxSTREAM_READ_ERROR;
+    if (count < size)
+        m_lasterror = m_pos == m_len ? wxSTREAM_EOF : wxSTREAM_READ_ERROR;
 
     return count;
 }
@@ -207,7 +297,7 @@ protected:
 
 private:
     wxFileOffset m_pos;
-    DECLARE_NO_COPY_CLASS(wxStoredOutputStream)
+    wxDECLARE_NO_COPY_CLASS(wxStoredOutputStream);
 };
 
 size_t wxStoredOutputStream::OnSysWrite(const void *buffer, size_t size)
@@ -269,7 +359,7 @@ private:
     size_t m_start;
     size_t m_end;
 
-    DECLARE_NO_COPY_CLASS(wxTeeInputStream)
+    wxDECLARE_NO_COPY_CLASS(wxTeeInputStream);
 };
 
 wxTeeInputStream::wxTeeInputStream(wxInputStream& stream)
@@ -302,7 +392,8 @@ wxInputStream& wxTeeInputStream::Read(void *buffer, size_t size)
 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();
+    if (count < size)
+        m_lasterror = m_parent_i_stream->GetLastError();
     return count;
 }
 
@@ -316,6 +407,7 @@ size_t wxTeeInputStream::GetData(char *buffer, size_t size)
             wxFAIL; // we've already returned data that's now being ungot
             m_end = len;
         }
+        m_parent_i_stream->Reset();
         m_parent_i_stream->Ungetch(m_wback, m_wbacksize);
         free(m_wback);
         m_wback = NULL;
@@ -363,7 +455,7 @@ private:
     enum { BUFSIZE = 8192 };
     wxCharBuffer m_dummy;
 
-    DECLARE_NO_COPY_CLASS(wxRawInputStream)
+    wxDECLARE_NO_COPY_CLASS(wxRawInputStream);
 };
 
 wxRawInputStream::wxRawInputStream(wxInputStream& stream)
@@ -485,12 +577,14 @@ public:
     wxZipMemory *Unique(size_t size);
 
 private:
-    ~wxZipMemory() { delete m_data; }
+    ~wxZipMemory() { delete [] m_data; }
 
     char *m_data;
     size_t m_size;
     size_t m_capacity;
     int m_ref;
+
+    wxSUPPRESS_GCC_PRIVATE_DTOR_WARNING(wxZipMemory)
 };
 
 wxZipMemory *wxZipMemory::Unique(size_t size)
@@ -505,7 +599,7 @@ wxZipMemory *wxZipMemory::Unique(size_t size)
     }
 
     if (zm->m_capacity < size) {
-        delete zm->m_data;
+        delete [] zm->m_data;
         zm->m_data = new char[size];
         zm->m_capacity = size;
     }
@@ -546,7 +640,7 @@ static void Unique(wxZipMemory*& zm, size_t size)
 // Collection of weak references to entries
 
 WX_DECLARE_HASH_MAP(long, wxZipEntry*, wxIntegerHash,
-                    wxIntegerEqual, _wxOffsetZipEntryMap);
+                    wxIntegerEqual, wxOffsetZipEntryMap_);
 
 class wxZipWeakLinks
 {
@@ -560,28 +654,32 @@ public:
 
     wxZipWeakLinks *AddEntry(wxZipEntry *entry, wxFileOffset key);
     void RemoveEntry(wxFileOffset key)
-        { m_entries.erase((_wxOffsetZipEntryMap::key_type)key); }
+        { m_entries.erase(wx_truncate_cast(key_type, key)); }
     wxZipEntry *GetEntry(wxFileOffset key) const;
     bool IsEmpty() const { return m_entries.empty(); }
 
 private:
     ~wxZipWeakLinks() { wxASSERT(IsEmpty()); }
 
+    typedef wxOffsetZipEntryMap_::key_type key_type;
+
     int m_ref;
-    _wxOffsetZipEntryMap m_entries;
+    wxOffsetZipEntryMap_ m_entries;
+
+    wxSUPPRESS_GCC_PRIVATE_DTOR_WARNING(wxZipWeakLinks)
 };
 
 wxZipWeakLinks *wxZipWeakLinks::AddEntry(wxZipEntry *entry, wxFileOffset key)
 {
-    m_entries[(_wxOffsetZipEntryMap::key_type)key] = entry;
+    m_entries[wx_truncate_cast(key_type, key)] = entry;
     m_ref++;
     return this;
 }
 
 wxZipEntry *wxZipWeakLinks::GetEntry(wxFileOffset key) const
 {
-    _wxOffsetZipEntryMap::const_iterator it =
-        m_entries.find((_wxOffsetZipEntryMap::key_type)key);
+    wxOffsetZipEntryMap_::const_iterator it =
+        m_entries.find(wx_truncate_cast(key_type, key));
     return it != m_entries.end() ?  it->second : NULL;
 }
 
@@ -684,18 +782,19 @@ wxString wxZipEntry::GetName(wxPathFormat format /*=wxPATH_NATIVE*/) const
 {
     bool isDir = IsDir() && !m_Name.empty();
 
+    // optimisations for common (and easy) cases
     switch (wxFileName::GetFormat(format)) {
         case wxPATH_DOS:
         {
-            wxString name(isDir ? m_Name + _T("\\") : m_Name);
-            for (size_t i = name.length() - 1; i > 0; --i)
-                if (name[i] == _T('/'))
-                    name[i] = _T('\\');
+            wxString name(isDir ? m_Name + wxT("\\") : m_Name);
+            for (size_t i = 0; i < name.length(); i++)
+                if (name[i] == wxT('/'))
+                    name[i] = wxT('\\');
             return name;
         }
 
         case wxPATH_UNIX:
-            return isDir ? m_Name + _T("/") : m_Name;
+            return isDir ? m_Name + wxT("/") : m_Name;
 
         default:
             ;
@@ -735,9 +834,9 @@ wxString wxZipEntry::GetInternalName(const wxString& name,
 
     while (!internal.empty() && *internal.begin() == '/')
         internal.erase(0, 1);
-    while (!internal.empty() && internal.compare(0, 2, _T("./")) == 0)
+    while (!internal.empty() && internal.compare(0, 2, wxT("./")) == 0)
         internal.erase(0, 2);
-    if (internal == _T(".") || internal == _T(".."))
+    if (internal == wxT(".") || internal == wxT(".."))
         internal = wxEmptyString;
 
     return internal;
@@ -869,7 +968,9 @@ size_t wxZipEntry::ReadLocal(wxInputStream& stream, wxMBConv& conv)
     wxUint16 nameLen, extraLen;
     wxUint32 compressedSize, size, crc;
 
-    wxDataInputStream ds(stream);
+    wxZipHeader ds(stream, LOCAL_SIZE - 4);
+    if (!ds)
+        return 0;
 
     ds >> m_VersionNeeded >> m_Flags >> m_Method;
     SetDateTime(wxDateTime().SetFromDOS(ds.Read32()));
@@ -885,11 +986,16 @@ size_t wxZipEntry::ReadLocal(wxInputStream& stream, wxMBConv& conv)
         m_Size = size;
 
     SetName(ReadString(stream, nameLen, conv), wxPATH_UNIX);
+    if (stream.LastRead() != nameLen + 0u)
+        return 0;
 
     if (extraLen || GetLocalExtraLen()) {
         Unique(m_LocalExtra, extraLen);
-        if (extraLen)
+        if (extraLen) {
             stream.Read(m_LocalExtra->GetData(), extraLen);
+            if (stream.LastRead() != extraLen + 0u)
+                return 0;
+        }
     }
 
     return LOCAL_SIZE + nameLen + extraLen;
@@ -898,10 +1004,10 @@ size_t wxZipEntry::ReadLocal(wxInputStream& stream, wxMBConv& conv)
 size_t wxZipEntry::WriteLocal(wxOutputStream& stream, wxMBConv& conv) const
 {
     wxString unixName = GetName(wxPATH_UNIX);
-    const wxWX2MBbuf name_buf = conv.cWX2MB(unixName);
+    const wxWX2MBbuf name_buf = unixName.mb_str(conv);
     const char *name = name_buf;
     if (!name) name = "";
-    wxUint16 nameLen = (wxUint16)strlen(name);
+    wxUint16 nameLen = wx_truncate_cast(wxUint16, strlen(name));
 
     wxDataOutputStream ds(stream);
 
@@ -909,11 +1015,13 @@ size_t wxZipEntry::WriteLocal(wxOutputStream& stream, wxMBConv& conv) const
     ds.Write32(GetDateTime().GetAsDOS());
 
     ds.Write32(m_Crc);
-    ds.Write32(m_CompressedSize != wxInvalidOffset ? (wxUint32)m_CompressedSize : 0);
-    ds.Write32(m_Size != wxInvalidOffset ? (wxUint32)m_Size : 0);
+    ds.Write32(m_CompressedSize != wxInvalidOffset ?
+               wx_truncate_cast(wxUint32, m_CompressedSize) : 0);
+    ds.Write32(m_Size != wxInvalidOffset ?
+               wx_truncate_cast(wxUint32, m_Size) : 0);
 
     ds << nameLen;
-    wxUint16 extraLen = (wxUint16)GetLocalExtraLen();
+    wxUint16 extraLen = wx_truncate_cast(wxUint16, GetLocalExtraLen());
     ds.Write16(extraLen);
 
     stream.Write(name, nameLen);
@@ -927,7 +1035,9 @@ size_t wxZipEntry::ReadCentral(wxInputStream& stream, wxMBConv& conv)
 {
     wxUint16 nameLen, extraLen, commentLen;
 
-    wxDataInputStream ds(stream);
+    wxZipHeader ds(stream, CENTRAL_SIZE - 4);
+    if (!ds)
+        return 0;
 
     ds >> m_VersionMadeBy >> m_SystemMadeBy;
 
@@ -944,17 +1054,25 @@ size_t wxZipEntry::ReadCentral(wxInputStream& stream, wxMBConv& conv)
     SetOffset(ds.Read32());
 
     SetName(ReadString(stream, nameLen, conv), wxPATH_UNIX);
+    if (stream.LastRead() != nameLen + 0u)
+        return 0;
 
     if (extraLen || GetExtraLen()) {
         Unique(m_Extra, extraLen);
-        if (extraLen)
+        if (extraLen) {
             stream.Read(m_Extra->GetData(), extraLen);
+            if (stream.LastRead() != extraLen + 0u)
+                return 0;
+        }
     }
 
-    if (commentLen)
+    if (commentLen) {
         m_Comment = ReadString(stream, commentLen, conv);
-    else
+        if (stream.LastRead() != commentLen + 0u)
+            return 0;
+    } else {
         m_Comment.clear();
+    }
 
     return CENTRAL_SIZE + nameLen + extraLen + commentLen;
 }
@@ -962,34 +1080,34 @@ size_t wxZipEntry::ReadCentral(wxInputStream& stream, wxMBConv& conv)
 size_t wxZipEntry::WriteCentral(wxOutputStream& stream, wxMBConv& conv) const
 {
     wxString unixName = GetName(wxPATH_UNIX);
-    const wxWX2MBbuf name_buf = conv.cWX2MB(unixName);
+    const wxWX2MBbuf name_buf = unixName.mb_str(conv);
     const char *name = name_buf;
     if (!name) name = "";
-    wxUint16 nameLen = (wxUint16)strlen(name);
+    wxUint16 nameLen = wx_truncate_cast(wxUint16, strlen(name));
 
-    const wxWX2MBbuf comment_buf = conv.cWX2MB(m_Comment);
+    const wxWX2MBbuf comment_buf = m_Comment.mb_str(conv);
     const char *comment = comment_buf;
     if (!comment) comment = "";
-    wxUint16 commentLen = (wxUint16)strlen(comment);
+    wxUint16 commentLen = wx_truncate_cast(wxUint16, strlen(comment));
 
-    wxUint16 extraLen = (wxUint16)GetExtraLen();
+    wxUint16 extraLen = wx_truncate_cast(wxUint16, GetExtraLen());
 
     wxDataOutputStream ds(stream);
 
     ds << CENTRAL_MAGIC << m_VersionMadeBy << m_SystemMadeBy;
 
-    ds.Write16((wxUint16)GetVersionNeeded());
-    ds.Write16((wxUint16)GetFlags());
-    ds.Write16((wxUint16)GetMethod());
+    ds.Write16(wx_truncate_cast(wxUint16, GetVersionNeeded()));
+    ds.Write16(wx_truncate_cast(wxUint16, GetFlags()));
+    ds.Write16(wx_truncate_cast(wxUint16, GetMethod()));
     ds.Write32(GetDateTime().GetAsDOS());
     ds.Write32(GetCrc());
-    ds.Write32((wxUint32)GetCompressedSize());
-    ds.Write32((wxUint32)GetSize());
+    ds.Write32(wx_truncate_cast(wxUint32, GetCompressedSize()));
+    ds.Write32(wx_truncate_cast(wxUint32, GetSize()));
     ds.Write16(nameLen);
     ds.Write16(extraLen);
 
     ds << commentLen << m_DiskStart << m_InternalAttributes
-       << m_ExternalAttributes << (wxUint32)GetOffset();
+       << m_ExternalAttributes << wx_truncate_cast(wxUint32, GetOffset());
 
     stream.Write(name, nameLen);
     if (extraLen)
@@ -1006,7 +1124,9 @@ size_t wxZipEntry::WriteCentral(wxOutputStream& stream, wxMBConv& conv) const
 //
 size_t wxZipEntry::ReadDescriptor(wxInputStream& stream)
 {
-    wxDataInputStream ds(stream);
+    wxZipHeader ds(stream, SUMS_SIZE);
+    if (!ds)
+        return 0;
 
     m_Crc = ds.Read32();
     m_CompressedSize = ds.Read32();
@@ -1015,23 +1135,24 @@ size_t wxZipEntry::ReadDescriptor(wxInputStream& stream)
     // 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);
+        wxZipHeader buf(stream, 8);
+        wxUint32 u1 = buf.GetSize() >= 4 ? buf.Read32() : (wxUint32)LOCAL_MAGIC;
+        wxUint32 u2 = buf.GetSize() == 8 ? buf.Read32() : 0;
 
         // look for the signature of the following record to decide which
         if ((u1 == LOCAL_MAGIC || u1 == CENTRAL_MAGIC) &&
             (u2 != LOCAL_MAGIC && u2 != CENTRAL_MAGIC))
         {
             // it's a pkzip style record after all!
-            stream.Ungetch(buf, sizeof(buf));
+            if (buf.GetSize() > 0)
+                stream.Ungetch(buf.GetData(), buf.GetSize());
         }
         else
         {
             // it's an info-zip record as expected
-            stream.Ungetch(buf + 4, sizeof(buf) - 4);
-            m_Crc = (wxUint32)m_CompressedSize;
+            if (buf.GetSize() > 4)
+                stream.Ungetch(buf.GetData() + 4, buf.GetSize() - 4);
+            m_Crc = wx_truncate_cast(wxUint32, m_CompressedSize);
             m_CompressedSize = m_Size;
             m_Size = u1;
             return SUMS_SIZE + 4;
@@ -1051,8 +1172,8 @@ size_t wxZipEntry::WriteDescriptor(wxOutputStream& stream, wxUint32 crc,
     wxDataOutputStream ds(stream);
 
     ds.Write32(crc);
-    ds.Write32((wxUint32)compressedSize);
-    ds.Write32((wxUint32)size);
+    ds.Write32(wx_truncate_cast(wxUint32, compressedSize));
+    ds.Write32(wx_truncate_cast(wxUint32, size));
 
     return SUMS_SIZE;
 }
@@ -1074,13 +1195,20 @@ public:
     wxFileOffset GetOffset() const              { return m_Offset; }
     wxString GetComment() const                 { return m_Comment; }
 
-    void SetDiskNumber(int num)                 { m_DiskNumber = (wxUint16)num; }
-    void SetStartDisk(int num)                  { m_StartDisk = (wxUint16)num; }
-    void SetEntriesHere(int num)                { m_EntriesHere = (wxUint16)num; }
-    void SetTotalEntries(int num)               { m_TotalEntries = (wxUint16)num; }
-    void SetSize(wxFileOffset size)             { m_Size = (wxUint32)size; }
-    void SetOffset(wxFileOffset offset)         { m_Offset = (wxUint32)offset; }
-    void SetComment(const wxString& comment)    { m_Comment = comment; }
+    void SetDiskNumber(int num)
+        { m_DiskNumber = wx_truncate_cast(wxUint16, num); }
+    void SetStartDisk(int num)
+        { m_StartDisk = wx_truncate_cast(wxUint16, num); }
+    void SetEntriesHere(int num)
+        { m_EntriesHere = wx_truncate_cast(wxUint16, num); }
+    void SetTotalEntries(int num)
+        { m_TotalEntries = wx_truncate_cast(wxUint16, num); }
+    void SetSize(wxFileOffset size)
+        { m_Size = wx_truncate_cast(wxUint32, size); }
+    void SetOffset(wxFileOffset offset)
+        { m_Offset = wx_truncate_cast(wxUint32, offset); }
+    void SetComment(const wxString& comment)
+        { m_Comment = comment; }
 
     bool Read(wxInputStream& stream, wxMBConv& conv);
     bool Write(wxOutputStream& stream, wxMBConv& conv) const;
@@ -1107,7 +1235,7 @@ wxZipEndRec::wxZipEndRec()
 
 bool wxZipEndRec::Write(wxOutputStream& stream, wxMBConv& conv) const
 {
-    const wxWX2MBbuf comment_buf = conv.cWX2MB(m_Comment);
+    const wxWX2MBbuf comment_buf = m_Comment.mb_str(conv);
     const char *comment = comment_buf;
     if (!comment) comment = "";
     wxUint16 commentLen = (wxUint16)strlen(comment);
@@ -1124,23 +1252,28 @@ bool wxZipEndRec::Write(wxOutputStream& stream, wxMBConv& conv) const
 
 bool wxZipEndRec::Read(wxInputStream& stream, wxMBConv& conv)
 {
-    wxDataInputStream ds(stream);
+    wxZipHeader ds(stream, END_SIZE - 4);
+    if (!ds)
+        return false;
+
     wxUint16 commentLen;
 
     ds >> m_DiskNumber >> m_StartDisk >> m_EntriesHere
        >> m_TotalEntries >> m_Size >> m_Offset >> commentLen;
 
-    if (commentLen)
+    if (commentLen) {
         m_Comment = ReadString(stream, commentLen, conv);
+        if (stream.LastRead() != commentLen + 0u)
+            return false;
+    }
 
-    if (stream.IsOk())
-        if (m_DiskNumber == 0 && m_StartDisk == 0 &&
-                m_EntriesHere == m_TotalEntries)
-            return true;
-        else
-            wxLogError(_("unsupported zip archive"));
+    if (m_DiskNumber != 0 || m_StartDisk != 0 ||
+            m_EntriesHere != m_TotalEntries)
+    {
+        wxLogWarning(_("assuming this is a multi-part zip concatenated"));
+    }
 
-    return false;
+    return true;
 }
 
 
@@ -1165,14 +1298,17 @@ private:
 
     int m_ref;
     wxZipOutputStream *m_stream;
+
+    wxSUPPRESS_GCC_PRIVATE_DTOR_WARNING(wxZipStreamLink)
 };
 
 
 /////////////////////////////////////////////////////////////////////////////
 // Input stream
 
-wxDECLARE_SCOPED_PTR(wxZipEntry, _wxZipEntryPtr)
-wxDEFINE_SCOPED_PTR (wxZipEntry, _wxZipEntryPtr)
+// leave the default wxZipEntryPtr free for users
+wxDECLARE_SCOPED_PTR(wxZipEntry, wxZipEntryPtr_)
+wxDEFINE_SCOPED_PTR (wxZipEntry, wxZipEntryPtr_)
 
 // constructor
 //
@@ -1180,22 +1316,32 @@ wxZipInputStream::wxZipInputStream(wxInputStream& stream,
                                    wxMBConv& conv /*=wxConvLocal*/)
   : wxArchiveInputStream(stream, conv)
 {
-    m_ffile = NULL;
     Init();
 }
 
-// Compatibility constructor
+wxZipInputStream::wxZipInputStream(wxInputStream *stream,
+                                   wxMBConv& conv /*=wxConvLocal*/)
+  : wxArchiveInputStream(stream, conv)
+{
+    Init();
+}
+
+#if WXWIN_COMPATIBILITY_2_6 && wxUSE_FFILE
+
+// Part of the compatibility constructor, which has been made inline to
+// avoid a problem with it not being exported by mingw 3.2.3
 //
-wxZipInputStream::wxZipInputStream(const wxString& archive,
-                                   const wxString& file)
-  : wxArchiveInputStream(OpenFile(archive), wxConvLocal)
+void wxZipInputStream::Init(const wxString& file)
 {
     // no error messages
     wxLogNull nolog;
     Init();
-    _wxZipEntryPtr entry;
+    m_allowSeeking = true;
+    wxFFileInputStream *ffile;
+    ffile = static_cast<wxFFileInputStream*>(m_parent_i_stream);
+    wxZipEntryPtr_ entry;
 
-    if (m_ffile->Ok()) {
+    if (ffile->IsOk()) {
         do {
             entry.reset(GetNextEntry());
         }
@@ -1206,13 +1352,14 @@ wxZipInputStream::wxZipInputStream(const wxString& archive,
         m_lasterror = wxSTREAM_READ_ERROR;
 }
 
-wxInputStream& wxZipInputStream::OpenFile(const wxString& archive)
+wxInputStream* wxZipInputStream::OpenFile(const wxString& archive)
 {
     wxLogNull nolog;
-    m_ffile = new wxFFileInputStream(archive);
-    return *m_ffile;
+    return new wxFFileInputStream(archive);
 }
 
+#endif // WXWIN_COMPATIBILITY_2_6 && wxUSE_FFILE
+
 void wxZipInputStream::Init()
 {
     m_store = new wxStoredInputStream(*m_parent_i_stream);
@@ -1229,6 +1376,9 @@ void wxZipInputStream::Init()
     m_signature = 0;
     m_TotalEntries = 0;
     m_lasterror = m_parent_i_stream->GetLastError();
+#if WXWIN_COMPATIBILITY_2_6
+    m_allowSeeking = false;
+#endif
 }
 
 wxZipInputStream::~wxZipInputStream()
@@ -1238,7 +1388,6 @@ wxZipInputStream::~wxZipInputStream()
     delete m_store;
     delete m_inflate;
     delete m_rawin;
-    delete m_ffile;
 
     m_weaklinks->Release(this);
 
@@ -1300,9 +1449,7 @@ bool wxZipInputStream::LoadEndRecord()
         else {
             wxLogNull nolog;
             wxFileOffset pos = m_parent_i_stream->TellI();
-            // FIXME
-            //if (pos != wxInvalidOffset)
-            if (pos >= 0 && pos <= LONG_MAX)
+            if (pos != wxInvalidOffset)
                 m_offsetAdjustment = m_position = pos;
             return true;
         }
@@ -1312,23 +1459,19 @@ bool wxZipInputStream::LoadEndRecord()
 
     // 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"));
-    }
+    if (!endrec.Read(*m_parent_i_stream, GetConv()))
+        return false;
 
     m_TotalEntries = endrec.GetTotalEntries();
     m_Comment = endrec.GetComment();
 
+    wxUint32 magic = m_TotalEntries ? CENTRAL_MAGIC : END_MAGIC;
+
     // Now find the central-directory. we have the file offset of
     // the CD, so look there first.
     if (m_parent_i_stream->SeekI(endrec.GetOffset()) != wxInvalidOffset &&
-            ReadSignature() == CENTRAL_MAGIC) {
-        m_signature = CENTRAL_MAGIC;
+            ReadSignature() == magic) {
+        m_signature = magic;
         m_position = endrec.GetOffset();
         m_offsetAdjustment = 0;
         return true;
@@ -1338,8 +1481,8 @@ bool wxZipInputStream::LoadEndRecord()
     // 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;
+            != wxInvalidOffset && ReadSignature() == magic) {
+        m_signature = magic;
         m_position = endPos - endrec.GetSize();
         m_offsetAdjustment = m_position - endrec.GetOffset();
         return true;
@@ -1383,7 +1526,8 @@ bool wxZipInputStream::FindEndRecord()
     wxFileOffset minpos = wxMax(pos - 65535L, 0);
 
     while (pos > minpos) {
-        size_t len = (size_t)(pos - wxMax(pos - (BUFSIZE - 3), minpos));
+        size_t len = wx_truncate_cast(size_t,
+                        pos - wxMax(pos - (BUFSIZE - 3), minpos));
         memcpy(buf.data() + len, buf, 3);
         pos -= len;
 
@@ -1416,7 +1560,7 @@ wxZipEntry *wxZipInputStream::GetNextEntry()
     if (!IsOk())
         return NULL;
 
-    _wxZipEntryPtr entry(new wxZipEntry(m_entry));
+    wxZipEntryPtr_ entry(new wxZipEntry(m_entry));
     entry->m_backlink = m_weaklinks->AddEntry(entry.get(), entry->GetKey());
     return entry.release();
 }
@@ -1437,12 +1581,13 @@ wxStreamError wxZipInputStream::ReadCentral()
     if (QuietSeek(*m_parent_i_stream, m_position + 4) == wxInvalidOffset)
         return wxSTREAM_READ_ERROR;
 
-    m_position += m_entry.ReadCentral(*m_parent_i_stream, GetConv());
-    if (m_parent_i_stream->GetLastError() == wxSTREAM_READ_ERROR) {
+    size_t size = m_entry.ReadCentral(*m_parent_i_stream, GetConv());
+    if (!size) {
         m_signature = 0;
         return wxSTREAM_READ_ERROR;
     }
 
+    m_position += size;
     m_signature = ReadSignature();
 
     if (m_offsetAdjustment)
@@ -1471,9 +1616,10 @@ wxStreamError wxZipInputStream::ReadLocal(bool readEndRec /*=false*/)
         if (m_weaklinks->IsEmpty() && m_streamlink == NULL)
             return wxSTREAM_EOF;
 
-        m_position += m_entry.ReadCentral(*m_parent_i_stream, GetConv());
+        size_t size = m_entry.ReadCentral(*m_parent_i_stream, GetConv());
+        m_position += size;
         m_signature = 0;
-        if (m_parent_i_stream->GetLastError() == wxSTREAM_READ_ERROR)
+        if (!size)
             return wxSTREAM_READ_ERROR;
 
         wxZipEntry *entry = m_weaklinks->GetEntry(m_entry.GetOffset());
@@ -1507,22 +1653,20 @@ wxStreamError wxZipInputStream::ReadLocal(bool readEndRec /*=false*/)
         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_signature == LOCAL_MAGIC) {
+        m_headerSize = m_entry.ReadLocal(*m_parent_i_stream, GetConv());
+        m_signature = 0;
+        m_entry.SetOffset(m_position);
+        m_entry.SetKey(m_position);
 
-    if (m_parent_i_stream->GetLastError() == wxSTREAM_READ_ERROR) {
-        return wxSTREAM_READ_ERROR;
-    } else {
-        m_TotalEntries++;
-        return wxSTREAM_NO_ERROR;
+        if (m_headerSize) {
+            m_TotalEntries++;
+            return wxSTREAM_NO_ERROR;
+        }
     }
+
+    wxLogError(_("error reading zip local header"));
+    return wxSTREAM_READ_ERROR;
 }
 
 wxUint32 wxZipInputStream::ReadSignature()
@@ -1547,7 +1691,8 @@ bool wxZipInputStream::DoOpen(wxZipEntry *entry, bool raw)
             return false;
     if (m_lasterror == wxSTREAM_READ_ERROR)
         return false;
-    wxCHECK(!IsOpened(), false);
+    if (IsOpened())
+        CloseEntry();
 
     m_raw = raw;
 
@@ -1575,7 +1720,7 @@ bool wxZipInputStream::DoOpen(wxZipEntry *entry, bool raw)
 
     if (m_parentSeekable || AtHeader()) {
         m_headerSize = m_entry.ReadLocal(*m_parent_i_stream, GetConv());
-        if (m_parentSeekable) {
+        if (m_headerSize && m_parentSeekable) {
             wxZipEntry *ref = m_weaklinks->GetEntry(m_entry.GetKey());
             if (ref) {
                 Copy(ref->m_LocalExtra, m_entry.m_LocalExtra);
@@ -1589,7 +1734,8 @@ bool wxZipInputStream::DoOpen(wxZipEntry *entry, bool raw)
         }
     }
 
-    m_lasterror = m_parent_i_stream->GetLastError();
+    if (m_headerSize)
+        m_lasterror = wxSTREAM_NO_ERROR;
     return IsOk();
 }
 
@@ -1627,7 +1773,7 @@ bool wxZipInputStream::OpenDecompressor(bool raw /*=false*/)
     return IsOk();
 }
 
-// Can be overriden to add support for additional decompression methods
+// Can be overridden to add support for additional decompression methods
 //
 wxInputStream *wxZipInputStream::OpenDecompressor(wxInputStream& stream)
 {
@@ -1708,7 +1854,8 @@ size_t wxZipInputStream::OnSysRead(void *buffer, size_t size)
     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 (count < size)
+        m_lasterror = m_decomp->GetLastError();
 
     if (Eof()) {
         if ((m_entry.GetFlags() & wxZIP_SUMS_FOLLOW) != 0) {
@@ -1726,15 +1873,19 @@ size_t wxZipInputStream::OnSysRead(void *buffer, size_t size)
         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;
+            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;
             }
         }
     }
@@ -1742,11 +1893,15 @@ size_t wxZipInputStream::OnSysRead(void *buffer, size_t size)
     return count;
 }
 
+#if WXWIN_COMPATIBILITY_2_6
+
 // Borrowed from VS's zip stream (c) 1999 Vaclav Slavik
 //
 wxFileOffset wxZipInputStream::OnSysSeek(wxFileOffset seek, wxSeekMode mode)
 {
-    if (!m_ffile)
+    // seeking works when the stream is created with the compatibility
+    // constructor
+    if (!m_allowSeeking)
         return wxInvalidOffset;
     if (!IsOpened())
         if ((AtHeader() && !DoOpen()) || !OpenDecompressor())
@@ -1770,31 +1925,30 @@ wxFileOffset wxZipInputStream::OnSysSeek(wxFileOffset seek, wxSeekMode mode)
         default : nextpos = pos; break; /* just to fool compiler, never happens */
     }
 
-    size_t toskip wxDUMMY_INITIALIZE(0);
+    wxFileOffset toskip wxDUMMY_INITIALIZE(0);
     if ( nextpos >= pos )
     {
-        toskip = (size_t)(nextpos - pos);
+        toskip = nextpos - pos;
     }
     else
     {
         wxZipEntry current(m_entry);
-        CloseEntry();
         if (!OpenEntry(current))
         {
             m_lasterror = wxSTREAM_READ_ERROR;
             return pos;
         }
-        toskip = (size_t)nextpos;
+        toskip = nextpos;
     }
 
     if ( toskip > 0 )
     {
-        const size_t BUFSIZE = 4096;
+        const int BUFSIZE = 4096;
         size_t sz;
         char buffer[BUFSIZE];
         while ( toskip > 0 )
         {
-            sz = wxMin(toskip, BUFSIZE);
+            sz = wx_truncate_cast(size_t, wxMin(toskip, BUFSIZE));
             Read(buffer, sz);
             toskip -= sz;
         }
@@ -1804,37 +1958,53 @@ wxFileOffset wxZipInputStream::OnSysSeek(wxFileOffset seek, wxSeekMode mode)
     return pos;
 }
 
+#endif // WXWIN_COMPATIBILITY_2_6
+
 
 /////////////////////////////////////////////////////////////////////////////
 // Output stream
 
 #include "wx/listimpl.cpp"
-WX_DEFINE_LIST(_wxZipEntryList);
+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)
+  : wxArchiveOutputStream(stream, conv)
+{
+    Init(level);
+}
+
+wxZipOutputStream::wxZipOutputStream(wxOutputStream *stream,
+                                     int level      /*=-1*/,
+                                     wxMBConv& conv /*=wxConvLocal*/)
+  : wxArchiveOutputStream(stream, conv)
 {
+    Init(level);
+}
+
+void wxZipOutputStream::Init(int level)
+{
+    m_store = new wxStoredOutputStream(*m_parent_o_stream);
+    m_deflate = NULL;
+    m_backlink = NULL;
+    m_initialData = new char[OUTPUT_LATENCY];
+    m_initialSize = 0;
+    m_pending = NULL;
+    m_raw = false;
+    m_headerOffset = 0;
+    m_headerSize = 0;
+    m_entrySize = 0;
+    m_comp = NULL;
+    m_level = level;
+    m_offsetAdjustment = wxInvalidOffset;
+    m_endrecWritten = false;
 }
 
 wxZipOutputStream::~wxZipOutputStream()
 {
     Close();
-    WX_CLEAR_LIST(_wxZipEntryList, m_entries);
+    WX_CLEAR_LIST(wxZipEntryList_, m_entries);
     delete m_store;
     delete m_deflate;
     delete m_pending;
@@ -1863,7 +2033,7 @@ bool wxZipOutputStream::PutNextDirEntry(
 bool wxZipOutputStream::CopyEntry(wxZipEntry *entry,
                                   wxZipInputStream& inputStream)
 {
-    _wxZipEntryPtr e(entry);
+    wxZipEntryPtr_ e(entry);
 
     return
         inputStream.DoOpen(e.get(), true) &&
@@ -1889,7 +2059,7 @@ bool wxZipOutputStream::CopyEntry(wxArchiveEntry *entry,
         return false;
     }
 
-    return CopyEntry(zipEntry, wx_static_cast(wxZipInputStream&, stream));
+    return CopyEntry(zipEntry, static_cast<wxZipInputStream&>(stream));
 }
 
 bool wxZipOutputStream::CopyArchiveMetaData(wxZipInputStream& inputStream)
@@ -1903,7 +2073,7 @@ bool wxZipOutputStream::CopyArchiveMetaData(wxZipInputStream& inputStream)
 
 bool wxZipOutputStream::CopyArchiveMetaData(wxArchiveInputStream& stream)
 {
-    return CopyArchiveMetaData(wx_static_cast(wxZipInputStream&, stream));
+    return CopyArchiveMetaData(static_cast<wxZipInputStream&>(stream));
 }
 
 void wxZipOutputStream::SetLevel(int level)
@@ -1930,14 +2100,18 @@ bool wxZipOutputStream::DoCreate(wxZipEntry *entry, bool raw /*=false*/)
 
     // and if this is the first entry test for seekability
     if (m_headerOffset == 0 && m_parent_o_stream->IsSeekable()) {
+#if wxUSE_LOG
         bool logging = wxLog::IsEnabled();
         wxLogNull nolog;
+#endif // wxUSE_LOG
         wxFileOffset here = m_parent_o_stream->TellO();
 
         if (here != wxInvalidOffset && here >= 4) {
             if (m_parent_o_stream->SeekO(here - 4) == here - 4) {
                 m_offsetAdjustment = here - 4;
+#if wxUSE_LOG
                 wxLog::EnableLogging(logging);
+#endif // wxUSE_LOG
                 m_parent_o_stream->SeekO(here);
             }
         }
@@ -1954,7 +2128,7 @@ bool wxZipOutputStream::DoCreate(wxZipEntry *entry, bool raw /*=false*/)
     return true;
 }
 
-// Can be overriden to add support for additional compression methods
+// Can be overridden to add support for additional compression methods
 //
 wxOutputStream *wxZipOutputStream::OpenCompressor(
     wxOutputStream& stream,
@@ -2029,7 +2203,7 @@ bool wxZipOutputStream::CloseCompressor(wxOutputStream *comp)
 void wxZipOutputStream::CreatePendingEntry(const void *buffer, size_t size)
 {
     wxASSERT(IsOk() && m_pending && !m_comp);
-    _wxZipEntryPtr spPending(m_pending);
+    wxZipEntryPtr_ spPending(m_pending);
     m_pending = NULL;
 
     Buffer bufs[] = {
@@ -2070,7 +2244,7 @@ void wxZipOutputStream::CreatePendingEntry(const void *buffer, size_t size)
 void wxZipOutputStream::CreatePendingEntry()
 {
     wxASSERT(IsOk() && m_pending && !m_comp);
-    _wxZipEntryPtr spPending(m_pending);
+    wxZipEntryPtr_ spPending(m_pending);
     m_pending = NULL;
     m_lasterror = wxSTREAM_WRITE_ERROR;
 
@@ -2124,8 +2298,12 @@ bool wxZipOutputStream::Close()
 {
     CloseEntry();
 
-    if (m_lasterror == wxSTREAM_WRITE_ERROR || m_entries.size() == 0)
+    if (m_lasterror == wxSTREAM_WRITE_ERROR
+        || (m_entries.size() == 0 && m_endrecWritten))
+    {
+        wxFilterOutputStream::Close();
         return false;
+    }
 
     wxZipEndRec endrec;
 
@@ -2134,7 +2312,7 @@ bool wxZipOutputStream::Close()
     endrec.SetOffset(m_headerOffset);
     endrec.SetComment(m_Comment);
 
-    _wxZipEntryList::iterator it;
+    wxZipEntryList_::iterator it;
     wxFileOffset size = 0;
 
     for (it = m_entries.begin(); it != m_entries.end(); ++it) {
@@ -2147,7 +2325,9 @@ bool wxZipOutputStream::Close()
     endrec.Write(*m_parent_o_stream, GetConv());
 
     m_lasterror = m_parent_o_stream->GetLastError();
-    if (!IsOk())
+    m_endrecWritten = true;
+
+    if (!wxFilterOutputStream::Close() || !IsOk())
         return false;
     m_lasterror = wxSTREAM_EOF;
     return true;
@@ -2256,4 +2436,4 @@ size_t wxZipOutputStream::OnSysWrite(const void *buffer, size_t size)
     return m_comp->LastWrite();
 }
 
-#endif // wxUSE_ZLIB && wxUSE_STREAMS && wxUSE_ZIPSTREAM
+#endif // wxUSE_ZIPSTREAM