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}