Change date api
Remove msdos_time dependency, and introduce simplified DateTime object. This object does bounds checking to see if dates are representable in msdos.
This commit is contained in:
parent
3f9bccbfc7
commit
f94d4b7a78
5 changed files with 198 additions and 71 deletions
|
@ -12,9 +12,8 @@ Library to support the reading and writing of zip files.
|
|||
"""
|
||||
|
||||
[dependencies]
|
||||
time = "0.1"
|
||||
time = { version = "0.1", optional = true }
|
||||
podio = "0.1"
|
||||
msdos_time = "0.1"
|
||||
bzip2 = { version = "0.3", optional = true }
|
||||
libflate = { version = "0.1.16", optional = true }
|
||||
crc32fast = "1.0"
|
||||
|
@ -26,7 +25,7 @@ walkdir = "1.0"
|
|||
|
||||
[features]
|
||||
deflate = ["libflate"]
|
||||
default = ["bzip2", "deflate"]
|
||||
default = ["bzip2", "deflate", "time"]
|
||||
|
||||
[[bench]]
|
||||
name = "read_entry"
|
||||
|
|
|
@ -7,13 +7,14 @@ extern crate bzip2;
|
|||
extern crate crc32fast;
|
||||
#[cfg(feature = "deflate")]
|
||||
extern crate libflate;
|
||||
extern crate msdos_time;
|
||||
extern crate podio;
|
||||
#[cfg(feature = "time")]
|
||||
extern crate time;
|
||||
|
||||
pub use read::ZipArchive;
|
||||
pub use write::ZipWriter;
|
||||
pub use compression::CompressionMethod;
|
||||
pub use types::DateTime;
|
||||
|
||||
mod spec;
|
||||
mod crc32;
|
||||
|
|
|
@ -12,7 +12,6 @@ use std::borrow::Cow;
|
|||
use podio::{ReadPodExt, LittleEndian};
|
||||
use types::{ZipFileData, System, DateTime};
|
||||
use cp437::FromCp437;
|
||||
use msdos_time::MsDosDateTime;
|
||||
|
||||
#[cfg(feature = "deflate")]
|
||||
use libflate;
|
||||
|
@ -351,7 +350,7 @@ fn central_header_to_zip_file<R: Read+io::Seek>(reader: &mut R, archive_offset:
|
|||
version_made_by: version_made_by as u8,
|
||||
encrypted: encrypted,
|
||||
compression_method: CompressionMethod::from_u16(compression_method),
|
||||
last_modified_time: DateTime::MsDos(MsDosDateTime::new(last_mod_time, last_mod_date)),
|
||||
last_modified_time: DateTime::from_msdos(last_mod_time, last_mod_date),
|
||||
crc32: crc32,
|
||||
compressed_size: compressed_size as u64,
|
||||
uncompressed_size: uncompressed_size as u64,
|
||||
|
@ -463,8 +462,8 @@ impl<'a> ZipFile<'a> {
|
|||
self.data.uncompressed_size
|
||||
}
|
||||
/// Get the time the file was last modified
|
||||
pub fn last_modified(&self) -> ::time::Tm {
|
||||
self.data.last_modified_time.to_tm()
|
||||
pub fn last_modified(&self) -> DateTime {
|
||||
self.data.last_modified_time
|
||||
}
|
||||
/// Get unix mode for the file
|
||||
pub fn unix_mode(&self) -> Option<u32> {
|
||||
|
@ -592,7 +591,7 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(reader: &'a mut R) -> ZipResult
|
|||
version_made_by: version_made_by as u8,
|
||||
encrypted: encrypted,
|
||||
compression_method: compression_method,
|
||||
last_modified_time: DateTime::MsDos(MsDosDateTime::new(last_mod_time, last_mod_date)),
|
||||
last_modified_time: DateTime::from_msdos(last_mod_time, last_mod_date),
|
||||
crc32: crc32,
|
||||
compressed_size: compressed_size as u64,
|
||||
uncompressed_size: uncompressed_size as u64,
|
||||
|
|
201
src/types.rs
201
src/types.rs
|
@ -1,8 +1,5 @@
|
|||
//! Types that specify what is contained in a ZIP.
|
||||
|
||||
use time;
|
||||
use msdos_time::{TmMsDosExt, MsDosDateTime};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum System
|
||||
{
|
||||
|
@ -26,40 +23,148 @@ impl System {
|
|||
}
|
||||
}
|
||||
|
||||
const TM_1980_01_01 : time::Tm = time::Tm {
|
||||
tm_sec: 0,
|
||||
tm_min: 0,
|
||||
tm_hour: 0,
|
||||
tm_mday: 1,
|
||||
tm_mon: 0,
|
||||
tm_year: 80,
|
||||
tm_wday: 2,
|
||||
tm_yday: 0,
|
||||
tm_isdst: -1,
|
||||
tm_utcoff: 0,
|
||||
tm_nsec: 0
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DateTime {
|
||||
Tm(time::Tm),
|
||||
MsDos(MsDosDateTime)
|
||||
/// A DateTime field to be used for storing timestamps in a zip file
|
||||
///
|
||||
/// This structure does bounds checking to ensure the date is able to be stored in a zip file.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DateTime {
|
||||
year: u16,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
}
|
||||
|
||||
impl DateTime {
|
||||
pub fn to_tm(&self) -> time::Tm {
|
||||
match self {
|
||||
&DateTime::Tm(ref tm) => *tm,
|
||||
&DateTime::MsDos(ref ms) => time::Tm::from_msdos(*ms).unwrap_or(TM_1980_01_01)
|
||||
/// Constructs an 'default' datetime of 1980-01-01 00:00:00
|
||||
pub fn default() -> DateTime {
|
||||
DateTime {
|
||||
year: 1980,
|
||||
month: 1,
|
||||
day: 1,
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_msdos(&self) -> Result<MsDosDateTime, ::std::io::Error> {
|
||||
match self {
|
||||
&DateTime::Tm(ref tm) => tm.to_msdos(),
|
||||
&DateTime::MsDos(ref ms) => Ok(*ms)
|
||||
/// Converts an msdos (u16, u16) pair to a DateTime object
|
||||
pub fn from_msdos(datepart: u16, timepart: u16) -> DateTime {
|
||||
let seconds = (timepart & 0b0000000000011111) << 1;
|
||||
let minutes = (timepart & 0b0000011111100000) >> 5;
|
||||
let hours = (timepart & 0b1111100000000000) >> 11;
|
||||
let days = (datepart & 0b0000000000011111) >> 0;
|
||||
let months = (datepart & 0b0000000111100000) >> 5;
|
||||
let years = (datepart & 0b1111111000000000) >> 9;
|
||||
|
||||
DateTime {
|
||||
year: (years + 1980) as u16,
|
||||
month: months as u8,
|
||||
day: days as u8,
|
||||
hour: hours as u8,
|
||||
minute: minutes as u8,
|
||||
second: seconds as u8,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a DateTime from a specific date and time
|
||||
///
|
||||
/// The bounds are:
|
||||
/// * year: [1980, 2107]
|
||||
/// * month: [1, 12]
|
||||
/// * day: [1, 31]
|
||||
/// * hour: [0, 23]
|
||||
/// * minute: [0, 59]
|
||||
/// * second: [0, 60]
|
||||
pub fn from_date_and_time(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> Result<DateTime, ()> {
|
||||
(DateTime { year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 0 })
|
||||
.with_date(year, month, day)?
|
||||
.with_time(hour, minute, second)
|
||||
}
|
||||
|
||||
/// Constructs a DateTime with a specific date set.
|
||||
///
|
||||
/// The bounds are:
|
||||
/// * year: [1980, 2107]
|
||||
/// * month: [1, 12]
|
||||
/// * day: [1, 31]
|
||||
pub fn with_date(self, year: u16, month: u8, day: u8) -> Result<DateTime, ()> {
|
||||
if year >= 1980 && year <= 2107
|
||||
&& month >= 1 && month <= 12
|
||||
&& day >= 1 && day <= 31
|
||||
{
|
||||
Ok(DateTime {
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: self.hour,
|
||||
minute: self.minute,
|
||||
second: self.second,
|
||||
})
|
||||
}
|
||||
else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a DateTime with a specific time set.
|
||||
///
|
||||
/// The bounds are:
|
||||
/// * hour: [0, 23]
|
||||
/// * minute: [0, 59]
|
||||
/// * second: [0, 60]
|
||||
pub fn with_time(self, hour: u8, minute: u8, second: u8) -> Result<DateTime, ()> {
|
||||
if hour <= 23 && minute <= 59 && second <= 60 {
|
||||
Ok(DateTime {
|
||||
year: self.year,
|
||||
month: self.month,
|
||||
day: self.day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
})
|
||||
}
|
||||
else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
/// Converts a ::time::Tm object to a DateTime
|
||||
///
|
||||
/// Returns `Err` when this object is out of bounds
|
||||
pub fn from_time(tm: ::time::Tm) -> Result<DateTime, ()> {
|
||||
if tm.tm_year >= 1980 && tm.tm_year <= 2107
|
||||
&& tm.tm_mon >= 1 && tm.tm_mon <= 31
|
||||
&& tm.tm_mday >= 1 && tm.tm_mday <= 31
|
||||
&& tm.tm_hour >= 0 && tm.tm_hour <= 23
|
||||
&& tm.tm_min >= 0 && tm.tm_min <= 59
|
||||
&& tm.tm_sec >= 0 && tm.tm_sec <= 60
|
||||
{
|
||||
Ok(DateTime {
|
||||
year: (tm.tm_year + 1900) as u16,
|
||||
month: (tm.tm_mon + 1) as u8,
|
||||
day: tm.tm_mday as u8,
|
||||
hour: tm.tm_hour as u8,
|
||||
minute: tm.tm_min as u8,
|
||||
second: tm.tm_sec as u8,
|
||||
})
|
||||
}
|
||||
else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the time portion of this datetime in the msdos representation
|
||||
pub fn timepart(&self) -> u16 {
|
||||
((self.second as u16) >> 1) | ((self.minute as u16) << 5) | ((self.hour as u16) << 11)
|
||||
}
|
||||
|
||||
/// Gets the date portion of this datetime in the msdos representation
|
||||
pub fn datepart(&self) -> u16 {
|
||||
(self.day as u16) | ((self.month as u16) << 5) | ((self.year - 1980) << 9)
|
||||
}
|
||||
}
|
||||
|
||||
pub const DEFAULT_VERSION: u8 = 46;
|
||||
|
@ -157,7 +262,7 @@ mod test {
|
|||
version_made_by: 0,
|
||||
encrypted: false,
|
||||
compression_method: ::compression::CompressionMethod::Stored,
|
||||
last_modified_time: DateTime::Tm(time::empty_tm()),
|
||||
last_modified_time: DateTime::default(),
|
||||
crc32: 0,
|
||||
compressed_size: 0,
|
||||
uncompressed_size: 0,
|
||||
|
@ -170,4 +275,40 @@ mod test {
|
|||
};
|
||||
assert_eq!(data.file_name_sanitized(), ::std::path::PathBuf::from("path/etc/passwd"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn datetime_default() {
|
||||
use super::DateTime;
|
||||
let dt = DateTime::default();
|
||||
assert_eq!(dt.timepart(), 0);
|
||||
assert_eq!(dt.datepart(), 0b0000000_0001_00001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn datetime_max() {
|
||||
use super::DateTime;
|
||||
let dt = DateTime::from_date_and_time(2107, 12, 31, 23, 59, 60).unwrap();
|
||||
assert_eq!(dt.timepart(), 0b10111_111011_11110);
|
||||
assert_eq!(dt.datepart(), 0b1111111_1100_11111);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn datetime_bounds() {
|
||||
use super::DateTime;
|
||||
let dt = DateTime::default();
|
||||
|
||||
assert!(dt.with_time(23, 59, 60).is_ok());
|
||||
assert!(dt.with_time(24, 0, 0).is_err());
|
||||
assert!(dt.with_time(0, 60, 0).is_err());
|
||||
assert!(dt.with_time(0, 0, 61).is_err());
|
||||
|
||||
assert!(dt.with_date(2107, 12, 31).is_ok());
|
||||
assert!(dt.with_date(1980, 1, 1).is_ok());
|
||||
assert!(dt.with_date(1979, 1, 1).is_err());
|
||||
assert!(dt.with_date(1980, 0, 1).is_err());
|
||||
assert!(dt.with_date(1980, 1, 0).is_err());
|
||||
assert!(dt.with_date(2108, 12, 31).is_err());
|
||||
assert!(dt.with_date(2107, 13, 31).is_err());
|
||||
assert!(dt.with_date(2107, 12, 32).is_err());
|
||||
}
|
||||
}
|
||||
|
|
51
src/write.rs
51
src/write.rs
|
@ -9,6 +9,7 @@ use std::default::Default;
|
|||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::mem;
|
||||
#[cfg(feature = "time")]
|
||||
use time;
|
||||
use podio::{WritePodExt, LittleEndian};
|
||||
|
||||
|
@ -75,27 +76,18 @@ struct ZipWriterStats
|
|||
#[derive(Copy, Clone)]
|
||||
pub struct FileOptions {
|
||||
compression_method: CompressionMethod,
|
||||
last_modified_time: time::Tm,
|
||||
last_modified_time: DateTime,
|
||||
permissions: Option<u32>,
|
||||
}
|
||||
|
||||
impl FileOptions {
|
||||
#[cfg(feature = "deflate")]
|
||||
/// Construct a new FileOptions object
|
||||
pub fn default() -> FileOptions {
|
||||
FileOptions {
|
||||
compression_method: CompressionMethod::Deflated,
|
||||
last_modified_time: time::now(),
|
||||
permissions: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "deflate"))]
|
||||
/// Construct a new FileOptions object
|
||||
pub fn default() -> FileOptions {
|
||||
FileOptions {
|
||||
compression_method: CompressionMethod::Stored,
|
||||
last_modified_time: time::now(),
|
||||
#[cfg(feature = "deflate")] compression_method: CompressionMethod::Deflated,
|
||||
#[cfg(not(feature = "deflate"))] compression_method: CompressionMethod::Stored,
|
||||
#[cfg(feature = "time")] last_modified_time: DateTime::from_time(time::now()).unwrap_or(DateTime::default()),
|
||||
#[cfg(not(feature = "time"))] last_modified_time: DateTime::default(),
|
||||
permissions: None,
|
||||
}
|
||||
}
|
||||
|
@ -112,8 +104,9 @@ impl FileOptions {
|
|||
|
||||
/// Set the last modified time
|
||||
///
|
||||
/// The default is the current timestamp
|
||||
pub fn last_modified_time(mut self, mod_time: time::Tm) -> FileOptions {
|
||||
/// The default is the current timestamp if the 'time' feature is enabled, and 1980-01-01
|
||||
/// otherwise
|
||||
pub fn last_modified_time(mut self, mod_time: DateTime) -> FileOptions {
|
||||
self.last_modified_time = mod_time;
|
||||
self
|
||||
}
|
||||
|
@ -201,7 +194,7 @@ impl<W: Write+io::Seek> ZipWriter<W>
|
|||
version_made_by: DEFAULT_VERSION,
|
||||
encrypted: false,
|
||||
compression_method: options.compression_method,
|
||||
last_modified_time: DateTime::Tm(options.last_modified_time),
|
||||
last_modified_time: options.last_modified_time,
|
||||
crc32: 0,
|
||||
compressed_size: 0,
|
||||
uncompressed_size: 0,
|
||||
|
@ -437,9 +430,8 @@ fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipR
|
|||
// Compression method
|
||||
writer.write_u16::<LittleEndian>(file.compression_method.to_u16())?;
|
||||
// last mod file time and last mod file date
|
||||
let msdos_datetime = file.last_modified_time.to_msdos()?;
|
||||
writer.write_u16::<LittleEndian>(msdos_datetime.timepart)?;
|
||||
writer.write_u16::<LittleEndian>(msdos_datetime.datepart)?;
|
||||
writer.write_u16::<LittleEndian>(file.last_modified_time.timepart())?;
|
||||
writer.write_u16::<LittleEndian>(file.last_modified_time.datepart())?;
|
||||
// crc-32
|
||||
writer.write_u32::<LittleEndian>(file.crc32)?;
|
||||
// compressed size
|
||||
|
@ -484,9 +476,8 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
|
|||
// compression method
|
||||
writer.write_u16::<LittleEndian>(file.compression_method.to_u16())?;
|
||||
// last mod file time + date
|
||||
let msdos_datetime = file.last_modified_time.to_msdos()?;
|
||||
writer.write_u16::<LittleEndian>(msdos_datetime.timepart)?;
|
||||
writer.write_u16::<LittleEndian>(msdos_datetime.datepart)?;
|
||||
writer.write_u16::<LittleEndian>(file.last_modified_time.timepart())?;
|
||||
writer.write_u16::<LittleEndian>(file.last_modified_time.datepart())?;
|
||||
// crc-32
|
||||
writer.write_u32::<LittleEndian>(file.crc32)?;
|
||||
// compressed size
|
||||
|
@ -529,7 +520,7 @@ fn build_extra_field(_file: &ZipFileData) -> ZipResult<Vec<u8>>
|
|||
mod test {
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use time;
|
||||
use types::DateTime;
|
||||
use super::{FileOptions, ZipWriter};
|
||||
use compression::CompressionMethod;
|
||||
|
||||
|
@ -544,10 +535,9 @@ mod test {
|
|||
#[test]
|
||||
fn write_zip_dir() {
|
||||
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
|
||||
writer.add_directory("test", FileOptions::default().last_modified_time(time::Tm {
|
||||
tm_year: 2018 - 1900, tm_mon: 8 - 1, tm_mday: 15, tm_hour: 20, tm_min: 45, tm_sec: 6,
|
||||
tm_isdst: -1, tm_yday: 0, tm_wday: 0, tm_utcoff: 0, tm_nsec: 0,
|
||||
})).unwrap();
|
||||
writer.add_directory("test", FileOptions::default().last_modified_time(
|
||||
DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap()
|
||||
)).unwrap();
|
||||
let result = writer.finish().unwrap();
|
||||
assert_eq!(result.get_ref().len(), 114);
|
||||
assert_eq!(*result.get_ref(), &[
|
||||
|
@ -561,12 +551,9 @@ mod test {
|
|||
#[test]
|
||||
fn write_mimetype_zip() {
|
||||
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
|
||||
let mut mtime = time::empty_tm();
|
||||
mtime.tm_year = 80;
|
||||
mtime.tm_mday = 1;
|
||||
let options = FileOptions {
|
||||
compression_method: CompressionMethod::Stored,
|
||||
last_modified_time: mtime,
|
||||
last_modified_time: DateTime::default(),
|
||||
permissions: Some(33188),
|
||||
};
|
||||
writer.start_file("mimetype", options).unwrap();
|
||||
|
|
Loading…
Add table
Reference in a new issue