1mod header;
2mod read;
3mod write;
4
5use crate::{
6 chunk::{ChunkStreamWriter, RawChunk},
7 cipher::CipherWriter,
8 compress::CompressionWriter,
9};
10pub use header::*;
11use std::io::prelude::*;
12pub(crate) use {read::*, write::*};
13
14pub struct Archive<T> {
66 inner: T,
67 header: ArchiveHeader,
68 next_archive: bool,
70 buf: Vec<RawChunk>,
71}
72
73impl<T> Archive<T> {
74 const fn new(inner: T, header: ArchiveHeader) -> Self {
75 Self::with_buffer(inner, header, Vec::new())
76 }
77
78 const fn with_buffer(inner: T, header: ArchiveHeader, buf: Vec<RawChunk>) -> Self {
79 Self {
80 inner,
81 header,
82 next_archive: false,
83 buf,
84 }
85 }
86
87 #[inline]
95 pub const fn has_next_archive(&self) -> bool {
96 self.next_archive
97 }
98}
99
100pub struct SolidArchive<T: Write> {
132 archive_header: ArchiveHeader,
133 inner: CompressionWriter<CipherWriter<ChunkStreamWriter<T>>>,
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::{entry::*, Duration};
140 use std::io::{self, Cursor};
141 #[cfg(all(target_family = "wasm", target_os = "unknown"))]
142 use wasm_bindgen_test::wasm_bindgen_test as test;
143
144 #[test]
145 fn store_archive() {
146 archive(
147 b"src data bytes",
148 WriteOptions::builder().compression(Compression::No).build(),
149 )
150 .unwrap()
151 }
152
153 #[test]
154 fn deflate_archive() {
155 archive(
156 b"src data bytes",
157 WriteOptions::builder()
158 .compression(Compression::Deflate)
159 .build(),
160 )
161 .unwrap()
162 }
163
164 #[test]
165 fn zstd_archive() {
166 archive(
167 b"src data bytes",
168 WriteOptions::builder()
169 .compression(Compression::ZStandard)
170 .build(),
171 )
172 .unwrap()
173 }
174
175 #[test]
176 fn xz_archive() {
177 archive(
178 b"src data bytes",
179 WriteOptions::builder().compression(Compression::XZ).build(),
180 )
181 .unwrap();
182 }
183
184 #[test]
185 fn store_with_aes_cbc_archive() {
186 archive(
187 b"plain text",
188 WriteOptions::builder()
189 .compression(Compression::No)
190 .encryption(Encryption::Aes)
191 .cipher_mode(CipherMode::CBC)
192 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
193 .password(Some("password"))
194 .build(),
195 )
196 .unwrap();
197 }
198
199 #[test]
200 fn zstd_with_aes_ctr_archive() {
201 archive(
202 b"plain text",
203 WriteOptions::builder()
204 .compression(Compression::ZStandard)
205 .encryption(Encryption::Aes)
206 .cipher_mode(CipherMode::CTR)
207 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
208 .password(Some("password"))
209 .build(),
210 )
211 .unwrap();
212 }
213
214 #[test]
215 fn zstd_with_aes_cbc_archive() {
216 archive(
217 b"plain text",
218 WriteOptions::builder()
219 .compression(Compression::ZStandard)
220 .encryption(Encryption::Aes)
221 .cipher_mode(CipherMode::CBC)
222 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
223 .password(Some("password"))
224 .build(),
225 )
226 .unwrap();
227 }
228
229 #[test]
230 fn zstd_with_camellia_ctr_archive() {
231 archive(
232 b"plain text",
233 WriteOptions::builder()
234 .compression(Compression::ZStandard)
235 .encryption(Encryption::Camellia)
236 .cipher_mode(CipherMode::CTR)
237 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
238 .password(Some("password"))
239 .build(),
240 )
241 .unwrap();
242 }
243
244 #[test]
245 fn zstd_with_camellia_cbc_archive() {
246 archive(
247 b"plain text",
248 WriteOptions::builder()
249 .compression(Compression::ZStandard)
250 .encryption(Encryption::Camellia)
251 .cipher_mode(CipherMode::CBC)
252 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
253 .password(Some("password"))
254 .build(),
255 )
256 .unwrap();
257 }
258
259 #[test]
260 fn xz_with_aes_cbc_archive() {
261 archive(
262 b"plain text",
263 WriteOptions::builder()
264 .compression(Compression::XZ)
265 .encryption(Encryption::Aes)
266 .cipher_mode(CipherMode::CBC)
267 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
268 .password(Some("password"))
269 .build(),
270 )
271 .unwrap()
272 }
273
274 #[test]
275 fn xz_with_camellia_cbc_archive() {
276 archive(
277 b"plain text",
278 WriteOptions::builder()
279 .compression(Compression::XZ)
280 .encryption(Encryption::Camellia)
281 .cipher_mode(CipherMode::CBC)
282 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
283 .password(Some("password"))
284 .build(),
285 )
286 .unwrap()
287 }
288
289 fn create_archive(src: &[u8], options: WriteOptions) -> io::Result<Vec<u8>> {
290 let mut writer = Archive::write_header(Vec::with_capacity(src.len()))?;
291 writer.add_entry({
292 let mut builder = EntryBuilder::new_file("test/text".into(), options)?;
293 builder.write_all(src)?;
294 builder.build()?
295 })?;
296 writer.finalize()
297 }
298
299 fn archive(src: &[u8], options: WriteOptions) -> io::Result<()> {
300 let read_options = ReadOptions::with_password(options.password());
301 let archive = create_archive(src, options)?;
302 let mut archive_reader = Archive::read_header(archive.as_slice())?;
303 let item = archive_reader.entries_skip_solid().next().unwrap()?;
304 let mut reader = item.reader(read_options)?;
305 let mut dist = Vec::new();
306 io::copy(&mut reader, &mut dist)?;
307 assert_eq!(src, dist.as_slice());
308 Ok(())
309 }
310
311 fn solid_archive(write_option: WriteOptions) {
312 let password = write_option.password().map(|it| it.to_string());
313 let mut archive = Archive::write_solid_header(Vec::new(), write_option).unwrap();
314 for i in 0..200 {
315 archive
316 .add_entry({
317 let mut builder = EntryBuilder::new_file(
318 format!("test/text{i}").into(),
319 WriteOptions::store(),
320 )
321 .unwrap();
322 builder
323 .write_all(format!("text{i}").repeat(i).as_bytes())
324 .unwrap();
325 builder.build().unwrap()
326 })
327 .unwrap();
328 }
329 let buf = archive.finalize().unwrap();
330 let mut archive = Archive::read_header(&buf[..]).unwrap();
331 let mut entries = archive.entries();
332 let entry = entries.next().unwrap().unwrap();
333 if let ReadEntry::Solid(entry) = entry {
334 let mut entries = entry.entries(password.as_deref()).unwrap();
335 for i in 0..200 {
336 let entry = entries.next().unwrap().unwrap();
337 let mut reader = entry.reader(ReadOptions::builder().build()).unwrap();
338 let mut body = Vec::new();
339 reader.read_to_end(&mut body).unwrap();
340 assert_eq!(format!("text{i}").repeat(i).as_bytes(), &body[..]);
341 }
342 } else {
343 panic!()
344 }
345 }
346
347 #[test]
348 fn solid_store_camellia_cbc() {
349 solid_archive(
350 WriteOptions::builder()
351 .compression(Compression::No)
352 .encryption(Encryption::Camellia)
353 .cipher_mode(CipherMode::CBC)
354 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
355 .password(Some("PASSWORD"))
356 .build(),
357 );
358 }
359
360 #[test]
361 fn solid_entry() {
362 let archive = {
363 let mut writer = Archive::write_header(Vec::new()).unwrap();
364 let dir_entry = {
365 let builder = EntryBuilder::new_dir("test".into());
366 builder.build().unwrap()
367 };
368 let file_entry = {
369 let options = WriteOptions::store();
370 let mut builder = EntryBuilder::new_file("test/text".into(), options).unwrap();
371 builder.write_all(b"text").unwrap();
372 builder.build().unwrap()
373 };
374 writer
375 .add_entry({
376 let mut builder = SolidEntryBuilder::new(WriteOptions::store()).unwrap();
377 builder.add_entry(dir_entry).unwrap();
378 builder.add_entry(file_entry).unwrap();
379 builder.build().unwrap()
380 })
381 .unwrap();
382 writer.finalize().unwrap()
383 };
384
385 let mut archive_reader = Archive::read_header(archive.as_slice()).unwrap();
386 let mut entries = archive_reader.entries_with_password(Some("password"));
387 entries.next().unwrap().expect("failed to read entry");
388 entries.next().unwrap().expect("failed to read entry");
389 assert!(entries.next().is_none());
390 }
391
392 #[test]
393 fn copy_entry() {
394 let archive = create_archive(b"archive text", WriteOptions::builder().build())
395 .expect("failed to create archive");
396 let mut reader =
397 Archive::read_header(archive.as_slice()).expect("failed to read archive header");
398
399 let mut writer = Archive::write_header(Vec::new()).expect("failed to write archive header");
400
401 for entry in reader.raw_entries() {
402 writer
403 .add_entry(entry.expect("failed to read entry"))
404 .expect("failed to add entry");
405 }
406 assert_eq!(
407 archive,
408 writer.finalize().expect("failed to finish archive")
409 )
410 }
411
412 #[test]
413 fn append() {
414 let mut writer = Archive::write_header(Vec::new()).unwrap();
415 writer
416 .add_entry({
417 let builder =
418 EntryBuilder::new_file("text1.txt".into(), WriteOptions::builder().build())
419 .unwrap();
420 builder.build().unwrap()
421 })
422 .unwrap();
423 let result = writer.finalize().unwrap();
424
425 let mut appender = Archive::read_header(Cursor::new(result)).unwrap();
426 appender.seek_to_end().unwrap();
427 appender
428 .add_entry({
429 let builder =
430 EntryBuilder::new_file("text2.txt".into(), WriteOptions::builder().build())
431 .unwrap();
432 builder.build().unwrap()
433 })
434 .unwrap();
435 let appended = appender.finalize().unwrap().into_inner();
436
437 let mut reader = Archive::read_header(appended.as_slice()).unwrap();
438
439 let mut entries = reader.entries_skip_solid();
440 assert!(entries.next().is_some());
441 assert!(entries.next().is_some());
442 assert!(entries.next().is_none());
443 }
444
445 #[test]
446 fn metadata() {
447 let original_entry = {
448 let mut builder =
449 EntryBuilder::new_file("name".into(), WriteOptions::builder().build()).unwrap();
450 builder.created(Duration::seconds(31));
451 builder.modified(Duration::seconds(32));
452 builder.accessed(Duration::seconds(33));
453 builder.permission(Permission::new(1, "uname".into(), 2, "gname".into(), 0o775));
454 builder.write_all(b"entry data").unwrap();
455 builder.build().unwrap()
456 };
457
458 let mut archive = Archive::write_header(Vec::new()).unwrap();
459 archive.add_entry(original_entry.clone()).unwrap();
460
461 let buf = archive.finalize().unwrap();
462
463 let mut archive = Archive::read_header(buf.as_slice()).unwrap();
464
465 let mut entries = archive.entries_with_password(None);
466 let read_entry = entries.next().unwrap().unwrap();
467
468 assert_eq!(
469 original_entry.metadata().created(),
470 read_entry.metadata().created()
471 );
472 assert_eq!(
473 original_entry.metadata().modified(),
474 read_entry.metadata().modified()
475 );
476 assert_eq!(
477 original_entry.metadata().accessed(),
478 read_entry.metadata().accessed()
479 );
480 assert_eq!(
481 original_entry.metadata().permission(),
482 read_entry.metadata().permission()
483 );
484 assert_eq!(
485 original_entry.metadata().compressed_size(),
486 read_entry.metadata().compressed_size()
487 );
488 assert_eq!(
489 original_entry.metadata().raw_file_size(),
490 read_entry.metadata().raw_file_size()
491 );
492 }
493}