omegaupload/common/src/lib.rs

249 lines
6.5 KiB
Rust
Raw Normal View History

2021-10-19 02:18:33 -07:00
#![warn(clippy::nursery, clippy::pedantic)]
//! Contains common functions and structures used by multiple projects
2021-10-21 18:35:54 -07:00
use std::fmt::Display;
2021-10-19 02:18:33 -07:00
use std::str::FromStr;
2021-10-21 18:35:54 -07:00
use bytes::Bytes;
use chrono::{DateTime, Duration, Utc};
use headers::{Header, HeaderName, HeaderValue};
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
2021-10-19 02:18:33 -07:00
use thiserror::Error;
pub use url::Url;
2021-10-30 18:38:55 -07:00
use crate::crypto::Key;
2021-10-19 02:18:33 -07:00
2021-10-30 18:38:55 -07:00
pub mod base64;
pub mod crypto;
2021-10-19 02:18:33 -07:00
2021-10-30 18:38:55 -07:00
pub const API_ENDPOINT: &str = "/api";
2021-10-19 02:18:33 -07:00
pub struct ParsedUrl {
pub sanitized_url: Url,
pub decryption_key: Key,
pub needs_password: bool,
}
#[derive(Default)]
pub struct PartialParsedUrl {
pub decryption_key: Option<Key>,
pub needs_password: bool,
}
impl From<&str> for PartialParsedUrl {
fn from(fragment: &str) -> Self {
let args = fragment.split('!').filter_map(|kv| {
let (k, v) = {
let mut iter = kv.split(':');
(iter.next(), iter.next())
};
Some((k?, v))
});
let mut decryption_key = None;
let mut needs_password = false;
for (key, value) in args {
match (key, value) {
("key", Some(value)) => {
2021-10-27 01:49:06 -07:00
decryption_key = base64::decode(value).map(|k| *Key::from_slice(&k)).ok();
2021-10-19 02:18:33 -07:00
}
("pw", _) => {
needs_password = true;
}
_ => (),
}
}
Self {
decryption_key,
needs_password,
}
}
}
#[derive(Debug, Error)]
pub enum ParseUrlError {
#[error("The provided url was bad")]
BadUrl,
#[error("Missing decryption key")]
NeedKey,
#[error("Missing nonce")]
NeedNonce,
#[error("Missing decryption key and nonce")]
NeedKeyAndNonce,
}
impl FromStr for ParsedUrl {
type Err = ParseUrlError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut url = Url::from_str(s).map_err(|_| ParseUrlError::BadUrl)?;
let fragment = url.fragment().ok_or(ParseUrlError::NeedKeyAndNonce)?;
if fragment.is_empty() {
return Err(ParseUrlError::NeedKeyAndNonce);
}
let PartialParsedUrl {
decryption_key,
needs_password,
} = PartialParsedUrl::from(fragment);
url.set_fragment(None);
2021-10-30 18:38:55 -07:00
let decryption_key = match &decryption_key {
Some(k) => Ok(*k),
None => Err(ParseUrlError::NeedKey),
2021-10-19 02:18:33 -07:00
}?;
Ok(Self {
sanitized_url: url,
decryption_key,
needs_password,
})
2021-10-16 09:50:11 -07:00
}
}
2021-10-21 18:35:54 -07:00
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub enum Expiration {
BurnAfterReading,
2021-10-27 19:16:43 -07:00
BurnAfterReadingWithDeadline(DateTime<Utc>),
2021-10-21 18:35:54 -07:00
UnixTime(DateTime<Utc>),
}
2021-10-27 19:16:43 -07:00
// This impl is used for the CLI
impl FromStr for Expiration {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"read" => Ok(Self::BurnAfterReading),
"5m" => Ok(Self::UnixTime(Utc::now() + Duration::minutes(5))),
"10m" => Ok(Self::UnixTime(Utc::now() + Duration::minutes(10))),
"1h" => Ok(Self::UnixTime(Utc::now() + Duration::hours(1))),
"1d" => Ok(Self::UnixTime(Utc::now() + Duration::days(1))),
// We disallow permanent pastes
_ => Err(s.to_owned()),
}
}
}
2021-10-21 18:35:54 -07:00
impl Display for Expiration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
2021-10-27 19:16:43 -07:00
Expiration::BurnAfterReading | Expiration::BurnAfterReadingWithDeadline(_) => {
2021-10-24 02:25:42 -07:00
write!(f, "This item has been burned. You now have the only copy.")
2021-10-21 18:35:54 -07:00
}
Expiration::UnixTime(time) => write!(
f,
"{}",
2021-10-24 02:25:42 -07:00
time.format("This item will expire on %A, %B %-d, %Y at %T %Z.")
2021-10-21 18:35:54 -07:00
),
}
}
}
lazy_static! {
pub static ref EXPIRATION_HEADER_NAME: HeaderName = HeaderName::from_static("burn-after");
}
impl Header for Expiration {
fn name() -> &'static HeaderName {
&*EXPIRATION_HEADER_NAME
}
fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
where
Self: Sized,
I: Iterator<Item = &'i HeaderValue>,
{
2021-10-27 19:16:43 -07:00
let bytes = values.next().ok_or_else(headers::Error::invalid)?;
Self::try_from(bytes).map_err(|_| headers::Error::invalid())
2021-10-21 18:35:54 -07:00
}
fn encode<E: Extend<HeaderValue>>(&self, container: &mut E) {
container.extend(std::iter::once(self.into()));
}
}
impl From<&Expiration> for HeaderValue {
fn from(expiration: &Expiration) -> Self {
2021-10-22 19:15:23 -07:00
// SAFETY: All possible values of `Expiration` are valid header values,
// so we don't need the extra check.
2021-10-21 18:35:54 -07:00
unsafe {
Self::from_maybe_shared_unchecked(match expiration {
2021-10-27 19:16:43 -07:00
Expiration::BurnAfterReadingWithDeadline(_) | Expiration::BurnAfterReading => {
Bytes::from_static(b"0")
}
2021-10-21 18:35:54 -07:00
Expiration::UnixTime(duration) => Bytes::from(duration.to_rfc3339()),
})
}
}
}
impl From<Expiration> for HeaderValue {
fn from(expiration: Expiration) -> Self {
(&expiration).into()
}
}
2021-10-27 19:16:43 -07:00
pub struct ParseHeaderValueError;
2021-10-24 16:16:02 -07:00
#[cfg(feature = "wasm")]
impl TryFrom<web_sys::Headers> for Expiration {
type Error = ParseHeaderValueError;
fn try_from(headers: web_sys::Headers) -> Result<Self, Self::Error> {
headers
.get(http::header::EXPIRES.as_str())
.ok()
.flatten()
.as_deref()
.and_then(|v| Self::try_from(v).ok())
2021-10-24 16:16:02 -07:00
.ok_or(ParseHeaderValueError)
}
}
2021-10-27 19:16:43 -07:00
impl TryFrom<HeaderValue> for Expiration {
type Error = ParseHeaderValueError;
fn try_from(value: HeaderValue) -> Result<Self, Self::Error> {
Self::try_from(&value)
}
}
2021-10-21 18:35:54 -07:00
impl TryFrom<&HeaderValue> for Expiration {
type Error = ParseHeaderValueError;
fn try_from(value: &HeaderValue) -> Result<Self, Self::Error> {
2021-10-27 19:16:43 -07:00
std::str::from_utf8(value.as_bytes())
2021-10-23 10:10:55 -07:00
.map_err(|_| ParseHeaderValueError)
.and_then(Self::try_from)
}
}
impl TryFrom<&str> for Expiration {
type Error = ParseHeaderValueError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
2021-10-27 19:16:43 -07:00
if value == "0" {
return Ok(Self::BurnAfterReading);
}
2021-10-23 10:10:55 -07:00
value
2021-10-21 18:35:54 -07:00
.parse::<DateTime<Utc>>()
.map_err(|_| ParseHeaderValueError)
.map(Self::UnixTime)
}
}
impl Default for Expiration {
fn default() -> Self {
Self::UnixTime(Utc::now() + Duration::days(1))
}
}