1mod header;
2mod read;
3mod write;
4
5use crate::{
6 chunk::{ChunkStreamWriter, RawChunk},
7 cipher::CipherWriter,
8 compress::CompressionWriter,
9};
10use core::num::NonZeroU32;
11pub use header::*;
12use std::io::prelude::*;
13pub(crate) use {read::*, write::*};
14
15pub struct Archive<T> {
67 inner: T,
68 header: ArchiveHeader,
69 max_chunk_size: Option<NonZeroU32>,
70 next_archive: bool,
72 buf: Vec<RawChunk>,
73}
74
75impl<T> Archive<T> {
76 const fn new(inner: T, header: ArchiveHeader) -> Self {
77 Self::with_buffer(inner, header, Vec::new())
78 }
79
80 const fn with_buffer(inner: T, header: ArchiveHeader, buf: Vec<RawChunk>) -> Self {
81 Self {
82 inner,
83 header,
84 max_chunk_size: None,
85 next_archive: false,
86 buf,
87 }
88 }
89
90 #[inline]
104 pub fn set_max_chunk_size(&mut self, size: NonZeroU32) {
105 self.max_chunk_size = Some(size);
106 }
107
108 #[inline]
116 pub const fn has_next_archive(&self) -> bool {
117 self.next_archive
118 }
119
120 #[must_use = "call `finalize` instead if you don't need the inner value"]
158 #[inline]
159 pub fn into_inner(self) -> T {
160 self.inner
161 }
162}
163
164pub struct SolidArchive<T: Write> {
196 archive_header: ArchiveHeader,
197 inner: CompressionWriter<CipherWriter<ChunkStreamWriter<T>>>,
198 max_chunk_size: Option<NonZeroU32>,
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::{Duration, entry::*};
205 use std::io::{self, Cursor};
206 #[cfg(all(target_family = "wasm", target_os = "unknown"))]
207 use wasm_bindgen_test::wasm_bindgen_test as test;
208
209 #[test]
210 fn store_archive() {
211 archive(
212 b"src data bytes",
213 WriteOptions::builder().compression(Compression::No).build(),
214 )
215 .unwrap()
216 }
217
218 #[test]
219 fn deflate_archive() {
220 archive(
221 b"src data bytes",
222 WriteOptions::builder()
223 .compression(Compression::Deflate)
224 .build(),
225 )
226 .unwrap()
227 }
228
229 #[test]
230 fn zstd_archive() {
231 archive(
232 b"src data bytes",
233 WriteOptions::builder()
234 .compression(Compression::ZStandard)
235 .build(),
236 )
237 .unwrap()
238 }
239
240 #[test]
241 fn xz_archive() {
242 archive(
243 b"src data bytes",
244 WriteOptions::builder().compression(Compression::XZ).build(),
245 )
246 .unwrap();
247 }
248
249 #[test]
250 fn store_with_aes_cbc_archive() {
251 archive(
252 b"plain text",
253 WriteOptions::builder()
254 .compression(Compression::No)
255 .encryption(Encryption::Aes)
256 .cipher_mode(CipherMode::CBC)
257 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
258 .password(Some("password"))
259 .build(),
260 )
261 .unwrap();
262 }
263
264 #[test]
265 fn zstd_with_aes_ctr_archive() {
266 archive(
267 b"plain text",
268 WriteOptions::builder()
269 .compression(Compression::ZStandard)
270 .encryption(Encryption::Aes)
271 .cipher_mode(CipherMode::CTR)
272 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
273 .password(Some("password"))
274 .build(),
275 )
276 .unwrap();
277 }
278
279 #[test]
280 fn zstd_with_aes_cbc_archive() {
281 archive(
282 b"plain text",
283 WriteOptions::builder()
284 .compression(Compression::ZStandard)
285 .encryption(Encryption::Aes)
286 .cipher_mode(CipherMode::CBC)
287 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
288 .password(Some("password"))
289 .build(),
290 )
291 .unwrap();
292 }
293
294 #[test]
295 fn zstd_with_camellia_ctr_archive() {
296 archive(
297 b"plain text",
298 WriteOptions::builder()
299 .compression(Compression::ZStandard)
300 .encryption(Encryption::Camellia)
301 .cipher_mode(CipherMode::CTR)
302 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
303 .password(Some("password"))
304 .build(),
305 )
306 .unwrap();
307 }
308
309 #[test]
310 fn zstd_with_camellia_cbc_archive() {
311 archive(
312 b"plain text",
313 WriteOptions::builder()
314 .compression(Compression::ZStandard)
315 .encryption(Encryption::Camellia)
316 .cipher_mode(CipherMode::CBC)
317 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
318 .password(Some("password"))
319 .build(),
320 )
321 .unwrap();
322 }
323
324 #[test]
325 fn xz_with_aes_cbc_archive() {
326 archive(
327 b"plain text",
328 WriteOptions::builder()
329 .compression(Compression::XZ)
330 .encryption(Encryption::Aes)
331 .cipher_mode(CipherMode::CBC)
332 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
333 .password(Some("password"))
334 .build(),
335 )
336 .unwrap()
337 }
338
339 #[test]
340 fn xz_with_camellia_cbc_archive() {
341 archive(
342 b"plain text",
343 WriteOptions::builder()
344 .compression(Compression::XZ)
345 .encryption(Encryption::Camellia)
346 .cipher_mode(CipherMode::CBC)
347 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
348 .password(Some("password"))
349 .build(),
350 )
351 .unwrap()
352 }
353
354 fn create_archive(src: &[u8], options: WriteOptions) -> io::Result<Vec<u8>> {
355 let mut writer = Archive::write_header(Vec::with_capacity(src.len()))?;
356 writer.add_entry({
357 let mut builder = EntryBuilder::new_file("test/text".into(), options)?;
358 builder.write_all(src)?;
359 builder.build()?
360 })?;
361 writer.finalize()
362 }
363
364 fn archive(src: &[u8], options: WriteOptions) -> io::Result<()> {
365 let read_options = ReadOptions::with_password(options.password());
366 let archive = create_archive(src, options)?;
367 let mut archive_reader = Archive::read_header(archive.as_slice())?;
368 let item = archive_reader.entries().skip_solid().next().unwrap()?;
369 let mut reader = item.reader(read_options)?;
370 let mut dist = Vec::new();
371 io::copy(&mut reader, &mut dist)?;
372 assert_eq!(src, dist.as_slice());
373 Ok(())
374 }
375
376 fn solid_archive(write_option: WriteOptions) {
377 let password = write_option.password().map(|it| it.to_vec());
378 let mut archive = Archive::write_solid_header(Vec::new(), write_option).unwrap();
379 for i in 0..200 {
380 archive
381 .add_entry({
382 let mut builder = EntryBuilder::new_file(
383 format!("test/text{i}").into(),
384 WriteOptions::store(),
385 )
386 .unwrap();
387 builder
388 .write_all(format!("text{i}").repeat(i).as_bytes())
389 .unwrap();
390 builder.build().unwrap()
391 })
392 .unwrap();
393 }
394 let buf = archive.finalize().unwrap();
395 let mut archive = Archive::read_header(&buf[..]).unwrap();
396 let mut entries = archive.entries();
397 let entry = entries.next().unwrap().unwrap();
398 if let ReadEntry::Solid(entry) = entry {
399 let mut entries = entry.entries(password.as_deref()).unwrap();
400 for i in 0..200 {
401 let entry = entries.next().unwrap().unwrap();
402 let mut reader = entry.reader(ReadOptions::builder().build()).unwrap();
403 let mut body = Vec::new();
404 reader.read_to_end(&mut body).unwrap();
405 assert_eq!(format!("text{i}").repeat(i).as_bytes(), &body[..]);
406 }
407 } else {
408 panic!()
409 }
410 }
411
412 #[test]
413 fn solid_store_camellia_cbc() {
414 solid_archive(
415 WriteOptions::builder()
416 .compression(Compression::No)
417 .encryption(Encryption::Camellia)
418 .cipher_mode(CipherMode::CBC)
419 .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(1)))
420 .password(Some("PASSWORD"))
421 .build(),
422 );
423 }
424
425 #[test]
426 fn solid_entry() {
427 let archive = {
428 let mut writer = Archive::write_header(Vec::new()).unwrap();
429 let dir_entry = {
430 let builder = EntryBuilder::new_dir("test".into());
431 builder.build().unwrap()
432 };
433 let file_entry = {
434 let options = WriteOptions::store();
435 let mut builder = EntryBuilder::new_file("test/text".into(), options).unwrap();
436 builder.write_all(b"text").unwrap();
437 builder.build().unwrap()
438 };
439 writer
440 .add_entry({
441 let mut builder = SolidEntryBuilder::new(WriteOptions::store()).unwrap();
442 builder.add_entry(dir_entry).unwrap();
443 builder.add_entry(file_entry).unwrap();
444 builder.build().unwrap()
445 })
446 .unwrap();
447 writer.finalize().unwrap()
448 };
449
450 let mut archive_reader = Archive::read_header(archive.as_slice()).unwrap();
451 let mut entries = archive_reader.entries_with_password(Some(b"password"));
452 entries.next().unwrap().expect("failed to read entry");
453 entries.next().unwrap().expect("failed to read entry");
454 assert!(entries.next().is_none());
455 }
456
457 #[test]
458 fn copy_entry() {
459 let archive = create_archive(b"archive text", WriteOptions::builder().build())
460 .expect("failed to create archive");
461 let mut reader =
462 Archive::read_header(archive.as_slice()).expect("failed to read archive header");
463
464 let mut writer = Archive::write_header(Vec::new()).expect("failed to write archive header");
465
466 for entry in reader.raw_entries() {
467 writer
468 .add_entry(entry.expect("failed to read entry"))
469 .expect("failed to add entry");
470 }
471 assert_eq!(
472 archive,
473 writer.finalize().expect("failed to finish archive")
474 )
475 }
476
477 #[test]
478 fn append() {
479 let mut writer = Archive::write_header(Vec::new()).unwrap();
480 writer
481 .add_entry({
482 let builder =
483 EntryBuilder::new_file("text1.txt".into(), WriteOptions::builder().build())
484 .unwrap();
485 builder.build().unwrap()
486 })
487 .unwrap();
488 let result = writer.finalize().unwrap();
489
490 let mut appender = Archive::read_header(Cursor::new(result)).unwrap();
491 appender.seek_to_end().unwrap();
492 appender
493 .add_entry({
494 let builder =
495 EntryBuilder::new_file("text2.txt".into(), WriteOptions::builder().build())
496 .unwrap();
497 builder.build().unwrap()
498 })
499 .unwrap();
500 let appended = appender.finalize().unwrap().into_inner();
501
502 let mut reader = Archive::read_header(appended.as_slice()).unwrap();
503
504 let mut entries = reader.entries();
505 assert!(entries.next().is_some());
506 assert!(entries.next().is_some());
507 assert!(entries.next().is_none());
508 }
509
510 #[test]
511 fn metadata() {
512 let original_entry = {
513 let mut builder =
514 EntryBuilder::new_file("name".into(), WriteOptions::builder().build()).unwrap();
515 builder.created(Duration::seconds(31));
516 builder.modified(Duration::seconds(32));
517 builder.accessed(Duration::seconds(33));
518 builder.permission(Permission::new(1, "uname".into(), 2, "gname".into(), 0o775));
519 builder.write_all(b"entry data").unwrap();
520 builder.build().unwrap()
521 };
522
523 let mut archive = Archive::write_header(Vec::new()).unwrap();
524 archive.add_entry(original_entry.clone()).unwrap();
525
526 let buf = archive.finalize().unwrap();
527
528 let mut archive = Archive::read_header(buf.as_slice()).unwrap();
529
530 let mut entries = archive.entries_with_password(None);
531 let read_entry = entries.next().unwrap().unwrap();
532
533 assert_eq!(
534 original_entry.metadata().created(),
535 read_entry.metadata().created()
536 );
537 assert_eq!(
538 original_entry.metadata().modified(),
539 read_entry.metadata().modified()
540 );
541 assert_eq!(
542 original_entry.metadata().accessed(),
543 read_entry.metadata().accessed()
544 );
545 assert_eq!(
546 original_entry.metadata().permission(),
547 read_entry.metadata().permission()
548 );
549 assert_eq!(
550 original_entry.metadata().compressed_size(),
551 read_entry.metadata().compressed_size()
552 );
553 assert_eq!(
554 original_entry.metadata().raw_file_size(),
555 read_entry.metadata().raw_file_size()
556 );
557 }
558}