2021-10-19 02:18:33 -07:00
|
|
|
#![warn(clippy::nursery, clippy::pedantic)]
|
|
|
|
|
|
|
|
//! Contains common functions and structures used by multiple projects
|
|
|
|
|
2021-10-31 14:01:27 -07:00
|
|
|
// Copyright (c) 2021 Edward Shen
|
|
|
|
//
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
|
|
// in the Software without restriction, including without limitation the rights
|
|
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
//
|
|
|
|
// The above copyright notice and this permission notice shall be included in
|
|
|
|
// all copies or substantial portions of the Software.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
// SOFTWARE.
|
|
|
|
|
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;
|
2021-10-30 21:00:09 -07:00
|
|
|
pub use secrecy;
|
|
|
|
use secrecy::Secret;
|
2021-10-21 18:35:54 -07:00
|
|
|
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,
|
2021-10-30 21:00:09 -07:00
|
|
|
pub decryption_key: Secret<Key>,
|
2021-10-19 02:18:33 -07:00
|
|
|
pub needs_password: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct PartialParsedUrl {
|
2021-10-30 21:00:09 -07:00
|
|
|
pub decryption_key: Option<Secret<Key>>,
|
2021-10-19 02:18:33 -07:00
|
|
|
pub needs_password: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&str> for PartialParsedUrl {
|
|
|
|
fn from(fragment: &str) -> Self {
|
2021-10-30 21:00:09 -07:00
|
|
|
// Short circuit if the fragment only contains the key.
|
|
|
|
|
|
|
|
// Base64 has an interesting property that the length of an encoded text
|
|
|
|
// is always 4/3rds larger than the original data.
|
|
|
|
if !fragment.contains("key") {
|
2021-10-31 00:57:52 -07:00
|
|
|
let decryption_key = base64::decode(fragment).ok().and_then(Key::new_secret);
|
2021-10-30 21:00:09 -07:00
|
|
|
|
|
|
|
return Self {
|
|
|
|
decryption_key,
|
|
|
|
needs_password: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-10-19 02:18:33 -07:00
|
|
|
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-31 00:57:52 -07:00
|
|
|
decryption_key = base64::decode(value).ok().and_then(Key::new_secret);
|
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,
|
|
|
|
}
|
|
|
|
|
|
|
|
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)?;
|
2021-10-30 21:00:09 -07:00
|
|
|
let fragment = url.fragment().ok_or(ParseUrlError::NeedKey)?;
|
2021-10-19 02:18:33 -07:00
|
|
|
if fragment.is_empty() {
|
2021-10-30 21:00:09 -07:00
|
|
|
return Err(ParseUrlError::NeedKey);
|
2021-10-19 02:18:33 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
let PartialParsedUrl {
|
2021-10-30 21:00:09 -07:00
|
|
|
mut decryption_key,
|
2021-10-19 02:18:33 -07:00
|
|
|
needs_password,
|
|
|
|
} = PartialParsedUrl::from(fragment);
|
|
|
|
|
|
|
|
url.set_fragment(None);
|
|
|
|
|
2021-10-30 21:00:09 -07:00
|
|
|
let decryption_key = decryption_key.take().ok_or(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()
|
2021-10-25 17:31:30 -07:00
|
|
|
.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))
|
|
|
|
}
|
|
|
|
}
|