Merge branch 'zip64-fix'

Notably fix ZIP64 creation with individual files larger than 4GB.

See https://github.com/wxWidgets/wxWidgets/pull/730
This commit is contained in:
Vadim Zeitlin
2018-02-16 16:03:32 +01:00
3 changed files with 128 additions and 25 deletions

View File

@@ -110,6 +110,14 @@ enum wxZipFlags
wxZIP_RESERVED = 0xF000
};
enum wxZipArchiveFormat
{
/// Default zip format
wxZIP_FORMAT_DEFAULT,
/// ZIP64 format
wxZIP_FORMAT_ZIP64
};
// Forward decls
//
class WXDLLIMPEXP_FWD_BASE wxZipEntry;
@@ -131,6 +139,8 @@ public:
/////////////////////////////////////////////////////////////////////////////
// Zip Entry - holds the meta data for a file in the zip
class wxDataOutputStream;
class WXDLLIMPEXP_BASE wxZipEntry : public wxArchiveEntry
{
public:
@@ -225,7 +235,7 @@ private:
wxArchiveEntry* DoClone() const wxOVERRIDE { return ZipClone(); }
size_t ReadLocal(wxInputStream& stream, wxMBConv& conv);
size_t WriteLocal(wxOutputStream& stream, wxMBConv& conv) const;
size_t WriteLocal(wxOutputStream& stream, wxMBConv& conv, wxZipArchiveFormat zipFormat);
size_t ReadCentral(wxInputStream& stream, wxMBConv& conv);
size_t WriteCentral(wxOutputStream& stream, wxMBConv& conv) const;
@@ -234,6 +244,9 @@ private:
size_t WriteDescriptor(wxOutputStream& stream, wxUint32 crc,
wxFileOffset compressedSize, wxFileOffset size);
void WriteLocalFileSizes(wxDataOutputStream& ds) const;
void WriteLocalZip64ExtraInfo(wxOutputStream& stream) const;
bool LoadExtraInfo(const char* extraData, wxUint16 extraLen, bool localInfo);
wxUint16 GetInternalFlags(bool checkForUTF8) const;
@@ -256,6 +269,7 @@ private:
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
wxUint16 m_z64infoOffset; // Offset of ZIP64 local extra data for file sizes
class wxZipMemory *m_Extra;
class wxZipMemory *m_LocalExtra;
@@ -307,6 +321,9 @@ public:
int GetLevel() const { return m_level; }
void WXZIPFIX SetLevel(int level);
void SetFormat(wxZipArchiveFormat format) { m_format = format; };
wxZipArchiveFormat GetFormat() const { return m_format; };
protected:
virtual size_t WXZIPFIX OnSysWrite(const void *buffer, size_t size) wxOVERRIDE;
virtual wxFileOffset OnSysTell() const wxOVERRIDE { return m_entrySize; }
@@ -351,6 +368,7 @@ private:
wxFileOffset m_offsetAdjustment;
wxString m_Comment;
bool m_endrecWritten;
wxZipArchiveFormat m_format;
wxDECLARE_NO_COPY_CLASS(wxZipOutputStream);
};

View File

@@ -80,6 +80,19 @@ enum wxZipFlags
wxZIP_RESERVED = 0xF000
};
/**
Zip archive format
@since 3.1.1
*/
enum wxZipArchiveFormat
{
/// Default zip format: use ZIP64 if it is determined to be necessary.
wxZIP_FORMAT_DEFAULT,
/// ZIP64 format: force the use of ZIP64 format.
wxZIP_FORMAT_ZIP64
};
/**
@class wxZipNotifier
@@ -558,11 +571,17 @@ public:
//@{
/**
Takes ownership of @a entry and uses it to create a new entry in the zip.
If you do not specify a size and plan to put more than 4GB data into the
entry see SetFormat()
*/
bool PutNextEntry(wxZipEntry* entry);
/**
Create a new entry with the given name, timestamp and size.
If you do not specify a size and plan to put more than 4GB data into the
entry see SetFormat()
*/
bool PutNextEntry(const wxString& name,
const wxDateTime& dt = wxDateTime::Now(),
@@ -574,5 +593,33 @@ public:
It is written at the end of the zip.
*/
void SetComment(const wxString& comment);
/**
Set the format of the archive.
The normal zip format is limited to single files and the complete
archive smaller than 4GB with less then 65k files. If any of these
limits are exceeded, this class will automatically create a ZIP64 file,
so in most situations calling SetFormat() is not necessary.
However to support single entries with more than 4GB of data
(compressed or original) whose sizes are unknown when adding the
entry with PutNextEntry(), the format has to be set to
wxZIP_FORMAT_ZIP64 before adding such entries.
@since 3.1.1
*/
void SetFormat(wxZipArchiveFormat format);
/**
Get the format of the archive.
This returns the value passed to SetFormat() and not necessarily the
actual archive format (e.g. this method could return
wxZIP_FORMAT_DEFAULT even if a ZIP64 will end up being created).
@since 3.1.1
wxZipArchiveFormat GetFormat() const;
};

View File

@@ -133,7 +133,7 @@ static inline wxUint64 CrackUint64(const char *m)
(static_cast<wxUint64>(n[6]) << 48) |
(static_cast<wxUint64>(n[5]) << 40) |
(static_cast<wxUint64>(n[4]) << 32) |
(n[3] << 24) | (n[2] << 16) | (n[1] << 8) | n[0];
(static_cast<wxUint64>(n[3]) << 24) | (n[2] << 16) | (n[1] << 8) | n[0];
}
// Decode a little endian wxUint32 number from a character array
@@ -757,6 +757,7 @@ wxZipEntry::wxZipEntry(
m_DiskStart(0),
m_InternalAttributes(0),
m_ExternalAttributes(0),
m_z64infoOffset(0),
m_Extra(NULL),
m_LocalExtra(NULL),
m_zipnotifier(NULL),
@@ -792,6 +793,7 @@ wxZipEntry::wxZipEntry(const wxZipEntry& e)
m_DiskStart(e.m_DiskStart),
m_InternalAttributes(e.m_InternalAttributes),
m_ExternalAttributes(e.m_ExternalAttributes),
m_z64infoOffset(0),
m_Extra(AddRef(e.m_Extra)),
m_LocalExtra(AddRef(e.m_LocalExtra)),
m_zipnotifier(NULL),
@@ -1127,7 +1129,7 @@ size_t wxZipEntry::ReadLocal(wxInputStream& stream, wxMBConv& conv)
return LOCAL_SIZE + nameLen + extraLen;
}
size_t wxZipEntry::WriteLocal(wxOutputStream& stream, wxMBConv& conv) const
size_t wxZipEntry::WriteLocal(wxOutputStream& stream, wxMBConv& conv, wxZipArchiveFormat zipFormat)
{
wxString unixName = GetName(wxPATH_UNIX);
const wxWX2MBbuf name_buf = unixName.mb_str(conv);
@@ -1135,9 +1137,11 @@ size_t wxZipEntry::WriteLocal(wxOutputStream& stream, wxMBConv& conv) const
if (!name) name = "";
wxUint16 nameLen = wx_truncate_cast(wxUint16, strlen(name));
bool z64Required = m_CompressedSize > 0xffffffff || m_Size > 0xffffffff;
if ( (zipFormat == wxZIP_FORMAT_ZIP64) ||
m_CompressedSize >= 0xffffffff || m_Size >= 0xffffffff )
m_z64infoOffset = LOCAL_SIZE + nameLen;
wxUint16 versionNeeded =
z64Required ? Z64_VERSION_NEEDED_TO_EXTRACT : int(m_VersionNeeded);
(m_z64infoOffset > 0) ? Z64_VERSION_NEEDED_TO_EXTRACT : int(m_VersionNeeded);
wxDataOutputStream ds(stream);
@@ -1145,25 +1149,17 @@ size_t wxZipEntry::WriteLocal(wxOutputStream& stream, wxMBConv& conv) const
ds.Write32(GetDateTime().GetAsDOS());
ds.Write32(m_Crc);
ds.Write32(m_CompressedSize != wxInvalidOffset ?
LimitUint32(m_CompressedSize) : 0);
ds.Write32(m_Size != wxInvalidOffset ?
LimitUint32(m_Size) : 0);
WriteLocalFileSizes(ds);
ds << nameLen;
wxUint16 extraLen = wx_truncate_cast(wxUint16, GetLocalExtraLen());
if (z64Required)
if (m_z64infoOffset)
extraLen += 20; // tag and 2x64bit file sizes
ds.Write16(extraLen);
stream.Write(name, nameLen);
if (z64Required)
{
ds.Write16(1); // id
ds.Write16(16); // record size
ds.Write64(static_cast<wxInt64>(m_CompressedSize));
ds.Write64(static_cast<wxInt64>(m_Size));
}
if (m_z64infoOffset > 0)
WriteLocalZip64ExtraInfo(stream);
if (GetLocalExtraLen())
stream.Write(m_LocalExtra->GetData(), GetLocalExtraLen());
@@ -1264,7 +1260,7 @@ size_t wxZipEntry::WriteCentral(wxOutputStream& stream, wxMBConv& conv) const
}
wxUint16 versionNeeded =
(z64Required) ? Z64_VERSION_NEEDED_TO_EXTRACT : GetVersionNeeded();
(z64Required || m_z64infoOffset) ? Z64_VERSION_NEEDED_TO_EXTRACT : GetVersionNeeded();
wxDataOutputStream ds(stream);
@@ -1288,10 +1284,10 @@ size_t wxZipEntry::WriteCentral(wxOutputStream& stream, wxMBConv& conv) const
{
ds.Write16(1); // tag
ds.Write16(z64InfoLen); // record size
if (m_CompressedSize > 0xffffffff)
ds.Write64(static_cast<wxInt64>(m_CompressedSize));
if (m_Size > 0xffffffff)
ds.Write64(static_cast<wxInt64>(m_Size));
if (m_CompressedSize > 0xffffffff)
ds.Write64(static_cast<wxInt64>(m_CompressedSize));
if (m_Offset > 0xffffffff)
ds.Write64(static_cast<wxInt64>(m_Offset));
}
@@ -1357,12 +1353,40 @@ size_t wxZipEntry::WriteDescriptor(wxOutputStream& stream, wxUint32 crc,
wxDataOutputStream ds(stream);
ds.Write32(crc);
ds.Write32(wx_truncate_cast(wxUint32, compressedSize));
ds.Write32(wx_truncate_cast(wxUint32, size));
WriteLocalFileSizes(ds);
return SUMS_SIZE;
}
void wxZipEntry::WriteLocalFileSizes(wxDataOutputStream& ds) const
{
wxUint32 compressedSize;
wxUint32 size;
if (m_z64infoOffset > 0)
{
compressedSize = 0xffffffff;
size = 0xffffffff;
}
else
{
compressedSize = m_CompressedSize != wxInvalidOffset ?
wx_truncate_cast(wxUint32, m_CompressedSize) : 0;
size = m_Size != wxInvalidOffset ?
wx_truncate_cast(wxUint32, m_Size) : 0;
}
ds.Write32(compressedSize);
ds.Write32(size);
}
void wxZipEntry::WriteLocalZip64ExtraInfo(wxOutputStream& stream) const
{
wxDataOutputStream ds(stream);
ds.Write16(1); // id
ds.Write16(16); // record size
ds.Write64(static_cast<wxUint64>(m_Size));
ds.Write64(static_cast<wxUint64>(m_CompressedSize));
}
// Returns the flags as specified or including the UTF-8 flag if filename
// contains non ASCII characters
wxUint16 wxZipEntry::GetInternalFlags(bool checkForUTF8) const
@@ -2173,6 +2197,7 @@ void wxZipOutputStream::Init(int level)
m_level = level;
m_offsetAdjustment = wxInvalidOffset;
m_endrecWritten = false;
m_format = wxZIP_FORMAT_DEFAULT;
}
wxZipOutputStream::~wxZipOutputStream()
@@ -2401,7 +2426,7 @@ void wxZipOutputStream::CreatePendingEntry(const void *buffer, size_t size)
if (spPending->m_CompressedSize != wxInvalidOffset)
spPending->m_Flags |= wxZIP_SUMS_FOLLOW;
m_headerSize = spPending->WriteLocal(*m_parent_o_stream, GetConv());
m_headerSize = spPending->WriteLocal(*m_parent_o_stream, GetConv(), m_format);
m_lasterror = m_parent_o_stream->GetLastError();
if (IsOk()) {
@@ -2454,7 +2479,7 @@ void wxZipOutputStream::CreatePendingEntry()
}
spPending->m_Flags &= ~wxZIP_SUMS_FOLLOW;
m_headerSize = spPending->WriteLocal(*m_parent_o_stream, GetConv());
m_headerSize = spPending->WriteLocal(*m_parent_o_stream, GetConv(), m_format);
if (m_parent_o_stream->IsOk()) {
m_entries.push_back(spPending.release());
@@ -2552,6 +2577,12 @@ bool wxZipOutputStream::CloseEntry()
m_parent_o_stream->SeekO(headerOffset + SUMS_OFFSET);
entry.WriteDescriptor(*m_parent_o_stream, m_crcAccumulator,
compressedSize, m_entrySize);
if (entry.m_z64infoOffset > 0)
{
// Skip to ZIP64 extended information extra field
m_parent_o_stream->SeekO(headerOffset + entry.m_z64infoOffset);
entry.WriteLocalZip64ExtraInfo(*m_parent_o_stream);
}
m_parent_o_stream->SeekO(here);
m_lasterror = m_parent_o_stream->GetLastError();
} else {
@@ -2565,7 +2596,14 @@ bool wxZipOutputStream::CloseEntry()
m_store->Close();
m_raw = false;
if (IsOk())
if (IsOk() && entry.m_z64infoOffset == 0 &&
(entry.m_CompressedSize > 0xffffffff || entry.m_Size > 0xffffffff))
{
wxLogError(_("error writing zip entry '%s': file too large without ZIP64"),
entry.GetName().c_str());
m_lasterror = wxSTREAM_WRITE_ERROR;
}
else if (IsOk())
m_lasterror = m_parent_o_stream->GetLastError();
else
wxLogError(_("error writing zip entry '%s': bad crc or length"),