summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/util/unzip.cpp
diff options
context:
space:
mode:
author Vas Crabb <vas@vastheman.com>2016-06-25 20:56:46 +1000
committer Vas Crabb <vas@vastheman.com>2016-06-25 20:56:59 +1000
commit7f22918675bbac5012d88f6ac97921066414b03f (patch)
treeb424dd13021049c1d7bcaaaba2fc082c60690d6b /src/lib/util/unzip.cpp
parent0b37361fdf2aaaf9cc46f378b499b5a395e2492c (diff)
Supply modified time for files in archives [Vas Crabb]
Diffstat (limited to 'src/lib/util/unzip.cpp')
-rw-r--r--src/lib/util/unzip.cpp163
1 files changed, 148 insertions, 15 deletions
diff --git a/src/lib/util/unzip.cpp b/src/lib/util/unzip.cpp
index 021d9602dae..a7f3295551d 100644
--- a/src/lib/util/unzip.cpp
+++ b/src/lib/util/unzip.cpp
@@ -14,19 +14,22 @@
#include "hashing.h"
#include "osdcore.h"
+#include "lzma/C/LzmaDec.h"
+
+#include <zlib.h>
+
#include <algorithm>
#include <array>
#include <cassert>
+#include <chrono>
#include <cstring>
#include <cstdlib>
+#include <ctime>
#include <mutex>
+#include <ratio>
#include <utility>
#include <vector>
-#include <zlib.h>
-
-#include "lzma/C/LzmaDec.h"
-
namespace util {
namespace {
@@ -164,11 +167,14 @@ public:
bool current_is_directory() const { return m_curr_is_dir; }
const std::string &current_name() const { return m_header.file_name; }
std::uint64_t current_uncompressed_length() const { return m_header.uncompressed_length; }
+ std::chrono::system_clock::time_point current_last_modified() const { return m_header.modified; }
std::uint32_t current_crc() const { return m_header.crc; }
archive_file::error decompress(void *buffer, std::uint32_t length);
private:
+ typedef std::chrono::duration<std::uint64_t, std::ratio<1, 10000000> > ntfs_duration;
+
zip_file_impl(const zip_file_impl &) = delete;
zip_file_impl(zip_file_impl &&) = delete;
zip_file_impl &operator=(const zip_file_impl &) = delete;
@@ -195,6 +201,24 @@ private:
return archive_file::error::NONE;
}
+ static std::chrono::system_clock::time_point decode_dos_time(std::uint16_t date, std::uint16_t time)
+ {
+ // FIXME: work out why this doesn't always work
+ // negative tm_isdst should automatically determine whether DST is in effect for the date,
+ // but on Windows apparently it doesn't, so you get time offsets
+ std::tm datetime;
+ datetime.tm_sec = (time << 1) & 0x003e;
+ datetime.tm_min = (time >> 5) & 0x003f;
+ datetime.tm_hour = (time >> 11) & 0x001f;
+ datetime.tm_mday = (date >> 0) & 0x001f;
+ datetime.tm_mon = ((date >> 5) & 0x000f) - 1;
+ datetime.tm_year = ((date >> 9) & 0x007f) + 80;
+ datetime.tm_wday = 0;
+ datetime.tm_yday = 0;
+ datetime.tm_isdst = -1;
+ return std::chrono::system_clock::from_time_t(std::mktime(&datetime));
+ }
+
// ZIP file parsing
archive_file::error read_ecd();
archive_file::error get_compressed_data_offset(std::uint64_t &offset);
@@ -204,18 +228,22 @@ private:
archive_file::error decompress_data_type_8(std::uint64_t offset, void *buffer, std::uint32_t length);
archive_file::error decompress_data_type_14(std::uint64_t offset, void *buffer, std::uint32_t length);
+ // precalculation
+ static ntfs_duration calculate_ntfs_offset();
+
struct file_header
{
- std::uint16_t version_created; // version made by
- std::uint16_t version_needed; // version needed to extract
- std::uint16_t bit_flag; // general purpose bit flag
- std::uint16_t compression; // compression method
- std::uint32_t crc; // crc-32
- std::uint64_t compressed_length; // compressed size
- std::uint64_t uncompressed_length; // uncompressed size
- std::uint32_t start_disk_number; // disk number start
- std::uint64_t local_header_offset; // relative offset of local header
- std::string file_name; // file name
+ std::uint16_t version_created; // version made by
+ std::uint16_t version_needed; // version needed to extract
+ std::uint16_t bit_flag; // general purpose bit flag
+ std::uint16_t compression; // compression method
+ std::chrono::system_clock::time_point modified; // last mod file date/time
+ std::uint32_t crc; // crc-32
+ std::uint64_t compressed_length; // compressed size
+ std::uint64_t uncompressed_length; // uncompressed size
+ std::uint32_t start_disk_number; // disk number start
+ std::uint64_t local_header_offset; // relative offset of local header
+ std::string file_name; // file name
};
// contains extracted end of central directory information
@@ -231,6 +259,7 @@ private:
static constexpr std::size_t DECOMPRESS_BUFSIZE = 16384;
static constexpr std::size_t CACHE_SIZE = 8; // number of open files to cache
+ static const ntfs_duration s_ntfs_offset;
static std::array<ptr, CACHE_SIZE> s_cache;
static std::mutex s_cache_mutex;
@@ -274,6 +303,7 @@ public:
virtual bool current_is_directory() const override { return m_impl->current_is_directory(); }
virtual const std::string &current_name() const override { return m_impl->current_name(); }
virtual std::uint64_t current_uncompressed_length() const override { return m_impl->current_uncompressed_length(); }
+ virtual std::chrono::system_clock::time_point current_last_modified() const override { return m_impl->current_last_modified(); }
virtual std::uint32_t current_crc() const override { return m_impl->current_crc(); }
virtual error decompress(void *buffer, std::uint32_t length) override { return m_impl->decompress(buffer, length); }
@@ -540,6 +570,59 @@ private:
};
+class ntfs_tag_reader : private reader_base
+{
+public:
+ ntfs_tag_reader(void const *buf, std::size_t len) : reader_base(buf), m_length(len) { }
+
+ std::uint16_t tag() const { return read_word(0x00); }
+ std::uint16_t size() const { return read_word(0x02); }
+ void const * data() const { return m_buffer + 0x04; }
+ ntfs_tag_reader next() const { return ntfs_tag_reader(m_buffer + total_length(), m_length - total_length()); }
+
+ bool length_sufficient() const { return (m_length >= minimum_length()) && (m_length >= total_length()); }
+
+ std::size_t total_length() const { return minimum_length() + size(); }
+ static std::size_t minimum_length() { return 0x04; }
+
+private:
+ std::size_t m_length;
+};
+
+
+class ntfs_reader : private reader_base
+{
+public:
+ ntfs_reader(extra_field_reader const &field) : reader_base(field.data()), m_length(field.data_size()) { }
+
+ std::uint32_t reserved() const { return read_dword(0x00); }
+ ntfs_tag_reader tag1() const { return ntfs_tag_reader(m_buffer + 0x04, m_length - 4); }
+
+ std::size_t total_length() const { return m_length; }
+ static std::size_t minimum_length() { return 0x08; }
+
+private:
+ std::size_t m_length;
+};
+
+
+class ntfs_times_reader : private reader_base
+{
+public:
+ ntfs_times_reader(ntfs_tag_reader const &tag) : reader_base(tag.data()) { }
+
+ std::uint64_t mtime() const { return read_qword(0x00); }
+ std::uint64_t atime() const { return read_qword(0x08); }
+ std::uint64_t ctime() const { return read_qword(0x10); }
+
+ std::size_t total_length() const { return minimum_length(); }
+ static std::size_t minimum_length() { return 0x18; }
+
+private:
+ std::size_t m_length;
+};
+
+
class general_flag_reader
{
public:
@@ -566,7 +649,7 @@ private:
GLOBAL VARIABLES
***************************************************************************/
-/** @brief The zip cache[ zip cache size]. */
+const zip_file_impl::ntfs_duration zip_file_impl::s_ntfs_offset(calculate_ntfs_offset());
std::array<zip_file_impl::ptr, zip_file_impl::CACHE_SIZE> zip_file_impl::s_cache;
std::mutex zip_file_impl::s_cache_mutex;
@@ -631,6 +714,7 @@ int zip_file_impl::search(std::uint32_t search_crc, const std::string &search_fi
m_header.version_needed = reader.version_needed();
m_header.bit_flag = reader.general_flag();
m_header.compression = reader.compression_method();
+ m_header.modified = decode_dos_time(reader.modified_date(), reader.modified_time());
m_header.crc = reader.crc32();
m_header.compressed_length = reader.compressed_size();
m_header.uncompressed_length = reader.uncompressed_size();
@@ -676,6 +760,21 @@ int zip_file_impl::search(std::uint32_t search_crc, const std::string &search_fi
}
}
}
+
+ // look for NTFS extra field
+ if ((extra.header_id() == 0x000a) && (extra.data_size() >= ntfs_reader::minimum_length()))
+ {
+ ntfs_reader const ntfs(extra);
+ for (auto tag = ntfs.tag1(); tag.length_sufficient(); tag = tag.next())
+ {
+ if ((tag.tag() == 0x0001) && (tag.size() >= ntfs_times_reader::minimum_length()))
+ {
+ ntfs_times_reader const times(tag);
+ ntfs_duration const ticks(times.mtime());
+ m_header.modified = std::chrono::system_clock::from_time_t(0) + (ticks - s_ntfs_offset);
+ }
+ }
+ }
}
// FIXME: if (!is_utf8) convert filename to UTF8 (assume CP437 or something)
@@ -1312,6 +1411,40 @@ archive_file::error zip_file_impl::decompress_data_type_14(std::uint64_t offset,
}
}
+
+zip_file_impl::ntfs_duration zip_file_impl::calculate_ntfs_offset()
+{
+ constexpr auto days_in_year(365);
+ constexpr auto days_in_four_years((days_in_year * 4) + 1);
+ constexpr auto days_in_century((days_in_four_years * 25) - 1);
+ constexpr auto days_in_four_centuries((days_in_century * 4) + 1);
+
+ constexpr ntfs_duration day(std::chrono::hours(24));
+ constexpr ntfs_duration year(day * days_in_year);
+ constexpr ntfs_duration four_years(day * days_in_four_years);
+ constexpr ntfs_duration century(day * days_in_century);
+ constexpr ntfs_duration four_centuries(day * days_in_four_centuries);
+
+ std::time_t const zero(0);
+ std::tm const epoch(*std::gmtime(&zero));
+
+ ntfs_duration result(day * epoch.tm_yday);
+ result += std::chrono::hours(epoch.tm_hour);
+ result += std::chrono::minutes(epoch.tm_min);
+ result += std::chrono::seconds(epoch.tm_sec);
+
+ int years(1900 - 1601 + epoch.tm_year);
+ result += four_centuries * (years / 400);
+ years %= 400;
+ result += century * (years / 100);
+ years %= 100;
+ result += four_years * (years / 4);
+ years %= 4;
+ result += year * years;
+
+ return result;
+}
+
} // anonymous namespace