use std::fmt::Write;
use std::path::Path;
use base64::Engine;
use rand::Rng;
use crate::api::client::HttpApiClient;
use crate::cdn::aes_ecb;
use crate::cdn::cdn_upload::{build_cdn_upload_url, upload_buffer_to_cdn};
use crate::error::{Error, Result};
use crate::types::{GetUploadUrlRequest, UploadMediaType, build_base_info};
use crate::util::random::random_hex;
#[derive(Debug, Clone)]
pub struct CdnUploadResult {
pub encrypt_query_param: String,
pub aes_key_base64: String,
pub aes_key_hex: String,
pub file_size: u64,
pub file_size_ciphertext: u64,
pub filekey: String,
}
pub(crate) async fn upload_file(
api: &HttpApiClient,
cdn_base_url: &str,
file_path: &Path,
media_type: UploadMediaType,
to_user_id: &str,
) -> Result<CdnUploadResult> {
let plaintext = tokio::fs::read(file_path).await?;
let rawsize = plaintext.len() as u64;
let rawfilemd5 = {
use md5::Digest;
let mut hasher = md5::Md5::new();
hasher.update(&plaintext);
format!("{:x}", hasher.finalize())
};
let filesize = aes_ecb::padded_size(plaintext.len()) as u64;
let filekey = generate_filekey();
let mut aes_key = [0u8; 16];
rand::rng().fill(&mut aes_key);
let aes_key_hex = aes_key
.iter()
.fold(String::with_capacity(32), |mut acc, b| {
let _ = write!(acc, "{b:02x}");
acc
});
tracing::debug!(
file = ?file_path,
rawsize,
filesize,
filekey = %filekey,
"upload_file: starting"
);
let upload_resp = api
.get_upload_url(&GetUploadUrlRequest {
filekey: filekey.clone(),
media_type,
to_user_id: to_user_id.to_owned(),
rawsize,
rawfilemd5,
filesize,
no_need_thumb: Some(true),
thumb_rawsize: None,
thumb_rawfilemd5: None,
thumb_filesize: None,
aeskey: aes_key_hex.clone(),
base_info: build_base_info(),
})
.await?;
let upload_full_url = upload_resp
.upload_full_url
.as_deref()
.map(str::trim)
.filter(|s| !s.is_empty());
let upload_param = upload_resp.upload_param.as_deref();
let cdn_url = if let Some(full) = upload_full_url {
full.to_owned()
} else if let Some(param) = upload_param {
build_cdn_upload_url(cdn_base_url, param, &filekey)
} else {
return Err(Error::CdnUpload(
"getUploadUrl returned no upload URL".into(),
));
};
let download_param = upload_buffer_to_cdn(&plaintext, &aes_key, &cdn_url).await?;
let aes_key_base64 = base64::engine::general_purpose::STANDARD.encode(aes_key_hex.as_bytes());
Ok(CdnUploadResult {
encrypt_query_param: download_param,
aes_key_base64,
aes_key_hex,
file_size: rawsize,
file_size_ciphertext: filesize,
filekey,
})
}
pub fn generate_filekey() -> String {
random_hex(16)
}