]> OzVa Git service - ozva-cloud/commitdiff
feat: add api to get the hash of a file (#375)
authorsigoden <sigoden@gmail.com>
Fri, 19 Apr 2024 00:48:54 +0000 (08:48 +0800)
committerGitHub <noreply@github.com>
Fri, 19 Apr 2024 00:48:54 +0000 (08:48 +0800)
Cargo.lock
Cargo.toml
src/server.rs
tests/http.rs

index be55342a1750d20ba9f5d202e34f39370d36b20b..2c51186791935fa9fb51e0b498b9f4982c198f13 100644 (file)
@@ -507,6 +507,7 @@ dependencies = [
  "serde_json",
  "serde_yaml",
  "sha-crypt",
+ "sha2",
  "smart-default",
  "socket2",
  "tokio",
index 1fcd455173d780650798beca6eba963b2d1c7765..5a5418207056c93da32324bdde5b281770af7351 100644 (file)
@@ -52,6 +52,7 @@ hyper-util = { version = "0.1", features = ["server-auto", "tokio"] }
 http-body-util = "0.1"
 bytes = "1.5"
 pin-project-lite = "0.2"
+sha2 = "0.10.8"
 
 [features]
 default = ["tls"]
index d891bc6a6725f0f14614aff6c0af6b819107a296..832cd1d039a9f141da0f705d02ce6171f84f5337 100644 (file)
@@ -29,6 +29,7 @@ use hyper::{
     Method, StatusCode, Uri,
 };
 use serde::Serialize;
+use sha2::{Digest, Sha256};
 use std::borrow::Cow;
 use std::cmp::Ordering;
 use std::collections::HashMap;
@@ -307,6 +308,8 @@ impl Server {
                     } else if query_params.contains_key("view") {
                         self.handle_edit_file(path, DataKind::View, head_only, user, &mut res)
                             .await?;
+                    } else if query_params.contains_key("hash") {
+                        self.handle_hash_file(path, head_only, &mut res).await?;
                     } else {
                         self.handle_send_file(path, headers, head_only, &mut res)
                             .await?;
@@ -915,6 +918,24 @@ impl Server {
         Ok(())
     }
 
+    async fn handle_hash_file(
+        &self,
+        path: &Path,
+        head_only: bool,
+        res: &mut Response,
+    ) -> Result<()> {
+        let output = sha256_file(path).await?;
+        res.headers_mut()
+            .typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
+        res.headers_mut()
+            .typed_insert(ContentLength(output.as_bytes().len() as u64));
+        if head_only {
+            return Ok(());
+        }
+        *res.body_mut() = body_full(output);
+        Ok(())
+    }
+
     async fn handle_propfind_dir(
         &self,
         path: &Path,
@@ -1716,3 +1737,20 @@ fn parse_upload_offset(headers: &HeaderMap<HeaderValue>, size: u64) -> Result<Op
     let (start, _) = parse_range(value, size).ok_or_else(err)?;
     Ok(Some(start))
 }
+
+async fn sha256_file(path: &Path) -> Result<String> {
+    let mut file = fs::File::open(path).await?;
+    let mut hasher = Sha256::new();
+    let mut buffer = [0u8; 8192];
+
+    loop {
+        let bytes_read = file.read(&mut buffer).await?;
+        if bytes_read == 0 {
+            break;
+        }
+        hasher.update(&buffer[..bytes_read]);
+    }
+
+    let result = hasher.finalize();
+    Ok(format!("{:x}", result))
+}
index f5ad7c41d84d2147c5ad46dd070dcd625ac2c4f8..8ea58947510ea4496d4db796c3fbf69ce594bb73 100644 (file)
@@ -189,6 +189,21 @@ fn head_file(server: TestServer) -> Result<(), Error> {
     Ok(())
 }
 
+#[rstest]
+fn hash_file(server: TestServer) -> Result<(), Error> {
+    let resp = reqwest::blocking::get(format!("{}index.html?hash", server.url()))?;
+    assert_eq!(
+        resp.headers().get("content-type").unwrap(),
+        "text/html; charset=utf-8"
+    );
+    assert_eq!(resp.status(), 200);
+    assert_eq!(
+        resp.text()?,
+        "c8dd395e3202674b9512f7b7f956e0d96a8ba8f572e785b0d5413ab83766dbc4"
+    );
+    Ok(())
+}
+
 #[rstest]
 fn get_file_404(server: TestServer) -> Result<(), Error> {
     let resp = reqwest::blocking::get(format!("{}404", server.url()))?;