compile-time limit the size of post body

new-idea
Avril 4 years ago
parent b07a320234
commit 4e813abed5
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -23,3 +23,6 @@ pub const POST_EXPIRE: tokio::time::Duration = tokio::time::Duration::from_secs(
* 24 // Day * 24 // Day
* 120 * 120
); );
/// Max size of encrypted body
pub const POST_BODY_MAX_SIZE: usize = (1024 * 1024) * 20; // 20MB

@ -215,11 +215,86 @@ impl<'a, F: FormatSpec + ?Sized> TryFrom<String> for FormattedString<F>
pub mod formats pub mod formats
{ {
use super::*; use super::*;
cfg_if! {
if #[cfg(feature="nightly")] {
/// A format valid for any string
pub type AnyFormat = !;
} else {
/// A format valid for any string
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AnyFormat{}
}
}
impl FormatSpec for AnyFormat
{
type Error = std::convert::Infallible;
#[inline] fn validate(_: &str) -> Result<(), Self::Error>
{
Ok(())
}
}
/// A format spec that must satisfy both these format specs in order
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BothFormat<T, U = AnyFormat>(PhantomData<(T, U)>)
where T: FormatSpec, U: FormatSpec;
#[derive(Debug)]
pub enum MultiFormatError<T,U>
{
First(T),
Second(U),
}
impl<T, U> FormatSpec for BothFormat<T,U>
where T: FormatSpec,
U: FormatSpec,
T::Error : error::Error + 'static + Send + Sync,
U::Error : error::Error + 'static + Send + Sync,
{
type Error = MultiFormatError<T::Error, U::Error>;
fn validate(s: &str) -> Result<(), Self::Error> {
T::validate(s).map_err(MultiFormatError::First)?;
U::validate(s).map_err(MultiFormatError::Second)?;
Ok(())
}
}
impl<T: 'static, U: 'static> error::Error for MultiFormatError<T,U>
where T: error::Error,
U: error::Error
{
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match &self {
Self::First(f) => Some(f),
Self::Second(n) => Some(n),
}
}
}
impl<T, U> fmt::Display for MultiFormatError<T,U>
where T: fmt::Display,
U: fmt::Display
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
match self {
Self::First(_) => write!(f, "the first condition failed"),
Self::Second(_) => write!(f, "the second condition failed"),
}
}
}
/// A hex string format specifier /// A hex string format specifier
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum HexFormat{} pub enum HexFormat{}
/// A string with a constant max length
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum MaxLenFormat<const MAX: usize>{} pub enum MaxLenFormat<const MAX: usize>{}

@ -5,20 +5,26 @@ use hard_format::formats::{
PEMFormattedString, PEMFormattedString,
PEMFormattedStr, PEMFormattedStr,
MaxLenString, MaxLenString,
self,
}; };
use tripcode::Tripcode; use tripcode::Tripcode;
id_type!(PostID; "A unique post ID"); id_type!(PostID; "A unique post ID");
/// Max length of `email` or `name` feild
const ID_MAX_LEN: usize = defaults::POST_ID_MAX_LEN;
/// String type that limits its bytes to the ID string max limit. /// String type that limits its bytes to the ID string max limit.
pub type IDMaxString = MaxLenString<ID_MAX_LEN>; pub type IDMaxString = MaxLenString<{defaults::POST_ID_MAX_LEN}>;
/// The timestamp type used in posts /// The timestamp type used in posts
pub type PostTimestamp = chrono::DateTime<defaults::Timezone>; pub type PostTimestamp = chrono::DateTime<defaults::Timezone>;
/// A size limited PEM formatting specifier
type PostBodyFormat = formats::BothFormat<formats::MaxLenFormat<{defaults::POST_BODY_MAX_SIZE}>, formats::PEMFormat>;
/// A size limited PEM string
pub type PostBodyString = hard_format::FormattedString<PostBodyFormat>;
/// A size limited PEM string
pub type PostBodyStr = hard_format::FormattedStr<PostBodyFormat>;
/// A single completed post /// A single completed post
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Post pub struct Post
@ -31,7 +37,8 @@ pub struct Post
email: Option<IDMaxString>, email: Option<IDMaxString>,
/// The client-side encrypted body string /// The client-side encrypted body string
body: PEMFormattedString, body: PostBodyString,
/// Signature of the body (optional). /// Signature of the body (optional).
signature: Option<PEMFormattedString>, signature: Option<PEMFormattedString>,
@ -55,16 +62,16 @@ impl Post
/// Time since this post was created as a Chrono `Duration`. /// Time since this post was created as a Chrono `Duration`.
pub fn time_since_creation(&self) -> chrono::Duration pub fn time_since_creation(&self) -> chrono::Duration
{ {
defaults::Timezone::now() - self.created defaults::Timezone::now() - self.created
} }
/// Has this post expired? /// Has this post expired?
/// ///
/// Expired posts should be removed /// Expired posts should be removed
pub fn expired(&self) -> bool pub fn expired(&self) -> bool
{ {
if let Ok(dur) = self.time_since_creation().to_std() if let Ok(dur) = &self.time_since_creation().to_std()
{ {
dur >= *self.expires_in.as_ref().unwrap_or(&defaults::POST_EXPIRE) dur >= self.expires_in.as_ref().unwrap_or(&defaults::POST_EXPIRE)
} else { } else {
// Conversion failed. Expire the post // Conversion failed. Expire the post
true true
@ -96,10 +103,15 @@ impl Post
} }
/// The body of this post /// The body of this post
pub fn body(&self) -> &PEMFormattedStr pub fn body(&self) -> &PostBodyStr
{ {
self.body.as_ref() self.body.as_ref()
} }
/// The PEM formatted signature of this post, if there is one.
pub fn signature(&self) -> Option<&PEMFormattedStr>
{
self.signature.as_ref().map(|x| x.as_ref())
}
} }
#[cfg(test)] #[cfg(test)]
@ -114,7 +126,7 @@ mod tests
name: Some("Some name".to_owned().try_into().unwrap()), name: Some("Some name".to_owned().try_into().unwrap()),
email: None, email: None,
tripcode: Some(super::Tripcode::generate("uhh hello").unwrap()), tripcode: Some(super::Tripcode::generate("uhh hello").unwrap()),
body: unsafe { super::PEMFormattedStr::new_unchecked("test").to_owned() }, //temporary body: unsafe { super::PostBodyStr::new_unchecked("test").to_owned() }, //temporary
signature: None, signature: None,
hash: Default::default(), hash: Default::default(),
@ -127,5 +139,5 @@ mod tests
let post2: super::Post = serde_json::from_slice(&post_json[..]).expect("Deserialise"); let post2: super::Post = serde_json::from_slice(&post_json[..]).expect("Deserialise");
assert_eq!(post, post2); assert_eq!(post, post2);
println!("Post was: {:?}", post); println!("Post was: {:?}", post);
} }
} }

Loading…
Cancel
Save