atagen

jelq

public

an http based roguelike rpg about jelqing

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