atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
bad9f5b8202f43735ec38acb2070d214d84302e6
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git

Blame: src/http/middleware.rs

782069860236atagen2026-10-10 12:44 UTC1
use std::io::Write as _;
782069860236atagen2026-10-10 12:44 UTC2
782069860236atagen2026-10-10 12:44 UTC3
use axum::{
782069860236atagen2026-10-10 12:44 UTC4
    body::Body,
782069860236atagen2026-10-10 12:44 UTC5
    extract::Request,
782069860236atagen2026-10-10 12:44 UTC6
    http::{HeaderValue, StatusCode, Version, header},
782069860236atagen2026-10-10 12:44 UTC7
    middleware::Next,
782069860236atagen2026-10-10 12:44 UTC8
    response::{IntoResponse, Response},
782069860236atagen2026-10-10 12:44 UTC9
};
782069860236atagen2026-10-10 12:44 UTC10
use flate2::{Compression, write::GzEncoder};
782069860236atagen2026-10-10 12:44 UTC11
ef7d6dee5976atagen2026-10-10 13:23 UTC12
use serde_json::Value;
ef7d6dee5976atagen2026-10-10 13:23 UTC13
ef7d6dee5976atagen2026-10-10 13:23 UTC14
use crate::{
ef7d6dee5976atagen2026-10-10 13:23 UTC15
    headers::{X_OBSERVED_BY, X_POWERED_BY},
ef7d6dee5976atagen2026-10-10 13:23 UTC16
    locale::Locale,
ef7d6dee5976atagen2026-10-10 13:23 UTC17
    problem::Problem,
ef7d6dee5976atagen2026-10-10 13:23 UTC18
};
782069860236atagen2026-10-10 12:44 UTC19
782069860236atagen2026-10-10 12:44 UTC20
const MAX_URI: usize = 512;
782069860236atagen2026-10-10 12:44 UTC21
const MAX_HEADER: usize = 1024;
8b8e83329a1datagen2026-10-12 12:01 UTC22
const MAX_HEADERS: usize = 64;
8b8e83329a1datagen2026-10-12 12:01 UTC23
/// Responses are re-read to be translated or gzipped. The listings are paged, so none comes close.
8b8e83329a1datagen2026-10-12 12:01 UTC24
const BUFFER_LIMIT: usize = 1 << 20;
782069860236atagen2026-10-10 12:44 UTC25
782069860236atagen2026-10-10 12:44 UTC26
/// Marks a response for gzip, which the cock ring asks for.
782069860236atagen2026-10-10 12:44 UTC27
#[derive(Clone)]
782069860236atagen2026-10-10 12:44 UTC28
pub struct Compress;
782069860236atagen2026-10-10 12:44 UTC29
782069860236atagen2026-10-10 12:44 UTC30
fn reject(request: &Request) -> Option<Problem> {
782069860236atagen2026-10-10 12:44 UTC31
    if request.version() < Version::HTTP_11 {
782069860236atagen2026-10-10 12:44 UTC32
        return Some(Problem::new(
782069860236atagen2026-10-10 12:44 UTC33
            StatusCode::HTTP_VERSION_NOT_SUPPORTED,
782069860236atagen2026-10-10 12:44 UTC34
            "pdf",
782069860236atagen2026-10-10 12:44 UTC35
            "HTTP/1.0 is the version the original jelqing PDF was written for. The server has read it.",
782069860236atagen2026-10-10 12:44 UTC36
        ));
782069860236atagen2026-10-10 12:44 UTC37
    }
782069860236atagen2026-10-10 12:44 UTC38
    if request.uri().to_string().len() > MAX_URI {
782069860236atagen2026-10-10 12:44 UTC39
        return Some(Problem::new(
782069860236atagen2026-10-10 12:44 UTC40
            StatusCode::URI_TOO_LONG,
782069860236atagen2026-10-10 12:44 UTC41
            "uri-too-long",
782069860236atagen2026-10-10 12:44 UTC42
            "The URI is longer than anything else here.",
782069860236atagen2026-10-10 12:44 UTC43
        ));
782069860236atagen2026-10-10 12:44 UTC44
    }
8b8e83329a1datagen2026-10-12 12:01 UTC45
    let headers = request.headers();
8b8e83329a1datagen2026-10-12 12:01 UTC46
    let oversized = headers
8b8e83329a1datagen2026-10-12 12:01 UTC47
        .iter()
8b8e83329a1datagen2026-10-12 12:01 UTC48
        .any(|(name, value)| name.as_str().len() + value.len() > MAX_HEADER);
8b8e83329a1datagen2026-10-12 12:01 UTC49
    if oversized || headers.len() > MAX_HEADERS {
782069860236atagen2026-10-10 12:44 UTC50
        return Some(Problem::new(
782069860236atagen2026-10-10 12:44 UTC51
            StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
782069860236atagen2026-10-10 12:44 UTC52
            "head-too-large",
782069860236atagen2026-10-10 12:44 UTC53
            "Head too large.",
782069860236atagen2026-10-10 12:44 UTC54
        ));
782069860236atagen2026-10-10 12:44 UTC55
    }
782069860236atagen2026-10-10 12:44 UTC56
    let expectation = request.headers().get(header::EXPECT)?;
782069860236atagen2026-10-10 12:44 UTC57
    if expectation.as_bytes().eq_ignore_ascii_case(b"100-continue") {
782069860236atagen2026-10-10 12:44 UTC58
        return None;
782069860236atagen2026-10-10 12:44 UTC59
    }
782069860236atagen2026-10-10 12:44 UTC60
    Some(Problem::new(
782069860236atagen2026-10-10 12:44 UTC61
        StatusCode::EXPECTATION_FAILED,
782069860236atagen2026-10-10 12:44 UTC62
        "expectations",
782069860236atagen2026-10-10 12:44 UTC63
        format!(
782069860236atagen2026-10-10 12:44 UTC64
            "Expect: {} cannot be met.",
782069860236atagen2026-10-10 12:44 UTC65
            expectation.to_str().unwrap_or("that")
782069860236atagen2026-10-10 12:44 UTC66
        ),
782069860236atagen2026-10-10 12:44 UTC67
    ))
782069860236atagen2026-10-10 12:44 UTC68
}
782069860236atagen2026-10-10 12:44 UTC69
782069860236atagen2026-10-10 12:44 UTC70
async fn compress(response: Response) -> Response {
ec6fd4bb5b60atagen2026-10-11 04:34 UTC71
    let compressible = response
ec6fd4bb5b60atagen2026-10-11 04:34 UTC72
        .headers()
ec6fd4bb5b60atagen2026-10-11 04:34 UTC73
        .get(header::CONTENT_TYPE)
ec6fd4bb5b60atagen2026-10-11 04:34 UTC74
        .and_then(|value| value.to_str().ok())
ec6fd4bb5b60atagen2026-10-11 04:34 UTC75
        .is_some_and(|type_| {
ec6fd4bb5b60atagen2026-10-11 04:34 UTC76
            type_.starts_with("application/json")
ec6fd4bb5b60atagen2026-10-11 04:34 UTC77
                || type_.starts_with("application/problem+json")
ec6fd4bb5b60atagen2026-10-11 04:34 UTC78
                || type_.starts_with("application/xml")
ec6fd4bb5b60atagen2026-10-11 04:34 UTC79
        });
ec6fd4bb5b60atagen2026-10-11 04:34 UTC80
    if !compressible {
ec6fd4bb5b60atagen2026-10-11 04:34 UTC81
        return response;
ec6fd4bb5b60atagen2026-10-11 04:34 UTC82
    }
782069860236atagen2026-10-10 12:44 UTC83
    let (mut parts, body) = response.into_parts();
8b8e83329a1datagen2026-10-12 12:01 UTC84
    let Ok(bytes) = axum::body::to_bytes(body, BUFFER_LIMIT).await else {
782069860236atagen2026-10-10 12:44 UTC85
        return Response::from_parts(parts, Body::empty());
782069860236atagen2026-10-10 12:44 UTC86
    };
782069860236atagen2026-10-10 12:44 UTC87
    let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
782069860236atagen2026-10-10 12:44 UTC88
    let gzipped = encoder.write_all(&bytes).and_then(|()| encoder.finish());
782069860236atagen2026-10-10 12:44 UTC89
    let Ok(gzipped) = gzipped else {
782069860236atagen2026-10-10 12:44 UTC90
        return Response::from_parts(parts, Body::from(bytes));
782069860236atagen2026-10-10 12:44 UTC91
    };
782069860236atagen2026-10-10 12:44 UTC92
    parts
782069860236atagen2026-10-10 12:44 UTC93
        .headers
782069860236atagen2026-10-10 12:44 UTC94
        .insert(header::CONTENT_ENCODING, HeaderValue::from_static("gzip"));
782069860236atagen2026-10-10 12:44 UTC95
    parts.headers.remove(header::CONTENT_LENGTH);
782069860236atagen2026-10-10 12:44 UTC96
    parts
782069860236atagen2026-10-10 12:44 UTC97
        .headers
782069860236atagen2026-10-10 12:44 UTC98
        .append(header::VARY, HeaderValue::from_static("Accept-Encoding"));
782069860236atagen2026-10-10 12:44 UTC99
    Response::from_parts(parts, Body::from(gzipped))
782069860236atagen2026-10-10 12:44 UTC100
}
782069860236atagen2026-10-10 12:44 UTC101
ef7d6dee5976atagen2026-10-10 13:23 UTC102
async fn localise(response: Response, locale: Locale) -> Response {
ef7d6dee5976atagen2026-10-10 13:23 UTC103
    if locale == Locale::British {
ef7d6dee5976atagen2026-10-10 13:23 UTC104
        return response;
ef7d6dee5976atagen2026-10-10 13:23 UTC105
    }
ef7d6dee5976atagen2026-10-10 13:23 UTC106
    let (mut parts, body) = response.into_parts();
ef7d6dee5976atagen2026-10-10 13:23 UTC107
    for name in [header::VIA, header::VARY, X_OBSERVED_BY] {
ef7d6dee5976atagen2026-10-10 13:23 UTC108
        let localised: Vec<HeaderValue> = parts
ef7d6dee5976atagen2026-10-10 13:23 UTC109
            .headers
ef7d6dee5976atagen2026-10-10 13:23 UTC110
            .get_all(&name)
ef7d6dee5976atagen2026-10-10 13:23 UTC111
            .iter()
ef7d6dee5976atagen2026-10-10 13:23 UTC112
            .filter_map(|v| v.to_str().ok())
ef7d6dee5976atagen2026-10-10 13:23 UTC113
            .filter_map(|v| HeaderValue::from_str(&locale.localise(v)).ok())
ef7d6dee5976atagen2026-10-10 13:23 UTC114
            .collect();
ef7d6dee5976atagen2026-10-10 13:23 UTC115
        if !localised.is_empty() {
ef7d6dee5976atagen2026-10-10 13:23 UTC116
            parts.headers.remove(&name);
ef7d6dee5976atagen2026-10-10 13:23 UTC117
            for value in localised {
ef7d6dee5976atagen2026-10-10 13:23 UTC118
                parts.headers.append(name.clone(), value);
ef7d6dee5976atagen2026-10-10 13:23 UTC119
            }
ef7d6dee5976atagen2026-10-10 13:23 UTC120
        }
ef7d6dee5976atagen2026-10-10 13:23 UTC121
    }
ef7d6dee5976atagen2026-10-10 13:23 UTC122
    let json = parts
ef7d6dee5976atagen2026-10-10 13:23 UTC123
        .headers
ef7d6dee5976atagen2026-10-10 13:23 UTC124
        .get(header::CONTENT_TYPE)
782069860236atagen2026-10-10 12:44 UTC125
        .and_then(|v| v.to_str().ok())
ef7d6dee5976atagen2026-10-10 13:23 UTC126
        .is_some_and(|ct| {
ef7d6dee5976atagen2026-10-10 13:23 UTC127
            ct.starts_with("application/json") || ct.starts_with("application/problem+json")
ef7d6dee5976atagen2026-10-10 13:23 UTC128
        });
ef7d6dee5976atagen2026-10-10 13:23 UTC129
    if !json || parts.status == StatusCode::PARTIAL_CONTENT {
ef7d6dee5976atagen2026-10-10 13:23 UTC130
        return Response::from_parts(parts, body);
ef7d6dee5976atagen2026-10-10 13:23 UTC131
    }
8b8e83329a1datagen2026-10-12 12:01 UTC132
    let Ok(bytes) = axum::body::to_bytes(body, BUFFER_LIMIT).await else {
ef7d6dee5976atagen2026-10-10 13:23 UTC133
        return Response::from_parts(parts, Body::empty());
ef7d6dee5976atagen2026-10-10 13:23 UTC134
    };
ef7d6dee5976atagen2026-10-10 13:23 UTC135
    let Ok(mut value) = serde_json::from_slice::<Value>(&bytes) else {
ef7d6dee5976atagen2026-10-10 13:23 UTC136
        return Response::from_parts(parts, Body::from(bytes));
ef7d6dee5976atagen2026-10-10 13:23 UTC137
    };
ef7d6dee5976atagen2026-10-10 13:23 UTC138
    locale.localise_json(&mut value);
ef7d6dee5976atagen2026-10-10 13:23 UTC139
    parts.headers.remove(header::CONTENT_LENGTH);
ef7d6dee5976atagen2026-10-10 13:23 UTC140
    Response::from_parts(parts, Body::from(value.to_string()))
ef7d6dee5976atagen2026-10-10 13:23 UTC141
}
ef7d6dee5976atagen2026-10-10 13:23 UTC142
ef7d6dee5976atagen2026-10-10 13:23 UTC143
pub async fn chrome(request: Request, next: Next) -> Response {
ef7d6dee5976atagen2026-10-10 13:23 UTC144
    let (gzip, locale) = {
ef7d6dee5976atagen2026-10-10 13:23 UTC145
        let header_text = |name| {
ef7d6dee5976atagen2026-10-10 13:23 UTC146
            request
ef7d6dee5976atagen2026-10-10 13:23 UTC147
                .headers()
ef7d6dee5976atagen2026-10-10 13:23 UTC148
                .get(name)
ef7d6dee5976atagen2026-10-10 13:23 UTC149
                .and_then(|v| v.to_str().ok())
ef7d6dee5976atagen2026-10-10 13:23 UTC150
                .map(str::to_owned)
ef7d6dee5976atagen2026-10-10 13:23 UTC151
        };
ef7d6dee5976atagen2026-10-10 13:23 UTC152
        let gzip = header_text(header::ACCEPT_ENCODING)
ef7d6dee5976atagen2026-10-10 13:23 UTC153
            .is_some_and(|e| e.to_ascii_lowercase().contains("gzip"));
ef7d6dee5976atagen2026-10-10 13:23 UTC154
        let locale = Locale::negotiate(header_text(header::ACCEPT_LANGUAGE).as_deref());
ef7d6dee5976atagen2026-10-10 13:23 UTC155
        (gzip, locale)
ef7d6dee5976atagen2026-10-10 13:23 UTC156
    };
ef7d6dee5976atagen2026-10-10 13:23 UTC157
    let response = match reject(&request) {
782069860236atagen2026-10-10 12:44 UTC158
        Some(problem) => problem.into_response(),
782069860236atagen2026-10-10 12:44 UTC159
        None => next.run(request).await,
782069860236atagen2026-10-10 12:44 UTC160
    };
ef7d6dee5976atagen2026-10-10 13:23 UTC161
    let mut response = localise(response, locale).await;
782069860236atagen2026-10-10 12:44 UTC162
    if gzip && response.extensions().get::<Compress>().is_some() {
782069860236atagen2026-10-10 12:44 UTC163
        response = compress(response).await;
782069860236atagen2026-10-10 12:44 UTC164
    }
782069860236atagen2026-10-10 12:44 UTC165
    let headers = response.headers_mut();
782069860236atagen2026-10-10 12:44 UTC166
    headers.insert(header::SERVER, HeaderValue::from_static("jelqd/0.6.9"));
782069860236atagen2026-10-10 12:44 UTC167
    headers.insert(X_POWERED_BY, HeaderValue::from_static("blood"));
ef7d6dee5976atagen2026-10-10 13:23 UTC168
    headers.insert(
ef7d6dee5976atagen2026-10-10 13:23 UTC169
        header::CONTENT_LANGUAGE,
ef7d6dee5976atagen2026-10-10 13:23 UTC170
        HeaderValue::from_static(locale.tag()),
ef7d6dee5976atagen2026-10-10 13:23 UTC171
    );
ef7d6dee5976atagen2026-10-10 13:23 UTC172
    headers.append(header::VARY, HeaderValue::from_static("Accept-Language"));
782069860236atagen2026-10-10 12:44 UTC173
    headers
782069860236atagen2026-10-10 12:44 UTC174
        .entry(header::CACHE_CONTROL)
782069860236atagen2026-10-10 12:44 UTC175
        .or_insert(HeaderValue::from_static("no-store, private"));
782069860236atagen2026-10-10 12:44 UTC176
    response
782069860236atagen2026-10-10 12:44 UTC177
}
7288fc269bb6atagen2026-10-10 13:36 UTC178
7288fc269bb6atagen2026-10-10 13:36 UTC179
/// A handler panic would otherwise drop the connection with no reply at all.
7288fc269bb6atagen2026-10-10 13:36 UTC180
pub fn panicked(_: Box<dyn std::any::Any + Send + 'static>) -> Response {
7288fc269bb6atagen2026-10-10 13:36 UTC181
    Problem::new(
7288fc269bb6atagen2026-10-10 13:36 UTC182
        StatusCode::INTERNAL_SERVER_ERROR,
7288fc269bb6atagen2026-10-10 13:36 UTC183
        "server-fault",
7288fc269bb6atagen2026-10-10 13:36 UTC184
        "The server has had an internal event of its own. It is not discussing it.",
7288fc269bb6atagen2026-10-10 13:36 UTC185
    )
7288fc269bb6atagen2026-10-10 13:36 UTC186
    .into_response()
7288fc269bb6atagen2026-10-10 13:36 UTC187
}