libpna/archive/write.rs
1use crate::{
2 archive::{Archive, ArchiveHeader, PNA_HEADER, SolidArchive},
3 chunk::{Chunk, ChunkExt, ChunkStreamWriter, ChunkType, RawChunk},
4 cipher::CipherWriter,
5 compress::CompressionWriter,
6 entry::{
7 Entry, EntryHeader, EntryName, EntryPart, Metadata, NormalEntry, SealedEntryExt,
8 SolidHeader, WriteCipher, WriteOption, WriteOptions, get_writer, get_writer_context,
9 },
10 io::TryIntoInner,
11};
12#[cfg(feature = "unstable-async")]
13use futures_io::AsyncWrite;
14#[cfg(feature = "unstable-async")]
15use futures_util::AsyncWriteExt;
16use std::io::{self, Write};
17
18/// Internal Writer type alias.
19pub(crate) type InternalDataWriter<W> = CompressionWriter<CipherWriter<W>>;
20
21/// Internal Writer type alias.
22pub(crate) type InternalArchiveDataWriter<W> = InternalDataWriter<ChunkStreamWriter<W>>;
23
24/// Writer that compresses and encrypts according to the given options.
25pub struct EntryDataWriter<W: Write>(InternalArchiveDataWriter<W>);
26
27impl<W: Write> Write for EntryDataWriter<W> {
28 #[inline]
29 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
30 self.0.write(buf)
31 }
32
33 #[inline]
34 fn flush(&mut self) -> io::Result<()> {
35 self.0.flush()
36 }
37}
38
39pub struct SolidArchiveEntryDataWriter<'w, W: Write>(
40 InternalArchiveDataWriter<&'w mut InternalArchiveDataWriter<W>>,
41);
42
43impl<W: Write> Write for SolidArchiveEntryDataWriter<'_, W> {
44 #[inline]
45 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
46 self.0.write(buf)
47 }
48
49 #[inline]
50 fn flush(&mut self) -> io::Result<()> {
51 self.0.flush()
52 }
53}
54
55impl<W: Write> Archive<W> {
56 /// Writes the archive header to the given `Write` object and return a new [Archive].
57 ///
58 /// # Arguments
59 ///
60 /// * `write` - The [Write] object to write the header to.
61 ///
62 /// # Returns
63 ///
64 /// A new [`io::Result<Archive<W>>`]
65 ///
66 /// # Examples
67 ///
68 /// ```no_run
69 /// use libpna::Archive;
70 /// use std::fs;
71 /// # use std::io;
72 ///
73 /// # fn main() -> io::Result<()> {
74 /// let file = fs::File::create("example.pna")?;
75 /// let mut archive = Archive::write_header(file)?;
76 /// archive.finalize()?;
77 /// # Ok(())
78 /// # }
79 /// ```
80 ///
81 /// # Errors
82 ///
83 /// Returns an error if an I/O error occurs while writing header to the writer.
84 #[inline]
85 pub fn write_header(write: W) -> io::Result<Self> {
86 let header = ArchiveHeader::new(0, 0, 0);
87 Self::write_header_with(write, header)
88 }
89
90 #[inline]
91 fn write_header_with(mut write: W, header: ArchiveHeader) -> io::Result<Self> {
92 write.write_all(PNA_HEADER)?;
93 (ChunkType::AHED, header.to_bytes()).write_chunk_in(&mut write)?;
94 Ok(Self::new(write, header))
95 }
96
97 /// Writes a regular file as a normal entry into the archive.
98 ///
99 /// # Examples
100 /// ```no_run
101 /// use libpna::{Archive, Metadata, WriteOptions};
102 /// # use std::error::Error;
103 /// use std::fs;
104 /// use std::io::{self, prelude::*};
105 ///
106 /// # fn main() -> Result<(), Box<dyn Error>> {
107 /// let file = fs::File::create("foo.pna")?;
108 /// let mut archive = Archive::write_header(file)?;
109 /// archive.write_file(
110 /// "bar.txt".into(),
111 /// Metadata::new(),
112 /// WriteOptions::builder().build(),
113 /// |writer| writer.write_all(b"text"),
114 /// )?;
115 /// archive.finalize()?;
116 /// # Ok(())
117 /// # }
118 /// ```
119 ///
120 /// # Errors
121 ///
122 /// Returns an error if an I/O error occurs while writing the entry, or if the closure returns an error.
123 #[inline]
124 pub fn write_file<F>(
125 &mut self,
126 name: EntryName,
127 metadata: Metadata,
128 option: impl WriteOption,
129 mut f: F,
130 ) -> io::Result<()>
131 where
132 F: FnMut(&mut EntryDataWriter<&mut W>) -> io::Result<()>,
133 {
134 write_file_entry(&mut self.inner, name, metadata, option, |w| {
135 let mut w = EntryDataWriter(w);
136 f(&mut w)?;
137 Ok(w.0)
138 })
139 }
140
141 /// Adds a new entry to the archive.
142 ///
143 /// # Arguments
144 ///
145 /// * `entry` - The entry to add to the archive.
146 ///
147 /// # Examples
148 ///
149 /// ```no_run
150 /// use libpna::{Archive, EntryBuilder, WriteOptions};
151 /// use std::fs;
152 /// # use std::io;
153 ///
154 /// # fn main() -> io::Result<()> {
155 /// let file = fs::File::create("example.pna")?;
156 /// let mut archive = Archive::write_header(file)?;
157 /// archive.add_entry(
158 /// EntryBuilder::new_file("example.txt".into(), WriteOptions::builder().build())?.build()?,
159 /// )?;
160 /// archive.finalize()?;
161 /// # Ok(())
162 /// # }
163 /// ```
164 ///
165 /// # Errors
166 ///
167 /// Returns an error if an I/O error occurs while writing a given entry.
168 #[inline]
169 pub fn add_entry(&mut self, entry: impl Entry) -> io::Result<usize> {
170 entry.write_in(&mut self.inner)
171 }
172
173 /// Adds a part of an entry to the archive.
174 ///
175 /// # Arguments
176 ///
177 /// * `entry_part` - The part of an entry to add to the archive.
178 ///
179 /// # Examples
180 ///
181 /// ```no_run
182 /// # use libpna::{Archive, EntryBuilder, EntryPart, WriteOptions};
183 /// # use std::fs::File;
184 /// # use std::io;
185 ///
186 /// # fn main() -> io::Result<()> {
187 /// let part1_file = File::create("example.part1.pna")?;
188 /// let mut archive_part1 = Archive::write_header(part1_file)?;
189 /// let entry =
190 /// EntryBuilder::new_file("example.txt".into(), WriteOptions::builder().build())?.build()?;
191 /// archive_part1.add_entry_part(EntryPart::from(entry))?;
192 ///
193 /// let part2_file = File::create("example.part2.pna")?;
194 /// let archive_part2 = archive_part1.split_to_next_archive(part2_file)?;
195 /// archive_part2.finalize()?;
196 /// # Ok(())
197 /// # }
198 /// ```
199 ///
200 /// # Errors
201 ///
202 /// Returns an error if an I/O error occurs while writing the entry part.
203 #[inline]
204 pub fn add_entry_part<T>(&mut self, entry_part: EntryPart<T>) -> io::Result<usize>
205 where
206 RawChunk<T>: Chunk,
207 {
208 let mut written_len = 0;
209 for chunk in entry_part.0 {
210 written_len += chunk.write_chunk_in(&mut self.inner)?;
211 }
212 Ok(written_len)
213 }
214
215 #[inline]
216 fn add_next_archive_marker(&mut self) -> io::Result<usize> {
217 (ChunkType::ANXT, []).write_chunk_in(&mut self.inner)
218 }
219
220 /// Split to the next archive.
221 ///
222 /// # Examples
223 /// ```no_run
224 /// # use libpna::{Archive, EntryBuilder, EntryPart, WriteOptions};
225 /// # use std::fs::File;
226 /// # use std::io;
227 ///
228 /// # fn main() -> io::Result<()> {
229 /// let part1_file = File::create("example.part1.pna")?;
230 /// let mut archive_part1 = Archive::write_header(part1_file)?;
231 /// let entry =
232 /// EntryBuilder::new_file("example.txt".into(), WriteOptions::builder().build())?.build()?;
233 /// archive_part1.add_entry_part(EntryPart::from(entry))?;
234 ///
235 /// let part2_file = File::create("example.part2.pna")?;
236 /// let archive_part2 = archive_part1.split_to_next_archive(part2_file)?;
237 /// archive_part2.finalize()?;
238 /// # Ok(())
239 /// # }
240 /// ```
241 ///
242 /// # Errors
243 ///
244 /// Returns an error if an I/O error occurs while splitting to the next archive.
245 #[inline]
246 pub fn split_to_next_archive<OW: Write>(mut self, writer: OW) -> io::Result<Archive<OW>> {
247 let next_archive_number = self.header.archive_number + 1;
248 let header = ArchiveHeader::new(0, 0, next_archive_number);
249 self.add_next_archive_marker()?;
250 self.finalize()?;
251 Archive::write_header_with(writer, header)
252 }
253
254 /// Writes the end-of-archive marker and finalizes the archive.
255 ///
256 /// Marks that the PNA archive contains no more entries.
257 /// Normally, a PNA archive reader will continue reading entries in the hope that the entry exists until it encounters this end marker.
258 /// This end marker should always be recorded at the end of the file unless there is a special reason to do so.
259 ///
260 /// # Examples
261 /// Creates an empty archive.
262 /// ```no_run
263 /// # use std::io;
264 /// # use std::fs::File;
265 /// # use libpna::Archive;
266 ///
267 /// # fn main() -> io::Result<()> {
268 /// let file = File::create("foo.pna")?;
269 /// let mut archive = Archive::write_header(file)?;
270 /// archive.finalize()?;
271 /// # Ok(())
272 /// # }
273 /// ```
274 ///
275 /// # Errors
276 /// Returns an error if writing the end-of-archive marker fails.
277 #[inline]
278 #[must_use = "archive is not complete until finalize succeeds"]
279 pub fn finalize(mut self) -> io::Result<W> {
280 (ChunkType::AEND, []).write_chunk_in(&mut self.inner)?;
281 Ok(self.inner)
282 }
283}
284
285#[cfg(feature = "unstable-async")]
286impl<W: AsyncWrite + Unpin> Archive<W> {
287 /// Writes the archive header to the given object and return a new [Archive].
288 /// This API is unstable.
289 ///
290 /// # Errors
291 ///
292 /// Returns an error if an I/O error occurs while writing header to the writer.
293 #[inline]
294 pub async fn write_header_async(write: W) -> io::Result<Self> {
295 let header = ArchiveHeader::new(0, 0, 0);
296 Self::write_header_with_async(write, header).await
297 }
298
299 #[inline]
300 async fn write_header_with_async(mut write: W, header: ArchiveHeader) -> io::Result<Self> {
301 write.write_all(PNA_HEADER).await?;
302 let mut chunk_writer = crate::chunk::ChunkWriter::new(&mut write);
303 chunk_writer
304 .write_chunk_async((ChunkType::AHED, header.to_bytes()))
305 .await?;
306 Ok(Self::new(write, header))
307 }
308
309 /// Adds a new entry to the archive.
310 /// This API is unstable.
311 ///
312 /// # Errors
313 ///
314 /// Returns an error if an I/O error occurs while writing a given entry.
315 #[inline]
316 pub async fn add_entry_async(&mut self, entry: impl Entry) -> io::Result<usize> {
317 let mut bytes = Vec::new();
318 entry.write_in(&mut bytes)?;
319 self.inner.write_all(&bytes).await?;
320 Ok(bytes.len())
321 }
322
323 /// Writes the end-of-archive marker and finalizes the archive.
324 /// This API is unstable.
325 ///
326 /// # Errors
327 ///
328 /// Returns an error if writing the end-of-archive marker fails.
329 #[inline]
330 pub async fn finalize_async(mut self) -> io::Result<W> {
331 let mut chunk_writer = crate::chunk::ChunkWriter::new(&mut self.inner);
332 chunk_writer
333 .write_chunk_async((ChunkType::AEND, []))
334 .await?;
335 Ok(self.inner)
336 }
337}
338
339impl<W: Write> Archive<W> {
340 /// Writes the archive header to the given `Write` object and return a new [SolidArchive].
341 ///
342 /// # Arguments
343 ///
344 /// * `write` - The [Write] object to write the header to.
345 /// * `option` - The [WriteOptions] object of a solid mode option.
346 ///
347 /// # Returns
348 ///
349 /// A new [`io::Result<SolidArchive<W>>`]
350 ///
351 /// # Examples
352 ///
353 /// ```no_run
354 /// use libpna::{Archive, WriteOptions};
355 /// use std::fs::File;
356 /// # use std::io;
357 ///
358 /// # fn main() -> io::Result<()> {
359 /// let option = WriteOptions::builder().build();
360 /// let file = File::create("example.pna")?;
361 /// let mut archive = Archive::write_solid_header(file, option)?;
362 /// archive.finalize()?;
363 /// # Ok(())
364 /// # }
365 /// ```
366 ///
367 /// # Errors
368 ///
369 /// Returns an error if an I/O error occurs while writing header to the writer.
370 #[inline]
371 pub fn write_solid_header(write: W, option: impl WriteOption) -> io::Result<SolidArchive<W>> {
372 let archive = Self::write_header(write)?;
373 archive.into_solid_archive(option)
374 }
375
376 #[inline]
377 fn into_solid_archive(mut self, option: impl WriteOption) -> io::Result<SolidArchive<W>> {
378 let header = SolidHeader::new(
379 option.compression(),
380 option.encryption(),
381 option.cipher_mode(),
382 );
383 let context = get_writer_context(option)?;
384
385 (ChunkType::SHED, header.to_bytes()).write_chunk_in(&mut self.inner)?;
386 if let Some(WriteCipher { context: c, .. }) = &context.cipher {
387 (ChunkType::PHSF, c.phsf.as_bytes()).write_chunk_in(&mut self.inner)?;
388 (ChunkType::SDAT, c.iv.as_slice()).write_chunk_in(&mut self.inner)?;
389 }
390 self.inner.flush()?;
391 let writer = get_writer(
392 ChunkStreamWriter::new(ChunkType::SDAT, self.inner),
393 &context,
394 )?;
395
396 Ok(SolidArchive {
397 archive_header: self.header,
398 inner: writer,
399 })
400 }
401}
402
403impl<W: Write> SolidArchive<W> {
404 /// Adds a new entry to the archive.
405 ///
406 /// # Arguments
407 ///
408 /// * `entry` - The entry to add to the archive.
409 ///
410 /// # Examples
411 ///
412 /// ```no_run
413 /// use libpna::{Archive, EntryBuilder, WriteOptions};
414 /// use std::fs::File;
415 /// # use std::io;
416 ///
417 /// # fn main() -> io::Result<()> {
418 /// let option = WriteOptions::builder().build();
419 /// let file = File::create("example.pna")?;
420 /// let mut archive = Archive::write_solid_header(file, option)?;
421 /// archive
422 /// .add_entry(EntryBuilder::new_file("example.txt".into(), WriteOptions::store())?.build()?)?;
423 /// archive.finalize()?;
424 /// # Ok(())
425 /// # }
426 /// ```
427 ///
428 /// # Errors
429 ///
430 /// Returns an error if an I/O error occurs while writing a given entry.
431 #[inline]
432 pub fn add_entry<T>(&mut self, entry: NormalEntry<T>) -> io::Result<usize>
433 where
434 NormalEntry<T>: Entry,
435 {
436 entry.write_in(&mut self.inner)
437 }
438
439 /// Writes a regular file as a solid entry into the archive.
440 ///
441 /// # Errors
442 ///
443 /// Returns an error if an I/O error occurs while writing the entry, or if the closure returns an error.
444 ///
445 /// # Examples
446 /// ```no_run
447 /// use libpna::{Archive, Metadata, WriteOptions};
448 /// # use std::error::Error;
449 /// use std::fs;
450 /// use std::io::{self, prelude::*};
451 ///
452 /// # fn main() -> Result<(), Box<dyn Error>> {
453 /// let file = fs::File::create("foo.pna")?;
454 /// let option = WriteOptions::builder().build();
455 /// let mut archive = Archive::write_solid_header(file, option)?;
456 /// archive.write_file("bar.txt".into(), Metadata::new(), |writer| {
457 /// writer.write_all(b"text")
458 /// })?;
459 /// archive.finalize()?;
460 /// # Ok(())
461 /// # }
462 /// ```
463 #[inline]
464 pub fn write_file<F>(&mut self, name: EntryName, metadata: Metadata, mut f: F) -> io::Result<()>
465 where
466 F: FnMut(&mut SolidArchiveEntryDataWriter<W>) -> io::Result<()>,
467 {
468 let option = WriteOptions::store();
469 write_file_entry(&mut self.inner, name, metadata, option, |w| {
470 let mut w = SolidArchiveEntryDataWriter(w);
471 f(&mut w)?;
472 Ok(w.0)
473 })
474 }
475
476 /// Writes the end-of-archive marker and finalizes the archive.
477 ///
478 /// Marks that the PNA archive contains no more entries.
479 /// Normally, a PNA archive reader will continue reading entries in the hope that the entry exists until it encounters this end marker.
480 /// This end marker should always be recorded at the end of the file unless there is a special reason to do so.
481 ///
482 /// # Errors
483 /// Returns an error if writing the end-of-archive marker fails.
484 ///
485 /// # Examples
486 /// Creates an empty archive.
487 /// ```no_run
488 /// use libpna::{Archive, WriteOptions};
489 /// use std::fs::File;
490 /// # use std::io;
491 ///
492 /// # fn main() -> io::Result<()> {
493 /// let option = WriteOptions::builder().build();
494 /// let file = File::create("example.pna")?;
495 /// let mut archive = Archive::write_solid_header(file, option)?;
496 /// archive.finalize()?;
497 /// # Ok(())
498 /// # }
499 /// ```
500 #[inline]
501 #[must_use = "archive is not complete until finalize succeeds"]
502 pub fn finalize(self) -> io::Result<W> {
503 let archive = self.finalize_solid_entry()?;
504 archive.finalize()
505 }
506
507 #[inline]
508 fn finalize_solid_entry(mut self) -> io::Result<Archive<W>> {
509 self.inner.flush()?;
510 let mut inner = self.inner.try_into_inner()?.try_into_inner()?.into_inner();
511 (ChunkType::SEND, []).write_chunk_in(&mut inner)?;
512 Ok(Archive::new(inner, self.archive_header))
513 }
514}
515
516pub(crate) fn write_file_entry<W, F>(
517 inner: &mut W,
518 name: EntryName,
519 metadata: Metadata,
520 option: impl WriteOption,
521 mut f: F,
522) -> io::Result<()>
523where
524 W: Write,
525 F: FnMut(InternalArchiveDataWriter<&mut W>) -> io::Result<InternalArchiveDataWriter<&mut W>>,
526{
527 let header = EntryHeader::for_file(
528 option.compression(),
529 option.encryption(),
530 option.cipher_mode(),
531 name,
532 );
533 (ChunkType::FHED, header.to_bytes()).write_chunk_in(inner)?;
534 if let Some(c) = metadata.created {
535 (ChunkType::cTIM, c.whole_seconds().to_be_bytes()).write_chunk_in(inner)?;
536 if c.subsec_nanoseconds() != 0 {
537 (ChunkType::cTNS, c.subsec_nanoseconds().to_be_bytes()).write_chunk_in(inner)?;
538 }
539 }
540 if let Some(m) = metadata.modified {
541 (ChunkType::mTIM, m.whole_seconds().to_be_bytes()).write_chunk_in(inner)?;
542 if m.subsec_nanoseconds() != 0 {
543 (ChunkType::mTNS, m.subsec_nanoseconds().to_be_bytes()).write_chunk_in(inner)?;
544 }
545 }
546 if let Some(a) = metadata.accessed {
547 (ChunkType::aTIM, a.whole_seconds().to_be_bytes()).write_chunk_in(inner)?;
548 if a.subsec_nanoseconds() != 0 {
549 (ChunkType::aTNS, a.subsec_nanoseconds().to_be_bytes()).write_chunk_in(inner)?;
550 }
551 }
552 if let Some(p) = metadata.permission {
553 (ChunkType::fPRM, p.to_bytes()).write_chunk_in(inner)?;
554 }
555 let context = get_writer_context(option)?;
556 if let Some(WriteCipher { context: c, .. }) = &context.cipher {
557 (ChunkType::PHSF, c.phsf.as_bytes()).write_chunk_in(inner)?;
558 (ChunkType::FDAT, &c.iv[..]).write_chunk_in(inner)?;
559 }
560 let inner = {
561 let writer = ChunkStreamWriter::new(ChunkType::FDAT, inner);
562 let writer = get_writer(writer, &context)?;
563 let mut writer = f(writer)?;
564 writer.flush()?;
565 writer.try_into_inner()?.try_into_inner()?.into_inner()
566 };
567 (ChunkType::FEND, Vec::<u8>::new()).write_chunk_in(inner)?;
568 Ok(())
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574 use crate::ReadOptions;
575 use std::io::Read;
576 #[cfg(all(target_family = "wasm", target_os = "unknown"))]
577 use wasm_bindgen_test::wasm_bindgen_test as test;
578
579 #[test]
580 fn encode() {
581 let writer = Archive::write_header(Vec::new()).expect("failed to write header");
582 let file = writer.finalize().expect("failed to finalize");
583 let expected = include_bytes!("../../../resources/test/empty.pna");
584 assert_eq!(file.as_slice(), expected.as_slice());
585 }
586
587 #[test]
588 fn archive_write_file_entry() {
589 let option = WriteOptions::builder().build();
590 let mut writer = Archive::write_header(Vec::new()).expect("failed to write header");
591 writer
592 .write_file(
593 EntryName::from_lossy("text.txt"),
594 Metadata::new(),
595 option,
596 |writer| writer.write_all(b"text"),
597 )
598 .expect("failed to write");
599 let file = writer.finalize().expect("failed to finalize");
600 let mut reader = Archive::read_header(&file[..]).expect("failed to read archive");
601 let mut entries = reader.entries_with_password(None);
602 let entry = entries
603 .next()
604 .expect("failed to get entry")
605 .expect("failed to read entry");
606 let mut data_reader = entry
607 .reader(ReadOptions::builder().build())
608 .expect("failed to read entry data");
609 let mut data = Vec::new();
610 data_reader
611 .read_to_end(&mut data)
612 .expect("failed to read data");
613 assert_eq!(&data[..], b"text");
614 }
615
616 #[test]
617 fn solid_write_file_entry() {
618 let option = WriteOptions::builder().build();
619 let mut writer =
620 Archive::write_solid_header(Vec::new(), option).expect("failed to write header");
621 writer
622 .write_file(
623 EntryName::from_lossy("text.txt"),
624 Metadata::new(),
625 |writer| writer.write_all(b"text"),
626 )
627 .expect("failed to write");
628 let file = writer.finalize().expect("failed to finalize");
629 let mut reader = Archive::read_header(&file[..]).expect("failed to read archive");
630 let mut entries = reader.entries_with_password(None);
631 let entry = entries
632 .next()
633 .expect("failed to get entry")
634 .expect("failed to read entry");
635 let mut data_reader = entry
636 .reader(ReadOptions::builder().build())
637 .expect("failed to read entry data");
638 let mut data = Vec::new();
639 data_reader
640 .read_to_end(&mut data)
641 .expect("failed to read data");
642 assert_eq!(&data[..], b"text");
643 }
644
645 #[cfg(feature = "unstable-async")]
646 #[tokio::test]
647 async fn encode_async() {
648 use tokio_util::compat::TokioAsyncWriteCompatExt;
649
650 let archive_bytes = {
651 let file = Vec::new().compat_write();
652 let writer = Archive::write_header_async(file).await.unwrap();
653 writer.finalize_async().await.unwrap().into_inner()
654 };
655 let expected = include_bytes!("../../../resources/test/empty.pna");
656 assert_eq!(archive_bytes.as_slice(), expected.as_slice());
657 }
658}