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 { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 64 | let (mut parts, body) = response.into_parts(); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 65 | let Ok(bytes) = axum::body::to_bytes(body, usize::MAX).await else { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 66 | return Response::from_parts(parts, Body::empty()); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 67 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 68 | let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 69 | let gzipped = encoder.write_all(&bytes).and_then(|()| encoder.finish()); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 70 | let Ok(gzipped) = gzipped else { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 71 | return Response::from_parts(parts, Body::from(bytes)); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 72 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 73 | parts |
782069860236 | atagen | 2026-10-10 12:44 UTC | 74 | .headers |
782069860236 | atagen | 2026-10-10 12:44 UTC | 75 | .insert(header::CONTENT_ENCODING, HeaderValue::from_static("gzip")); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 76 | parts.headers.remove(header::CONTENT_LENGTH); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 77 | parts |
782069860236 | atagen | 2026-10-10 12:44 UTC | 78 | .headers |
782069860236 | atagen | 2026-10-10 12:44 UTC | 79 | .append(header::VARY, HeaderValue::from_static("Accept-Encoding")); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 80 | Response::from_parts(parts, Body::from(gzipped)) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 81 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 82 | |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 83 | async fn localise(response: Response, locale: Locale) -> Response { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 84 | if locale == Locale::British { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 85 | return response; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 86 | } |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 87 | let (mut parts, body) = response.into_parts(); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 88 | for name in [header::VIA, header::VARY, X_OBSERVED_BY] { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 89 | let localised: Vec<HeaderValue> = parts |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 90 | .headers |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 91 | .get_all(&name) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 92 | .iter() |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 93 | .filter_map(|v| v.to_str().ok()) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 94 | .filter_map(|v| HeaderValue::from_str(&locale.localise(v)).ok()) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 95 | .collect(); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 96 | if !localised.is_empty() { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 97 | parts.headers.remove(&name); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 98 | for value in localised { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 99 | parts.headers.append(name.clone(), value); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 100 | } |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 101 | } |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 102 | } |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 103 | let json = parts |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 104 | .headers |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 105 | .get(header::CONTENT_TYPE) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 106 | .and_then(|v| v.to_str().ok()) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 107 | .is_some_and(|ct| { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 108 | ct.starts_with("application/json") || ct.starts_with("application/problem+json") |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 109 | }); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 110 | if !json || parts.status == StatusCode::PARTIAL_CONTENT { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 111 | return Response::from_parts(parts, body); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 112 | } |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 113 | let Ok(bytes) = axum::body::to_bytes(body, usize::MAX).await else { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 114 | return Response::from_parts(parts, Body::empty()); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 115 | }; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 116 | let Ok(mut value) = serde_json::from_slice::<Value>(&bytes) else { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 117 | return Response::from_parts(parts, Body::from(bytes)); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 118 | }; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 119 | locale.localise_json(&mut value); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 120 | parts.headers.remove(header::CONTENT_LENGTH); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 121 | Response::from_parts(parts, Body::from(value.to_string())) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 122 | } |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 123 | |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 124 | pub async fn chrome(request: Request, next: Next) -> Response { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 125 | let (gzip, locale) = { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 126 | let header_text = |name| { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 127 | request |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 128 | .headers() |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 129 | .get(name) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 130 | .and_then(|v| v.to_str().ok()) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 131 | .map(str::to_owned) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 132 | }; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 133 | let gzip = header_text(header::ACCEPT_ENCODING) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 134 | .is_some_and(|e| e.to_ascii_lowercase().contains("gzip")); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 135 | let locale = Locale::negotiate(header_text(header::ACCEPT_LANGUAGE).as_deref()); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 136 | (gzip, locale) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 137 | }; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 138 | let response = match reject(&request) { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 139 | Some(problem) => problem.into_response(), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 140 | None => next.run(request).await, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 141 | }; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 142 | let mut response = localise(response, locale).await; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 143 | if gzip && response.extensions().get::<Compress>().is_some() { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 144 | response = compress(response).await; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 145 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 146 | let headers = response.headers_mut(); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 147 | headers.insert(header::SERVER, HeaderValue::from_static("jelqd/0.6.9")); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 148 | headers.insert(X_POWERED_BY, HeaderValue::from_static("blood")); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 149 | headers.insert( |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 150 | header::CONTENT_LANGUAGE, |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 151 | HeaderValue::from_static(locale.tag()), |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 152 | ); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 153 | headers.append(header::VARY, HeaderValue::from_static("Accept-Language")); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 154 | headers |
782069860236 | atagen | 2026-10-10 12:44 UTC | 155 | .entry(header::CACHE_CONTROL) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 156 | .or_insert(HeaderValue::from_static("no-store, private")); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 157 | response |
782069860236 | atagen | 2026-10-10 12:44 UTC | 158 | } |