1use std::io::{Read, Seek, SeekFrom};
11
12use crate::date_time::DosDateTime;
13use crate::error::{ArchiveError, Result};
14
15#[derive(Debug, Clone)]
17pub struct TarFileHeader {
18 pub name: String,
20 pub size: u64,
22 pub mtime: u64,
24 pub mode: u32,
26 pub entry_type: TarEntryType,
28 pub link_name: Option<String>,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum TarEntryType {
35 Regular,
37 HardLink,
39 Symlink,
41 Char,
43 Block,
45 Directory,
47 Fifo,
49 Continuous,
51 Other(u8),
53}
54
55impl From<tar::EntryType> for TarEntryType {
56 fn from(t: tar::EntryType) -> Self {
57 match t {
58 tar::EntryType::Regular => TarEntryType::Regular,
59 tar::EntryType::Link => TarEntryType::HardLink,
60 tar::EntryType::Symlink => TarEntryType::Symlink,
61 tar::EntryType::Char => TarEntryType::Char,
62 tar::EntryType::Block => TarEntryType::Block,
63 tar::EntryType::Directory => TarEntryType::Directory,
64 tar::EntryType::Fifo => TarEntryType::Fifo,
65 tar::EntryType::Continuous => TarEntryType::Continuous,
66 _ => TarEntryType::Other(t.as_byte()),
67 }
68 }
69}
70
71impl TarFileHeader {
72 pub fn modified_time(&self) -> Option<DosDateTime> {
74 if self.mtime == 0 {
77 return None;
78 }
79
80 use chrono::{Datelike, TimeZone, Timelike, Utc};
82 if let Some(dt) = Utc.timestamp_opt(self.mtime as i64, 0).single() {
83 let year = dt.year() as u16;
84 let month = dt.month() as u16;
85 let day = dt.day() as u16;
86 let hour = dt.hour() as u16;
87 let minute = dt.minute() as u16;
88 let second = dt.second() as u16;
89
90 if year >= 1980 {
91 let dos_date = ((year - 1980) << 9) | (month << 5) | day;
93 let dos_time = (hour << 11) | (minute << 5) | (second / 2);
94 let combined = ((dos_date as u32) << 16) | (dos_time as u32);
96 return Some(DosDateTime::new(combined));
97 }
98 }
99 None
100 }
101}
102
103struct TarEntry {
105 header: TarFileHeader,
106 data_offset: u64,
108}
109
110pub struct TarArchive<T: Read + Seek> {
112 reader: T,
113 entries: Vec<TarEntry>,
115 current_index: usize,
117}
118
119impl<T: Read + Seek> TarArchive<T> {
120 pub fn new(mut reader: T) -> Result<Self> {
122 let mut entries = Vec::new();
124
125 reader.seek(SeekFrom::Start(0))?;
127
128 {
129 let mut archive = tar::Archive::new(&mut reader);
130
131 for entry_result in archive
132 .entries()
133 .map_err(|e| ArchiveError::io_error(format!("Failed to read TAR entries: {}", e)))?
134 {
135 let entry = entry_result.map_err(|e| ArchiveError::io_error(format!("Failed to read TAR entry: {}", e)))?;
136
137 let header = entry.header();
138 let name = entry
139 .path()
140 .map_err(|e| ArchiveError::io_error(format!("Failed to read entry path: {}", e)))?
141 .to_string_lossy()
142 .to_string();
143
144 let size = header.size().unwrap_or(0);
145 let mtime = header.mtime().unwrap_or(0);
146 let mode = header.mode().unwrap_or(0);
147 let entry_type = header.entry_type().into();
148 let link_name = header.link_name().ok().flatten().map(|p| p.to_string_lossy().to_string());
149
150 let raw_header_position = entry.raw_header_position();
151 let data_offset = raw_header_position + 512;
153
154 entries.push(TarEntry {
155 header: TarFileHeader {
156 name,
157 size,
158 mtime,
159 mode,
160 entry_type,
161 link_name,
162 },
163 data_offset,
164 });
165 }
166 }
167
168 reader.seek(SeekFrom::Start(0))?;
170
171 Ok(Self {
172 reader,
173 entries,
174 current_index: 0,
175 })
176 }
177
178 pub fn get_next_entry(&mut self) -> Result<Option<TarFileHeader>> {
180 if self.current_index >= self.entries.len() {
181 return Ok(None);
182 }
183
184 let entry = &self.entries[self.current_index];
185 Ok(Some(entry.header.clone()))
186 }
187
188 pub fn skip(&mut self, _header: &TarFileHeader) -> Result<()> {
190 if self.current_index < self.entries.len() {
191 self.current_index += 1;
192 }
193 Ok(())
194 }
195
196 pub fn read(&mut self, header: &TarFileHeader) -> Result<Vec<u8>> {
198 let entry = self
200 .entries
201 .iter()
202 .find(|e| e.header.name == header.name)
203 .ok_or_else(|| ArchiveError::io_error(format!("Entry not found: {}", header.name)))?;
204
205 self.reader.seek(SeekFrom::Start(entry.data_offset))?;
207
208 let mut data = vec![0u8; entry.header.size as usize];
210 self.reader.read_exact(&mut data)?;
211
212 self.current_index += 1;
214
215 Ok(data)
216 }
217
218 pub fn entry_count(&self) -> usize {
220 self.entries.len()
221 }
222}