v0.24.5
This commit is contained in:
commit
5e23e9af02
240 files changed
+99957
No files matched your search
Vendored
+184
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user