use std::collections::BTreeMap;
use std::{fs};
use std::path::PathBuf;
use br_reqwest::{Client};
use chrono::Utc;
use json::{object, JsonValue};
use crate::{Connection, OssMode};
#[derive(Clone)]
pub struct Tencent {
pub endpoint: String,
pub access_key_id: String,
pub access_key_secret: String,
}
impl Tencent {
pub fn new(connection: Connection) -> Self {
Self {
endpoint: connection.endpoint,
access_key_id: connection.access_key_id,
access_key_secret: connection.access_key_secret,
}
}
fn sign(&mut self, http: &mut Client, key: &str, filepath: &str, body: Vec<u8>, content_type: &str) {
let md5 = br_crypto::md5::encrypt_base64(body.as_slice());
let keytime = generate_key_time(3600);
let mut headers: BTreeMap<&str, JsonValue> = BTreeMap::new();
headers.insert("Host", self.endpoint.to_string().into());
headers.insert("Content-Type", content_type.into());
headers.insert("Content-Length", body.len().into());
headers.insert("Content-MD5", md5.into());
let paramslist: Vec<String> = vec![];
let mut headerlist = vec![];
let mut http_headers = vec![];
let http_parameters: Vec<String> = vec![];
for (&item, value) in headers.iter() {
http.header(item, &value.to_string());
headerlist.push(item);
}
for &item in headerlist.iter() {
let value = headers.get(item).unwrap();
let value = br_crypto::encoding::urlencoding_encode(value.to_string().as_str());
http_headers.push(format!("{}={}", item.to_lowercase(), value));
}
let http_parameters = http_parameters.join("&");
let http_headers = http_headers.join("&");
let sign_key = br_crypto::sha1::encrypt_hmac(&self.access_key_secret, keytime.as_bytes());
let httpstring = format!("{}\n{key}\n{http_parameters}\n{http_headers}\n", http.method.to_str().to_lowercase());
let httpstring = br_crypto::sha1::encrypt_hex(httpstring.as_bytes());
let string_to_sign = format!("sha1\n{keytime}\n{httpstring}\n");
let signature = br_crypto::sha1::encrypt_hmac(&sign_key, string_to_sign.as_bytes());
let mut authorization = vec![];
authorization.push(format!("q-sign-algorithm={}", "sha1"));
authorization.push(format!("q-ak={}", self.access_key_id));
authorization.push(format!("q-sign-time={keytime}"));
authorization.push(format!("q-key-time={keytime}"));
authorization.push(format!("q-header-list={}", headerlist.join(";").to_lowercase()));
authorization.push(format!("q-url-param-list={}", paramslist.join(";").to_lowercase()));
authorization.push(format!("q-signature={signature}"));
http.header("Authorization", &authorization.join("&"));
http.raw_stream(filepath);
}
fn sign_get(&mut self, http: &mut Client, key: &str) {
let keytime = generate_key_time(3600);
let mut headers: BTreeMap<&str, JsonValue> = BTreeMap::new();
headers.insert("Host", self.endpoint.to_string().into());
let paramslist: Vec<String> = vec![];
let mut headerlist = vec![];
let mut http_headers = vec![];
let http_parameters: Vec<String> = vec![];
for (&item, value) in headers.iter() {
http.header(item, &value.to_string());
headerlist.push(item);
}
for &item in headerlist.iter() {
let value = headers.get(item).unwrap();
let value = br_crypto::encoding::urlencoding_encode(value.to_string().as_str());
http_headers.push(format!("{}={}", item.to_lowercase(), value));
}
let http_parameters = http_parameters.join("&");
let http_headers = http_headers.join("&");
let sign_key = br_crypto::sha1::encrypt_hmac(&self.access_key_secret, keytime.as_bytes());
let httpstring = format!("{}\n{key}\n{http_parameters}\n{http_headers}\n", http.method.to_str().to_lowercase());
let httpstring = br_crypto::sha1::encrypt_hex(httpstring.as_bytes());
let string_to_sign = format!("sha1\n{keytime}\n{httpstring}\n");
let signature = br_crypto::sha1::encrypt_hmac(&sign_key, string_to_sign.as_bytes());
let mut authorization = vec![];
authorization.push(format!("q-sign-algorithm={}", "sha1"));
authorization.push(format!("q-ak={}", self.access_key_id));
authorization.push(format!("q-sign-time={keytime}"));
authorization.push(format!("q-key-time={keytime}"));
authorization.push(format!("q-header-list={}", headerlist.join(";").to_lowercase()));
authorization.push(format!("q-url-param-list={}", paramslist.join(";").to_lowercase()));
authorization.push(format!("q-signature={signature}"));
http.header("Authorization", &authorization.join("&"));
http.query(object! {});
}
fn sign_head(&mut self, http: &mut Client, key: &str) {
let keytime = generate_key_time(3600);
let mut headers: BTreeMap<&str, JsonValue> = BTreeMap::new();
headers.insert("Host", self.endpoint.to_string().into());
let paramslist: Vec<String> = vec![];
let mut headerlist = vec![];
let mut http_headers = vec![];
let http_parameters: Vec<String> = vec![];
for (&item, value) in headers.iter() {
http.header(item, &value.to_string());
headerlist.push(item);
}
for &item in headerlist.iter() {
let value = headers.get(item).unwrap();
let value = br_crypto::encoding::urlencoding_encode(value.to_string().as_str());
http_headers.push(format!("{}={}", item.to_lowercase(), value));
}
let http_parameters = http_parameters.join("&");
let http_headers = http_headers.join("&");
let sign_key = br_crypto::sha1::encrypt_hmac(&self.access_key_secret, keytime.as_bytes());
let httpstring = format!("{}\n{key}\n{http_parameters}\n{http_headers}\n", http.method.to_str().to_lowercase());
let httpstring = br_crypto::sha1::encrypt_hex(httpstring.as_bytes());
let string_to_sign = format!("sha1\n{keytime}\n{httpstring}\n");
let signature = br_crypto::sha1::encrypt_hmac(&sign_key, string_to_sign.as_bytes());
let mut authorization = vec![];
authorization.push(format!("q-sign-algorithm={}", "sha1"));
authorization.push(format!("q-ak={}", self.access_key_id));
authorization.push(format!("q-sign-time={keytime}"));
authorization.push(format!("q-key-time={keytime}"));
authorization.push(format!("q-header-list={}", headerlist.join(";").to_lowercase()));
authorization.push(format!("q-url-param-list={}", paramslist.join(";").to_lowercase()));
authorization.push(format!("q-signature={signature}"));
http.header("Authorization", &authorization.join("&"));
}
}
fn generate_key_time(valid_secs: i64) -> String {
let start_timestamp = Utc::now().timestamp();
let end_timestamp = start_timestamp + valid_secs;
format!("{start_timestamp};{end_timestamp}")
}
impl OssMode for Tencent {
fn upload(&mut self, dirname: &str, file: PathBuf, content_type: &str) -> Result<(String, String), String> {
let mut http = Client::new();
let body = match fs::read(file.clone()) {
Ok(e) => e,
Err(e) => return Err(e.to_string()),
};
let md5 = br_crypto::md5::encrypt_hex(body.as_slice());
let uri_pathname = format!("{dirname}{md5}");
let url = format!("https://{}{uri_pathname}", self.endpoint);
http.put(url.as_str());
self.sign(&mut http, &uri_pathname, file.to_str().unwrap(), body, content_type);
let res = http.send()?;
if res.status() != 200 {
return Err(res.xml()?["Message"].to_string());
}
let etag = res.headers()["etag"].to_string().trim_start_matches("\"").trim_end_matches("\"").to_lowercase();
Ok((etag, url))
}
fn download(&mut self, dirname: &str, key: &str) -> Result<(String, String, String, Vec<u8>), String> {
let mut http = Client::new();
let uri_pathname = format!("{dirname}{key}");
let url = format!("https://{}{uri_pathname}", self.endpoint);
http.get(&url);
self.sign_get(&mut http, &uri_pathname);
let res = http.send()?;
if res.status() != 200 {
return Err(res.xml()?["Message"].to_string());
}
let etag = res.headers()["etag"].to_string().trim_start_matches("\"").trim_end_matches("\"").to_lowercase();
let content_type = res.headers()["content-type"].to_string();
let body = res.stream();
Ok((etag, url, content_type, body))
}
fn query(&mut self, dirname: &str, key: &str) -> Result<(String, String), String> {
let mut http = Client::new();
let uri_pathname = format!("{dirname}{key}");
let url = format!("https://{}{uri_pathname}", self.endpoint);
http.head(&url);
self.sign_head(&mut http, &uri_pathname);
let res = http.send()?;
if res.status() != 200 {
return Err("No such resource".to_string());
}
let etag = res.headers()["etag"].to_string().trim_start_matches("\"").trim_end_matches("\"").to_lowercase();
Ok((etag, url))
}
fn del(&mut self, dirname: &str, key: &str) -> Result<bool, String> {
let mut http = Client::new();
let uri_pathname = format!("{dirname}{key}");
let url = format!("https://{}{uri_pathname}", self.endpoint);
http.delete(&url);
self.sign_head(&mut http, &uri_pathname);
let res = http.send()?;
if res.status() != 204 {
return Err("No such resource".to_string());
}
Ok(true)
}
}