1
2use super::StorageZone;
3use crate::client::Client;
4use crate::error::{APIResult, error_from_response, Error as APIError};
5use crate::files::{file_content, checksum, Error as FileError, urlencode_path};
6use std::path::Path;
7use reqwest::Method;
8
9#[derive(Debug, thiserror::Error)]
11pub enum Error {
12 #[error(transparent)]
14 File(#[from] FileError),
15 #[error("expected SHA256 checksum {expected}, got {received}")]
16 MismatchedSha256 {
19 expected: String,
21 received: String,
23 }
24}
25
26fn get_url(storage_zone: StorageZone, zone_name: &str, dest: &str) -> String {
27 let dest = dest.strip_prefix('/').unwrap_or(dest);
28 let dest = urlencode_path(dest);
29 format!("https://{}/{}/{}", storage_zone.url(), zone_name, dest)
30}
31
32async fn upload_data(client: &Client, url: String, content: Vec<u8>, expected_sha256: Option<String>) -> APIResult<(), Error> {
33 #[cfg(feature = "tracing")]
34 tracing::debug!("hashing file contents");
35 let hash = checksum(&content);
36 if let Some(expected) = expected_sha256 {
37 let expected = expected.to_uppercase();
38
39 #[cfg(feature = "tracing")]
40 tracing::debug!(%expected, %hash, "checking that hash matches expected value");
41
42 if expected != hash {
43 return Err(APIError::new(
44 Method::PUT,
45 url,
46 Error::MismatchedSha256 { expected, received: hash }
47 ));
48 }
49 }
50
51 let request = client
52 .put(&url)
53 .header("Checksum", hash)
54 .header("content-type", "application/octet-stream")
55 .body(content)
56 .build()
57 .map_err(crate::Error::map_request_err(
58 Method::PUT,
59 url.clone(),
60 ))?;
61
62 let resp = client
63 .send_logged(request)
64 .await
65 .map_err(crate::Error::map_request_err(
66 Method::PUT,
67 url.clone(),
68 ))?;
69
70 error_from_response(resp)
71 .await
72 .map(|_| ())
73 .map_err(crate::Error::map_response_err(Method::PUT, url))
74}
75
76#[allow(clippy::module_name_repetitions)]
83pub async fn upload_file_by_path(client: &Client, storage_zone: StorageZone, zone_name: &str, dest: &str, file: &Path, expected_sha256: Option<String>) -> APIResult<(), Error> {
84 let url = get_url(storage_zone, zone_name, dest);
85 let content = file_content(file).await
86 .map_err(Error::from)
87 .map_err(crate::Error::map_err(
88 Method::PUT,
89 url.clone(),
90 ))?;
91
92 upload_data(client, url, content, expected_sha256).await
93}
94
95#[allow(clippy::module_name_repetitions)]
102pub async fn upload_file_data(client: &Client, storage_zone: StorageZone, zone_name: &str, dest: &str, content: Vec<u8>, expected_sha256: Option<String>) -> APIResult<(), Error> {
103 let url = get_url(storage_zone, zone_name, dest);
104 upload_data(client, url, content, expected_sha256).await
105}