Skip to main content

fileloft_core/
util.rs

1use http::HeaderMap;
2
3use crate::config::Config;
4use crate::error::TusError;
5use crate::proto::{HDR_TUS_RESUMABLE, TUS_VERSION};
6
7/// Verify the `Tus-Resumable` header is present and matches our version.
8pub fn check_tus_resumable(headers: &HeaderMap) -> Result<(), TusError> {
9    match headers.get(HDR_TUS_RESUMABLE) {
10        None => Err(TusError::MissingTusResumable),
11        Some(v) => {
12            let s = v.to_str().unwrap_or("");
13            if s == TUS_VERSION {
14                Ok(())
15            } else {
16                Err(TusError::UnsupportedVersion {
17                    version: s.to_string(),
18                })
19            }
20        }
21    }
22}
23
24/// Parse `Upload-Offset` from headers. Returns `Err` if missing or not a valid u64.
25pub fn parse_upload_offset(headers: &HeaderMap) -> Result<u64, TusError> {
26    headers
27        .get(crate::proto::HDR_UPLOAD_OFFSET)
28        .ok_or(TusError::MissingUploadOffset)?
29        .to_str()
30        .ok()
31        .and_then(|s| s.parse::<u64>().ok())
32        .ok_or(TusError::MissingUploadOffset)
33}
34
35/// Parse `Upload-Length` from headers. Returns `None` if absent.
36pub fn parse_upload_length(headers: &HeaderMap) -> Result<Option<u64>, TusError> {
37    match headers.get(crate::proto::HDR_UPLOAD_LENGTH) {
38        None => Ok(None),
39        Some(v) => v
40            .to_str()
41            .ok()
42            .and_then(|s| s.parse::<u64>().ok())
43            .map(Some)
44            .ok_or_else(|| TusError::InvalidMetadata("invalid Upload-Length value".into())),
45    }
46}
47
48/// Returns `true` if the request has `Upload-Defer-Length: 1`.
49pub fn has_defer_length(headers: &HeaderMap) -> bool {
50    headers
51        .get(crate::proto::HDR_UPLOAD_DEFER_LENGTH)
52        .and_then(|v| v.to_str().ok())
53        .map(|s| s.trim() == "1")
54        .unwrap_or(false)
55}
56
57/// Build a header value from a `u64` (decimal digits are always valid in HTTP header values).
58pub fn u64_header(n: u64) -> http::HeaderValue {
59    let s = n.to_string();
60    http::HeaderValue::try_from(s.as_str()).unwrap_or_else(|_| http::HeaderValue::from_static("0"))
61}
62
63/// Absolute origin for `Location` (scheme + host, no path) when `base_url` is unset.
64pub(crate) fn request_base_url(config: &Config, headers: &HeaderMap) -> String {
65    if let Some(ref base) = config.base_url {
66        return base.trim_end_matches('/').to_string();
67    }
68    let scheme = if config.trust_forwarded_headers {
69        headers
70            .get("x-forwarded-proto")
71            .and_then(|v| v.to_str().ok())
72            .and_then(|s| s.split(',').next().map(str::trim))
73            .filter(|s| !s.is_empty())
74            .unwrap_or("http")
75    } else {
76        "http"
77    };
78    let host = if config.trust_forwarded_headers {
79        headers
80            .get("x-forwarded-host")
81            .or_else(|| headers.get("host"))
82            .and_then(|v| v.to_str().ok())
83            .map(|s| s.split(',').next().unwrap_or(s).trim())
84            .filter(|s| !s.is_empty())
85            .unwrap_or("localhost")
86    } else {
87        headers
88            .get("host")
89            .and_then(|v| v.to_str().ok())
90            .unwrap_or("localhost")
91    };
92    format!("{scheme}://{host}")
93}
94
95/// Build a header value from a static string.
96pub fn static_header(s: &'static str) -> http::HeaderValue {
97    http::HeaderValue::from_static(s)
98}