1pub extern crate mime;
10
11#[macro_use]
12extern crate educe;
13
14mod temp_file_async_reader;
15
16use std::{
17 io::{self, Cursor},
18 marker::Unpin,
19 path::Path,
20 sync::Arc,
21};
22
23use mime::Mime;
24use rocket::{
25 fs::TempFile,
26 http::Status,
27 request::Request,
28 response::{self, Responder, Response},
29 tokio::{fs::File as AsyncFile, io::AsyncRead},
30};
31use temp_file_async_reader::TempFileAsyncReader;
32
33#[derive(Educe)]
34#[educe(Debug)]
35enum RawResponseData<'o> {
36 Slice(&'o [u8]),
37 Vec(Vec<u8>),
38 Reader {
39 #[educe(Debug(ignore))]
40 data: Box<dyn AsyncRead + Send + Unpin + 'o>,
41 content_length: Option<u64>,
42 },
43 File(Arc<Path>, AsyncFile),
44 TempFile(Box<TempFile<'o>>),
45}
46
47pub type RawResponse = RawResponsePro<'static>;
48
49#[derive(Debug)]
50pub struct RawResponsePro<'o> {
51 file_name: Option<String>,
52 content_type: Option<Mime>,
53 data: RawResponseData<'o>,
54}
55
56impl<'o> RawResponsePro<'o> {
57 pub fn from_slice<S: Into<String>>(
59 data: &'o [u8],
60 file_name: Option<S>,
61 content_type: Option<Mime>,
62 ) -> RawResponsePro<'o> {
63 let file_name = file_name.map(|file_name| file_name.into());
64
65 let data = RawResponseData::Slice(data);
66
67 RawResponsePro {
68 file_name,
69 content_type,
70 data,
71 }
72 }
73
74 pub fn from_vec<S: Into<String>>(
76 vec: Vec<u8>,
77 file_name: Option<S>,
78 content_type: Option<Mime>,
79 ) -> RawResponsePro<'o> {
80 let file_name = file_name.map(|file_name| file_name.into());
81
82 let data = RawResponseData::Vec(vec);
83
84 RawResponsePro {
85 file_name,
86 content_type,
87 data,
88 }
89 }
90
91 pub fn from_reader<R: AsyncRead + Send + Unpin + 'o, S: Into<String>>(
93 reader: R,
94 file_name: Option<S>,
95 content_type: Option<Mime>,
96 content_length: Option<u64>,
97 ) -> RawResponsePro<'o> {
98 let file_name = file_name.map(|file_name| file_name.into());
99
100 let data = RawResponseData::Reader {
101 data: Box::new(reader),
102 content_length,
103 };
104
105 RawResponsePro {
106 file_name,
107 content_type,
108 data,
109 }
110 }
111
112 pub async fn from_file<P: Into<Arc<Path>>, S: Into<String>>(
114 path: P,
115 file_name: Option<S>,
116 content_type: Option<Mime>,
117 ) -> Result<RawResponsePro<'o>, io::Error> {
118 let path = path.into();
119
120 let file = AsyncFile::open(path.as_ref()).await?;
121
122 let file_name = file_name.map(|file_name| file_name.into());
123
124 let data = RawResponseData::File(path, file);
125
126 Ok(RawResponsePro {
127 file_name,
128 content_type,
129 data,
130 })
131 }
132
133 pub fn from_temp_file<S: Into<String>>(
135 temp_file: TempFile<'o>,
136 file_name: Option<S>,
137 content_type: Option<Mime>,
138 ) -> RawResponsePro<'o> {
139 let file_name = file_name.map(|file_name| file_name.into());
140
141 let data = RawResponseData::TempFile(Box::new(temp_file));
142
143 RawResponsePro {
144 file_name,
145 content_type,
146 data,
147 }
148 }
149}
150
151macro_rules! file_name {
152 ($s:expr, $res:expr) => {
153 if let Some(file_name) = $s.file_name {
154 if file_name.is_empty() {
155 $res.raw_header("Content-Disposition", "inline");
156 } else {
157 let mut v = String::from("inline; filename*=UTF-8''");
158
159 url_escape::encode_component_to_string(file_name, &mut v);
160
161 $res.raw_header("Content-Disposition", v);
162 }
163 }
164 };
165}
166
167macro_rules! content_type {
168 ($s:expr, $res:expr) => {
169 if let Some(content_type) = $s.content_type {
170 $res.raw_header("Content-Type", content_type.to_string());
171 }
172 };
173}
174
175impl<'r, 'o: 'r> Responder<'r, 'o> for RawResponsePro<'o> {
176 fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
177 let mut response = Response::build();
178
179 match self.data {
180 RawResponseData::Slice(data) => {
181 file_name!(self, response);
182 content_type!(self, response);
183
184 response.sized_body(data.len(), Cursor::new(data));
185 },
186 RawResponseData::Vec(data) => {
187 file_name!(self, response);
188 content_type!(self, response);
189
190 response.sized_body(data.len(), Cursor::new(data));
191 },
192 RawResponseData::Reader {
193 data,
194 content_length,
195 } => {
196 file_name!(self, response);
197 content_type!(self, response);
198
199 if let Some(content_length) = content_length {
200 response.raw_header("Content-Length", content_length.to_string());
201 }
202
203 response.streamed_body(data);
204 },
205 RawResponseData::File(path, file) => {
206 if let Some(file_name) = self.file_name {
207 if file_name.is_empty() {
208 response.raw_header("Content-Disposition", "inline");
209 } else {
210 let mut v = String::from("inline; filename*=UTF-8''");
211
212 url_escape::encode_component_to_string(file_name, &mut v);
213
214 response.raw_header("Content-Disposition", v);
215 }
216 } else if let Some(file_name) =
217 path.file_name().map(|file_name| file_name.to_string_lossy())
218 {
219 let mut v = String::from("inline; filename*=UTF-8''");
220
221 url_escape::encode_component_to_string(file_name, &mut v);
222
223 response.raw_header("Content-Disposition", v);
224 } else {
225 response.raw_header("Content-Disposition", "inline");
226 }
227
228 if let Some(content_type) = self.content_type {
229 response.raw_header("Content-Type", content_type.to_string());
230 } else if let Some(extension) = path.extension() {
231 if let Some(extension) = extension.to_str() {
232 let content_type = mime_guess::from_ext(extension).first_or_octet_stream();
233
234 response.raw_header("Content-Type", content_type.to_string());
235 }
236 }
237
238 response.sized_body(None, file);
239 },
240 RawResponseData::TempFile(file) => {
241 if let Some(file_name) = self.file_name {
242 if file_name.is_empty() {
243 response.raw_header("Content-Disposition", "inline");
244 } else {
245 let mut v = String::from("inline; filename*=UTF-8''");
246
247 url_escape::encode_component_to_string(file_name, &mut v);
248
249 response.raw_header("Content-Disposition", v);
250 }
251 } else if let Some(file_name) = file.name() {
252 if file_name.is_empty() {
253 response.raw_header("Content-Disposition", "inline");
254 } else {
255 let mut v = String::from("attachment; filename*=UTF-8''");
256
257 url_escape::encode_component_to_string(file_name, &mut v);
258
259 response.raw_header("Content-Disposition", v);
260 }
261 } else {
262 response.raw_header("Content-Disposition", "inline");
263 }
264
265 if let Some(content_type) = self.content_type {
266 response.raw_header("Content-Type", content_type.to_string());
267 } else if let Some(content_type) = file.content_type() {
268 response.raw_header("Content-Type", content_type.to_string());
269 } else if let Some(extension) = file.name().map(Path::new).and_then(Path::extension)
270 {
271 if let Some(extension) = extension.to_str() {
272 let content_type = mime_guess::from_ext(extension).first_or_octet_stream();
273
274 response.raw_header("Content-Type", content_type.to_string());
275 }
276 }
277
278 response.raw_header("Content-Length", file.len().to_string());
279
280 response.streamed_body(
281 TempFileAsyncReader::from(file).map_err(|_| Status::InternalServerError)?,
282 );
283 },
284 }
285
286 response.ok()
287 }
288}