atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
e76ab808e7ad49d0b6fccd17728da137daacdb8c
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 {
782069860236atagen2026-10-10 12:44 UTC64
    let (mut parts, body) = response.into_parts();
782069860236atagen2026-10-10 12:44 UTC65
    let Ok(bytes) = axum::body::to_bytes(body, usize::MAX).await else {
782069860236atagen2026-10-10 12:44 UTC66
        return Response::from_parts(parts, Body::empty());
782069860236atagen2026-10-10 12:44 UTC67
    };
782069860236atagen2026-10-10 12:44 UTC68
    let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
782069860236atagen2026-10-10 12:44 UTC69
    let gzipped = encoder.write_all(&bytes).and_then(|()| encoder.finish());
782069860236atagen2026-10-10 12:44 UTC70
    let Ok(gzipped) = gzipped else {
782069860236atagen2026-10-10 12:44 UTC71
        return Response::from_parts(parts, Body::from(bytes));
782069860236atagen2026-10-10 12:44 UTC72
    };
782069860236atagen2026-10-10 12:44 UTC73
    parts
782069860236atagen2026-10-10 12:44 UTC74
        .headers
782069860236atagen2026-10-10 12:44 UTC75
        .insert(header::CONTENT_ENCODING, HeaderValue::from_static("gzip"));
782069860236atagen2026-10-10 12:44 UTC76
    parts.headers.remove(header::CONTENT_LENGTH);
782069860236atagen2026-10-10 12:44 UTC77
    parts
782069860236atagen2026-10-10 12:44 UTC78
        .headers
782069860236atagen2026-10-10 12:44 UTC79
        .append(header::VARY, HeaderValue::from_static("Accept-Encoding"));
782069860236atagen2026-10-10 12:44 UTC80
    Response::from_parts(parts, Body::from(gzipped))
782069860236atagen2026-10-10 12:44 UTC81
}
782069860236atagen2026-10-10 12:44 UTC82
ef7d6dee5976atagen2026-10-10 13:23 UTC83
async fn localise(response: Response, locale: Locale) -> Response {
ef7d6dee5976atagen2026-10-10 13:23 UTC84
    if locale == Locale::British {
ef7d6dee5976atagen2026-10-10 13:23 UTC85
        return response;
ef7d6dee5976atagen2026-10-10 13:23 UTC86
    }
ef7d6dee5976atagen2026-10-10 13:23 UTC87
    let (mut parts, body) = response.into_parts();
ef7d6dee5976atagen2026-10-10 13:23 UTC88
    for name in [header::VIA, header::VARY, X_OBSERVED_BY] {
ef7d6dee5976atagen2026-10-10 13:23 UTC89
        let localised: Vec<HeaderValue> = parts
ef7d6dee5976atagen2026-10-10 13:23 UTC90
            .headers
ef7d6dee5976atagen2026-10-10 13:23 UTC91
            .get_all(&name)
ef7d6dee5976atagen2026-10-10 13:23 UTC92
            .iter()
ef7d6dee5976atagen2026-10-10 13:23 UTC93
            .filter_map(|v| v.to_str().ok())
ef7d6dee5976atagen2026-10-10 13:23 UTC94
            .filter_map(|v| HeaderValue::from_str(&locale.localise(v)).ok())
ef7d6dee5976atagen2026-10-10 13:23 UTC95
            .collect();
ef7d6dee5976atagen2026-10-10 13:23 UTC96
        if !localised.is_empty() {
ef7d6dee5976atagen2026-10-10 13:23 UTC97
            parts.headers.remove(&name);
ef7d6dee5976atagen2026-10-10 13:23 UTC98
            for value in localised {
ef7d6dee5976atagen2026-10-10 13:23 UTC99
                parts.headers.append(name.clone(), value);
ef7d6dee5976atagen2026-10-10 13:23 UTC100
            }
ef7d6dee5976atagen2026-10-10 13:23 UTC101
        }
ef7d6dee5976atagen2026-10-10 13:23 UTC102
    }
ef7d6dee5976atagen2026-10-10 13:23 UTC103
    let json = parts
ef7d6dee5976atagen2026-10-10 13:23 UTC104
        .headers
ef7d6dee5976atagen2026-10-10 13:23 UTC105
        .get(header::CONTENT_TYPE)
782069860236atagen2026-10-10 12:44 UTC106
        .and_then(|v| v.to_str().ok())
ef7d6dee5976atagen2026-10-10 13:23 UTC107
        .is_some_and(|ct| {
ef7d6dee5976atagen2026-10-10 13:23 UTC108
            ct.starts_with("application/json") || ct.starts_with("application/problem+json")
ef7d6dee5976atagen2026-10-10 13:23 UTC109
        });
ef7d6dee5976atagen2026-10-10 13:23 UTC110
    if !json || parts.status == StatusCode::PARTIAL_CONTENT {
ef7d6dee5976atagen2026-10-10 13:23 UTC111
        return Response::from_parts(parts, body);
ef7d6dee5976atagen2026-10-10 13:23 UTC112
    }
ef7d6dee5976atagen2026-10-10 13:23 UTC113
    let Ok(bytes) = axum::body::to_bytes(body, usize::MAX).await else {
ef7d6dee5976atagen2026-10-10 13:23 UTC114
        return Response::from_parts(parts, Body::empty());
ef7d6dee5976atagen2026-10-10 13:23 UTC115
    };
ef7d6dee5976atagen2026-10-10 13:23 UTC116
    let Ok(mut value) = serde_json::from_slice::<Value>(&bytes) else {
ef7d6dee5976atagen2026-10-10 13:23 UTC117
        return Response::from_parts(parts, Body::from(bytes));
ef7d6dee5976atagen2026-10-10 13:23 UTC118
    };
ef7d6dee5976atagen2026-10-10 13:23 UTC119
    locale.localise_json(&mut value);
ef7d6dee5976atagen2026-10-10 13:23 UTC120
    parts.headers.remove(header::CONTENT_LENGTH);
ef7d6dee5976atagen2026-10-10 13:23 UTC121
    Response::from_parts(parts, Body::from(value.to_string()))
ef7d6dee5976atagen2026-10-10 13:23 UTC122
}
ef7d6dee5976atagen2026-10-10 13:23 UTC123
ef7d6dee5976atagen2026-10-10 13:23 UTC124
pub async fn chrome(request: Request, next: Next) -> Response {
ef7d6dee5976atagen2026-10-10 13:23 UTC125
    let (gzip, locale) = {
ef7d6dee5976atagen2026-10-10 13:23 UTC126
        let header_text = |name| {
ef7d6dee5976atagen2026-10-10 13:23 UTC127
            request
ef7d6dee5976atagen2026-10-10 13:23 UTC128
                .headers()
ef7d6dee5976atagen2026-10-10 13:23 UTC129
                .get(name)
ef7d6dee5976atagen2026-10-10 13:23 UTC130
                .and_then(|v| v.to_str().ok())
ef7d6dee5976atagen2026-10-10 13:23 UTC131
                .map(str::to_owned)
ef7d6dee5976atagen2026-10-10 13:23 UTC132
        };
ef7d6dee5976atagen2026-10-10 13:23 UTC133
        let gzip = header_text(header::ACCEPT_ENCODING)
ef7d6dee5976atagen2026-10-10 13:23 UTC134
            .is_some_and(|e| e.to_ascii_lowercase().contains("gzip"));
ef7d6dee5976atagen2026-10-10 13:23 UTC135
        let locale = Locale::negotiate(header_text(header::ACCEPT_LANGUAGE).as_deref());
ef7d6dee5976atagen2026-10-10 13:23 UTC136
        (gzip, locale)
ef7d6dee5976atagen2026-10-10 13:23 UTC137
    };
ef7d6dee5976atagen2026-10-10 13:23 UTC138
    let response = match reject(&request) {
782069860236atagen2026-10-10 12:44 UTC139
        Some(problem) => problem.into_response(),
782069860236atagen2026-10-10 12:44 UTC140
        None => next.run(request).await,
782069860236atagen2026-10-10 12:44 UTC141
    };
ef7d6dee5976atagen2026-10-10 13:23 UTC142
    let mut response = localise(response, locale).await;
782069860236atagen2026-10-10 12:44 UTC143
    if gzip && response.extensions().get::<Compress>().is_some() {
782069860236atagen2026-10-10 12:44 UTC144
        response = compress(response).await;
782069860236atagen2026-10-10 12:44 UTC145
    }
782069860236atagen2026-10-10 12:44 UTC146
    let headers = response.headers_mut();
782069860236atagen2026-10-10 12:44 UTC147
    headers.insert(header::SERVER, HeaderValue::from_static("jelqd/0.6.9"));
782069860236atagen2026-10-10 12:44 UTC148
    headers.insert(X_POWERED_BY, HeaderValue::from_static("blood"));
ef7d6dee5976atagen2026-10-10 13:23 UTC149
    headers.insert(
ef7d6dee5976atagen2026-10-10 13:23 UTC150
        header::CONTENT_LANGUAGE,
ef7d6dee5976atagen2026-10-10 13:23 UTC151
        HeaderValue::from_static(locale.tag()),
ef7d6dee5976atagen2026-10-10 13:23 UTC152
    );
ef7d6dee5976atagen2026-10-10 13:23 UTC153
    headers.append(header::VARY, HeaderValue::from_static("Accept-Language"));
782069860236atagen2026-10-10 12:44 UTC154
    headers
782069860236atagen2026-10-10 12:44 UTC155
        .entry(header::CACHE_CONTROL)
782069860236atagen2026-10-10 12:44 UTC156
        .or_insert(HeaderValue::from_static("no-store, private"));
782069860236atagen2026-10-10 12:44 UTC157
    response
782069860236atagen2026-10-10 12:44 UTC158
}
7288fc269bb6atagen2026-10-10 13:36 UTC159
7288fc269bb6atagen2026-10-10 13:36 UTC160
/// A handler panic would otherwise drop the connection with no reply at all.
7288fc269bb6atagen2026-10-10 13:36 UTC161
pub fn panicked(_: Box<dyn std::any::Any + Send + 'static>) -> Response {
7288fc269bb6atagen2026-10-10 13:36 UTC162
    Problem::new(
7288fc269bb6atagen2026-10-10 13:36 UTC163
        StatusCode::INTERNAL_SERVER_ERROR,
7288fc269bb6atagen2026-10-10 13:36 UTC164
        "server-fault",
7288fc269bb6atagen2026-10-10 13:36 UTC165
        "The server has had an internal event of its own. It is not discussing it.",
7288fc269bb6atagen2026-10-10 13:36 UTC166
    )
7288fc269bb6atagen2026-10-10 13:36 UTC167
    .into_response()
7288fc269bb6atagen2026-10-10 13:36 UTC168
}