use super::upload_policy::{UploadPolicy, UploadPolicyBuilder};
use crate::{utils::base64, Config, Credential};
use assert_impl::assert_impl;
use once_cell::sync::OnceCell;
use std::{borrow::Cow, convert::From, ffi::c_void, fmt, result::Result, sync::Arc, time::Duration};
use thiserror::Error;
#[derive(Debug, Clone)]
pub struct UploadToken(Arc<UploadTokenInner>);
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
enum UploadTokenInner {
Token {
token: Box<str>,
policy: OnceCell<UploadPolicy>,
access_key: OnceCell<Box<str>>,
},
Policy {
policy: UploadPolicy,
credential: Credential,
token: OnceCell<Box<str>>,
},
Bucket {
bucket: Cow<'static, str>,
upload_token_lifetime: Duration,
credential: Credential,
},
}
impl UploadToken {
pub fn new(policy: UploadPolicy, credential: Credential) -> Self {
Self(Arc::new(UploadTokenInner::Policy {
policy,
credential,
token: OnceCell::new(),
}))
}
pub fn new_from_bucket(bucket: impl Into<Cow<'static, str>>, credential: Credential, config: &Config) -> Self {
Self(Arc::new(UploadTokenInner::Bucket {
credential,
bucket: bucket.into(),
upload_token_lifetime: config.upload_token_lifetime(),
}))
}
pub fn access_key(&self) -> UploadTokenParseResult<&str> {
match self.0.as_ref() {
UploadTokenInner::Token { token, access_key, .. } => access_key
.get_or_try_init(|| {
token
.find(':')
.map(|i| token.split_at(i).0.to_owned().into())
.ok_or_else(|| UploadTokenParseError::InvalidUploadTokenFormat)
})
.map(|access_key| access_key.as_ref()),
UploadTokenInner::Policy { credential, .. } | UploadTokenInner::Bucket { credential, .. } => {
Ok(credential.access_key())
}
}
}
pub fn policy<'a>(&'a self) -> UploadTokenParseResult<Cow<'a, UploadPolicy>> {
match self.0.as_ref() {
UploadTokenInner::Token { token, policy, .. } => policy
.get_or_try_init(|| {
let encoded_policy = token
.splitn(3, ':')
.last()
.ok_or(UploadTokenParseError::InvalidUploadTokenFormat)?;
let decoded_policy =
base64::decode(encoded_policy.as_bytes()).map_err(UploadTokenParseError::Base64DecodeError)?;
Ok(UploadPolicy::from_json(&decoded_policy).map_err(UploadTokenParseError::JSONDecodeError)?)
})
.map(|policy| policy.into()),
UploadTokenInner::Policy { policy, .. } => Ok(policy.into()),
UploadTokenInner::Bucket {
bucket,
upload_token_lifetime,
..
} => Ok(UploadPolicyBuilder::new_policy_for_bucket_and_upload_token_lifetime(
bucket.to_string(),
*upload_token_lifetime,
)
.build()
.into()),
}
}
#[doc(hidden)]
pub fn into_raw(self) -> *const c_void {
Arc::into_raw(self.0).cast()
}
#[doc(hidden)]
pub unsafe fn from_raw(ptr: *const c_void) -> Self {
Self(Arc::from_raw(ptr.cast::<UploadTokenInner>()))
}
#[allow(dead_code)]
fn ignore() {
assert_impl!(Send: Self);
assert_impl!(Sync: Self);
}
}
impl fmt::Display for UploadToken {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0.as_ref() {
UploadTokenInner::Token { token, .. } => token.fmt(f),
UploadTokenInner::Policy {
policy,
credential,
token,
} => token
.get_or_init(|| credential.sign_upload_policy(policy).into())
.fmt(f),
UploadTokenInner::Bucket {
bucket,
upload_token_lifetime,
credential,
} => credential
.sign_upload_policy(
&UploadPolicyBuilder::new_policy_for_bucket_and_upload_token_lifetime(
bucket.to_string(),
*upload_token_lifetime,
)
.build(),
)
.fmt(f),
}
}
}
impl<'p> From<Cow<'p, str>> for UploadToken {
fn from(s: Cow<'p, str>) -> Self {
Self(Arc::new(UploadTokenInner::Token {
token: s.into_owned().into(),
policy: OnceCell::new(),
access_key: OnceCell::new(),
}))
}
}
impl From<String> for UploadToken {
fn from(s: String) -> Self {
Self(Arc::new(UploadTokenInner::Token {
token: s.into(),
policy: OnceCell::new(),
access_key: OnceCell::new(),
}))
}
}
impl<'p> From<&'p str> for UploadToken {
fn from(s: &'p str) -> Self {
Self(Arc::new(UploadTokenInner::Token {
token: s.into(),
policy: OnceCell::new(),
access_key: OnceCell::new(),
}))
}
}
impl<'p> From<&'p UploadToken> for Cow<'p, UploadToken> {
#[inline]
fn from(token: &'p UploadToken) -> Self {
Cow::Borrowed(token)
}
}
impl From<UploadToken> for Cow<'_, UploadToken> {
#[inline]
fn from(token: UploadToken) -> Self {
Cow::Owned(token)
}
}
impl From<UploadToken> for String {
#[inline]
fn from(upload_token: UploadToken) -> Self {
upload_token.to_string()
}
}
#[derive(Error, Debug)]
pub enum UploadTokenParseError {
#[error("Invalid upload token format")]
InvalidUploadTokenFormat,
#[error("Base64 decode error: {0}")]
Base64DecodeError(#[from] base64::DecodeError),
#[error("JSON decode error: {0}")]
JSONDecodeError(#[from] serde_json::Error),
}
pub type UploadTokenParseResult<T> = Result<T, UploadTokenParseError>;
#[cfg(test)]
mod tests {
use super::{super::upload_policy::UploadPolicyBuilder, *};
use crate::Config;
use std::{boxed::Box, error::Error, result::Result};
#[test]
fn test_build_upload_token_from_upload_policy() -> Result<(), Box<dyn Error>> {
let policy = UploadPolicyBuilder::new_policy_for_object("test_bucket", "test:file", &Config::default()).build();
let token = UploadToken::new(policy, get_credential()).to_string();
assert!(token.starts_with(get_credential().access_key()));
let token = UploadToken::from(token);
let policy = token.policy()?;
assert_eq!(policy.bucket(), Some("test_bucket"));
assert_eq!(policy.key(), Some("test:file"));
accept_string(token.to_owned().into());
accept_upload_token(&token.to_string().into());
accept_upload_token(&token.to_string().as_str().into());
Ok(())
}
fn accept_string(_: String) {}
fn accept_upload_token(_: &UploadToken) {}
fn get_credential() -> Credential {
Credential::new("abcdefghklmnopq", "1234567890")
}
}