extern crate chrono;
extern crate regex;
use self::chrono::{DateTime, Duration, Utc};
use self::regex::Regex;
use thiserror::Error;
use url::{ParseError as UrlParseError, Url};
use crate::api::url::UrlBuilder;
use crate::config::SEND_DEFAULT_EXPIRE_TIME;
use crate::crypto::b64;
const SHARE_PATH_PATTERN: &str = r"^/?download/([[:alnum:]]{8,}={0,3})/?$";
const SHARE_FRAGMENT_PATTERN: &str = r"^([a-zA-Z0-9-_+/]+)?\s*$";
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RemoteFile {
id: String,
upload_at: Option<DateTime<Utc>>,
expire_at: DateTime<Utc>,
expire_uncertain: bool,
host: Url,
url: Url,
secret: Vec<u8>,
owner_token: Option<String>,
}
impl RemoteFile {
pub fn new(
id: String,
upload_at: Option<DateTime<Utc>>,
expire_at: Option<DateTime<Utc>>,
host: Url,
url: Url,
secret: Vec<u8>,
owner_token: Option<String>,
) -> Self {
let expire_uncertain = expire_at.is_none();
let expire_at =
expire_at.unwrap_or(Utc::now() + Duration::seconds(SEND_DEFAULT_EXPIRE_TIME as i64));
Self {
id,
upload_at,
expire_at,
expire_uncertain,
host,
url,
secret,
owner_token,
}
}
pub fn new_now(
id: String,
host: Url,
url: Url,
secret: Vec<u8>,
owner_token: Option<String>,
) -> Self {
let now = Utc::now();
let expire_at = now + Duration::seconds(SEND_DEFAULT_EXPIRE_TIME as i64);
Self::new(
id,
Some(now),
Some(expire_at),
host,
url,
secret,
owner_token,
)
}
pub fn parse_url(url: Url, owner_token: Option<String>) -> Result<RemoteFile, FileParseError> {
let mut host = url.clone();
host.set_fragment(None);
host.set_query(None);
host.set_path("");
let re_path = Regex::new(SHARE_PATH_PATTERN).unwrap();
let id = re_path
.captures(url.path())
.ok_or(FileParseError::InvalidUrl)?[1]
.trim()
.to_owned();
let mut secret = Vec::new();
if let Some(fragment) = url.fragment() {
let re_fragment = Regex::new(SHARE_FRAGMENT_PATTERN).unwrap();
if let Some(raw) = re_fragment
.captures(fragment)
.ok_or(FileParseError::InvalidSecret)?
.get(1)
{
secret =
b64::decode(raw.as_str().trim()).map_err(|_| FileParseError::InvalidSecret)?
}
}
Ok(Self::new(id, None, None, host, url, secret, owner_token))
}
pub fn id(&self) -> &str {
&self.id
}
pub fn expire_at(&self) -> DateTime<Utc> {
self.expire_at
}
pub fn expire_duration(&self) -> Duration {
let now = Utc::now();
if self.expire_at > now {
self.expire_at - now
} else {
Duration::zero()
}
}
pub fn set_expire_at(&mut self, expire_at: Option<DateTime<Utc>>) {
if let Some(expire_at) = expire_at {
self.expire_at = expire_at;
} else {
self.expire_at = Utc::now() + Duration::seconds(SEND_DEFAULT_EXPIRE_TIME as i64);
self.expire_uncertain = true;
}
}
pub fn set_expire_duration(&mut self, duration: Duration) {
self.set_expire_at(Some(Utc::now() + duration));
}
pub fn has_expired(&self) -> bool {
self.expire_at < Utc::now()
}
pub fn expire_uncertain(&self) -> bool {
self.expire_uncertain
}
pub fn url(&self) -> &Url {
&self.url
}
pub fn secret_raw(&self) -> &Vec<u8> {
&self.secret
}
pub fn secret(&self) -> String {
b64::encode(self.secret_raw())
}
pub fn set_secret(&mut self, secret: Vec<u8>) {
self.secret = secret;
}
pub fn has_secret(&self) -> bool {
!self.secret.is_empty()
}
pub fn owner_token(&self) -> Option<&String> {
self.owner_token.as_ref()
}
pub fn owner_token_mut(&mut self) -> &mut Option<String> {
&mut self.owner_token
}
pub fn set_owner_token(&mut self, token: Option<String>) {
self.owner_token = token;
}
pub fn has_owner_token(&self) -> bool {
self.owner_token
.clone()
.map(|t| !t.is_empty())
.unwrap_or(false)
}
pub fn host(&self) -> Url {
self.host.clone()
}
pub fn download_url(&self, secret: bool) -> Url {
UrlBuilder::download(&self, secret)
}
#[allow(unknown_lints)]
pub fn merge(&mut self, other: &RemoteFile, overwrite: bool) -> bool {
let mut changed = false;
if other.upload_at.is_some() && (self.upload_at.is_none() || overwrite) {
self.upload_at = other.upload_at;
changed = true;
}
if !other.expire_uncertain() && (self.expire_uncertain() || overwrite) {
self.expire_at = other.expire_at;
self.expire_uncertain = other.expire_uncertain();
changed = true;
}
if other.has_secret() && (!self.has_secret() || overwrite) {
self.secret = other.secret_raw().clone();
changed = true;
}
if other.owner_token.is_some() && (self.owner_token.is_none() || overwrite) {
self.owner_token = other.owner_token.clone();
changed = true;
}
changed
}
}
#[derive(Debug, Error)]
pub enum FileParseError {
#[error("failed to parse remote file, invalid URL format")]
UrlFormatError(#[from] UrlParseError),
#[error("failed to parse remote file, invalid URL")]
InvalidUrl,
#[error("failed to parse remote file, invalid secret in URL")]
InvalidSecret,
}