LoginSignup
1
1

More than 1 year has passed since last update.

Rust研究:ZIP圧縮

Last updated at Posted at 2023-03-27

ZIP 圧縮の練習をしたのでメモを残します。

zip クレートを使用します。

その1

関数 process1 は、aaa.txt、hoge/bbb.txt、../ccc.txt を圧縮して test1.zip に保存します。

use std::fs::File;
use std::io::Read;
use std::io::Write;
use std::io::Seek;
use std::path::Path;
use std::path::PathBuf;
use zip::result::ZipResult;
use zip::ZipWriter;
use zip::write::FileOptions;

fn process1() -> ZipResult<()> {

    let mut zw = ZipWriter::new(File::create("test1.zip")?);

    let opts = FileOptions::default();

    // aaa.txt を追加
    zw.start_file("aaa.txt", opts)?;
    zw.write("私の名前はカルメンです。".as_bytes())?;

    // hoge/bbb.txt を追加
    zw.start_file("hoge/bbb.txt", opts)?;
    zw.write(b"It's better to burn out than to fade away.")?;

    // [MEMO] "Zip Slip" と呼ばれるパス・バーサルの脆弱性 (CWE-22) のチェック
    // ../ccc.txt を追加。
    zw.start_file("../ccc.txt", opts)?;
    zw.write(b"!!! Zip Slip !!!")?;
    // ⇒ ../ccc.txt は test1.zip に追加されない。

    zw.flush()?;
    zw.finish()?;

    Ok(())
}

最後の ../ccc.txt は test1.zip に保存されませんでした。
"Zip Slip" と呼ばれるパス・トラバーサルの脆弱性 (CWE-22) に対応しているようです。

その2

関数 process2 は、target ディレクトリ配下のファイルをすべて圧縮して test2.zip に保存します。

ディレクトリ名を渡したらいい感じに再帰的にサブディレクトリをたどって圧縮してくれるような API だと思っていたら、全然そんなことはありませんでした。
仕方がないので自分で実装しました。

fn process2 () -> ZipResult<()> {

    let mut zw = ZipWriter::new(File::create("test2.zip")?);

    let opts = FileOptions::default();

    // target ディレクトリ配下のファイルをすべて圧縮
    let p = Path::new("target");
    if p.is_dir() {
        process_dir(&mut zw, p.to_path_buf(), opts)?;
    }

    zw.finish()?;

    Ok(())
}

// ディレクトリ配下を圧縮する。サブディレクトリを再帰的に処理する
fn process_dir <W:Write+Seek> (zw:&mut ZipWriter<W>, p:PathBuf, 
    opts:FileOptions) -> ZipResult<()> {

    for entry in p.read_dir()? {
        if let Ok(entry) = entry {
            println!("{:?}", entry.path());

            if entry.path().is_dir() {

                // ディレクトリのとき
                process_dir(zw, entry.path(), opts)?;

            } else {
                
                // ファイルのとき
                if entry.path().is_file() {
                    let mut bytes:Vec<u8> = vec![];

                    // ファイル読み出し
                    let mut r = File::open(entry.path())?;
                    r.read_to_end(&mut bytes)?;

                    // zip に追加
                    let filepath:String = entry.path().to_string_lossy().into_owned();
                    zw.start_file(&filepath, opts)?;
                    zw.write(&bytes)?;
                    zw.flush()?;
                }
            }
        }
    }

    Ok(())
}

ZipWriter のインスタンスを再帰的に受け渡すところでジェネリクスが必要で少しとまどいましたが、なんとか乗り越えられました。

ファイルに書き出すところは、BufWriter を使うともう少しパフォーマンスが上がりそうな気がします。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1