}
}
- let range = if use_range {
+ let ranges = if use_range {
headers.get(RANGE).map(|range| {
range
.to_str()
res.headers_mut().typed_insert(AcceptRanges::bytes());
- if let Some(range) = range {
- 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 {}-{}/{}", start, end, size);
- res.headers_mut()
- .insert(CONTENT_RANGE, content_range.parse()?);
- res.headers_mut()
- .insert(CONTENT_LENGTH, format!("{range_size}").parse()?);
- if head_only {
- return Ok(());
- }
+ if let Some(ranges) = ranges {
+ if let Some(ranges) = ranges {
+ if ranges.len() == 1 {
+ let (start, end) = ranges[0];
+ file.seek(SeekFrom::Start(start)).await?;
+ let range_size = end - start + 1;
+ *res.status_mut() = StatusCode::PARTIAL_CONTENT;
+ let content_range = format!("bytes {}-{}/{}", start, end, size);
+ res.headers_mut()
+ .insert(CONTENT_RANGE, content_range.parse()?);
+ res.headers_mut()
+ .insert(CONTENT_LENGTH, format!("{range_size}").parse()?);
+ if head_only {
+ return Ok(());
+ }
- let stream_body = StreamBody::new(
- LengthLimitedStream::new(file, range_size as usize)
- .map_ok(Frame::data)
- .map_err(|err| anyhow!("{err}")),
- );
- let boxed_body = stream_body.boxed();
- *res.body_mut() = boxed_body;
+ let stream_body = StreamBody::new(
+ LengthLimitedStream::new(file, range_size as usize)
+ .map_ok(Frame::data)
+ .map_err(|err| anyhow!("{err}")),
+ );
+ let boxed_body = stream_body.boxed();
+ *res.body_mut() = boxed_body;
+ } else {
+ *res.status_mut() = StatusCode::PARTIAL_CONTENT;
+ let boundary = Uuid::new_v4();
+ let mut body = Vec::new();
+ let content_type = get_content_type(path).await?;
+ for (start, end) in ranges {
+ file.seek(SeekFrom::Start(start)).await?;
+ let range_size = end - start + 1;
+ let content_range = format!("bytes {}-{}/{}", start, end, size);
+ let part_header = format!(
+ "--{boundary}\r\nContent-Type: {content_type}\r\nContent-Range: {content_range}\r\n\r\n",
+ );
+ body.extend_from_slice(part_header.as_bytes());
+ let mut buffer = vec![0; range_size as usize];
+ file.read_exact(&mut buffer).await?;
+ body.extend_from_slice(&buffer);
+ body.extend_from_slice(b"\r\n");
+ }
+ body.extend_from_slice(format!("--{boundary}--\r\n").as_bytes());
+ res.headers_mut().insert(
+ CONTENT_TYPE,
+ format!("multipart/byteranges; boundary={boundary}").parse()?,
+ );
+ res.headers_mut()
+ .insert(CONTENT_LENGTH, format!("{}", body.len()).parse()?);
+ if head_only {
+ return Ok(());
+ }
+ *res.body_mut() = body_full(body);
+ }
} else {
*res.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE;
res.headers_mut()
if value == "append" {
return Ok(Some(size));
}
- let (start, _) = parse_range(value, size).ok_or_else(err)?;
- Ok(Some(start))
+ // use the first range
+ let ranges = parse_range(value, size).ok_or_else(err)?;
+ let (start, _) = ranges.first().ok_or_else(err)?;
+ Ok(Some(*start))
}
async fn sha256_file(path: &Path) -> Result<String> {
anyhow::bail!("No supported private key in file");
}
-pub fn parse_range(range: &str, size: u64) -> Option<(u64, u64)> {
- let (unit, range) = range.split_once('=')?;
- if unit != "bytes" || range.contains(',') {
+pub fn parse_range(range: &str, size: u64) -> Option<Vec<(u64, u64)>> {
+ let (unit, ranges) = range.split_once('=')?;
+ if unit != "bytes" {
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))
+
+ let mut result = Vec::new();
+ for range in ranges.split(',') {
+ let (start, end) = range.trim().split_once('-')?;
+ if start.is_empty() {
+ let offset = end.parse::<u64>().ok()?;
+ if offset <= size {
+ result.push((size - offset, size - 1));
} else {
- let end = end.parse::<u64>().ok()?;
- if end < size {
- Some((start, end))
+ return None;
+ }
+ } else {
+ let start = start.parse::<u64>().ok()?;
+ if start < size {
+ if end.is_empty() {
+ result.push((start, size - 1));
} else {
- None
+ let end = end.parse::<u64>().ok()?;
+ if end < size {
+ result.push((start, end));
+ } else {
+ return None;
+ }
}
+ } else {
+ return None;
}
- } else {
- None
}
}
+
+ Some(result)
}
#[cfg(test)]
#[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=0-499", 500), Some(vec![(0, 499)]));
+ assert_eq!(parse_range("bytes=0-", 500), Some(vec![(0, 499)]));
+ assert_eq!(parse_range("bytes=299-", 500), Some(vec![(299, 499)]));
+ assert_eq!(parse_range("bytes=-500", 500), Some(vec![(0, 499)]));
+ assert_eq!(parse_range("bytes=-300", 500), Some(vec![(200, 499)]));
+ assert_eq!(
+ parse_range("bytes=0-199, 100-399, 400-, -200", 500),
+ Some(vec![(0, 199), (100, 399), (400, 499), (300, 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);
+ assert_eq!(parse_range("bytes=0-199,", 500), None);
+ assert_eq!(parse_range("bytes=0-199, 500-", 500), None);
}
}
mod utils;
use fixtures::{server, Error, TestServer};
-use reqwest::header::HeaderValue;
+use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use rstest::rstest;
#[rstest]
assert_eq!(resp.headers().get("content-range").unwrap(), "bytes */18");
Ok(())
}
+
+fn parse_multipart_body<'a>(body: &'a str, boundary: &str) -> Vec<(HeaderMap, &'a str)> {
+ body.split(&format!("--{}", boundary))
+ .filter(|part| !part.is_empty() && *part != "--\r\n")
+ .map(|part| {
+ let (head, body) = part.trim_ascii().split_once("\r\n\r\n").unwrap();
+ let headers = head
+ .split("\r\n")
+ .fold(HeaderMap::new(), |mut headers, header| {
+ let (key, value) = header.split_once(":").unwrap();
+ let key = HeaderName::from_bytes(key.as_bytes()).unwrap();
+ let value = HeaderValue::from_str(value.trim_ascii_start()).unwrap();
+ headers.insert(key, value);
+ headers
+ });
+ (headers, body)
+ })
+ .collect()
+}
+
+#[rstest]
+fn get_file_multipart_range(server: TestServer) -> Result<(), Error> {
+ let resp = fetch!(b"GET", format!("{}index.html", server.url()))
+ .header("range", HeaderValue::from_static("bytes=0-11, 6-17"))
+ .send()?;
+ assert_eq!(resp.status(), 206);
+ assert_eq!(resp.headers().get("accept-ranges").unwrap(), "bytes");
+
+ let content_type = resp
+ .headers()
+ .get("content-type")
+ .unwrap()
+ .to_str()?
+ .to_string();
+ assert!(content_type.starts_with("multipart/byteranges; boundary="));
+
+ let boundary = content_type.split_once('=').unwrap().1.trim_ascii_start();
+ assert!(!boundary.is_empty());
+
+ let body = resp.text()?;
+ let parts = parse_multipart_body(&body, boundary);
+ assert_eq!(parts.len(), 2);
+
+ let (headers, body) = &parts[0];
+ assert_eq!(headers.get("content-range").unwrap(), "bytes 0-11/18");
+ assert_eq!(*body, "This is inde");
+
+ let (headers, body) = &parts[1];
+ assert_eq!(headers.get("content-range").unwrap(), "bytes 6-17/18");
+ assert_eq!(*body, "s index.html");
+
+ Ok(())
+}
+
+#[rstest]
+fn get_file_multipart_range_invalid(server: TestServer) -> Result<(), Error> {
+ let resp = fetch!(b"GET", format!("{}index.html", server.url()))
+ .header("range", HeaderValue::from_static("bytes=0-6, 20-30"))
+ .send()?;
+ assert_eq!(resp.status(), 416);
+ assert_eq!(resp.headers().get("content-range").unwrap(), "bytes */18");
+ assert_eq!(resp.headers().get("accept-ranges").unwrap(), "bytes");
+ assert_eq!(resp.headers().get("content-length").unwrap(), "0");
+ Ok(())
+}