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}