1use crate::error::WebServerError;
4use bytes::Bytes;
5use http::{HeaderMap, Method, StatusCode as HttpStatusCode, Uri, Version};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::PathBuf;
9use std::time::{Duration, SystemTime};
10
11#[derive(Debug)]
13pub struct Request {
14 pub method: HttpMethod,
15 pub uri: Uri,
16 pub version: Version,
17 pub headers: Headers,
18 pub body: Body,
19 pub extensions: HashMap<String, String>, pub path_params: HashMap<String, String>,
22 pub cookies: HashMap<String, Cookie>,
24 pub form_data: Option<HashMap<String, String>>,
26 pub multipart: Option<MultipartForm>,
28}
29
30impl Request {
31 pub fn new(method: HttpMethod, uri: Uri) -> Self {
33 Self {
34 method,
35 uri,
36 version: Version::HTTP_11,
37 headers: Headers::new(),
38 body: Body::empty(),
39 extensions: HashMap::new(),
40 path_params: HashMap::new(),
41 cookies: HashMap::new(),
42 form_data: None,
43 multipart: None,
44 }
45 }
46
47 pub fn path(&self) -> &str {
49 self.uri.path()
50 }
51
52 pub fn query(&self) -> Option<&str> {
54 self.uri.query()
55 }
56
57 pub fn param(&self, name: &str) -> Option<&str> {
59 self.path_params.get(name).map(|s| s.as_str())
60 }
61
62 pub fn params(&self) -> &HashMap<String, String> {
64 &self.path_params
65 }
66
67 pub fn set_params(&mut self, params: HashMap<String, String>) {
69 self.path_params = params;
70 }
71
72 pub async fn json<T>(&self) -> crate::error::Result<T>
74 where
75 T: for<'de> Deserialize<'de>,
76 {
77 let bytes = self.body.bytes().await?;
78 serde_json::from_slice(&bytes).map_err(crate::error::WebServerError::JsonError)
79 }
80
81 pub async fn text(&self) -> crate::error::Result<String> {
83 let bytes = self.body.bytes().await?;
84 String::from_utf8(bytes.to_vec()).map_err(crate::error::WebServerError::Utf8Error)
85 }
86
87 pub fn cookie(&self, name: &str) -> Option<&Cookie> {
89 self.cookies.get(name)
90 }
91
92 pub fn cookies(&self) -> &HashMap<String, Cookie> {
94 &self.cookies
95 }
96
97 pub fn parse_cookies(&mut self) -> crate::error::Result<()> {
99 if let Some(cookie_header) = self.headers.get("Cookie") {
100 let cookies = Cookie::parse(cookie_header)?;
101 for cookie in cookies {
102 self.cookies.insert(cookie.name.clone(), cookie);
103 }
104 }
105 Ok(())
106 }
107
108 pub fn form(&self, name: &str) -> Option<&str> {
110 self.form_data.as_ref()?.get(name).map(|s| s.as_str())
111 }
112
113 pub fn form_data(&self) -> Option<&HashMap<String, String>> {
115 self.form_data.as_ref()
116 }
117
118 pub async fn parse_form(&mut self) -> crate::error::Result<()> {
120 if self.form_data.is_some() {
121 return Ok(()); }
123
124 let default_content_type = String::new();
125 let content_type = self
126 .headers
127 .get("Content-Type")
128 .unwrap_or(&default_content_type);
129 if !content_type.starts_with("application/x-www-form-urlencoded") {
130 return Ok(()); }
132
133 let body_text = self.text().await?;
134 let mut form_data = HashMap::new();
135
136 for pair in body_text.split('&') {
137 if let Some((key, value)) = pair.split_once('=') {
138 let key = key.replace("%20", " ").replace("+", " ");
140 let value = value.replace("%20", " ").replace("+", " ");
141 form_data.insert(key, value);
142 }
143 }
144
145 self.form_data = Some(form_data);
146 Ok(())
147 }
148
149 pub fn multipart(&self) -> Option<&MultipartForm> {
151 self.multipart.as_ref()
152 }
153
154 pub fn query_params(&self) -> HashMap<String, String> {
156 if let Some(query) = self.uri.query() {
157 let mut params = HashMap::new();
158 for pair in query.split('&') {
159 if let Some((key, value)) = pair.split_once('=') {
160 let key = urlencoding::decode(key).unwrap_or_default().into_owned();
162 let value = urlencoding::decode(value).unwrap_or_default().into_owned();
163 params.insert(key, value);
164 }
165 }
166 params
167 } else {
168 HashMap::new()
169 }
170 }
171
172 pub fn query_param(&self, name: &str) -> Option<String> {
174 self.query_params().get(name).cloned()
175 }
176
177 pub fn accepts(&self, content_type: &str) -> bool {
179 if let Some(accept_header) = self.headers.get("Accept") {
180 accept_header.contains(content_type) || accept_header.contains("*/*")
181 } else {
182 true }
184 }
185
186 pub fn content_type(&self) -> Option<&str> {
188 self.headers.get("Content-Type").map(|s| s.as_str())
189 }
190
191 pub fn is_json(&self) -> bool {
193 self.content_type()
194 .is_some_and(|ct| ct.contains("application/json"))
195 }
196
197 pub fn is_form(&self) -> bool {
199 self.content_type()
200 .is_some_and(|ct| ct.contains("application/x-www-form-urlencoded"))
201 }
202
203 pub fn is_multipart(&self) -> bool {
205 self.content_type()
206 .is_some_and(|ct| ct.contains("multipart/form-data"))
207 }
208
209 pub fn remote_addr(&self) -> Option<&str> {
211 if let Some(forwarded) = self.headers.get("X-Forwarded-For") {
213 if let Some(first_ip) = forwarded.split(',').next() {
215 return Some(first_ip.trim());
216 }
217 }
218
219 if let Some(real_ip) = self.headers.get("X-Real-IP") {
221 return Some(real_ip.as_str());
222 }
223
224 self.extensions.get("remote_addr").map(|s| s.as_str())
226 }
227
228 pub fn user_agent(&self) -> Option<&str> {
230 self.headers.get("User-Agent").map(|s| s.as_str())
231 }
232
233 pub fn path_param(&self, name: &str) -> Option<&str> {
235 self.path_params.get(name).map(|s| s.as_str())
236 }
237
238 pub fn path_params(&self) -> &HashMap<String, String> {
240 &self.path_params
241 }
242
243 pub fn set_path_param(&mut self, name: impl Into<String>, value: impl Into<String>) {
245 self.path_params.insert(name.into(), value.into());
246 }
247
248 pub async fn parse_multipart(&mut self) -> crate::error::Result<()> {
250 if self.multipart.is_some() {
251 return Ok(()); }
253
254 let default_content_type = String::new();
255 let content_type = self
256 .headers
257 .get("Content-Type")
258 .unwrap_or(&default_content_type);
259 if !content_type.starts_with("multipart/form-data") {
260 return Ok(()); }
262
263 let boundary = match content_type.split(";").nth(1) {
265 Some(part) => {
266 let part = part.trim();
267 if part.starts_with("boundary=") {
268 part.trim_start_matches("boundary=").trim_matches('"')
269 } else {
270 return Err(WebServerError::parse_error(
271 "Missing boundary in multipart Content-Type",
272 ));
273 }
274 }
275 None => {
276 return Err(WebServerError::parse_error(
277 "Missing boundary in multipart Content-Type",
278 ));
279 }
280 };
281
282 let mut form = MultipartForm::new();
284
285 let body_bytes = self.body.bytes().await?;
287
288 let mut current_part: Option<MultipartPart> = None;
290 let mut parsing_headers = true;
291 let mut part_content: Vec<u8> = Vec::new();
292
293 let boundary_start = format!("--{}", boundary);
294 let boundary_end = format!("--{}--", boundary);
295
296 let body_str = String::from_utf8_lossy(&body_bytes);
298 let lines: Vec<&str> = body_str.split("\r\n").collect();
299
300 let mut i = 0;
301 while i < lines.len() {
302 let line = lines[i];
303
304 if line == boundary_start {
306 if let Some(part) = current_part.take() {
308 form.add_part(part);
309 }
310
311 current_part = Some(MultipartPart::new());
313 parsing_headers = true;
314 part_content = Vec::new();
315 } else if line == boundary_end {
316 if let Some(part) = current_part.take() {
318 form.add_part(part);
319 }
320 break;
321 } else if parsing_headers {
322 if line.is_empty() {
324 parsing_headers = false;
326 } else if let Some(part) = &mut current_part {
327 if let Some((name, value)) = line.split_once(":") {
329 let name = name.trim();
330 let value = value.trim();
331
332 if name.eq_ignore_ascii_case("Content-Disposition") {
334 for param in value.split(";") {
336 let param = param.trim();
337
338 if param.starts_with("name=") {
339 let field_name =
340 param.trim_start_matches("name=").trim_matches('"');
341 part.field_name = Some(field_name.to_string());
342 } else if param.starts_with("filename=") {
343 let filename =
344 param.trim_start_matches("filename=").trim_matches('"');
345 part.filename = Some(filename.to_string());
346 }
347 }
348 }
349
350 part.headers.insert(name.to_string(), value.to_string());
351 }
352 }
353 } else {
354 if let Some(_part) = &mut current_part {
356 part_content.extend_from_slice(line.as_bytes());
357 if i < lines.len() - 1 && !lines[i + 1].starts_with("--") {
359 part_content.extend_from_slice(b"\r\n");
360 }
361 }
362 }
363
364 i += 1;
365 }
366
367 self.multipart = Some(form);
369
370 Ok(())
371 }
372}
373
374#[derive(Debug, Clone)]
376pub struct Response {
377 pub status: StatusCode,
378 pub headers: Headers,
379 pub body: Body,
380}
381
382impl Response {
383 pub fn new(status: StatusCode) -> Self {
385 Self {
386 status,
387 headers: Headers::new(),
388 body: Body::empty(),
389 }
390 }
391
392 pub fn ok() -> Self {
394 Self::new(StatusCode::OK)
395 }
396
397 pub fn body<B>(mut self, body: B) -> Self
399 where
400 B: Into<Body>,
401 {
402 self.body = body.into();
403 self
404 }
405
406 pub fn header<K, V>(mut self, key: K, value: V) -> Self
408 where
409 K: Into<String>,
410 V: Into<String>,
411 {
412 self.headers.insert(key.into(), value.into());
413 self
414 }
415
416 pub fn json<T>(value: &T) -> crate::error::Result<Self>
418 where
419 T: Serialize,
420 {
421 let json = serde_json::to_string(value)?;
422 Ok(Self::ok()
423 .header("content-type", "application/json")
424 .body(json))
425 }
426
427 pub fn cookie(mut self, cookie: Cookie) -> Self {
429 self.headers.set("Set-Cookie", cookie.to_header_value());
430 self
431 }
432
433 pub fn cookies(mut self, cookies: Vec<Cookie>) -> Self {
435 for cookie in cookies {
436 self.headers.add("Set-Cookie", cookie.to_header_value());
437 }
438 self
439 }
440
441 pub fn html(content: impl Into<String>) -> Self {
443 Self::ok()
444 .header("Content-Type", "text/html; charset=utf-8")
445 .body(content.into())
446 }
447
448 pub fn text(content: impl Into<String>) -> Self {
450 Self::ok()
451 .header("Content-Type", "text/plain; charset=utf-8")
452 .body(content.into())
453 }
454
455 pub fn redirect(location: impl Into<String>) -> Self {
457 Self::new(StatusCode::FOUND).header("Location", location.into())
458 }
459
460 pub fn redirect_permanent(location: impl Into<String>) -> Self {
462 Self::new(StatusCode::MOVED_PERMANENTLY).header("Location", location.into())
463 }
464
465 pub fn not_found() -> Self {
467 Self::new(StatusCode::NOT_FOUND).body("Not Found")
468 }
469
470 pub fn bad_request(message: impl Into<String>) -> Self {
472 Self::new(StatusCode::BAD_REQUEST).body(message.into())
473 }
474
475 pub fn internal_server_error(message: impl Into<String>) -> Self {
477 Self::new(StatusCode::INTERNAL_SERVER_ERROR).body(message.into())
478 }
479
480 pub fn cache(mut self, max_age: u32) -> Self {
482 self.headers
483 .insert("Cache-Control".to_string(), format!("max-age={}", max_age));
484 self
485 }
486
487 pub fn no_cache(mut self) -> Self {
489 self.headers.insert(
490 "Cache-Control".to_string(),
491 "no-cache, no-store, must-revalidate".to_string(),
492 );
493 self.headers
494 .insert("Pragma".to_string(), "no-cache".to_string());
495 self.headers.insert("Expires".to_string(), "0".to_string());
496 self
497 }
498
499 pub fn cors(mut self) -> Self {
501 self.headers
502 .insert("Access-Control-Allow-Origin".to_string(), "*".to_string());
503 self.headers.insert(
504 "Access-Control-Allow-Methods".to_string(),
505 "GET, POST, PUT, DELETE, OPTIONS".to_string(),
506 );
507 self.headers.insert(
508 "Access-Control-Allow-Headers".to_string(),
509 "Content-Type, Authorization".to_string(),
510 );
511 self
512 }
513
514 pub fn cors_origin(mut self, origin: impl Into<String>) -> Self {
516 self.headers
517 .insert("Access-Control-Allow-Origin".to_string(), origin.into());
518 self.headers.insert(
519 "Access-Control-Allow-Methods".to_string(),
520 "GET, POST, PUT, DELETE, OPTIONS".to_string(),
521 );
522 self.headers.insert(
523 "Access-Control-Allow-Headers".to_string(),
524 "Content-Type, Authorization".to_string(),
525 );
526 self
527 }
528
529 pub async fn file(path: impl Into<PathBuf>) -> crate::error::Result<Self> {
531 let path = path.into();
532
533 let data = std::fs::read(&path).map_err(|e| {
535 if e.kind() == std::io::ErrorKind::NotFound {
536 return crate::error::WebServerError::custom("File not found");
537 }
538 crate::error::WebServerError::custom(format!("Failed to read file: {}", e))
539 })?;
540
541 let content_type = mime_guess::from_path(&path)
543 .first_or_octet_stream()
544 .to_string();
545
546 Ok(Self::ok().header("Content-Type", content_type).body(data))
547 }
548
549 pub fn download(filename: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
551 let filename = filename.into();
552 Self::ok()
553 .header("Content-Type", "application/octet-stream")
554 .header(
555 "Content-Disposition",
556 format!("attachment; filename=\"{}\"", filename),
557 )
558 .body(data.into())
559 }
560
561 pub fn with_content_length(mut self) -> Self {
563 let length = self.body.len();
564 self.headers
565 .insert("Content-Length".to_string(), length.to_string());
566 self
567 }
568}
569
570#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
572pub enum HttpMethod {
573 GET,
574 POST,
575 PUT,
576 DELETE,
577 PATCH,
578 HEAD,
579 OPTIONS,
580 TRACE,
581 CONNECT,
582}
583
584impl HttpMethod {
585 pub fn as_str(&self) -> &'static str {
586 match self {
587 HttpMethod::GET => "GET",
588 HttpMethod::POST => "POST",
589 HttpMethod::PUT => "PUT",
590 HttpMethod::DELETE => "DELETE",
591 HttpMethod::PATCH => "PATCH",
592 HttpMethod::HEAD => "HEAD",
593 HttpMethod::OPTIONS => "OPTIONS",
594 HttpMethod::TRACE => "TRACE",
595 HttpMethod::CONNECT => "CONNECT",
596 }
597 }
598}
599
600impl std::fmt::Display for HttpMethod {
601 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
602 write!(f, "{}", self.as_str())
603 }
604}
605
606impl From<Method> for HttpMethod {
607 fn from(method: Method) -> Self {
608 match method {
609 Method::GET => Self::GET,
610 Method::POST => Self::POST,
611 Method::PUT => Self::PUT,
612 Method::DELETE => Self::DELETE,
613 Method::PATCH => Self::PATCH,
614 Method::HEAD => Self::HEAD,
615 Method::OPTIONS => Self::OPTIONS,
616 Method::TRACE => Self::TRACE,
617 Method::CONNECT => Self::CONNECT,
618 _ => Self::GET, }
620 }
621}
622
623impl From<HttpMethod> for Method {
624 fn from(method: HttpMethod) -> Self {
625 match method {
626 HttpMethod::GET => Method::GET,
627 HttpMethod::POST => Method::POST,
628 HttpMethod::PUT => Method::PUT,
629 HttpMethod::DELETE => Method::DELETE,
630 HttpMethod::PATCH => Method::PATCH,
631 HttpMethod::HEAD => Method::HEAD,
632 HttpMethod::OPTIONS => Method::OPTIONS,
633 HttpMethod::TRACE => Method::TRACE,
634 HttpMethod::CONNECT => Method::CONNECT,
635 }
636 }
637}
638
639#[derive(Debug, Clone, Copy, PartialEq, Eq)]
641pub struct StatusCode(pub u16);
642
643impl StatusCode {
644 pub const OK: StatusCode = StatusCode(200);
645 pub const CREATED: StatusCode = StatusCode(201);
646 pub const NO_CONTENT: StatusCode = StatusCode(204);
647 pub const MOVED_PERMANENTLY: StatusCode = StatusCode(301);
648 pub const FOUND: StatusCode = StatusCode(302);
649 pub const BAD_REQUEST: StatusCode = StatusCode(400);
650 pub const UNAUTHORIZED: StatusCode = StatusCode(401);
651 pub const FORBIDDEN: StatusCode = StatusCode(403);
652 pub const NOT_FOUND: StatusCode = StatusCode(404);
653 pub const METHOD_NOT_ALLOWED: StatusCode = StatusCode(405);
654 pub const CONFLICT: StatusCode = StatusCode(409);
655 pub const PAYLOAD_TOO_LARGE: StatusCode = StatusCode(413);
656 pub const TOO_MANY_REQUESTS: StatusCode = StatusCode(429);
657 pub const INTERNAL_SERVER_ERROR: StatusCode = StatusCode(500);
658 pub const BAD_GATEWAY: StatusCode = StatusCode(502);
659 pub const SERVICE_UNAVAILABLE: StatusCode = StatusCode(503);
660 pub const SWITCHING_PROTOCOLS: StatusCode = StatusCode(101);
661
662 pub fn as_u16(&self) -> u16 {
663 self.0
664 }
665}
666
667impl From<HttpStatusCode> for StatusCode {
668 fn from(status: HttpStatusCode) -> Self {
669 StatusCode(status.as_u16())
670 }
671}
672
673impl From<StatusCode> for HttpStatusCode {
674 fn from(status: StatusCode) -> Self {
675 HttpStatusCode::from_u16(status.0).unwrap_or(HttpStatusCode::INTERNAL_SERVER_ERROR)
676 }
677}
678
679#[derive(Debug, Clone)]
681pub struct Headers {
682 inner: HashMap<String, String>,
683}
684
685impl Default for Headers {
686 fn default() -> Self {
687 Self::new()
688 }
689}
690
691impl Headers {
692 pub fn new() -> Self {
693 Self {
694 inner: HashMap::new(),
695 }
696 }
697
698 pub fn insert(&mut self, key: String, value: String) {
699 self.inner.insert(key.to_lowercase(), value);
700 }
701
702 pub fn get(&self, key: &str) -> Option<&String> {
703 self.inner.get(&key.to_lowercase())
704 }
705
706 pub fn iter(&self) -> impl Iterator<Item = (&String, &String)> {
707 self.inner.iter()
708 }
709
710 pub fn set(&mut self, key: &str, value: String) {
712 self.insert(key.to_string(), value);
713 }
714
715 pub fn add(&mut self, key: &str, value: String) {
717 let key_lower = key.to_lowercase();
718 if let Some(_existing) = self.inner.get(&key_lower) {
719 self.inner.insert(key_lower, value);
722 } else {
723 self.inner.insert(key_lower, value);
724 }
725 }
726}
727
728impl From<HeaderMap> for Headers {
729 fn from(headers: HeaderMap) -> Self {
730 let mut inner = HashMap::new();
731 for (key, value) in headers.iter() {
732 if let Ok(value_str) = value.to_str() {
733 inner.insert(key.to_string(), value_str.to_string());
734 }
735 }
736 Self { inner }
737 }
738}
739
740impl From<Headers> for HeaderMap {
741 fn from(headers: Headers) -> Self {
742 let mut header_map = HeaderMap::new();
743 for (key, value) in headers.inner {
744 if let (Ok(name), Ok(val)) = (
745 key.parse::<http::HeaderName>(),
746 value.parse::<http::HeaderValue>(),
747 ) {
748 header_map.insert(name, val);
749 }
750 }
751 header_map
752 }
753}
754
755#[derive(Debug, Clone)]
757pub struct Body {
758 data: Bytes,
759}
760
761impl Body {
762 pub fn empty() -> Self {
763 Self { data: Bytes::new() }
764 }
765
766 pub fn from_bytes(bytes: Bytes) -> Self {
767 Self { data: bytes }
768 }
769
770 pub fn from_string(s: &str) -> Self {
771 Self {
772 data: Bytes::from(s.to_owned()),
773 }
774 }
775
776 pub async fn bytes(&self) -> crate::error::Result<Bytes> {
777 Ok(self.data.clone())
778 }
779
780 pub fn len(&self) -> usize {
781 self.data.len()
782 }
783
784 pub fn is_empty(&self) -> bool {
785 self.data.is_empty()
786 }
787}
788
789impl From<String> for Body {
790 fn from(s: String) -> Self {
791 Self::from_bytes(Bytes::from(s))
792 }
793}
794
795impl From<&str> for Body {
796 fn from(s: &str) -> Self {
797 Self::from_bytes(Bytes::from(s.to_string()))
798 }
799}
800
801impl From<Vec<u8>> for Body {
802 fn from(data: Vec<u8>) -> Self {
803 Self::from_bytes(Bytes::from(data))
804 }
805}
806
807impl From<Bytes> for Body {
808 fn from(bytes: Bytes) -> Self {
809 Self::from_bytes(bytes)
810 }
811}
812
813#[derive(Debug, Clone, PartialEq)]
815pub struct Cookie {
816 pub name: String,
817 pub value: String,
818 pub domain: Option<String>,
819 pub path: Option<String>,
820 pub expires: Option<SystemTime>,
821 pub max_age: Option<Duration>,
822 pub secure: bool,
823 pub http_only: bool,
824 pub same_site: Option<SameSite>,
825}
826
827#[derive(Debug, Clone, PartialEq)]
828pub enum SameSite {
829 Strict,
830 Lax,
831 None,
832}
833
834impl Cookie {
835 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
836 Self {
837 name: name.into(),
838 value: value.into(),
839 domain: None,
840 path: None,
841 expires: None,
842 max_age: None,
843 secure: false,
844 http_only: false,
845 same_site: None,
846 }
847 }
848
849 pub fn domain(mut self, domain: impl Into<String>) -> Self {
850 self.domain = Some(domain.into());
851 self
852 }
853
854 pub fn path(mut self, path: impl Into<String>) -> Self {
855 self.path = Some(path.into());
856 self
857 }
858
859 pub fn expires(mut self, expires: SystemTime) -> Self {
860 self.expires = Some(expires);
861 self
862 }
863
864 pub fn max_age(mut self, max_age: Duration) -> Self {
865 self.max_age = Some(max_age);
866 self
867 }
868
869 pub fn secure(mut self, secure: bool) -> Self {
870 self.secure = secure;
871 self
872 }
873
874 pub fn http_only(mut self, http_only: bool) -> Self {
875 self.http_only = http_only;
876 self
877 }
878
879 pub fn same_site(mut self, same_site: SameSite) -> Self {
880 self.same_site = Some(same_site);
881 self
882 }
883
884 pub fn parse(header_value: &str) -> crate::error::Result<Vec<Cookie>> {
886 let mut cookies = Vec::new();
887
888 for cookie_str in header_value.split(';') {
889 let cookie_str = cookie_str.trim();
890 if let Some((name, value)) = cookie_str.split_once('=') {
891 cookies.push(Cookie::new(name.trim(), value.trim()));
892 }
893 }
894
895 Ok(cookies)
896 }
897
898 pub fn to_header_value(&self) -> String {
900 let mut result = format!("{}={}", self.name, self.value);
901
902 if let Some(ref domain) = self.domain {
903 result.push_str(&format!("; Domain={}", domain));
904 }
905
906 if let Some(ref path) = self.path {
907 result.push_str(&format!("; Path={}", path));
908 }
909
910 if let Some(expires) = self.expires
911 && let Ok(duration) = expires.duration_since(SystemTime::UNIX_EPOCH)
912 {
913 result.push_str(&format!("; Expires={}", duration.as_secs()));
914 }
915
916 if let Some(max_age) = self.max_age {
917 result.push_str(&format!("; Max-Age={}", max_age.as_secs()));
918 }
919
920 if self.secure {
921 result.push_str("; Secure");
922 }
923
924 if self.http_only {
925 result.push_str("; HttpOnly");
926 }
927
928 if let Some(ref same_site) = self.same_site {
929 let same_site_str = match same_site {
930 SameSite::Strict => "Strict",
931 SameSite::Lax => "Lax",
932 SameSite::None => "None",
933 };
934 result.push_str(&format!("; SameSite={}", same_site_str));
935 }
936
937 result
938 }
939}
940
941#[derive(Debug, Clone)]
943pub enum FormValue {
944 Text(String),
945 Binary(Vec<u8>),
946 File(FileUpload),
947}
948
949#[derive(Debug, Clone)]
951pub struct FileUpload {
952 pub filename: Option<String>,
953 pub content_type: Option<String>,
954 pub data: Bytes,
955}
956
957impl FileUpload {
958 pub fn new(data: Bytes) -> Self {
959 Self {
960 filename: None,
961 content_type: None,
962 data,
963 }
964 }
965
966 pub fn with_filename(mut self, filename: impl Into<String>) -> Self {
967 self.filename = Some(filename.into());
968 self
969 }
970
971 pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
972 self.content_type = Some(content_type.into());
973 self
974 }
975
976 pub async fn save_to(&self, path: impl Into<PathBuf>) -> crate::error::Result<()> {
978 let path = path.into();
979 std::fs::write(&path, &self.data).map_err(|e| {
980 crate::error::WebServerError::custom(format!("Failed to save file: {}", e))
981 })
982 }
983
984 pub fn size(&self) -> usize {
986 self.data.len()
987 }
988}
989
990impl Default for FileUpload {
991 fn default() -> Self {
992 Self::new(Bytes::new())
993 }
994}
995
996#[derive(Debug, Clone)]
998pub struct MultipartForm {
999 pub fields: HashMap<String, Vec<FormValue>>,
1000 parts: Vec<MultipartPart>,
1001}
1002
1003#[derive(Debug, Clone, Default)]
1005pub struct MultipartPart {
1006 pub field_name: Option<String>,
1008 pub filename: Option<String>,
1010 pub headers: HashMap<String, String>,
1012 pub content: Vec<u8>,
1014}
1015
1016impl MultipartPart {
1017 pub fn new() -> Self {
1019 Self::default()
1020 }
1021}
1022
1023impl MultipartForm {
1024 pub fn new() -> Self {
1025 Self {
1026 fields: HashMap::new(),
1027 parts: Vec::new(),
1028 }
1029 }
1030
1031 pub fn add_part(&mut self, part: MultipartPart) {
1033 if let Some(field_name) = &part.field_name {
1035 let value = if let Some(filename) = &part.filename {
1036 let content_type = part
1037 .headers
1038 .get("Content-Type")
1039 .cloned()
1040 .unwrap_or_else(|| "application/octet-stream".to_string());
1041
1042 let file_upload = FileUpload::new(Bytes::from(part.content.clone()))
1043 .with_filename(filename.clone())
1044 .with_content_type(content_type);
1045
1046 FormValue::File(file_upload)
1047 } else {
1048 match String::from_utf8(part.content.clone()) {
1050 Ok(text) => FormValue::Text(text),
1051 Err(_) => FormValue::Binary(part.content.clone()),
1052 }
1053 };
1054
1055 self.fields
1056 .entry(field_name.clone())
1057 .or_default()
1058 .push(value);
1059 }
1060
1061 self.parts.push(part);
1063 }
1064
1065 pub fn get_text(&self, name: &str) -> Option<&str> {
1067 self.fields.get(name)?.first().and_then(|v| match v {
1068 FormValue::Text(text) => Some(text.as_str()),
1069 _ => None,
1070 })
1071 }
1072
1073 pub fn get_all_text(&self, name: &str) -> Vec<&str> {
1075 self.fields
1076 .get(name)
1077 .map(|values| {
1078 values
1079 .iter()
1080 .filter_map(|v| match v {
1081 FormValue::Text(text) => Some(text.as_str()),
1082 _ => None,
1083 })
1084 .collect()
1085 })
1086 .unwrap_or_default()
1087 }
1088
1089 pub fn get_file(&self, name: &str) -> Option<&FileUpload> {
1091 self.fields.get(name)?.first().and_then(|v| match v {
1092 FormValue::File(file) => Some(file),
1093 _ => None,
1094 })
1095 }
1096
1097 pub fn get_all_files(&self, name: &str) -> Vec<&FileUpload> {
1099 self.fields
1100 .get(name)
1101 .map(|values| {
1102 values
1103 .iter()
1104 .filter_map(|v| match v {
1105 FormValue::File(file) => Some(file),
1106 _ => None,
1107 })
1108 .collect()
1109 })
1110 .unwrap_or_default()
1111 }
1112
1113 pub fn add_text(&mut self, name: String, value: String) {
1115 self.fields
1116 .entry(name)
1117 .or_default()
1118 .push(FormValue::Text(value));
1119 }
1120
1121 pub fn add_file(&mut self, name: String, file: FileUpload) {
1123 self.fields
1124 .entry(name)
1125 .or_default()
1126 .push(FormValue::File(file));
1127 }
1128}
1129
1130impl Default for MultipartForm {
1131 fn default() -> Self {
1132 Self::new()
1133 }
1134}
1135
1136#[derive(Debug, Clone, PartialEq)]
1138pub enum WebSocketMessage {
1139 Text(String),
1141 Binary(Vec<u8>),
1143 Ping(Vec<u8>),
1145 Pong(Vec<u8>),
1147 Close(Option<WebSocketCloseCode>),
1149}
1150
1151#[derive(Debug, Clone, Copy, PartialEq)]
1153pub enum WebSocketCloseCode {
1154 Normal = 1000,
1156 GoingAway = 1001,
1158 ProtocolError = 1002,
1160 UnsupportedData = 1003,
1162 InvalidFramePayloadData = 1007,
1164 PolicyViolation = 1008,
1166 MessageTooBig = 1009,
1168 MandatoryExtension = 1010,
1170 InternalServerError = 1011,
1172}
1173
1174pub trait WebSocketHandler: Send + Sync + 'static {
1176 fn on_connect(&self) -> impl std::future::Future<Output = ()> + Send;
1178
1179 fn on_message(
1181 &self,
1182 message: WebSocketMessage,
1183 ) -> impl std::future::Future<Output = crate::error::Result<Option<WebSocketMessage>>> + Send;
1184
1185 fn on_close(
1187 &self,
1188 code: Option<WebSocketCloseCode>,
1189 ) -> impl std::future::Future<Output = ()> + Send;
1190}
1191
1192#[derive(Debug)]
1194pub struct WebSocketUpgrade {
1195 pub request: Request,
1197 pub key: String,
1199 pub version: String,
1201 pub protocols: Vec<String>,
1203}
1204
1205impl WebSocketUpgrade {
1206 pub fn from_request(request: Request) -> crate::error::Result<Self> {
1208 let key = request
1209 .headers
1210 .get("Sec-WebSocket-Key")
1211 .ok_or_else(|| {
1212 crate::error::WebServerError::custom("Missing Sec-WebSocket-Key header")
1213 })?
1214 .clone();
1215
1216 let version = request
1217 .headers
1218 .get("Sec-WebSocket-Version")
1219 .unwrap_or(&"13".to_string())
1220 .clone();
1221
1222 let protocols = request
1223 .headers
1224 .get("Sec-WebSocket-Protocol")
1225 .map(|s| s.split(',').map(|p| p.trim().to_string()).collect())
1226 .unwrap_or_default();
1227
1228 Ok(Self {
1229 request,
1230 key,
1231 version,
1232 protocols,
1233 })
1234 }
1235
1236 pub fn accept<H>(self, handler: H) -> WebSocketResponse<H>
1238 where
1239 H: WebSocketHandler,
1240 {
1241 WebSocketResponse {
1242 upgrade: self,
1243 handler,
1244 }
1245 }
1246
1247 pub fn generate_accept_key(&self) -> String {
1249 use base64::Engine;
1250 use sha1::{Digest, Sha1};
1251
1252 const WEBSOCKET_MAGIC_KEY: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
1253
1254 let mut hasher = Sha1::new();
1255 hasher.update(self.key.as_bytes());
1256 hasher.update(WEBSOCKET_MAGIC_KEY.as_bytes());
1257 let digest = hasher.finalize();
1258
1259 base64::engine::general_purpose::STANDARD.encode(digest)
1260 }
1261}
1262
1263#[derive(Debug)]
1265pub struct WebSocketResponse<H: WebSocketHandler> {
1266 pub upgrade: WebSocketUpgrade,
1267 pub handler: H,
1268}
1269
1270