an http based roguelike rpg about jelqing
Blame: src/http/middleware.rs
782069860236 | atagen | 2026-10-10 12:44 UTC | 1 | use std::io::Write as _; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 2 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 3 | use axum::{ |
782069860236 | atagen | 2026-10-10 12:44 UTC | 4 | body::Body, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 5 | extract::Request, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 6 | http::{HeaderValue, StatusCode, Version, header}, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 7 | middleware::Next, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 8 | response::{IntoResponse, Response}, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 9 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 10 | use flate2::{Compression, write::GzEncoder}; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 11 | |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 12 | use serde_json::Value; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 13 | |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 14 | use crate::{ |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 15 | headers::{X_OBSERVED_BY, X_POWERED_BY}, |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 16 | locale::Locale, |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 17 | problem::Problem, |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 18 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 19 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 20 | const MAX_URI: usize = 512; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 21 | const MAX_HEADER: usize = 1024; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 22 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 23 | /// Marks a response for gzip, which the cock ring asks for. |
782069860236 | atagen | 2026-10-10 12:44 UTC | 24 | #[derive(Clone)] |
782069860236 | atagen | 2026-10-10 12:44 UTC | 25 | pub struct Compress; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 26 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 27 | fn reject(request: &Request) -> Option<Problem> { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 28 | if request.version() < Version::HTTP_11 { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 29 | return Some(Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 30 | StatusCode::HTTP_VERSION_NOT_SUPPORTED, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 31 | "pdf", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 32 | "HTTP/1.0 is the version the original jelqing PDF was written for. The server has read it.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 33 | )); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 34 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 35 | if request.uri().to_string().len() > MAX_URI { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 36 | return Some(Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 37 | StatusCode::URI_TOO_LONG, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 38 | "uri-too-long", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 39 | "The URI is longer than anything else here.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 40 | )); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 41 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 42 | if request.headers().values().any(|v| v.len() > MAX_HEADER) { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 43 | return Some(Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 44 | StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 45 | "head-too-large", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 46 | "Head too large.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 47 | )); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 48 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 49 | let expectation = request.headers().get(header::EXPECT)?; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 50 | if expectation.as_bytes().eq_ignore_ascii_case(b"100-continue") { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 51 | return None; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 52 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 53 | Some(Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 54 | StatusCode::EXPECTATION_FAILED, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 55 | "expectations", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 56 | format!( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 57 | "Expect: {} cannot be met.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 58 | expectation.to_str().unwrap_or("that") |
782069860236 | atagen | 2026-10-10 12:44 UTC | 59 | ), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 60 | )) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 61 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 62 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 63 | async fn compress(response: Response) -> Response { |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 64 | let compressible = response |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 65 | .headers() |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 66 | .get(header::CONTENT_TYPE) |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 67 | .and_then(|value| value.to_str().ok()) |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 68 | .is_some_and(|type_| { |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 69 | type_.starts_with("application/json") |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 70 | || type_.starts_with("application/problem+json") |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 71 | || type_.starts_with("application/xml") |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 72 | }); |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 73 | if !compressible { |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 74 | return response; |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 75 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 76 | let (mut parts, body) = response.into_parts(); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 77 | let Ok(bytes) = axum::body::to_bytes(body, usize::MAX).await else { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 78 | return Response::from_parts(parts, Body::empty()); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 79 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 80 | let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 81 | let gzipped = encoder.write_all(&bytes).and_then(|()| encoder.finish()); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 82 | let Ok(gzipped) = gzipped else { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 83 | return Response::from_parts(parts, Body::from(bytes)); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 84 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 85 | parts |
782069860236 | atagen | 2026-10-10 12:44 UTC | 86 | .headers |
782069860236 | atagen | 2026-10-10 12:44 UTC | 87 | .insert(header::CONTENT_ENCODING, HeaderValue::from_static("gzip")); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 88 | parts.headers.remove(header::CONTENT_LENGTH); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 89 | parts |
782069860236 | atagen | 2026-10-10 12:44 UTC | 90 | .headers |
782069860236 | atagen | 2026-10-10 12:44 UTC | 91 | .append(header::VARY, HeaderValue::from_static("Accept-Encoding")); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 92 | Response::from_parts(parts, Body::from(gzipped)) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 93 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 94 | |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 95 | async fn localise(response: Response, locale: Locale) -> Response { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 96 | if locale == Locale::British { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 97 | return response; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 98 | } |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 99 | let (mut parts, body) = response.into_parts(); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 100 | for name in [header::VIA, header::VARY, X_OBSERVED_BY] { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 101 | let localised: Vec<HeaderValue> = parts |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 102 | .headers |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 103 | .get_all(&name) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 104 | .iter() |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 105 | .filter_map(|v| v.to_str().ok()) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 106 | .filter_map(|v| HeaderValue::from_str(&locale.localise(v)).ok()) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 107 | .collect(); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 108 | if !localised.is_empty() { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 109 | parts.headers.remove(&name); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 110 | for value in localised { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 111 | parts.headers.append(name.clone(), value); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 112 | } |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 113 | } |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 114 | } |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 115 | let json = parts |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 116 | .headers |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 117 | .get(header::CONTENT_TYPE) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 118 | .and_then(|v| v.to_str().ok()) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 119 | .is_some_and(|ct| { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 120 | ct.starts_with("application/json") || ct.starts_with("application/problem+json") |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 121 | }); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 122 | if !json || parts.status == StatusCode::PARTIAL_CONTENT { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 123 | return Response::from_parts(parts, body); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 124 | } |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 125 | let Ok(bytes) = axum::body::to_bytes(body, usize::MAX).await else { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 126 | return Response::from_parts(parts, Body::empty()); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 127 | }; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 128 | let Ok(mut value) = serde_json::from_slice::<Value>(&bytes) else { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 129 | return Response::from_parts(parts, Body::from(bytes)); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 130 | }; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 131 | locale.localise_json(&mut value); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 132 | parts.headers.remove(header::CONTENT_LENGTH); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 133 | Response::from_parts(parts, Body::from(value.to_string())) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 134 | } |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 135 | |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 136 | pub async fn chrome(request: Request, next: Next) -> Response { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 137 | let (gzip, locale) = { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 138 | let header_text = |name| { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 139 | request |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 140 | .headers() |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 141 | .get(name) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 142 | .and_then(|v| v.to_str().ok()) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 143 | .map(str::to_owned) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 144 | }; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 145 | let gzip = header_text(header::ACCEPT_ENCODING) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 146 | .is_some_and(|e| e.to_ascii_lowercase().contains("gzip")); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 147 | let locale = Locale::negotiate(header_text(header::ACCEPT_LANGUAGE).as_deref()); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 148 | (gzip, locale) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 149 | }; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 150 | let response = match reject(&request) { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 151 | Some(problem) => problem.into_response(), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 152 | None => next.run(request).await, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 153 | }; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 154 | let mut response = localise(response, locale).await; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 155 | if gzip && response.extensions().get::<Compress>().is_some() { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 156 | response = compress(response).await; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 157 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 158 | let headers = response.headers_mut(); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 159 | headers.insert(header::SERVER, HeaderValue::from_static("jelqd/0.6.9")); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 160 | headers.insert(X_POWERED_BY, HeaderValue::from_static("blood")); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 161 | headers.insert( |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 162 | header::CONTENT_LANGUAGE, |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 163 | HeaderValue::from_static(locale.tag()), |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 164 | ); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 165 | headers.append(header::VARY, HeaderValue::from_static("Accept-Language")); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 166 | headers |
782069860236 | atagen | 2026-10-10 12:44 UTC | 167 | .entry(header::CACHE_CONTROL) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 168 | .or_insert(HeaderValue::from_static("no-store, private")); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 169 | response |
782069860236 | atagen | 2026-10-10 12:44 UTC | 170 | } |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 171 | |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 172 | /// A handler panic would otherwise drop the connection with no reply at all. |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 173 | pub fn panicked(_: Box<dyn std::any::Any + Send + 'static>) -> Response { |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 174 | Problem::new( |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 175 | StatusCode::INTERNAL_SERVER_ERROR, |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 176 | "server-fault", |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 177 | "The server has had an internal event of its own. It is not discussing it.", |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 178 | ) |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 179 | .into_response() |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 180 | } |