mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-14 01:04:13 +02:00
Fix the dates of the files inside a .qetz project
miniz is built with MINIZ_NO_TIME so that saving the same project twice gives the same bytes. Without a clock it writes 0 as each entry's DOS date: day 0 of month 0, 1980, which is not a date. Unzip tools show it as 30 Nov 1979, others fail the conversion and show 13 Dec 1901 (reported on PR #1462). Write 1 Jan 1980 00:00, the first valid DOS date, into the local and central header of every entry once the archive is built. The bytes stay reproducible, and miniz stays unmodified. Using the save time instead was rejected: two saves would no longer give the same file, and miniz converts through the local time zone, so even a fixed time_t would differ between machines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <QSet>
|
||||
#include <QtEndian>
|
||||
|
||||
namespace {
|
||||
void setError(QString *error, const QString &text)
|
||||
@@ -44,6 +45,42 @@ bool safeName(const QString &name)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//Built with MINIZ_NO_TIME, miniz leaves each entry's DOS date at 0,
|
||||
//day 0 of month 0, which is no date: unzip shows 30 Nov 1979, other
|
||||
//tools 13 Dec 1901. Give every entry 1 Jan 1980 00:00 instead, the
|
||||
//first valid DOS date, so the bytes still do not depend on the time.
|
||||
constexpr quint16 EntryDosDate = (1 << 5) | 1;
|
||||
|
||||
bool setEntryDates(QByteArray *bytes)
|
||||
{
|
||||
char *data = bytes->data();
|
||||
mz_zip_archive zip;
|
||||
mz_zip_zero_struct(&zip);
|
||||
if (!mz_zip_reader_init_mem(&zip, data, size_t(bytes->size()), 0)) return false;
|
||||
|
||||
bool ok = true;
|
||||
const mz_uint count = mz_zip_reader_get_num_files(&zip);
|
||||
for (mz_uint i = 0 ; i < count && ok ; ++i)
|
||||
{
|
||||
mz_zip_archive_file_stat stat;
|
||||
if (!mz_zip_reader_file_stat(&zip, i, &stat)) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
//date at offset 12 of the local header, 14 of the central one
|
||||
const quint64 local = stat.m_local_header_ofs + 12;
|
||||
const quint64 central = zip.m_central_directory_file_ofs + stat.m_central_dir_ofs + 14;
|
||||
if (local + 2 > quint64(bytes->size()) || central + 2 > quint64(bytes->size())) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
qToLittleEndian<quint16>(EntryDosDate, data + local);
|
||||
qToLittleEndian<quint16>(EntryDosDate, data + central);
|
||||
}
|
||||
mz_zip_reader_end(&zip);
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,8 +124,12 @@ QByteArray QetZip::toBytes(const QList<Entry> &entries, QString *error)
|
||||
mz_zip_writer_end(&zip);
|
||||
return {};
|
||||
}
|
||||
const QByteArray bytes(static_cast<const char *>(buffer), qsizetype(size));
|
||||
QByteArray bytes(static_cast<const char *>(buffer), qsizetype(size));
|
||||
mz_zip_writer_end(&zip); //frees buffer
|
||||
if (!setEntryDates(&bytes)) {
|
||||
setError(error, QStringLiteral("cannot date the zip entries"));
|
||||
return {};
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
megabytes, not gigabytes -- and written through QSaveFile, so a failed
|
||||
save leaves the previous file as it was.
|
||||
|
||||
Entries carry no timestamp: saving the same project twice gives the
|
||||
same bytes.
|
||||
Every entry is dated 1 Jan 1980 00:00, not the time of saving: saving
|
||||
the same project twice gives the same bytes.
|
||||
*/
|
||||
namespace QetZip
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <QtTest>
|
||||
|
||||
#include <QTemporaryDir>
|
||||
#include <QtEndian>
|
||||
|
||||
#include "container/qetzip.h"
|
||||
#include "container/miniz/miniz.h"
|
||||
@@ -72,6 +73,33 @@ private slots:
|
||||
QCOMPARE(QetZip::toBytes(sample()), QetZip::toBytes(sample()));
|
||||
}
|
||||
|
||||
// every entry carries a valid date in both of its headers, not the
|
||||
// 0 miniz writes without a clock (shown as 1979 or 1901 by unzip tools)
|
||||
void entriesHaveValidDate()
|
||||
{
|
||||
const QByteArray zip = QetZip::toBytes(sample());
|
||||
QVERIFY(!zip.isEmpty());
|
||||
mz_zip_archive archive;
|
||||
mz_zip_zero_struct(&archive);
|
||||
QVERIFY(mz_zip_reader_init_mem(&archive, zip.constData(), size_t(zip.size()), 0));
|
||||
const mz_uint count = mz_zip_reader_get_num_files(&archive);
|
||||
QCOMPARE(int(count), sample().size());
|
||||
for (mz_uint i = 0 ; i < count ; ++i) {
|
||||
mz_zip_archive_file_stat stat;
|
||||
QVERIFY(mz_zip_reader_file_stat(&archive, i, &stat));
|
||||
const char *local = zip.constData() + stat.m_local_header_ofs;
|
||||
const char *central = zip.constData() + archive.m_central_directory_file_ofs + stat.m_central_dir_ofs;
|
||||
QCOMPARE(qFromLittleEndian<quint16>(local), quint16(0x50 | 0x4b << 8)); // "PK"
|
||||
QCOMPARE(qFromLittleEndian<quint16>(central), quint16(0x50 | 0x4b << 8));
|
||||
// 1 Jan 1980: day 1, month 1, year 0 since 1980; time 00:00:00
|
||||
QCOMPARE(qFromLittleEndian<quint16>(local + 12), quint16(0x21));
|
||||
QCOMPARE(qFromLittleEndian<quint16>(central + 14), quint16(0x21));
|
||||
QCOMPARE(qFromLittleEndian<quint16>(local + 10), quint16(0));
|
||||
QCOMPARE(qFromLittleEndian<quint16>(central + 12), quint16(0));
|
||||
}
|
||||
mz_zip_reader_end(&archive);
|
||||
}
|
||||
|
||||
void refusesUnsafeNamesOnWrite_data()
|
||||
{
|
||||
QTest::addColumn<QString>("name");
|
||||
|
||||
Reference in New Issue
Block a user