122 lines
2.4 KiB
Rust
122 lines
2.4 KiB
Rust
use std::{
|
|
ffi::OsString,
|
|
io::{
|
|
self,
|
|
Write,
|
|
},
|
|
time::Duration,
|
|
};
|
|
|
|
use anyhow::{
|
|
anyhow,
|
|
bail,
|
|
};
|
|
|
|
use futures_util::StreamExt;
|
|
|
|
use reqwest::{
|
|
StatusCode,
|
|
};
|
|
|
|
use sha2::{
|
|
Digest,
|
|
Sha512,
|
|
};
|
|
|
|
use tokio::{
|
|
sync::mpsc,
|
|
task::{
|
|
spawn_blocking,
|
|
},
|
|
};
|
|
|
|
pub async fn main (args: &[OsString]) -> anyhow::Result <()> {
|
|
let mut url = None;
|
|
let mut expected_sha512 = None;
|
|
|
|
let mut args = args [1..].iter ();
|
|
|
|
loop {
|
|
let arg = match args.next () {
|
|
None => break,
|
|
Some (x) => x,
|
|
};
|
|
|
|
match arg.to_str ().ok_or_else (|| anyhow! ("All arguments must be valid UTF-8"))?
|
|
{
|
|
"--help" => println! ("For now, just look at the source code"),
|
|
"--expect-sha512" => {
|
|
let expected = args.next ().ok_or_else (|| anyhow! ("--expect-sha512 needs an argument"))?;
|
|
expected_sha512 = Some (expected.to_str ().ok_or_else (|| anyhow! ("--expect-sha512's argument must be valid Unicode"))?);
|
|
}
|
|
arg => {
|
|
url = Some (arg);
|
|
break;
|
|
},
|
|
}
|
|
}
|
|
|
|
let url = match url {
|
|
None => bail! ("URL argument is required"),
|
|
Some (x) => x,
|
|
};
|
|
|
|
// Cookie 01FYZ3W64SM6KYNP48J6EWSCEF
|
|
// Try to keep the Clients similar here
|
|
|
|
let client = reqwest::Client::builder ()
|
|
.connect_timeout (Duration::from_secs (30))
|
|
.build ()?;
|
|
|
|
let resp = client.get (url)
|
|
.send ().await?;
|
|
|
|
if resp.status () != StatusCode::OK {
|
|
bail! ("Expected 200 OK, got {}", resp.status ());
|
|
}
|
|
|
|
let mut resp_stream = resp.bytes_stream ();
|
|
|
|
// The hasher is owned by a task because it makes ownership simpler
|
|
let (hash_tx, mut hash_rx) = mpsc::channel (1);
|
|
|
|
let hasher_task = spawn_blocking (move || {
|
|
let mut hasher = Sha512::new ();
|
|
|
|
while let Some (chunk) = tokio::runtime::Handle::current ().block_on (hash_rx.recv ()) {
|
|
hasher.update (&chunk);
|
|
}
|
|
|
|
anyhow::Result::<_>::Ok (hasher.finalize ())
|
|
});
|
|
|
|
while let Some (chunk) = resp_stream.next ().await {
|
|
let chunk = chunk?;
|
|
|
|
hash_tx.send (chunk.clone ()).await?;
|
|
|
|
{
|
|
let chunk = chunk.clone ();
|
|
spawn_blocking (move || {
|
|
io::stdout ().write_all (&chunk)?;
|
|
anyhow::Result::<_>::Ok (())
|
|
}).await??;
|
|
}
|
|
}
|
|
|
|
drop (hash_tx);
|
|
|
|
let hash = hasher_task.await??;
|
|
|
|
let actual_sha512 = hex::encode (&hash);
|
|
|
|
match expected_sha512 {
|
|
None => eprintln! ("Actual SHA512 = {}", actual_sha512),
|
|
Some (expected) => if ! actual_sha512.starts_with (&expected) {
|
|
bail! ("Expected SHA512 prefix {}, actual SHA512 {}", expected, actual_sha512);
|
|
},
|
|
}
|
|
|
|
Ok (())
|
|
}
|