an http based roguelike rpg about jelqing
| 1 | use std::io::Write as _; |
| 2 | |
| 3 | use axum::{ |
| 4 | body::Body, |
| 5 | extract::Request, |
| 6 | http::{HeaderValue, StatusCode, Version, header}, |
| 7 | middleware::Next, |
| 8 | response::{IntoResponse, Response}, |
| 9 | }; |
| 10 | use flate2::{Compression, write::GzEncoder}; |
| 11 | |
| 12 | use crate::{headers::X_POWERED_BY, problem::Problem}; |
| 13 | |
| 14 | const MAX_URI: usize = 512; |
| 15 | const MAX_HEADER: usize = 1024; |
| 16 | |
| 17 | /// Marks a response for gzip, which the cock ring asks for. |
| 18 | #[derive(Clone)] |
| 19 | pub struct Compress; |
| 20 | |
| 21 | fn reject(request: &Request) -> Option<Problem> { |
| 22 | if request.version() < Version::HTTP_11 { |
| 23 | return Some(Problem::new( |
| 24 | StatusCode::HTTP_VERSION_NOT_SUPPORTED, |
| 25 | "pdf", |
| 26 | "HTTP/1.0 is the version the original jelqing PDF was written for. The server has read it.", |
| 27 | )); |
| 28 | } |
| 29 | if request.uri().to_string().len() > MAX_URI { |
| 30 | return Some(Problem::new( |
| 31 | StatusCode::URI_TOO_LONG, |
| 32 | "uri-too-long", |
| 33 | "The URI is longer than anything else here.", |
| 34 | )); |
| 35 | } |
| 36 | if request.headers().values().any(|v| v.len() > MAX_HEADER) { |
| 37 | return Some(Problem::new( |
| 38 | StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE, |
| 39 | "head-too-large", |
| 40 | "Head too large.", |
| 41 | )); |
| 42 | } |
| 43 | let expectation = request.headers().get(header::EXPECT)?; |
| 44 | if expectation.as_bytes().eq_ignore_ascii_case(b"100-continue") { |
| 45 | return None; |
| 46 | } |
| 47 | Some(Problem::new( |
| 48 | StatusCode::EXPECTATION_FAILED, |
| 49 | "expectations", |
| 50 | format!( |
| 51 | "Expect: {} cannot be met.", |
| 52 | expectation.to_str().unwrap_or("that") |
| 53 | ), |
| 54 | )) |
| 55 | } |
| 56 | |
| 57 | async fn compress(response: Response) -> Response { |
| 58 | let (mut parts, body) = response.into_parts(); |
| 59 | let Ok(bytes) = axum::body::to_bytes(body, usize::MAX).await else { |
| 60 | return Response::from_parts(parts, Body::empty()); |
| 61 | }; |
| 62 | let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); |
| 63 | let gzipped = encoder.write_all(&bytes).and_then(|()| encoder.finish()); |
| 64 | let Ok(gzipped) = gzipped else { |
| 65 | return Response::from_parts(parts, Body::from(bytes)); |
| 66 | }; |
| 67 | parts |
| 68 | .headers |
| 69 | .insert(header::CONTENT_ENCODING, HeaderValue::from_static("gzip")); |
| 70 | parts.headers.remove(header::CONTENT_LENGTH); |
| 71 | parts |
| 72 | .headers |
| 73 | .append(header::VARY, HeaderValue::from_static("Accept-Encoding")); |
| 74 | Response::from_parts(parts, Body::from(gzipped)) |
| 75 | } |
| 76 | |
| 77 | pub async fn chrome(request: Request, next: Next) -> Response { |
| 78 | let gzip = request |
| 79 | .headers() |
| 80 | .get(header::ACCEPT_ENCODING) |
| 81 | .and_then(|v| v.to_str().ok()) |
| 82 | .is_some_and(|e| e.to_ascii_lowercase().contains("gzip")); |
| 83 | let mut response = match reject(&request) { |
| 84 | Some(problem) => problem.into_response(), |
| 85 | None => next.run(request).await, |
| 86 | }; |
| 87 | if gzip && response.extensions().get::<Compress>().is_some() { |
| 88 | response = compress(response).await; |
| 89 | } |
| 90 | let headers = response.headers_mut(); |
| 91 | headers.insert(header::SERVER, HeaderValue::from_static("jelqd/0.6.9")); |
| 92 | headers.insert(X_POWERED_BY, HeaderValue::from_static("blood")); |
| 93 | headers |
| 94 | .entry(header::CACHE_CONTROL) |
| 95 | .or_insert(HeaderValue::from_static("no-store, private")); |
| 96 | response |
| 97 | } |