libpna/archive/
write.rs

1use crate::{
2    archive::{Archive, ArchiveHeader, SolidArchive, PNA_HEADER},
3    chunk::{Chunk, ChunkExt, ChunkStreamWriter, ChunkType, RawChunk},
4    cipher::CipherWriter,
5    compress::CompressionWriter,
6    entry::{
7        get_writer, get_writer_context, Entry, EntryHeader, EntryName, EntryPart, Metadata,
8        NormalEntry, SealedEntryExt, SolidHeader, WriteCipher, WriteOption, WriteOptions,
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    pub fn finalize(mut self) -> io::Result<W> {
279        (ChunkType::AEND, []).write_chunk_in(&mut self.inner)?;
280        Ok(self.inner)
281    }
282}
283
284#[cfg(feature = "unstable-async")]
285impl<W: AsyncWrite + Unpin> Archive<W> {
286    /// Writes the archive header to the given object and return a new [Archive].
287    /// This API is unstable.
288    ///
289    /// # Errors
290    ///
291    /// Returns an error if an I/O error occurs while writing header to the writer.
292    #[inline]
293    pub async fn write_header_async(write: W) -> io::Result<Self> {
294        let header = ArchiveHeader::new(0, 0, 0);
295        Self::write_header_with_async(write, header).await
296    }
297
298    #[inline]
299    async fn write_header_with_async(mut write: W, header: ArchiveHeader) -> io::Result<Self> {
300        write.write_all(PNA_HEADER).await?;
301        let mut chunk_writer = crate::chunk::ChunkWriter::new(&mut write);
302        chunk_writer
303            .write_chunk_async((ChunkType::AHED, header.to_bytes()))
304            .await?;
305        Ok(Self::new(write, header))
306    }
307
308    /// Adds a new entry to the archive.
309    /// This API is unstable.
310    ///
311    /// # Errors
312    ///
313    /// Returns an error if an I/O error occurs while writing a given entry.
314    #[inline]
315    pub async fn add_entry_async(&mut self, entry: impl Entry) -> io::Result<usize> {
316        let mut bytes = Vec::new();
317        entry.write_in(&mut bytes)?;
318        self.inner.write_all(&bytes).await?;
319        Ok(bytes.len())
320    }
321
322    /// Writes the end-of-archive marker and finalizes the archive.
323    /// This API is unstable.
324    ///
325    /// # Errors
326    ///
327    /// Returns an error if writing the end-of-archive marker fails.
328    #[inline]
329    pub async fn finalize_async(mut self) -> io::Result<W> {
330        let mut chunk_writer = crate::chunk::ChunkWriter::new(&mut self.inner);
331        chunk_writer
332            .write_chunk_async((ChunkType::AEND, []))
333            .await?;
334        Ok(self.inner)
335    }
336}
337
338impl<W: Write> Archive<W> {
339    /// Writes the archive header to the given `Write` object and return a new [SolidArchive].
340    ///
341    /// # Arguments
342    ///
343    /// * `write` - The [Write] object to write the header to.
344    /// * `option` - The [WriteOptions] object of a solid mode option.
345    ///
346    /// # Returns
347    ///
348    /// A new [`io::Result<SolidArchive<W>>`]
349    ///
350    /// # Examples
351    ///
352    /// ```no_run
353    /// use libpna::{Archive, WriteOptions};
354    /// use std::fs::File;
355    /// # use std::io;
356    ///
357    /// # fn main() -> io::Result<()> {
358    /// let option = WriteOptions::builder().build();
359    /// let file = File::create("example.pna")?;
360    /// let mut archive = Archive::write_solid_header(file, option)?;
361    /// archive.finalize()?;
362    /// #    Ok(())
363    /// # }
364    /// ```
365    ///
366    /// # Errors
367    ///
368    /// Returns an error if an I/O error occurs while writing header to the writer.
369    #[inline]
370    pub fn write_solid_header(write: W, option: impl WriteOption) -> io::Result<SolidArchive<W>> {
371        let archive = Self::write_header(write)?;
372        archive.into_solid_archive(option)
373    }
374
375    #[inline]
376    fn into_solid_archive(mut self, option: impl WriteOption) -> io::Result<SolidArchive<W>> {
377        let header = SolidHeader::new(
378            option.compression(),
379            option.encryption(),
380            option.cipher_mode(),
381        );
382        let context = get_writer_context(option)?;
383
384        (ChunkType::SHED, header.to_bytes()).write_chunk_in(&mut self.inner)?;
385        if let Some(WriteCipher { context: c, .. }) = &context.cipher {
386            (ChunkType::PHSF, c.phsf.as_bytes()).write_chunk_in(&mut self.inner)?;
387            (ChunkType::SDAT, c.iv.as_slice()).write_chunk_in(&mut self.inner)?;
388        }
389        self.inner.flush()?;
390        let writer = get_writer(
391            ChunkStreamWriter::new(ChunkType::SDAT, self.inner),
392            &context,
393        )?;
394
395        Ok(SolidArchive {
396            archive_header: self.header,
397            inner: writer,
398        })
399    }
400}
401
402impl<W: Write> SolidArchive<W> {
403    /// Adds a new entry to the archive.
404    ///
405    /// # Arguments
406    ///
407    /// * `entry` - The entry to add to the archive.
408    ///
409    /// # Examples
410    ///
411    /// ```no_run
412    /// use libpna::{Archive, EntryBuilder, WriteOptions};
413    /// use std::fs::File;
414    /// # use std::io;
415    ///
416    /// # fn main() -> io::Result<()> {
417    /// let option = WriteOptions::builder().build();
418    /// let file = File::create("example.pna")?;
419    /// let mut archive = Archive::write_solid_header(file, option)?;
420    /// archive
421    ///     .add_entry(EntryBuilder::new_file("example.txt".into(), WriteOptions::store())?.build()?)?;
422    /// archive.finalize()?;
423    /// #     Ok(())
424    /// # }
425    /// ```
426    ///
427    /// # Errors
428    ///
429    /// Returns an error if an I/O error occurs while writing a given entry.
430    #[inline]
431    pub fn add_entry<T>(&mut self, entry: NormalEntry<T>) -> io::Result<usize>
432    where
433        NormalEntry<T>: Entry,
434    {
435        entry.write_in(&mut self.inner)
436    }
437
438    /// Writes a regular file as a solid entry into the archive.
439    ///
440    /// # Errors
441    ///
442    /// Returns an error if an I/O error occurs while writing the entry, or if the closure returns an error.
443    ///
444    /// # Examples
445    /// ```no_run
446    /// use libpna::{Archive, Metadata, WriteOptions};
447    /// # use std::error::Error;
448    /// use std::fs;
449    /// use std::io::{self, prelude::*};
450    ///
451    /// # fn main() -> Result<(), Box<dyn Error>> {
452    /// let file = fs::File::create("foo.pna")?;
453    /// let option = WriteOptions::builder().build();
454    /// let mut archive = Archive::write_solid_header(file, option)?;
455    /// archive.write_file("bar.txt".into(), Metadata::new(), |writer| {
456    ///     writer.write_all(b"text")
457    /// })?;
458    /// archive.finalize()?;
459    /// #    Ok(())
460    /// # }
461    /// ```
462    #[inline]
463    pub fn write_file<F>(&mut self, name: EntryName, metadata: Metadata, mut f: F) -> io::Result<()>
464    where
465        F: FnMut(&mut SolidArchiveEntryDataWriter<W>) -> io::Result<()>,
466    {
467        let option = WriteOptions::store();
468        write_file_entry(&mut self.inner, name, metadata, option, |w| {
469            let mut w = SolidArchiveEntryDataWriter(w);
470            f(&mut w)?;
471            Ok(w.0)
472        })
473    }
474
475    /// Writes the end-of-archive marker and finalizes the archive.
476    ///
477    /// Marks that the PNA archive contains no more entries.
478    /// Normally, a PNA archive reader will continue reading entries in the hope that the entry exists until it encounters this end marker.
479    /// This end marker should always be recorded at the end of the file unless there is a special reason to do so.
480    ///
481    /// # Errors
482    /// Returns an error if writing the end-of-archive marker fails.
483    ///
484    /// # Examples
485    /// Creates an empty archive.
486    /// ```no_run
487    /// use libpna::{Archive, WriteOptions};
488    /// use std::fs::File;
489    /// # use std::io;
490    ///
491    /// # fn main() -> io::Result<()> {
492    /// let option = WriteOptions::builder().build();
493    /// let file = File::create("example.pna")?;
494    /// let mut archive = Archive::write_solid_header(file, option)?;
495    /// archive.finalize()?;
496    /// #    Ok(())
497    /// # }
498    /// ```
499    #[inline]
500    pub fn finalize(self) -> io::Result<W> {
501        let archive = self.finalize_solid_entry()?;
502        archive.finalize()
503    }
504
505    #[inline]
506    fn finalize_solid_entry(mut self) -> io::Result<Archive<W>> {
507        self.inner.flush()?;
508        let mut inner = self.inner.try_into_inner()?.try_into_inner()?.into_inner();
509        (ChunkType::SEND, []).write_chunk_in(&mut inner)?;
510        Ok(Archive::new(inner, self.archive_header))
511    }
512}
513
514pub(crate) fn write_file_entry<W, F>(
515    inner: &mut W,
516    name: EntryName,
517    metadata: Metadata,
518    option: impl WriteOption,
519    mut f: F,
520) -> io::Result<()>
521where
522    W: Write,
523    F: FnMut(InternalArchiveDataWriter<&mut W>) -> io::Result<InternalArchiveDataWriter<&mut W>>,
524{
525    let header = EntryHeader::for_file(
526        option.compression(),
527        option.encryption(),
528        option.cipher_mode(),
529        name,
530    );
531    (ChunkType::FHED, header.to_bytes()).write_chunk_in(inner)?;
532    if let Some(c) = metadata.created {
533        (ChunkType::cTIM, c.whole_seconds().to_be_bytes()).write_chunk_in(inner)?;
534        if c.subsec_nanoseconds() != 0 {
535            (ChunkType::cTNS, c.subsec_nanoseconds().to_be_bytes()).write_chunk_in(inner)?;
536        }
537    }
538    if let Some(m) = metadata.modified {
539        (ChunkType::mTIM, m.whole_seconds().to_be_bytes()).write_chunk_in(inner)?;
540        if m.subsec_nanoseconds() != 0 {
541            (ChunkType::mTNS, m.subsec_nanoseconds().to_be_bytes()).write_chunk_in(inner)?;
542        }
543    }
544    if let Some(a) = metadata.accessed {
545        (ChunkType::aTIM, a.whole_seconds().to_be_bytes()).write_chunk_in(inner)?;
546        if a.subsec_nanoseconds() != 0 {
547            (ChunkType::aTNS, a.subsec_nanoseconds().to_be_bytes()).write_chunk_in(inner)?;
548        }
549    }
550    if let Some(p) = metadata.permission {
551        (ChunkType::fPRM, p.to_bytes()).write_chunk_in(inner)?;
552    }
553    let context = get_writer_context(option)?;
554    if let Some(WriteCipher { context: c, .. }) = &context.cipher {
555        (ChunkType::PHSF, c.phsf.as_bytes()).write_chunk_in(inner)?;
556        (ChunkType::FDAT, &c.iv[..]).write_chunk_in(inner)?;
557    }
558    let inner = {
559        let writer = ChunkStreamWriter::new(ChunkType::FDAT, inner);
560        let writer = get_writer(writer, &context)?;
561        let mut writer = f(writer)?;
562        writer.flush()?;
563        writer.try_into_inner()?.try_into_inner()?.into_inner()
564    };
565    (ChunkType::FEND, Vec::<u8>::new()).write_chunk_in(inner)?;
566    Ok(())
567}
568
569#[cfg(test)]
570mod tests {
571    use super::*;
572    use crate::ReadOptions;
573    use std::io::Read;
574    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
575    use wasm_bindgen_test::wasm_bindgen_test as test;
576
577    #[test]
578    fn encode() {
579        let writer = Archive::write_header(Vec::new()).expect("failed to write header");
580        let file = writer.finalize().expect("failed to finalize");
581        let expected = include_bytes!("../../../resources/test/empty.pna");
582        assert_eq!(file.as_slice(), expected.as_slice());
583    }
584
585    #[test]
586    fn archive_write_file_entry() {
587        let option = WriteOptions::builder().build();
588        let mut writer = Archive::write_header(Vec::new()).expect("failed to write header");
589        writer
590            .write_file(
591                EntryName::from_lossy("text.txt"),
592                Metadata::new(),
593                option,
594                |writer| writer.write_all(b"text"),
595            )
596            .expect("failed to write");
597        let file = writer.finalize().expect("failed to finalize");
598        let mut reader = Archive::read_header(&file[..]).expect("failed to read archive");
599        let mut entries = reader.entries_with_password(None);
600        let entry = entries
601            .next()
602            .expect("failed to get entry")
603            .expect("failed to read entry");
604        let mut data_reader = entry
605            .reader(ReadOptions::builder().build())
606            .expect("failed to read entry data");
607        let mut data = Vec::new();
608        data_reader
609            .read_to_end(&mut data)
610            .expect("failed to read data");
611        assert_eq!(&data[..], b"text");
612    }
613
614    #[test]
615    fn solid_write_file_entry() {
616        let option = WriteOptions::builder().build();
617        let mut writer =
618            Archive::write_solid_header(Vec::new(), option).expect("failed to write header");
619        writer
620            .write_file(
621                EntryName::from_lossy("text.txt"),
622                Metadata::new(),
623                |writer| writer.write_all(b"text"),
624            )
625            .expect("failed to write");
626        let file = writer.finalize().expect("failed to finalize");
627        let mut reader = Archive::read_header(&file[..]).expect("failed to read archive");
628        let mut entries = reader.entries_with_password(None);
629        let entry = entries
630            .next()
631            .expect("failed to get entry")
632            .expect("failed to read entry");
633        let mut data_reader = entry
634            .reader(ReadOptions::builder().build())
635            .expect("failed to read entry data");
636        let mut data = Vec::new();
637        data_reader
638            .read_to_end(&mut data)
639            .expect("failed to read data");
640        assert_eq!(&data[..], b"text");
641    }
642
643    #[cfg(feature = "unstable-async")]
644    #[tokio::test]
645    async fn encode_async() {
646        use tokio_util::compat::TokioAsyncWriteCompatExt;
647
648        let archive_bytes = {
649            let file = Vec::new().compat_write();
650            let writer = Archive::write_header_async(file).await.unwrap();
651            writer.finalize_async().await.unwrap().into_inner()
652        };
653        let expected = include_bytes!("../../../resources/test/empty.pna");
654        assert_eq!(archive_bytes.as_slice(), expected.as_slice());
655    }
656}