forked from M4SS-Code/range-requests
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlib.rs
More file actions
170 lines (144 loc) · 4.93 KB
/
Copy pathlib.rs
File metadata and controls
170 lines (144 loc) · 4.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#![cfg_attr(docsrs, feature(doc_cfg))]
use std::{
num::{NonZero, NonZeroU64},
ops::RangeInclusive,
};
use bytes::Bytes;
pub mod headers;
use crate::headers::{
content_range::{Bound, HttpContentRange, Unsatisfiable},
range::HttpRange,
};
/// Returns a [`BodyRange`] of [`Bytes`] if the provided [`HttpRange`] is satisfiable, otherwise it returns [`UnsatisfiableRange`].
///
/// [`HttpRange`]: crate::headers::range::HttpRange
pub fn serve_file_with_http_range(
body: Bytes,
http_range: Option<HttpRange>,
) -> Result<BodyRange<Bytes>, UnsatisfiableRange> {
let size = u64::try_from(body.len()).expect("we do not support 128bit usize");
let size = NonZeroU64::try_from(size).map_err(|_| {
UnsatisfiableRange(HttpContentRange::Unsatisfiable(Unsatisfiable::new(size)))
})?;
let content_range = file_range(size, http_range)?;
let start = usize::try_from(*content_range.range.start()).expect("u64 doesn't fit usize");
let end = usize::try_from(*content_range.range.end()).expect("u64 doesn't fit usize");
Ok(BodyRange {
body: body.slice(start..=end),
header: content_range.header,
})
}
/// Returns a [`ContentRange`] if the provided [`HttpRange`] is satisfiable, otherwise it returns [`UnsatisfiableRange`].
///
/// [`HttpRange`]: crate::headers::range::HttpRange
pub fn file_range(
size: NonZero<u64>,
http_range: Option<HttpRange>,
) -> Result<ContentRange, UnsatisfiableRange> {
let size = size.get();
let Some(http_range) = http_range else {
return Ok(ContentRange {
header: None,
range: 0..=size - 1,
});
};
let range = match http_range {
HttpRange::StartingPoint(start) if start < size => start..=size - 1,
HttpRange::Range(range) if range.start() < size => {
range.start()..=range.end().min(size - 1)
}
HttpRange::Suffix(suffix) if suffix > 0 => size.saturating_sub(suffix)..=size - 1,
_ => {
let content_range = HttpContentRange::Unsatisfiable(Unsatisfiable::new(size));
return Err(UnsatisfiableRange(content_range));
}
};
let content_range = HttpContentRange::Bound(Bound::new(range.clone(), Some(size)).unwrap());
Ok(ContentRange {
header: Some(content_range),
range,
})
}
/// A container for the payload slice and the optional `Content-Range` header.
///
/// The header is `None` only if the body was not sliced.
///
/// If the `axum` feature is enabled this struct also implements `IntoResponse`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BodyRange<T> {
body: T,
header: Option<HttpContentRange>,
}
impl<T> BodyRange<T> {
/// Returns the sliced body.
pub fn body(&self) -> &T {
&self.body
}
pub fn into_body(self) -> T {
self.body
}
/// Returns an option of [`HttpContentRange`].
/// If it's None the provided [`HttpRange`] was None too.
pub fn header(&self) -> Option<HttpContentRange> {
self.header
}
}
/// A container for the payload range and the optional `Content-Range` header.
///
/// The header is `None` only if the body was not sliced.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContentRange {
header: Option<HttpContentRange>,
range: RangeInclusive<u64>,
}
impl ContentRange {
/// Returns an option of [`HttpContentRange`].
/// If it's None the provided [`HttpRange`] was None too.
pub fn header(&self) -> Option<HttpContentRange> {
self.header
}
/// Returns a [`RangeInclusive`] of `u64` useful to manually slice the response body.
pub fn range(&self) -> &RangeInclusive<u64> {
&self.range
}
}
/// An unsatisfiable range request.
///
/// If the `axum` feature is enabled this struct also implements `IntoResponse`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnsatisfiableRange(HttpContentRange);
impl UnsatisfiableRange {
/// Returns the [`HttpContentRange`] header.
pub fn header(&self) -> HttpContentRange {
self.0
}
}
#[cfg(feature = "axum")]
mod axum {
use crate::{BodyRange, UnsatisfiableRange};
use axum_core::response::{IntoResponse, Response};
use bytes::Bytes;
use http::{HeaderValue, StatusCode, header::CONTENT_RANGE};
impl IntoResponse for BodyRange<Bytes> {
fn into_response(self) -> Response {
match self.header {
Some(range) => (
StatusCode::PARTIAL_CONTENT,
[(CONTENT_RANGE, HeaderValue::from(&range))],
self.body,
)
.into_response(),
None => (StatusCode::OK, self.body).into_response(),
}
}
}
impl IntoResponse for UnsatisfiableRange {
fn into_response(self) -> Response {
(
StatusCode::RANGE_NOT_SATISFIABLE,
[(CONTENT_RANGE, HeaderValue::from(&self.0))],
)
.into_response()
}
}
}