From 5b6d9557c6394bf529cc8fc07fe10e6b35f212ff Mon Sep 17 00:00:00 2001
From: Jiahao XU <Jiahao_XU@outlook.com>
Date: Wed, 20 Jul 2022 23:45:15 +1000
Subject: [PATCH] Impl `ZipStreamReader::extract`

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
---
 src/read/stream.rs | 62 +++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 59 insertions(+), 3 deletions(-)

diff --git a/src/read/stream.rs b/src/read/stream.rs
index 34320bd9..bb8e5ee5 100644
--- a/src/read/stream.rs
+++ b/src/read/stream.rs
@@ -1,9 +1,10 @@
-use std::io::Read;
+use std::fs;
+use std::io::{self, Read};
 use std::path::Path;
 
 use super::{
-    central_header_to_zip_file_inner, read_zipfile_from_stream, spec, ZipFile, ZipFileData,
-    ZipResult,
+    central_header_to_zip_file_inner, read_zipfile_from_stream, spec, ZipError, ZipFile,
+    ZipFileData, ZipResult,
 };
 
 use byteorder::{LittleEndian, ReadBytesExt};
@@ -50,6 +51,61 @@ impl<R: Read> ZipStreamReader<R> {
 
         Ok(())
     }
+
+    /// Extract a Zip archive into a directory, overwriting files if they
+    /// already exist. Paths are sanitized with [`ZipFile::enclosed_name`].
+    ///
+    /// Extraction is not atomic; If an error is encountered, some of the files
+    /// may be left on disk.
+    pub fn extract<P: AsRef<Path>>(self, directory: P) -> ZipResult<()> {
+        struct Extracter<'a>(&'a Path);
+        impl ZipStreamVisitor for Extracter<'_> {
+            fn visit_file(&mut self, file: &mut ZipFile<'_>) -> ZipResult<()> {
+                let filepath = file
+                    .enclosed_name()
+                    .ok_or(ZipError::InvalidArchive("Invalid file path"))?;
+
+                let outpath = self.0.join(filepath);
+
+                if file.name().ends_with('/') {
+                    fs::create_dir_all(&outpath)?;
+                } else {
+                    if let Some(p) = outpath.parent() {
+                        if !p.exists() {
+                            fs::create_dir_all(&p)?;
+                        }
+                    }
+                    let mut outfile = fs::File::create(&outpath)?;
+                    io::copy(file, &mut outfile)?;
+                }
+
+                Ok(())
+            }
+
+            fn visit_additional_metadata(
+                &mut self,
+                metadata: &ZipStreamFileMetadata,
+            ) -> ZipResult<()> {
+                #[cfg(unix)]
+                {
+                    let filepath = metadata
+                        .enclosed_name()
+                        .ok_or(ZipError::InvalidArchive("Invalid file path"))?;
+
+                    let outpath = self.0.join(filepath);
+
+                    use std::os::unix::fs::PermissionsExt;
+                    if let Some(mode) = metadata.unix_mode() {
+                        fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?;
+                    }
+                }
+
+                Ok(())
+            }
+        }
+
+        self.visit(&mut Extracter(directory.as_ref()))
+    }
 }
 
 /// Visitor for ZipStreamReader