1#![doc(html_root_url = "https://docs.rs/tus_client/0.1.1")]
45use crate::http::{default_headers, Headers, HttpHandler, HttpMethod, HttpRequest};
46use std::collections::HashMap;
47use std::collections::hash_map::RandomState;
48use std::error::Error as StdError;
49use std::fmt::{Display, Formatter};
50use std::fs::File;
51use std::io;
52use std::io::{BufReader, Read, Seek, SeekFrom};
53use std::num::ParseIntError;
54use std::ops::Deref;
55use std::path::Path;
56use std::str::FromStr;
57
58mod headers;
59pub mod http;
61
62#[cfg(feature = "reqwest")]
63mod reqwest;
64
65const DEFAULT_CHUNK_SIZE: usize = 5 * 1024 * 1024;
66
67pub struct Client<'a> {
69 use_method_override: bool,
70 http_handler: Box<dyn HttpHandler + 'a>,
71}
72
73pub struct CreateResponse{
74 pub upload_url: String,
75 pub headers: HashMap<String, String, RandomState>
76}
77
78impl<'a> Client<'a> {
79 pub fn new(http_handler: impl HttpHandler + 'a) -> Self {
82 Client {
83 use_method_override: false,
84 http_handler: Box::new(http_handler),
85 }
86 }
87
88 pub fn with_method_override(http_handler: impl HttpHandler + 'a) -> Self {
90 Client {
91 use_method_override: true,
92 http_handler: Box::new(http_handler),
93 }
94 }
95
96 pub fn get_info(&self, url: &str) -> Result<UploadInfo, Error> {
98 let req = self.create_request(HttpMethod::Head, url, None, Some(default_headers()));
99
100 let response = self.http_handler.deref().handle_request(req)?;
101
102 let bytes_uploaded = response.headers.get_by_key(headers::UPLOAD_OFFSET);
103 let total_size = response
104 .headers
105 .get_by_key(headers::UPLOAD_LENGTH)
106 .and_then(|l| l.parse::<usize>().ok());
107 let metadata = response
108 .headers
109 .get_by_key(headers::UPLOAD_METADATA)
110 .and_then(|data| base64::decode(data).ok())
111 .map(|decoded| {
112 String::from_utf8(decoded).unwrap().split(';').fold(
113 HashMap::new(),
114 |mut acc, key_val| {
115 let mut parts = key_val.splitn(2, ':');
116 if let Some(key) = parts.next() {
117 acc.insert(
118 String::from(key),
119 String::from(parts.next().unwrap_or_default()),
120 );
121 }
122 acc
123 },
124 )
125 });
126
127 if response.status_code.to_string().starts_with('4') || bytes_uploaded.is_none() {
128 return Err(Error::NotFoundError);
129 }
130
131 let bytes_uploaded = bytes_uploaded.unwrap().parse()?;
132
133 Ok(UploadInfo {
134 bytes_uploaded,
135 total_size,
136 metadata,
137 })
138 }
139
140 pub fn upload(&self, url: &str, path: &Path) -> Result<(), Error> {
142 self.upload_with_chunk_size(url, path, DEFAULT_CHUNK_SIZE)
143 }
144
145 pub fn upload_with_chunk_size(
147 &self,
148 url: &str,
149 path: &Path,
150 chunk_size: usize,
151 ) -> Result<(), Error> {
152 let info = self.get_info(url)?;
153 let file = File::open(path)?;
154 let file_len = file.metadata()?.len();
155
156 if let Some(total_size) = info.total_size {
157 if file_len as usize != total_size {
158 return Err(Error::UnequalSizeError);
159 }
160 }
161
162 let mut reader = BufReader::new(&file);
163 let mut buffer = vec![0; chunk_size];
164 let mut progress = info.bytes_uploaded;
165
166 reader.seek(SeekFrom::Start(progress as u64))?;
167
168 loop {
169 let bytes_read = reader.read(&mut buffer)?;
170 if bytes_read == 0 {
171 return Err(Error::FileReadError);
172 }
173
174 let req = self.create_request(
175 HttpMethod::Patch,
176 url,
177 Some(&buffer[..bytes_read]),
178 Some(create_upload_headers(progress)),
179 );
180
181 let response = self.http_handler.deref().handle_request(req)?;
182
183 if response.status_code == 409 {
184 return Err(Error::WrongUploadOffsetError);
185 }
186
187 if response.status_code == 404 {
188 return Err(Error::NotFoundError);
189 }
190
191 if response.status_code != 204 {
192 return Err(Error::UnexpectedStatusCode(response.status_code));
193 }
194
195 let upload_offset = match response.headers.get_by_key(headers::UPLOAD_OFFSET) {
196 Some(offset) => Ok(offset),
197 None => Err(Error::MissingHeader(headers::UPLOAD_OFFSET.to_owned())),
198 }?;
199
200 progress = upload_offset.parse()?;
201
202 if progress >= file_len as usize {
203 break;
204 }
205 }
206
207 Ok(())
208 }
209
210 pub fn get_server_info(&self, url: &str) -> Result<ServerInfo, Error> {
212 let req = self.create_request(HttpMethod::Options, url, None, None);
213
214 let response = self.http_handler.deref().handle_request(req)?;
215
216 if ![200_usize, 204].contains(&response.status_code) {
217 return Err(Error::UnexpectedStatusCode(response.status_code));
218 }
219
220 let supported_versions: Vec<String> = response
221 .headers
222 .get_by_key(headers::TUS_VERSION)
223 .unwrap()
224 .split(',')
225 .map(String::from)
226 .collect();
227 let extensions: Vec<TusExtension> =
228 if let Some(ext) = response.headers.get_by_key(headers::TUS_EXTENSION) {
229 ext.split(',')
230 .map(str::parse)
231 .filter(Result::is_ok)
232 .map(Result::unwrap)
233 .collect()
234 } else {
235 Vec::new()
236 };
237 let max_upload_size = response
238 .headers
239 .get_by_key(headers::TUS_MAX_SIZE)
240 .and_then(|h| h.parse::<usize>().ok());
241
242 Ok(ServerInfo {
243 supported_versions,
244 extensions,
245 max_upload_size,
246 })
247 }
248
249 pub fn create(&self, url: &str, path: &Path) -> Result<CreateResponse, Error> {
251 self.create_with_metadata(url, path, HashMap::new())
252 }
253
254 pub fn create_with_metadata(
256 &self,
257 url: &str,
258 path: &Path,
259 metadata: HashMap<String, String>,
260 ) -> Result<CreateResponse, Error> {
261 let mut headers = default_headers();
262 headers.insert(
263 headers::UPLOAD_LENGTH.to_owned(),
264 path.metadata()?.len().to_string(),
265 );
266 if !metadata.is_empty() {
267 let data = metadata
268 .iter()
269 .map(|(key, value)| format!("{} {}", key, base64::encode(value)))
270 .collect::<Vec<_>>()
271 .join(",");
272 headers.insert(headers::UPLOAD_METADATA.to_owned(), data);
273 }
274
275 let req = self.create_request(HttpMethod::Post, url, None, Some(headers));
276
277 let response = self.http_handler.deref().handle_request(req)?;
278
279 if response.status_code == 413 {
280 return Err(Error::FileTooLarge);
281 }
282
283 if response.status_code != 201 {
284 return Err(Error::UnexpectedStatusCode(response.status_code));
285 }
286
287 let location = response.headers.get_by_key(headers::LOCATION);
288
289 if location.is_none() {
290 return Err(Error::MissingHeader(headers::LOCATION.to_owned()));
291 }
292
293 Ok(CreateResponse {
294 upload_url: location.unwrap().to_string(),
295 headers: response.headers,
296 })
297 }
298
299 pub fn delete(&self, url: &str) -> Result<(), Error> {
301 let req = self.create_request(HttpMethod::Delete, url, None, Some(default_headers()));
302
303 let response = self.http_handler.deref().handle_request(req)?;
304
305 if response.status_code != 204 {
306 return Err(Error::UnexpectedStatusCode(response.status_code));
307 }
308
309 Ok(())
310 }
311
312 fn create_request<'b>(
313 &self,
314 method: HttpMethod,
315 url: &str,
316 body: Option<&'b [u8]>,
317 headers: Option<Headers>,
318 ) -> HttpRequest<'b> {
319 let mut headers = headers.unwrap_or_default();
320
321 let method = if self.use_method_override {
322 headers.insert(
323 headers::X_HTTP_METHOD_OVERRIDE.to_owned(),
324 method.to_string(),
325 );
326 HttpMethod::Post
327 } else {
328 method
329 };
330
331 HttpRequest {
332 method,
333 url: String::from(url),
334 body,
335 headers,
336 }
337 }
338}
339
340#[derive(Debug)]
342pub struct UploadInfo {
343 pub bytes_uploaded: usize,
345 pub total_size: Option<usize>,
347 pub metadata: Option<HashMap<String, String>>,
349}
350
351#[derive(Debug)]
353pub struct ServerInfo {
354 pub supported_versions: Vec<String>,
356 pub extensions: Vec<TusExtension>,
358 pub max_upload_size: Option<usize>,
360}
361
362#[derive(Debug, PartialEq)]
364pub enum TusExtension {
365 Creation,
367 Expiration,
369 Checksum,
371 Termination,
373 Concatenation,
375}
376
377impl FromStr for TusExtension {
378 type Err = ();
379
380 fn from_str(s: &str) -> Result<Self, Self::Err> {
381 match s.trim().to_lowercase().as_str() {
382 "creation" => Ok(TusExtension::Creation),
383 "expiration" => Ok(TusExtension::Expiration),
384 "checksum" => Ok(TusExtension::Checksum),
385 "termination" => Ok(TusExtension::Termination),
386 "concatenation" => Ok(TusExtension::Concatenation),
387 _ => Err(()),
388 }
389 }
390}
391
392#[derive(Debug)]
394pub enum Error {
395 UnexpectedStatusCode(usize),
397 NotFoundError,
399 MissingHeader(String),
401 IoError(io::Error),
403 ParsingError(ParseIntError),
405 UnequalSizeError,
407 FileReadError,
409 WrongUploadOffsetError,
411 FileTooLarge,
413 HttpHandlerError(String),
415}
416
417impl Display for Error {
418 fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
419 let message = match self {
420 Error::UnexpectedStatusCode(status_code) => format!("The status code returned by the server was not one of the expected ones: {}", status_code),
421 Error::NotFoundError => "The file specified was not found by the server".to_string(),
422 Error::MissingHeader(header_name) => format!("The '{}' header was missing from the server response", header_name),
423 Error::IoError(error) => format!("An error occurred while doing disk IO. This may be while reading a file, or during a network call: {}", error),
424 Error::ParsingError(error) => format!("Unable to parse a value, which should be an integer: {}", error),
425 Error::UnequalSizeError => "The size of the specified file, and the file size reported by the server do not match".to_string(),
426 Error::FileReadError => "Unable to read the specified file".to_string(),
427 Error::WrongUploadOffsetError => "The client tried to upload the file with an incorrect offset".to_string(),
428 Error::FileTooLarge => "The specified file is larger that what is supported by the server".to_string(),
429 Error::HttpHandlerError(message) => format!("An error occurred in the HTTP handler: {}", message),
430 };
431
432 write!(f, "{}", message)?;
433
434 Ok(())
435 }
436}
437
438impl StdError for Error {}
439
440impl From<io::Error> for Error {
441 fn from(e: io::Error) -> Self {
442 Error::IoError(e)
443 }
444}
445
446impl From<ParseIntError> for Error {
447 fn from(e: ParseIntError) -> Self {
448 Error::ParsingError(e)
449 }
450}
451
452trait HeaderMap {
453 fn get_by_key(&self, key: &str) -> Option<&String>;
454}
455
456impl HeaderMap for HashMap<String, String> {
457 fn get_by_key(&self, key: &str) -> Option<&String> {
458 self.keys()
459 .find(|k| k.to_lowercase().as_str() == key)
460 .and_then(|k| self.get(k))
461 }
462}
463
464fn create_upload_headers(progress: usize) -> Headers {
465 let mut headers = default_headers();
466 headers.insert(
467 headers::CONTENT_TYPE.to_owned(),
468 "application/offset+octet-stream".to_owned(),
469 );
470 headers.insert(headers::UPLOAD_OFFSET.to_owned(), progress.to_string());
471 headers
472}