use anyhow::Result;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::io::Read;
use std::path::Path;
use zip::read::ZipFile;
pub struct BlockMapBuilder {
block_map: AppxBlockMap,
buf: Vec<u8>,
}
impl Default for BlockMapBuilder {
fn default() -> Self {
Self {
block_map: AppxBlockMap::default(),
buf: Vec::with_capacity(65_536),
}
}
}
impl BlockMapBuilder {
pub fn add(&mut self, f: &mut ZipFile) -> Result<()> {
let name = Path::new(f.name())
.iter()
.map(|seg| seg.to_str().unwrap())
.collect::<Vec<_>>()
.join("\\");
let size = f.size();
let mut file = File {
lfh_size: 30 + name.len() as u16,
name,
size,
..Default::default()
};
loop {
self.buf.clear();
f.take(self.buf.capacity() as u64)
.read_to_end(&mut self.buf)?;
file.blocks.push(Block::new(&self.buf));
if self.buf.len() != self.buf.capacity() {
break;
}
}
self.block_map.files.push(file);
Ok(())
}
pub fn finish(self) -> AppxBlockMap {
self.block_map
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename(serialize = "BlockMap"))]
pub struct AppxBlockMap {
#[serde(rename(serialize = "xmlns"))]
#[serde(default = "default_namespace")]
ns: String,
#[serde(rename(serialize = "HashMethod"))]
#[serde(default = "default_hash_method")]
hash_method: String,
#[serde(rename(serialize = "File"))]
pub files: Vec<File>,
}
impl Default for AppxBlockMap {
fn default() -> Self {
Self {
ns: default_namespace(),
hash_method: default_hash_method(),
files: Default::default(),
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct File {
#[serde(rename(serialize = "Name"))]
pub name: String,
#[serde(rename(serialize = "Size"))]
pub size: u64,
#[serde(rename(serialize = "LfhSize"))]
pub lfh_size: u16,
#[serde(rename(serialize = "Block"))]
pub blocks: Vec<Block>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Block {
#[serde(rename(serialize = "Hash"))]
pub hash: String,
#[serde(rename(serialize = "Size"))]
pub size: Option<u16>,
}
impl Block {
pub fn new(bytes: &[u8]) -> Self {
Self {
hash: base64::encode(Sha256::digest(bytes)),
size: None,
}
}
}
fn default_namespace() -> String {
"http://schemas.microsoft.com/appx/2010/blockmap".into()
}
fn default_hash_method() -> String {
"http://www.w3.org/2001/04/xmlenc#sha256".into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_block_map() {
let mut map = AppxBlockMap::default();
let mut file = File {
name: "file.ext".into(),
size: 12,
lfh_size: 30,
blocks: Default::default(),
};
file.blocks.push(Block {
hash: "base64".into(),
size: Some(12),
});
map.files.push(file);
let _xml = quick_xml::se::to_string(&map).unwrap();
}
}