Files
pastable2/src/routes/input.rs
T
2026-11-14 17:54:29 +01:00

81 lines
2.4 KiB
Rust

use std::time::UNIX_EPOCH;
use axum::{
Form,
extract::{FromRequest, Request},
http::{HeaderValue, StatusCode, Uri, header::CONTENT_TYPE},
response::{IntoResponse, Response},
};
use serde::Deserialize;
#[derive(Default, Deserialize)]
pub struct PasteInput {
#[serde(alias = "href")]
pub content: String,
pub language: Option<String>,
pub expires: Option<i64>,
pub max_views: Option<i64>,
}
impl PasteInput {
pub fn validate_url(&mut self) -> Result<(), StatusCode> {
self.content = self.content.trim().to_owned();
let uri: Uri = self.content.parse().map_err(|_| StatusCode::BAD_REQUEST)?;
if !matches!(uri.scheme_str(), Some("http" | "https"))
|| uri.host().is_none()
|| HeaderValue::from_str(&self.content).is_err()
{
return Err(StatusCode::BAD_REQUEST);
}
Ok(())
}
pub fn expiration(&self) -> Result<Option<i64>, StatusCode> {
self.expires
.map(|expires| {
let now = UNIX_EPOCH
.elapsed()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.as_secs() as i64;
now.checked_add(expires).ok_or(StatusCode::BAD_REQUEST)
})
.transpose()
}
}
impl<S: Send + Sync> FromRequest<S> for PasteInput {
type Rejection = Response;
async fn from_request(request: Request, state: &S) -> Result<Self, Self::Rejection> {
let form = request
.headers()
.get(CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.split(';').next())
.is_some_and(|value| {
value
.trim()
.eq_ignore_ascii_case("application/x-www-form-urlencoded")
});
let input = if form {
Form::<Self>::from_request(request, state)
.await
.map_err(IntoResponse::into_response)?
.0
} else {
Self {
content: String::from_request(request, state)
.await
.map_err(IntoResponse::into_response)?,
..Self::default()
}
};
if input.expires.is_some_and(|expires| expires < 0)
|| input.max_views.is_some_and(|max| max < 1)
{
return Err(StatusCode::BAD_REQUEST.into_response());
}
Ok(input)
}
}