This commit is contained in:
gru committed 2026-10-03 00:27:09 +01:00
commit 5e23e9af02
240 files changed
+99957

No files matched your search

+184
View File
@@ -0,0 +1,184 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ByteRange {
start: Option<u64>,
end: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RangeSelection {
None,
Single(ByteRange),
Unsupported,
}
pub fn request_range(method: &Method, headers: &HeaderMap) -> RangeSelection {
let mut values = headers.get_all("range").iter();
let Some(value) = values.next() else {
return RangeSelection::None;
};
// Multiple field lines are equivalent to a combined range-set; RustProxy
// intentionally delegates those to the origin with multipart ranges.
if values.next().is_some() || headers.get_all("if-range").iter().count() > 1 {
return RangeSelection::Unsupported;
}
// RustProxy serves one byte range itself. HEAD, unknown units, malformed
// syntax and multipart requests remain transparent origin pass-through.
if method != Method::GET {
return RangeSelection::Unsupported;
}
let Ok(value) = value.to_str() else {
return RangeSelection::Unsupported;
};
let Some(spec) = value.trim().strip_prefix("bytes=") else {
return RangeSelection::Unsupported;
};
if spec.is_empty() || spec.contains(',') {
return RangeSelection::Unsupported;
}
let Some((left, right)) = spec.split_once('-') else {
return RangeSelection::Unsupported;
};
let start = if left.trim().is_empty() {
None
} else {
match left.trim().parse::<u64>() {
Ok(value) => Some(value),
Err(_) => return RangeSelection::Unsupported,
}
};
let end = if right.trim().is_empty() {
None
} else {
match right.trim().parse::<u64>() {
Ok(value) => Some(value),
Err(_) => return RangeSelection::Unsupported,
}
};
if start.is_none() && end.is_none() {
return RangeSelection::Unsupported;
}
if let (Some(start), Some(end)) = (start, end) {
if start > end {
return RangeSelection::Unsupported;
}
}
RangeSelection::Single(ByteRange { start, end })
}
fn if_range_matches(headers: &HeaderMap, if_range: Option<&HeaderValue>) -> bool {
let Some(if_range) = if_range else {
return true;
};
let Ok(candidate) = if_range.to_str() else {
return false;
};
let candidate = candidate.trim();
if candidate.starts_with('"') || candidate.starts_with("W/\"") {
if candidate.starts_with("W/") {
return false;
}
return headers
.get("etag")
.and_then(|value| value.to_str().ok())
.is_some_and(|etag| !etag.trim().starts_with("W/") && etag.trim() == candidate);
}
let Some(candidate_date) = httpdate::parse_http_date(candidate).ok() else {
return false;
};
headers
.get("last-modified")
.and_then(|value| value.to_str().ok())
.and_then(|value| httpdate::parse_http_date(value).ok())
.is_some_and(|modified| modified <= candidate_date)
}
fn resolve_byte_range(range: ByteRange, len: usize) -> Option<(usize, usize)> {
if len == 0 {
return None;
}
let len_u64 = len as u64;
match (range.start, range.end) {
(Some(start), Some(end)) => {
if start >= len_u64 {
return None;
}
let end = end.min(len_u64.saturating_sub(1));
Some((start as usize, end as usize))
}
(Some(start), None) => {
if start >= len_u64 {
None
} else {
Some((start as usize, len - 1))
}
}
(None, Some(suffix)) => {
if suffix == 0 {
return None;
}
let take = suffix.min(len_u64) as usize;
Some((len - take, len - 1))
}
(None, None) => None,
}
}
impl Hit {
pub fn apply_range(mut self, range: ByteRange, if_range: Option<&HeaderValue>) -> Self {
if !if_range_matches(self.response.headers(), if_range) {
return self;
}
let full_len = self.body.len();
self.response
.headers_mut()
.insert("accept-ranges", HeaderValue::from_static("bytes"));
self.response.extensions_mut().insert(crate::compression::Skip);
let Some((start, end)) = resolve_byte_range(range, full_len) else {
*self.response.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE;
*self.response.body_mut() = Body::empty();
self.response.headers_mut().insert(
"content-range",
HeaderValue::from_str(&format!("bytes */{full_len}")).expect("generated range"),
);
self.response
.headers_mut()
.insert("content-length", HeaderValue::from_static("0"));
return self;
};
let slice = self.body.slice(start..=end);
*self.response.status_mut() = StatusCode::PARTIAL_CONTENT;
*self.response.body_mut() = Body::from(slice.clone());
self.response.headers_mut().insert(
"content-range",
HeaderValue::from_str(&format!("bytes {start}-{end}/{full_len}"))
.expect("generated range"),
);
self.response.headers_mut().insert(
"content-length",
HeaderValue::from_str(&slice.len().to_string()).expect("generated length"),
);
self.body = slice;
self
}
}
impl Revalidation {
pub fn response_with_range(
&self,
headers: HeaderMap,
range: ByteRange,
if_range: Option<&HeaderValue>,
) -> Response {
let hit = Hit {
response: self.response(headers),
body: self.body.clone(),
hits: self.hits,
transformed: self.transformed,
remaining_ttl: self.ttl.saturating_sub(self.age),
stale: false,
background_revalidate: false,
resident_age_seconds: self.resident_age_at_lookup.as_secs(),
};
hit.apply_range(range, if_range).response
}
}