F2C3TLOI4KEEWK427YMSHZ47352IEYL24PHCW5ELJID7BCSZCRLAC
async fn add_url(
&self,
request: tonic::Request<proto::AddUrlRequest>,
) -> Result<tonic::Response<proto::DerivationReply>, tonic::Status> {
let r = request.into_inner();
debug!("add_url request {:?}", r);
let p = self
.deb_client
.http_download(
&r.url,
match r.hash_algorithm {
0 => {
let mut h = [0; 32];
h.clone_from_slice(&r.hash);
Hash::Blake3(h)
}
1 => {
let mut h = [0; 32];
h.clone_from_slice(&r.hash);
Hash::Sha256(h)
}
2 => {
let mut h = [0; 64];
h.clone_from_slice(&r.hash);
Hash::Sha512(h)
}
_ => unreachable!(),
},
)
.await
.unwrap();
Ok(tonic::Response::new(proto::DerivationReply {
result: Some(proto::derivation_reply::Result::Ok(
proto::DerivationResult {
destdir: vec![p.to_str().unwrap().to_string()],
paths: Vec::new(),
path_patterns: Vec::new(),
},
)),
}))
}
}
/// Hashes, used for example in URL downloads.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Hash {
#[allow(missing_docs)]
Blake3([u8; 32]),
#[allow(missing_docs)]
Sha256([u8; 32]),
#[allow(missing_docs)]
Sha512([u8; 64]),
}
/// Hasher for each different hash algorithm.
pub enum Hasher {
#[allow(missing_docs)]
Blake3(blake3::Hasher),
#[allow(missing_docs)]
Sha256(sha2::Sha256),
#[allow(missing_docs)]
Sha512(sha2::Sha512),
}
impl Hash {
/// Create a hasher for this hash algorithm.
pub fn hasher(&self) -> Hasher {
match self {
Hash::Blake3(_) => Hasher::Blake3(blake3::Hasher::new()),
Hash::Sha256(_) => Hasher::Sha256(sha2::Sha256::new()),
Hash::Sha512(_) => Hasher::Sha512(sha2::Sha512::new()),
}
}
}
impl Hasher {
/// Update the hasher with new bytes.
pub fn update(&mut self, b: &[u8]) {
match self {
Hasher::Blake3(h) => {
h.update(b);
}
Hasher::Sha256(h) => {
h.update(b);
}
Hasher::Sha512(h) => {
h.update(b);
}
}
}
/// Create the hash, to be compared with the one supplied by the user.
pub fn finalize(self) -> Hash {
match self {
Hasher::Blake3(h) => Hash::Blake3(h.finalize().into()),
Hasher::Sha256(h) => Hash::Sha256(h.finalize().into()),
Hasher::Sha512(h) => Hash::Sha512(h.finalize().into()),
}
}
}
impl std::ops::Deref for Hash {
type Target = [u8];
fn deref(&self) -> &Self::Target {
match self {
Hash::Blake3(h) => &h[..],
Hash::Sha256(h) => &h[..],
Hash::Sha512(h) => &h[..],
}
}
}
impl std::ops::DerefMut for Hash {
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
Hash::Blake3(h) => &mut h[..],
Hash::Sha256(h) => &mut h[..],
Hash::Sha512(h) => &mut h[..],
}
}
impl Client {
/// Download a URL.
pub async fn http_download(&self, url: &str, h: Hash) -> Result<PathBuf, Error> {
let expected_path = self
.store_path
.join(data_encoding::BASE32_DNSSEC.encode(&h));
if let Ok(mut f) = tokio::fs::File::open(&expected_path).await {
use tokio::io::AsyncReadExt;
let mut buf = [0; 4096];
let mut hasher = h.hasher();
while let Ok(n) = f.read(&mut buf).await {
if n == 0 {
break;
}
hasher.update(&buf[..n]);
}
let got = hasher.finalize();
return if got == h {
Ok(expected_path)
} else {
Err(Error::WrongHash {
expected: data_encoding::HEXLOWER.encode(&h),
got: data_encoding::HEXLOWER.encode(&got),
})
};
}
let mut r = self.client().get(url).send().await?;
let mut t: Option<tokio::task::JoinHandle<Result<tokio::fs::File, tokio::io::Error>>> =
None;
let file = NamedTempFile::new_in(&self.store_path)?;
let mut f = Some(tokio::fs::File::create(&file).await?);
let mut hasher = h.hasher();
while let Some(chunk) = r.chunk().await? {
hasher.update(&chunk);
if let Some(t) = t.take() {
f = Some(t.await.unwrap().unwrap())
}
let mut f = f.take().unwrap();
t = Some(tokio::spawn(async move {
f.write_all(&chunk).await?;
Ok(f)
}))
}
let got = hasher.finalize();
if got == h {
file.persist(&expected_path)?;
Ok(expected_path)
} else {
Err(Error::WrongHash {
expected: data_encoding::HEXLOWER.encode(&h),
got: data_encoding::HEXLOWER.encode(&got),
})
}
}
}