vfs_zip/write/
vfs04.rs

1use super::*;
2
3use ::vfs04::*;
4
5use ::zip::result::ZipError;
6use ::zip::write::FileOptions;
7
8use std::io::{self, Cursor};
9use std::mem::replace;
10use std::ops::Drop;
11use std::sync::{Arc, Weak};
12
13
14
15impl<IO: Write + Seek + Send + 'static> ZipWriteOnly<IO> {
16    fn normalize_file<'s>(&self, orig: &'s str) -> VfsResult<&'s str> {
17        if orig.contains('\\') || orig.ends_with('/') {
18            return Err(VfsError::InvalidPath { path: orig.into() }); // Invalid path for file
19        }
20        let path = if orig.starts_with('/') { &orig[1..] } else { orig };
21        if path.split('/').any(|c| c == "" || c == "." || c == "..") {
22            return Err(VfsError::InvalidPath { path: orig.into() });
23        }
24        Ok(path)
25    }
26
27    fn normalize_path_dir<'s>(&self, orig: &'s str) -> VfsResult<(&'s str, bool)> {
28        if orig == "" || orig == "/" {
29            Ok(("", true)) // root dir
30        } else if orig.ends_with('/') {
31            Ok((self.normalize_file(&orig[..orig.len()-1])?, true))
32        } else {
33            Ok((self.normalize_file(orig)?, false))
34        }
35    }
36}
37
38impl<IO: Write + Seek + Send + 'static> FileSystem for ZipWriteOnly<IO> {
39    fn create_dir(&self, path: &str) -> VfsResult<()> {
40        let path = self.normalize_path_dir(path)?.0;
41        let mut imp = self.imp.lock().unwrap();
42        if path == "" {
43            imp.dirs.insert(path.into());
44            return Ok(());
45        }
46        #[allow(unreachable_patterns)] // zip 0.5.3 ..= 0.5.6 didn't have any extra cases
47        match imp.writer.add_directory(path, FileOptions::default()) {
48            Err(ZipError::FileNotFound)             => return Err(VfsError::FileNotFound { path: path.into() }),
49            Err(ZipError::Io(e))                    => return Err(VfsError::IoError(e)),
50            Err(ZipError::InvalidArchive(e))        => return Err(VfsError::IoError(io::Error::new(io::ErrorKind::InvalidData, e))),
51            Err(ZipError::UnsupportedArchive(e))    => return Err(VfsError::IoError(io::Error::new(io::ErrorKind::InvalidData, e))),
52            Err(other)                              => return Err(VfsError::Other { message: other.to_string() }),
53            Ok(())                                  => {},
54        }
55        imp.dirs.insert(path.into());
56        Ok(())
57    }
58
59    fn create_file(&self, path: &str) -> VfsResult<Box<dyn Write>> {
60        let path = self.normalize_file(path)?;
61        Ok(Box::new(ZipFileWriter {
62            path:   path.into(),
63            buffer: Cursor::new(Vec::new()),
64            imp:    match self.weak {
65                true    => ZipFileWriterRef::Weak(Arc::downgrade(&self.imp)),
66                false   => ZipFileWriterRef::Strong(Arc::clone(&self.imp)),
67            }
68        }))
69    }
70
71    fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
72        let path = self.normalize_path_dir(path)?.0;
73        if path == "" {
74            Ok(VfsMetadata { file_type: VfsFileType::Directory, len: 0 })
75        } else if self.imp.lock().unwrap().dirs.contains(path) {
76            Ok(VfsMetadata { file_type: VfsFileType::Directory, len: 0 })
77        } else {
78            Err(VfsError::FileNotFound { path: path.into() })
79        }
80    }
81
82    fn exists(&self, path: &str) -> bool {
83        let path = match self.normalize_path_dir(path) {
84            Err(_) => return false,
85            Ok((path, _dir)) => path,
86        };
87        path == "" || self.imp.lock().unwrap().dirs.contains(path)
88    }
89
90    // these all involve reading, which zip::write::ZipWriter doesn't support
91    fn read_dir     (&self, _path: &str)            -> VfsResult<Box<dyn Iterator<Item = String>>>  { Err(VfsError::NotSupported) }
92    fn open_file    (&self, _path: &str)            -> VfsResult<Box<dyn SeekAndRead>>              { Err(VfsError::NotSupported) }
93    fn append_file  (&self, _path: &str)            -> VfsResult<Box<dyn Write>>                    { Err(VfsError::NotSupported) }
94    fn remove_file  (&self, _path: &str)            -> VfsResult<()>                                { Err(VfsError::NotSupported) }
95    fn remove_dir   (&self, _path: &str)            -> VfsResult<()>                                { Err(VfsError::NotSupported) }
96    fn copy_file    (&self, _src: &str, _dst: &str) -> VfsResult<()>                                { Err(VfsError::NotSupported) }
97    fn move_file    (&self, _src: &str, _dst: &str) -> VfsResult<()>                                { Err(VfsError::NotSupported) }
98    fn move_dir     (&self, _src: &str, _dst: &str) -> VfsResult<()>                                { Err(VfsError::NotSupported) }
99}
100
101enum ZipFileWriterRef<IO: Write + Seek + Send + 'static> {
102    Weak(Weak<Mutex<Imp<IO>>>),
103    Strong(Arc<Mutex<Imp<IO>>>),
104}
105
106struct ZipFileWriter<IO: Write + Seek + Send + 'static> {
107    path:   String,
108    buffer: Cursor<Vec<u8>>,
109    imp:    ZipFileWriterRef<IO>,
110}
111
112impl<IO: Write + Seek + Send> Write for ZipFileWriter<IO> {
113    // Forward all the Write methods I can to the underlying buffer
114    fn write                (&mut self, buf: &[u8])             -> io::Result<usize>    { self.buffer.write(buf) }
115    fn flush                (&mut self)                         -> io::Result<()>       { self.buffer.flush() }
116    fn write_all            (&mut self, buf: &[u8])             -> io::Result<()>       { self.buffer.write_all(buf) }
117    fn write_fmt            (&mut self, fmt: fmt::Arguments<'_>)-> io::Result<()>       { self.buffer.write_fmt(fmt) }
118
119    // unstable or missing in 1.34.0
120    //fn write_vectored       (&mut self, bufs: &[IoSlice<'_>])   -> io::Result<usize>    { self.buffer.write_vectored(bufs) }
121    //fn is_write_vectored    (&self)                             -> bool             { self.buffer.is_write_vectored() }
122    //fn write_all_vectored   (&mut self, mut bufs: &mut [IoSlice<'_>]) -> io::Result<()> { self.buffer.write_all_vectored(bufs) }
123}
124
125impl<IO: Write + Seek + Send> Drop for ZipFileWriter<IO> {
126    fn drop(&mut self) {
127        let path    = replace(&mut self.path, String::new());
128        let buffer  = replace(&mut self.buffer, Cursor::new(Vec::new())).into_inner();
129        let imp     = match replace(&mut self.imp, ZipFileWriterRef::Weak(Weak::default())) {
130            ZipFileWriterRef::Strong(s) => s,
131            ZipFileWriterRef::Weak(w) => match w.upgrade() {
132                Some(s) => s,
133                None => return,
134            }
135        };
136        let mut imp = imp.lock().unwrap();
137        if imp.writer.start_file(path, zip::write::FileOptions::default()).is_err() { return; }
138        let _ = imp.writer.write_all(&buffer[..]);
139    }
140}
141
142#[cfg(test)] mod tests {
143    use crate::*;
144    use super::{VfsError, VfsFileType, VfsPath, VfsResult};
145    use std::fs::{create_dir_all, File};
146
147    #[test] fn copy_early_vfs_zip() {
148        let _ = create_dir_all("target/tmp");
149        let src = VfsPath::new(ZipReadOnly::new_strict(File::open("test/data/early-vfs-zip.zip").unwrap()).unwrap());
150        let dst = VfsPath::new(ZipWriteOnly::new_weak(File::create("target/tmp/early-vfs-zip-copy.zip").unwrap()).unwrap());
151        let copied = copy_dir_merge(&dst, &src).unwrap();
152        assert_eq!(copied, 16);
153    }
154
155    /// NOTE: https://github.com/MaulingMonkey/vfs-zip/issues/1
156    fn copy_dir_merge(dst: &VfsPath, src: &VfsPath) -> VfsResult<usize> {
157        let mut n = 0;
158
159        if !src.exists() { return Err(VfsError::FileNotFound { path: src.as_str().into() }); }
160        if !dst.exists() { dst.create_dir()?; n += 1; }
161
162        for src in src.read_dir()? {
163            let dst = dst.join(src.filename().as_str())?;
164            match src.metadata()?.file_type {
165                VfsFileType::Directory  => n += copy_dir_merge(&dst, &src)?,
166                VfsFileType::File       => { src.copy_file(&dst)?; n += 1 },
167            }
168        }
169        Ok(n)
170    }
171}