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, pub expires: Option, pub max_views: Option, } 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, 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 FromRequest for PasteInput { type Rejection = Response; async fn from_request(request: Request, state: &S) -> Result { 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::::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) } }