use super::package::{PptError, Result};
use zerocopy::FromBytes;
use zerocopy_derive::FromBytes as DeriveFromBytes;
const CURRENT_USER_MIN_SIZE: usize = 28;
#[derive(Debug, Clone)]
pub struct CurrentUser {
current_edit_offset: u32,
release_version: u16,
username: String,
rel_path: String,
}
#[derive(Debug, Clone, DeriveFromBytes)]
#[repr(C)]
struct CurrentUserHeader {
header_token: u32,
current_edit_offset: u32,
username_len: u16,
release_version: u16,
}
impl CurrentUser {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < CURRENT_USER_MIN_SIZE {
return Err(PptError::Corrupted(
"CurrentUser stream too short".to_string(),
));
}
let header = CurrentUserHeader::read_from_bytes(&data[4..20])
.map_err(|_| PptError::Corrupted("Invalid CurrentUser header format".to_string()))?;
if header.header_token != 0xF3D1C4DF {
return Err(PptError::InvalidFormat(format!(
"Invalid CurrentUser header token: 0x{:08X}",
header.header_token
)));
}
let current_edit_offset = header.current_edit_offset;
let username_len = header.username_len;
let release_version = header.release_version;
let username = if username_len > 0 && data.len() >= 20 {
let username_byte_len = (username_len as usize) * 2; let username_start = 20;
let username_end = username_start + username_byte_len;
if username_end <= data.len() {
Self::parse_utf16le_string(&data[username_start..username_end])
} else {
String::new()
}
} else {
String::new()
};
let rel_path_start = 20 + (username_len as usize) * 2;
let rel_path = if rel_path_start < data.len() {
Self::parse_ascii_string(&data[rel_path_start..])
} else {
String::new()
};
Ok(Self {
current_edit_offset,
release_version,
username,
rel_path,
})
}
#[inline]
pub fn current_edit_offset(&self) -> u32 {
self.current_edit_offset
}
#[inline]
pub fn username(&self) -> &str {
&self.username
}
#[inline]
pub fn relative_path(&self) -> &str {
&self.rel_path
}
#[inline]
pub fn release_version(&self) -> u16 {
self.release_version
}
fn parse_utf16le_string(data: &[u8]) -> String {
if data.is_empty() || data.len() < 2 {
return String::new();
}
let estimated_chars = data.len() / 2;
let mut result = String::with_capacity(estimated_chars);
for chunk in data.chunks_exact(2) {
let code_unit = u16::from_le_bytes([chunk[0], chunk[1]]);
if code_unit == 0 {
break;
}
if let Some(ch) = char::from_u32(code_unit as u32) {
result.push(ch);
}
}
result.shrink_to_fit();
result
}
fn parse_ascii_string(data: &[u8]) -> String {
if data.is_empty() {
return String::new();
}
let null_pos = data.iter().position(|&b| b == 0).unwrap_or(data.len());
String::from_utf8_lossy(&data[..null_pos]).to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_current_user_min_size() {
let short_data = vec![0u8; 16];
let result = CurrentUser::parse(&short_data);
assert!(result.is_err());
}
#[test]
fn test_current_user_header_validation() {
let mut data = vec![0u8; 32];
data[4] = 0xFF;
data[5] = 0xFF;
data[6] = 0xFF;
data[7] = 0xFF;
let result = CurrentUser::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_current_user_valid() {
let mut data = vec![0u8; 32];
data[0] = 0x1C;
data[1] = 0x00;
data[2] = 0x00;
data[3] = 0x00;
data[4] = 0xDF;
data[5] = 0xC4;
data[6] = 0xD1;
data[7] = 0xF3;
data[8] = 0x00;
data[9] = 0x10;
data[10] = 0x00;
data[11] = 0x00;
data[12] = 0x00;
data[13] = 0x00;
let result = CurrentUser::parse(&data);
assert!(result.is_ok());
let current_user = result.unwrap();
assert_eq!(current_user.current_edit_offset(), 0x1000);
}
#[test]
fn test_utf16le_parsing() {
let data = vec![
0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00, ];
let result = CurrentUser::parse_utf16le_string(&data);
assert_eq!(result, "ABC");
}
#[test]
fn test_ascii_parsing() {
let data = b"Hello\0World";
let result = CurrentUser::parse_ascii_string(data);
assert_eq!(result, "Hello");
}
}