Skip to main content

koan_server/
origin.rs

1//! The address clients reach this server at, for links that leave it.
2
3use axum::http::{HeaderMap, header};
4
5/// `sharing.public_url` when set; otherwise the Host the request came in on,
6/// with the scheme a proxy in front says it terminated. koan itself serves
7/// plain HTTP, so without a forwarded scheme the request was plain HTTP.
8pub fn origin(headers: &HeaderMap, public_url: Option<&str>) -> Option<String> {
9    if let Some(url) = public_url.map(str::trim).filter(|u| !u.is_empty()) {
10        return Some(url.trim_end_matches('/').to_owned());
11    }
12    let value = |name: &str| headers.get(name).and_then(|v| v.to_str().ok());
13    let host = value("x-forwarded-host")
14        .or_else(|| value(header::HOST.as_str()))?
15        .split(',')
16        .next()?
17        .trim();
18    let scheme = value("x-forwarded-proto")
19        .and_then(|p| p.split(',').next())
20        .map(str::trim)
21        .filter(|p| matches!(*p, "http" | "https"))
22        .unwrap_or("http");
23    (!host.is_empty()).then(|| format!("{scheme}://{host}"))
24}
25
26#[cfg(test)]
27mod tests {
28    use super::*;
29
30    fn headers(pairs: &[(&'static str, &'static str)]) -> HeaderMap {
31        let mut h = HeaderMap::new();
32        for (k, v) in pairs {
33            h.insert(*k, v.parse().unwrap());
34        }
35        h
36    }
37
38    #[test]
39    fn the_configured_address_wins() {
40        let h = headers(&[("host", "10.0.0.2:4000")]);
41        assert_eq!(
42            origin(&h, Some("https://music.example.com/")).as_deref(),
43            Some("https://music.example.com")
44        );
45    }
46
47    #[test]
48    fn a_proxy_supplies_the_scheme_and_host() {
49        let h = headers(&[
50            ("host", "koan:4000"),
51            ("x-forwarded-host", "music.example.com"),
52            ("x-forwarded-proto", "https"),
53        ]);
54        assert_eq!(
55            origin(&h, None).as_deref(),
56            Some("https://music.example.com")
57        );
58    }
59
60    #[test]
61    fn a_direct_request_is_plain_http() {
62        let h = headers(&[("host", "192.168.1.5:4000")]);
63        assert_eq!(
64            origin(&h, Some("")).as_deref(),
65            Some("http://192.168.1.5:4000")
66        );
67        assert_eq!(origin(&HeaderMap::new(), None), None);
68    }
69}