Skip to main content

embedded_svc/utils/
http.rs

1use core::str;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4#[cfg_attr(feature = "defmt", derive(defmt::Format))]
5pub enum HeaderSetError {
6    TooManyHeaders,
7}
8
9#[derive(Debug)]
10#[cfg_attr(feature = "defmt", derive(defmt::Format))]
11pub struct Headers<'b, const N: usize = 64>([(&'b str, &'b str); N]);
12
13impl<'b, const N: usize> Headers<'b, N> {
14    pub const fn new() -> Self {
15        Self([("", ""); N])
16    }
17
18    pub fn content_len(&self) -> Option<u64> {
19        self.get("Content-Length")
20            .map(|content_len_str| content_len_str.parse::<u64>().unwrap())
21    }
22
23    pub fn content_type(&self) -> Option<&str> {
24        self.get("Content-Type")
25    }
26
27    pub fn content_encoding(&self) -> Option<&str> {
28        self.get("Content-Encoding")
29    }
30
31    pub fn transfer_encoding(&self) -> Option<&str> {
32        self.get("Transfer-Encoding")
33    }
34
35    pub fn host(&self) -> Option<&str> {
36        self.get("Host")
37    }
38
39    pub fn connection(&self) -> Option<&str> {
40        self.get("Connection")
41    }
42
43    pub fn cache_control(&self) -> Option<&str> {
44        self.get("Cache-Control")
45    }
46
47    pub fn upgrade(&self) -> Option<&str> {
48        self.get("Upgrade")
49    }
50
51    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
52        self.0
53            .iter()
54            .filter(|header| !header.0.is_empty())
55            .map(|header| (header.0, header.1))
56    }
57
58    pub fn get(&self, name: &str) -> Option<&str> {
59        self.iter()
60            .find(|(hname, _)| name.eq_ignore_ascii_case(hname))
61            .map(|(_, value)| value)
62    }
63
64    pub fn try_set(&mut self, name: &'b str, value: &'b str) -> Result<&mut Self, HeaderSetError> {
65        for header in &mut self.0 {
66            if header.0.is_empty() || header.0.eq_ignore_ascii_case(name) {
67                *header = (name, value);
68                return Ok(self);
69            }
70        }
71
72        Err(HeaderSetError::TooManyHeaders)
73    }
74
75    pub fn set(&mut self, name: &'b str, value: &'b str) -> &mut Self {
76        self.try_set(name, value).expect("No space left")
77    }
78
79    pub fn remove(&mut self, name: &str) -> &mut Self {
80        let index = self
81            .0
82            .iter()
83            .enumerate()
84            .find(|(_, header)| header.0.eq_ignore_ascii_case(name));
85
86        if let Some((mut index, _)) = index {
87            while index < self.0.len() - 1 {
88                self.0[index] = self.0[index + 1];
89
90                index += 1;
91            }
92
93            self.0[index] = ("", "");
94        }
95
96        self
97    }
98
99    pub fn set_content_len(
100        &mut self,
101        content_len: u64,
102        buf: &'b mut heapless::String<20>,
103    ) -> &mut Self {
104        *buf = heapless::String::<20>::try_from(content_len).unwrap();
105
106        self.set("Content-Length", buf.as_str())
107    }
108
109    pub fn set_content_type(&mut self, content_type: &'b str) -> &mut Self {
110        self.set("Content-Type", content_type)
111    }
112
113    pub fn set_content_encoding(&mut self, content_encoding: &'b str) -> &mut Self {
114        self.set("Content-Encoding", content_encoding)
115    }
116
117    pub fn set_transfer_encoding(&mut self, transfer_encoding: &'b str) -> &mut Self {
118        self.set("Transfer-Encoding", transfer_encoding)
119    }
120
121    pub fn set_transfer_encoding_chunked(&mut self) -> &mut Self {
122        self.set_transfer_encoding("Chunked")
123    }
124
125    pub fn set_host(&mut self, host: &'b str) -> &mut Self {
126        self.set("Host", host)
127    }
128
129    pub fn set_connection(&mut self, connection: &'b str) -> &mut Self {
130        self.set("Connection", connection)
131    }
132
133    pub fn set_connection_close(&mut self) -> &mut Self {
134        self.set_connection("Close")
135    }
136
137    pub fn set_connection_keep_alive(&mut self) -> &mut Self {
138        self.set_connection("Keep-Alive")
139    }
140
141    pub fn set_connection_upgrade(&mut self) -> &mut Self {
142        self.set_connection("Upgrade")
143    }
144
145    pub fn set_cache_control(&mut self, cache: &'b str) -> &mut Self {
146        self.set("Cache-Control", cache)
147    }
148
149    pub fn set_cache_control_no_cache(&mut self) -> &mut Self {
150        self.set_cache_control("No-Cache")
151    }
152
153    pub fn set_upgrade(&mut self, upgrade: &'b str) -> &mut Self {
154        self.set("Upgrade", upgrade)
155    }
156
157    pub fn set_upgrade_websocket(&mut self) -> &mut Self {
158        self.set_upgrade("websocket")
159    }
160
161    pub fn as_slice(&self) -> &[(&'b str, &'b str)] {
162        let index = self
163            .0
164            .iter()
165            .enumerate()
166            .find(|(_, header)| header.0.is_empty())
167            .map(|(index, _)| index)
168            .unwrap_or(N);
169
170        &self.0[..index]
171    }
172
173    pub fn release(self) -> [(&'b str, &'b str); N] {
174        self.0
175    }
176}
177
178impl<const N: usize> Default for Headers<'_, N> {
179    fn default() -> Self {
180        Self::new()
181    }
182}
183
184impl<const N: usize> crate::http::Headers for Headers<'_, N> {
185    fn header(&self, name: &str) -> Option<&'_ str> {
186        self.get(name)
187    }
188}
189
190pub mod cookies {
191    use core::iter;
192    use core::str::Split;
193
194    pub struct Cookies<'a>(&'a str);
195
196    impl<'a> Cookies<'a> {
197        pub fn new(cookies_str: &'a str) -> Self {
198            Self(cookies_str)
199        }
200
201        pub fn get(&self, name: &str) -> Option<&'a str> {
202            Cookies::new(self.0)
203                .into_iter()
204                .find(|(key, _)| *key == name)
205                .map(|(_, value)| value)
206        }
207
208        pub fn set<'b, I>(
209            iter: I,
210            name: &'b str,
211            value: &'b str,
212        ) -> impl Iterator<Item = (&'b str, &'b str)>
213        where
214            I: Iterator<Item = (&'b str, &'b str)> + 'b,
215        {
216            iter.filter(move |(key, _)| *key != name)
217                .chain(core::iter::once((name, value)))
218        }
219
220        pub fn remove<'b, I>(iter: I, name: &'b str) -> impl Iterator<Item = (&'b str, &'b str)>
221        where
222            I: Iterator<Item = (&'b str, &'b str)> + 'b,
223        {
224            iter.filter(move |(key, _)| *key != name)
225        }
226
227        pub fn serialize<'b, I>(iter: I) -> impl Iterator<Item = &'b str>
228        where
229            I: Iterator<Item = (&'b str, &'b str)> + 'b,
230        {
231            iter.flat_map(|(k, v)| {
232                iter::once(";")
233                    .chain(iter::once(k))
234                    .chain(iter::once("="))
235                    .chain(iter::once(v))
236            })
237            .skip(1)
238        }
239    }
240
241    impl<'a> IntoIterator for Cookies<'a> {
242        type Item = (&'a str, &'a str);
243
244        type IntoIter = CookieIterator<'a>;
245
246        fn into_iter(self) -> Self::IntoIter {
247            CookieIterator::new(self.0)
248        }
249    }
250
251    pub struct CookieIterator<'a>(Split<'a, char>);
252
253    impl<'a> CookieIterator<'a> {
254        pub fn new(cookies: &'a str) -> Self {
255            Self(cookies.split(';'))
256        }
257    }
258
259    impl<'a> Iterator for CookieIterator<'a> {
260        type Item = (&'a str, &'a str);
261
262        fn next(&mut self) -> Option<Self::Item> {
263            self.0
264                .next()
265                .map(|cookie_pair| cookie_pair.split('='))
266                .and_then(|mut cookie_pair| {
267                    cookie_pair
268                        .next()
269                        .map(|name| cookie_pair.next().map(|value| (name, value)))
270                })
271                .flatten()
272        }
273    }
274}
275
276pub mod server {
277    pub mod registration {
278        use crate::http::Method;
279
280        pub struct ChainHandler<H, N> {
281            pub path: &'static str,
282            pub method: Method,
283            pub handler: H,
284            pub next: N,
285        }
286
287        impl<H, N> ChainHandler<H, N> {
288            pub fn get<H2>(
289                self,
290                path: &'static str,
291                handler: H2,
292            ) -> ChainHandler<H2, ChainHandler<H, N>> {
293                self.request(path, Method::Get, handler)
294            }
295
296            pub fn post<H2>(
297                self,
298                path: &'static str,
299                handler: H2,
300            ) -> ChainHandler<H2, ChainHandler<H, N>> {
301                self.request(path, Method::Post, handler)
302            }
303
304            pub fn put<H2>(
305                self,
306                path: &'static str,
307                handler: H2,
308            ) -> ChainHandler<H2, ChainHandler<H, N>> {
309                self.request(path, Method::Put, handler)
310            }
311
312            pub fn delete<H2>(
313                self,
314                path: &'static str,
315                handler: H2,
316            ) -> ChainHandler<H2, ChainHandler<H, N>> {
317                self.request(path, Method::Delete, handler)
318            }
319
320            pub fn request<H2>(
321                self,
322                path: &'static str,
323                method: Method,
324                handler: H2,
325            ) -> ChainHandler<H2, ChainHandler<H, N>> {
326                ChainHandler {
327                    path,
328                    method,
329                    handler,
330                    next: self,
331                }
332            }
333        }
334
335        pub struct ChainRoot;
336
337        impl ChainRoot {
338            pub fn get<H2>(self, path: &'static str, handler: H2) -> ChainHandler<H2, ChainRoot> {
339                self.request(path, Method::Get, handler)
340            }
341
342            pub fn post<H2>(self, path: &'static str, handler: H2) -> ChainHandler<H2, ChainRoot> {
343                self.request(path, Method::Post, handler)
344            }
345
346            pub fn put<H2>(self, path: &'static str, handler: H2) -> ChainHandler<H2, ChainRoot> {
347                self.request(path, Method::Put, handler)
348            }
349
350            pub fn delete<H2>(
351                self,
352                path: &'static str,
353                handler: H2,
354            ) -> ChainHandler<H2, ChainRoot> {
355                self.request(path, Method::Delete, handler)
356            }
357
358            pub fn request<H2>(
359                self,
360                path: &'static str,
361                method: Method,
362                handler: H2,
363            ) -> ChainHandler<H2, ChainRoot> {
364                ChainHandler {
365                    path,
366                    method,
367                    handler,
368                    next: ChainRoot,
369                }
370            }
371        }
372    }
373
374    // TODO: Commented out as it needs a mutex, yet `embedded-svc` no longer has one
375    // An option is to depend on `embassy-sync`, yet this decision would be deplayed until
376    // we figure out in general what to do with the utility code in `embedded-svc`.
377    // pub mod session {
378    //     use core::convert::TryInto;
379    //     use core::fmt;
380    //     use core::time::Duration;
381
382    //     use crate::http::server::*;
383
384    //     use crate::utils::http::cookies::*;
385    //     use crate::utils::mutex::{Mutex, RawMutex};
386
387    //     #[derive(Debug)]
388    //     #[cfg_attr(feature = "defmt", derive(defmt::Format))]
389    //     pub enum SessionError {
390    //         MaxSessionsReachedError,
391    //     }
392
393    //     impl fmt::Display for SessionError {
394    //         fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395    //             match self {
396    //                 Self::MaxSessionsReachedError => {
397    //                     write!(f, "Max number of sessions reached")
398    //                 }
399    //             }
400    //         }
401    //     }
402
403    //     #[cfg(feature = "std")]
404    //     impl std::error::Error for SessionError {}
405
406    //     pub trait Session: Send {
407    //         type SessionData;
408
409    //         fn is_existing(&self, session_id: Option<&str>) -> bool;
410
411    //         fn with_existing<R, F>(&self, session_id: Option<&str>, f: F) -> Option<R>
412    //         where
413    //             F: FnOnce(&mut Self::SessionData) -> R;
414
415    //         fn with<R, F>(&self, session_id: &str, f: F) -> Result<R, SessionError>
416    //         where
417    //             F: FnOnce(&mut Self::SessionData) -> R;
418
419    //         fn invalidate(&self, session_id: Option<&str>) -> bool;
420    //     }
421
422    //     #[derive(Debug, Default)]
423    //     #[cfg_attr(feature = "defmt", derive(defmt::Format))]
424    //     pub struct SessionData<S> {
425    //         id: heapless::String<32>,
426    //         last_accessed: Duration,
427    //         timeout: Duration,
428    //         data: S,
429    //     }
430
431    //     pub struct SessionImpl<M, S, T, const N: usize = 16>
432    //     where
433    //         M: RawMutex,
434    //         S: Default + Send,
435    //     {
436    //         current_time: T,
437    //         data: Mutex<M, [SessionData<S>; N]>,
438    //         default_session_timeout: Duration,
439    //     }
440
441    //     impl<M, S, T, const N: usize> SessionImpl<M, S, T, N>
442    //     where
443    //         M: RawMutex,
444    //         S: Default + Send,
445    //     {
446    //         fn cleanup(&self, current_time: Duration) {
447    //             let mut data = self.data.lock();
448
449    //             for entry in &mut *data {
450    //                 if entry.last_accessed + entry.timeout < current_time {
451    //                     entry.id = heapless::String::new();
452    //                 }
453    //             }
454    //         }
455    //     }
456
457    //     impl<M, S, T, const N: usize> Session for SessionImpl<M, S, T, N>
458    //     where
459    //         M: RawMutex + Send + Sync,
460    //         S: Default + Send,
461    //         T: Fn() -> Duration + Send,
462    //     {
463    //         type SessionData = S;
464
465    //         fn is_existing(&self, session_id: Option<&str>) -> bool {
466    //             let current_time = (self.current_time)();
467    //             self.cleanup(current_time);
468
469    //             if let Some(session_id) = session_id {
470    //                 let mut data = self.data.lock();
471
472    //                 data.iter_mut()
473    //                     .find(|entry| entry.id.as_str() == session_id)
474    //                     .map(|entry| entry.last_accessed = current_time)
475    //                     .is_some()
476    //             } else {
477    //                 false
478    //             }
479    //         }
480
481    //         fn with_existing<R, F>(&self, session_id: Option<&str>, f: F) -> Option<R>
482    //         where
483    //             F: FnOnce(&mut Self::SessionData) -> R,
484    //         {
485    //             let current_time = (self.current_time)();
486    //             self.cleanup(current_time);
487
488    //             if let Some(session_id) = session_id {
489    //                 let mut data = self.data.lock();
490
491    //                 data.iter_mut()
492    //                     .find(|entry| entry.id.as_str() == session_id)
493    //                     .map(|entry| {
494    //                         entry.last_accessed = current_time;
495    //                         f(&mut entry.data)
496    //                     })
497    //             } else {
498    //                 None
499    //             }
500    //         }
501
502    //         fn with<'b, R, F>(&self, session_id: &str, f: F) -> Result<R, SessionError>
503    //         where
504    //             F: FnOnce(&mut Self::SessionData) -> R,
505    //         {
506    //             let current_time = (self.current_time)();
507    //             self.cleanup(current_time);
508
509    //             let mut data = self.data.lock();
510
511    //             if let Some(entry) = data
512    //                 .iter_mut()
513    //                 .find(|entry| entry.id.as_str() == session_id)
514    //                 .map(|entry| {
515    //                     entry.last_accessed = current_time;
516
517    //                     entry
518    //                 })
519    //             {
520    //                 Ok(f(&mut entry.data))
521    //             } else if let Some(entry) = data.iter_mut().find(|entry| entry.id == "") {
522    //                 entry.id = session_id.try_into().unwrap();
523    //                 entry.data = Default::default();
524    //                 entry.timeout = self.default_session_timeout;
525    //                 entry.last_accessed = current_time;
526
527    //                 Ok(f(&mut entry.data))
528    //             } else {
529    //                 Err(SessionError::MaxSessionsReachedError)
530    //             }
531    //         }
532
533    //         fn invalidate(&self, session_id: Option<&str>) -> bool {
534    //             let current_time = (self.current_time)();
535    //             self.cleanup(current_time);
536
537    //             if let Some(session_id) = session_id {
538    //                 let mut data = self.data.lock();
539
540    //                 if let Some(entry) = data
541    //                     .iter_mut()
542    //                     .find(|entry| entry.id.as_str() == session_id)
543    //                 {
544    //                     entry.id = heapless::String::new();
545    //                     true
546    //                 } else {
547    //                     false
548    //                 }
549    //             } else {
550    //                 false
551    //             }
552    //         }
553    //     }
554
555    //     pub fn get_cookie_session_id<H>(headers: &H) -> Option<&str>
556    //     where
557    //         H: Headers,
558    //     {
559    //         headers
560    //             .header("Cookie")
561    //             .and_then(|cookies_str| Cookies::new(cookies_str).get("SESSIONID"))
562    //     }
563
564    //     pub fn set_cookie_session_id<'a, const N: usize, H>(
565    //         headers: H,
566    //         session_id: &str,
567    //         cookies: &mut heapless::String<N>,
568    //     ) where
569    //         H: Headers + 'a,
570    //     {
571    //         let cookies_str = headers.header("Cookie").unwrap_or("");
572
573    //         for cookie in Cookies::serialize(Cookies::set(
574    //             Cookies::new(cookies_str).into_iter(),
575    //             "SESSIONID",
576    //             session_id,
577    //         )) {
578    //             cookies.push_str(cookie).unwrap(); // TODO
579    //         }
580    //     }
581    // }
582}