use crate::auth::{www_authenticate, AccessPaths, AccessPerm};
use crate::http_utils::{body_full, IncomingStream, LengthLimitedStream};
use crate::utils::{
- decode_uri, encode_uri, get_file_mtime_and_mode, get_file_name, glob, try_get_file_name,
+ decode_uri, encode_uri, get_file_mtime_and_mode, get_file_name, glob, parse_range,
+ try_get_file_name,
};
use crate::Args;
) -> Result<()> {
let (file, meta) = tokio::join!(fs::File::open(path), fs::metadata(path),);
let (mut file, meta) = (file?, meta?);
+ let size = meta.len();
let mut use_range = true;
if let Some((etag, last_modified)) = extract_cache_headers(&meta) {
let cached = {
}
let range = if use_range {
- parse_range(headers)
+ headers.get(RANGE).map(|range| {
+ range
+ .to_str()
+ .ok()
+ .and_then(|range| parse_range(range, size))
+ })
} else {
None
};
res.headers_mut().typed_insert(AcceptRanges::bytes());
- let size = meta.len();
-
if let Some(range) = range {
- if range
- .end
- .map_or_else(|| range.start < size, |v| v >= range.start)
- && file.seek(SeekFrom::Start(range.start)).await.is_ok()
- {
- let end = range.end.unwrap_or(size - 1).min(size - 1);
- let range_size = end - range.start + 1;
+ if let Some((start, end)) = range {
+ file.seek(SeekFrom::Start(start)).await?;
+ let range_size = end - start + 1;
*res.status_mut() = StatusCode::PARTIAL_CONTENT;
- let content_range = format!("bytes {}-{}/{}", range.start, end, size);
+ let content_range = format!("bytes {}-{}/{}", start, end, size);
res.headers_mut()
.insert(CONTENT_RANGE, content_range.parse()?);
res.headers_mut()
Some((etag, last_modified))
}
-#[derive(Debug)]
-struct RangeValue {
- start: u64,
- end: Option<u64>,
-}
-
-fn parse_range(headers: &HeaderMap<HeaderValue>) -> Option<RangeValue> {
- let range_hdr = headers.get(RANGE)?;
- let hdr = range_hdr.to_str().ok()?;
- let mut sp = hdr.splitn(2, '=');
- let units = sp.next()?;
- if units == "bytes" {
- let range = sp.next()?;
- let mut sp_range = range.splitn(2, '-');
- let start: u64 = sp_range.next()?.parse().ok()?;
- let end: Option<u64> = if let Some(end) = sp_range.next() {
- if end.is_empty() {
- None
- } else {
- Some(end.parse().ok()?)
- }
- } else {
- None
- };
- Some(RangeValue { start, end })
- } else {
- None
- }
-}
-
fn status_forbid(res: &mut Response) {
*res.status_mut() = StatusCode::FORBIDDEN;
*res.body_mut() = body_full("Forbidden");
anyhow::bail!("No supported private key in file");
}
-#[test]
-fn test_glob_key() {
- assert!(glob("", ""));
- assert!(glob(".*", ".git"));
- assert!(glob("abc", "abc"));
- assert!(glob("a*c", "abc"));
- assert!(glob("a?c", "abc"));
- assert!(glob("a*c", "abbc"));
- assert!(glob("*c", "abc"));
- assert!(glob("a*", "abc"));
- assert!(glob("?c", "bc"));
- assert!(glob("a?", "ab"));
- assert!(!glob("abc", "adc"));
- assert!(!glob("abc", "abcd"));
- assert!(!glob("a?c", "abbc"));
- assert!(!glob("*.log", "log"));
- assert!(glob("*.abc-cba", "xyz.abc-cba"));
- assert!(glob("*.abc-cba", "123.xyz.abc-cba"));
- assert!(glob("*.log", ".log"));
- assert!(glob("*.log", "a.log"));
- assert!(glob("*/", "abc/"));
- assert!(!glob("*/", "abc"));
+pub fn parse_range(range: &str, size: u64) -> Option<(u64, u64)> {
+ let (unit, range) = range.split_once('=')?;
+ if unit != "bytes" || range.contains(',') {
+ return None;
+ }
+ let (start, end) = range.split_once('-')?;
+ if start.is_empty() {
+ let offset = end.parse::<u64>().ok()?;
+ if offset <= size {
+ Some((size - offset, size - 1))
+ } else {
+ None
+ }
+ } else {
+ let start = start.parse::<u64>().ok()?;
+ if start < size {
+ if end.is_empty() {
+ Some((start, size - 1))
+ } else {
+ let end = end.parse::<u64>().ok()?;
+ if end < size {
+ Some((start, end))
+ } else {
+ None
+ }
+ }
+ } else {
+ None
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_glob_key() {
+ assert!(glob("", ""));
+ assert!(glob(".*", ".git"));
+ assert!(glob("abc", "abc"));
+ assert!(glob("a*c", "abc"));
+ assert!(glob("a?c", "abc"));
+ assert!(glob("a*c", "abbc"));
+ assert!(glob("*c", "abc"));
+ assert!(glob("a*", "abc"));
+ assert!(glob("?c", "bc"));
+ assert!(glob("a?", "ab"));
+ assert!(!glob("abc", "adc"));
+ assert!(!glob("abc", "abcd"));
+ assert!(!glob("a?c", "abbc"));
+ assert!(!glob("*.log", "log"));
+ assert!(glob("*.abc-cba", "xyz.abc-cba"));
+ assert!(glob("*.abc-cba", "123.xyz.abc-cba"));
+ assert!(glob("*.log", ".log"));
+ assert!(glob("*.log", "a.log"));
+ assert!(glob("*/", "abc/"));
+ assert!(!glob("*/", "abc"));
+ }
+
+ #[test]
+ fn test_parse_range() {
+ assert_eq!(parse_range("bytes=0-499", 500), Some((0, 499)));
+ assert_eq!(parse_range("bytes=0-", 500), Some((0, 499)));
+ assert_eq!(parse_range("bytes=299-", 500), Some((299, 499)));
+ assert_eq!(parse_range("bytes=-500", 500), Some((0, 499)));
+ assert_eq!(parse_range("bytes=-300", 500), Some((200, 499)));
+ assert_eq!(parse_range("bytes=500-", 500), None);
+ assert_eq!(parse_range("bytes=-501", 500), None);
+ assert_eq!(parse_range("bytes=0-500", 500), None);
+ }
}