fileloft_core/handler/
mod.rs1mod delete;
2mod head;
3mod options;
4mod patch;
5mod post;
6
7use std::sync::Arc;
8
9use bytes::Bytes;
10use http::{HeaderMap, HeaderValue, Method, StatusCode, Uri};
11use tokio::sync::broadcast;
12
13use crate::{
14 config::Config,
15 error::TusError,
16 hooks::{HookEvent, HookSender},
17 lock::SendLocker,
18 proto::{HDR_CACHE_CONTROL, HDR_TUS_RESUMABLE, TUS_VERSION},
19 store::SendDataStore,
20 util::static_header,
21};
22
23pub struct TusRequest {
26 pub method: Method,
27 pub uri: Uri,
28 pub upload_id: Option<String>,
31 pub headers: HeaderMap,
32 pub body: Option<Box<dyn tokio::io::AsyncRead + Send + Sync + Unpin>>,
34}
35
36pub struct TusResponse {
39 pub status: StatusCode,
40 pub headers: HeaderMap,
41 pub body: Bytes,
43}
44
45pub struct TusHandler<S, L = NoLocker> {
54 pub(crate) store: S,
55 pub(crate) locker: Option<L>,
56 pub(crate) config: Arc<Config>,
57 pub(crate) hook_tx: Option<HookSender>,
58}
59
60impl<S, L> TusHandler<S, L>
61where
62 S: SendDataStore + Send + Sync + 'static,
63 L: SendLocker + Send + Sync + 'static,
64{
65 pub fn new(store: S, locker: Option<L>, config: Config) -> Self {
66 let hook_tx = if config.hooks.channel_capacity > 0 {
67 let (tx, _) = broadcast::channel(config.hooks.channel_capacity);
68 Some(tx)
69 } else {
70 None
71 };
72 Self {
73 store,
74 locker,
75 config: Arc::new(config),
76 hook_tx,
77 }
78 }
79
80 pub fn hook_receiver(&self) -> Option<broadcast::Receiver<HookEvent>> {
82 self.hook_tx.as_ref().map(|tx| tx.subscribe())
83 }
84
85 pub async fn handle(&self, req: TusRequest) -> TusResponse {
87 let result = match req.method {
88 Method::OPTIONS => options::handle(self, &req).await,
89 Method::HEAD => head::handle(self, &req).await,
90 Method::POST => post::handle(self, req).await,
91 Method::PATCH => patch::handle(self, req).await,
92 Method::DELETE => delete::handle(self, &req).await,
93 _ => Err(TusError::MethodNotAllowed),
94 };
95 match result {
96 Ok(resp) => resp,
97 Err(err) => self.error_response(err),
98 }
99 }
100
101 pub(crate) fn response(
103 &self,
104 status: StatusCode,
105 extra_headers: HeaderMap,
106 body: Bytes,
107 ) -> TusResponse {
108 let mut headers = self.base_headers();
109 headers.extend(extra_headers);
110 TusResponse { status, headers, body }
111 }
112
113 pub(crate) fn error_response(&self, err: TusError) -> TusResponse {
115 let status = err.status_code();
116 let body = Bytes::from(err.to_string());
117 let mut headers = self.base_headers();
118 headers.insert(
119 http::header::CONTENT_TYPE,
120 HeaderValue::from_static("text/plain; charset=utf-8"),
121 );
122 TusResponse { status, headers, body }
123 }
124
125 fn base_headers(&self) -> HeaderMap {
127 let mut h = HeaderMap::new();
128 h.insert(HDR_TUS_RESUMABLE, static_header(TUS_VERSION));
129 h.insert(HDR_CACHE_CONTROL, static_header("no-store"));
130 if self.config.enable_cors {
131 h.insert(
132 crate::proto::HDR_ACCESS_CONTROL_ALLOW_ORIGIN,
133 static_header("*"),
134 );
135 h.insert(
136 crate::proto::HDR_ACCESS_CONTROL_EXPOSE_HEADERS,
137 static_header(
138 "Upload-Offset,Upload-Length,Upload-Metadata,Upload-Expires,\
139 Upload-Defer-Length,Location,Tus-Resumable,Tus-Version,Tus-Extension,\
140 Tus-Max-Size,Tus-Checksum-Algorithm",
141 ),
142 );
143 }
144 h
145 }
146
147 pub(crate) fn emit(&self, event: HookEvent) {
149 if let Some(tx) = &self.hook_tx {
150 let _ = tx.send(event);
151 }
152 }
153}
154
155pub struct NoLocker;
157
158impl crate::lock::SendLock for NoLocker {
159 async fn release(self) -> Result<(), TusError> {
160 Ok(())
161 }
162}
163
164impl crate::lock::SendLocker for NoLocker {
165 type LockType = NoLocker;
166 async fn acquire(&self, _id: &crate::info::UploadId) -> Result<NoLocker, TusError> {
167 Ok(NoLocker)
168 }
169}