use crate::error::{Error, Result};
const HWP_SIGNATURE: &[u8] = b"HWP Document File";
const FILE_HEADER_SIZE: usize = 256;
#[allow(dead_code)]
mod flags {
pub const COMPRESSED: u32 = 1 << 0;
pub const ENCRYPTED: u32 = 1 << 1;
pub const DISTRIBUTION: u32 = 1 << 2;
pub const SCRIPT: u32 = 1 << 3;
pub const DRM: u32 = 1 << 4;
pub const XML_TEMPLATE: u32 = 1 << 5;
pub const HISTORY: u32 = 1 << 6;
pub const SIGNATURE: u32 = 1 << 7;
pub const PUBLIC_KEY_ENCRYPT: u32 = 1 << 8;
pub const SIGNATURE_RESERVED: u32 = 1 << 9;
pub const CERTIFICATE_DRM: u32 = 1 << 10;
pub const CCL: u32 = 1 << 11;
pub const MOBILE: u32 = 1 << 12;
pub const PRIVACY: u32 = 1 << 13;
pub const TRACK_CHANGES: u32 = 1 << 14;
pub const KOGL: u32 = 1 << 15;
pub const VIDEO_CONTROL: u32 = 1 << 16;
pub const ORDER_FIELD: u32 = 1 << 17;
}
#[derive(Debug, Clone)]
pub struct FileHeader {
pub version: Version,
pub properties: u32,
pub license: Option<String>,
}
impl FileHeader {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < FILE_HEADER_SIZE {
return Err(Error::InvalidData(format!(
"FileHeader too small: {} bytes, expected {}",
data.len(),
FILE_HEADER_SIZE
)));
}
if !data[..17].eq(HWP_SIGNATURE) {
return Err(Error::InvalidData("Invalid HWP signature".into()));
}
let version = Version {
major: data[35],
minor: data[34],
build: data[33],
revision: data[32],
};
let properties = u32::from_le_bytes([data[36], data[37], data[38], data[39]]);
Ok(Self {
version,
properties,
license: None,
})
}
pub fn version_string(&self) -> String {
self.version.to_string()
}
pub fn is_compressed(&self) -> bool {
self.properties & flags::COMPRESSED != 0
}
pub fn is_encrypted(&self) -> bool {
self.properties & flags::ENCRYPTED != 0
}
pub fn is_distribution(&self) -> bool {
self.properties & flags::DISTRIBUTION != 0
}
pub fn is_drm_protected(&self) -> bool {
self.properties & flags::DRM != 0
}
pub fn has_scripts(&self) -> bool {
self.properties & flags::SCRIPT != 0
}
pub fn has_track_changes(&self) -> bool {
self.properties & flags::TRACK_CHANGES != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Version {
pub major: u8,
pub minor: u8,
pub build: u8,
pub revision: u8,
}
impl Version {
pub fn new(major: u8, minor: u8, build: u8, revision: u8) -> Self {
Self {
major,
minor,
build,
revision,
}
}
pub fn at_least(&self, major: u8, minor: u8, build: u8, revision: u8) -> bool {
(self.major, self.minor, self.build, self.revision) >= (major, minor, build, revision)
}
}
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}.{}.{}.{}",
self.major, self.minor, self.build, self.revision
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_display() {
let v = Version::new(5, 1, 0, 1);
assert_eq!(v.to_string(), "5.1.0.1");
}
#[test]
fn test_version_comparison() {
let v = Version::new(5, 1, 0, 1);
assert!(v.at_least(5, 0, 0, 0));
assert!(v.at_least(5, 1, 0, 0));
assert!(v.at_least(5, 1, 0, 1));
assert!(!v.at_least(5, 1, 0, 2));
assert!(!v.at_least(5, 2, 0, 0));
}
#[test]
fn test_parse_header() {
let mut data = vec![0u8; 256];
data[..17].copy_from_slice(b"HWP Document File");
data[32] = 1; data[33] = 0; data[34] = 1; data[35] = 5; data[36] = 0x01;
let header = FileHeader::parse(&data).unwrap();
assert_eq!(header.version.major, 5);
assert_eq!(header.version.minor, 1);
assert!(header.is_compressed());
assert!(!header.is_encrypted());
}
}