1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use std::ops::Range;
4
5use bytes::Bytes;
6
7pub mod headers;
8
9use crate::headers::{
10 content_range::{Bound, HttpContentRange, Unsatisfiable},
11 range::HttpRange,
12};
13
14pub fn serve_file_with_http_range(
18 body: Bytes,
19 http_range: Option<HttpRange>,
20) -> Result<BodyRange<Bytes>, UnsatisfiableRange> {
21 let size = u64::try_from(body.len()).expect("we do not support 128bit usize");
22
23 let content_range = file_range(size, http_range)?;
24
25 let start = usize::try_from(content_range.range.start).expect("u64 doesn't fit usize");
26 let end = usize::try_from(content_range.range.end).expect("u64 doesn't fit usize");
27
28 Ok(BodyRange {
29 body: body.slice(start..end),
30 header: content_range.header,
31 })
32}
33
34pub fn file_range(
38 size: u64,
39 http_range: Option<HttpRange>,
40) -> Result<ContentRange, UnsatisfiableRange> {
41 let Some(http_range) = http_range else {
42 return Ok(ContentRange {
43 header: None,
44 range: 0..size,
45 });
46 };
47
48 let range = match http_range {
49 HttpRange::StartingPoint(start) if start < size => start..size,
50 HttpRange::Range(range) if range.start() < size => {
51 range.start()..range.end().saturating_add(1).min(size)
52 }
53 HttpRange::Suffix(suffix) if suffix > 0 && size > 0 => size.saturating_sub(suffix)..size,
54 HttpRange::Suffix(suffix) if suffix > 0 => {
60 return Ok(ContentRange {
61 header: None,
62 range: 0..size,
63 });
64 }
65 _ => {
66 let content_range = HttpContentRange::Unsatisfiable(Unsatisfiable::new(size));
67 return Err(UnsatisfiableRange(content_range));
68 }
69 };
70
71 let content_range =
72 HttpContentRange::Bound(Bound::new(range.start..=range.end - 1, Some(size)).unwrap());
73
74 Ok(ContentRange {
75 header: Some(content_range),
76 range,
77 })
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct BodyRange<T> {
87 body: T,
88 header: Option<HttpContentRange>,
89}
90
91impl<T> BodyRange<T> {
92 pub fn body(&self) -> &T {
94 &self.body
95 }
96
97 pub fn into_body(self) -> T {
98 self.body
99 }
100
101 pub fn header(&self) -> Option<HttpContentRange> {
106 self.header
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct ContentRange {
115 header: Option<HttpContentRange>,
116 range: Range<u64>,
117}
118
119impl ContentRange {
120 pub fn header(&self) -> Option<HttpContentRange> {
125 self.header
126 }
127
128 pub fn range(&self) -> &Range<u64> {
130 &self.range
131 }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct UnsatisfiableRange(HttpContentRange);
139
140impl UnsatisfiableRange {
141 pub fn header(&self) -> HttpContentRange {
143 self.0
144 }
145}
146
147#[cfg(feature = "axum")]
148mod axum {
149 use crate::{BodyRange, UnsatisfiableRange};
150
151 use axum_core::response::{IntoResponse, Response};
152 use bytes::Bytes;
153 use http::{HeaderValue, StatusCode, header::CONTENT_RANGE};
154
155 impl IntoResponse for BodyRange<Bytes> {
156 fn into_response(self) -> Response {
157 match self.header {
158 Some(range) => (
159 StatusCode::PARTIAL_CONTENT,
160 [(CONTENT_RANGE, HeaderValue::from(&range))],
161 self.body,
162 )
163 .into_response(),
164 None => (StatusCode::OK, self.body).into_response(),
165 }
166 }
167 }
168
169 impl IntoResponse for UnsatisfiableRange {
170 fn into_response(self) -> Response {
171 (
172 StatusCode::RANGE_NOT_SATISFIABLE,
173 [(CONTENT_RANGE, HeaderValue::from(&self.0))],
174 )
175 .into_response()
176 }
177 }
178}