edge_http/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![allow(async_fn_in_trait)]
3#![warn(clippy::large_futures)]
4#![allow(clippy::uninlined_format_args)]
5#![allow(unknown_lints)]
6
7use core::fmt::Display;
8use core::str;
9
10use httparse::{Header, EMPTY_HEADER};
11
12use ws::{is_upgrade_accepted, is_upgrade_request, MAX_BASE64_KEY_RESPONSE_LEN, NONCE_LEN};
13
14pub const DEFAULT_MAX_HEADERS_COUNT: usize = 64;
15
16// This mod MUST go first, so that the others see its macros.
17pub(crate) mod fmt;
18
19#[cfg(feature = "io")]
20pub mod io;
21
22/// Errors related to invalid combinations of connection type
23/// and body type (Content-Length, Transfer-Encoding) in the headers
24#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
25pub enum HeadersMismatchError {
26    /// Connection type mismatch: Keep-Alive connection type in the response,
27    /// while the request contained a Close connection type
28    ResponseConnectionTypeMismatchError,
29    /// Body type mismatch: the body type in the headers cannot be used with the specified connection type and HTTP protocol.
30    /// This is often a user-error, but might also come from the other peer not following the protocol.
31    /// I.e.:
32    /// - Chunked body with an HTTP1.0 connection
33    /// - Raw body with a Keep-Alive connection
34    /// - etc.
35    BodyTypeError(&'static str),
36}
37
38impl Display for HeadersMismatchError {
39    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40        match self {
41            Self::ResponseConnectionTypeMismatchError => write!(
42                f,
43                "Response connection type is different from the request connection type"
44            ),
45            Self::BodyTypeError(s) => write!(f, "Body type mismatch: {s}"),
46        }
47    }
48}
49
50#[cfg(feature = "defmt")]
51impl defmt::Format for HeadersMismatchError {
52    fn format(&self, f: defmt::Formatter<'_>) {
53        match self {
54            Self::ResponseConnectionTypeMismatchError => defmt::write!(
55                f,
56                "Response connection type is different from the request connection type"
57            ),
58            Self::BodyTypeError(s) => defmt::write!(f, "Body type mismatch: {}", s),
59        }
60    }
61}
62
63/// Http methods
64#[derive(Copy, Clone, Debug, PartialEq, Eq)]
65#[cfg_attr(feature = "std", derive(Hash))]
66pub enum Method {
67    Delete,
68    Get,
69    Head,
70    Post,
71    Put,
72    Connect,
73    Options,
74    Trace,
75    Copy,
76    Lock,
77    MkCol,
78    Move,
79    Propfind,
80    Proppatch,
81    Search,
82    Unlock,
83    Bind,
84    Rebind,
85    Unbind,
86    Acl,
87    Report,
88    MkActivity,
89    Checkout,
90    Merge,
91    MSearch,
92    Notify,
93    Subscribe,
94    Unsubscribe,
95    Patch,
96    Purge,
97    MkCalendar,
98    Link,
99    Unlink,
100}
101
102impl Method {
103    pub fn new(method: &str) -> Option<Self> {
104        if method.eq_ignore_ascii_case("Delete") {
105            Some(Self::Delete)
106        } else if method.eq_ignore_ascii_case("Get") {
107            Some(Self::Get)
108        } else if method.eq_ignore_ascii_case("Head") {
109            Some(Self::Head)
110        } else if method.eq_ignore_ascii_case("Post") {
111            Some(Self::Post)
112        } else if method.eq_ignore_ascii_case("Put") {
113            Some(Self::Put)
114        } else if method.eq_ignore_ascii_case("Connect") {
115            Some(Self::Connect)
116        } else if method.eq_ignore_ascii_case("Options") {
117            Some(Self::Options)
118        } else if method.eq_ignore_ascii_case("Trace") {
119            Some(Self::Trace)
120        } else if method.eq_ignore_ascii_case("Copy") {
121            Some(Self::Copy)
122        } else if method.eq_ignore_ascii_case("Lock") {
123            Some(Self::Lock)
124        } else if method.eq_ignore_ascii_case("MkCol") {
125            Some(Self::MkCol)
126        } else if method.eq_ignore_ascii_case("Move") {
127            Some(Self::Move)
128        } else if method.eq_ignore_ascii_case("Propfind") {
129            Some(Self::Propfind)
130        } else if method.eq_ignore_ascii_case("Proppatch") {
131            Some(Self::Proppatch)
132        } else if method.eq_ignore_ascii_case("Search") {
133            Some(Self::Search)
134        } else if method.eq_ignore_ascii_case("Unlock") {
135            Some(Self::Unlock)
136        } else if method.eq_ignore_ascii_case("Bind") {
137            Some(Self::Bind)
138        } else if method.eq_ignore_ascii_case("Rebind") {
139            Some(Self::Rebind)
140        } else if method.eq_ignore_ascii_case("Unbind") {
141            Some(Self::Unbind)
142        } else if method.eq_ignore_ascii_case("Acl") {
143            Some(Self::Acl)
144        } else if method.eq_ignore_ascii_case("Report") {
145            Some(Self::Report)
146        } else if method.eq_ignore_ascii_case("MkActivity") {
147            Some(Self::MkActivity)
148        } else if method.eq_ignore_ascii_case("Checkout") {
149            Some(Self::Checkout)
150        } else if method.eq_ignore_ascii_case("Merge") {
151            Some(Self::Merge)
152        } else if method.eq_ignore_ascii_case("MSearch") {
153            Some(Self::MSearch)
154        } else if method.eq_ignore_ascii_case("Notify") {
155            Some(Self::Notify)
156        } else if method.eq_ignore_ascii_case("Subscribe") {
157            Some(Self::Subscribe)
158        } else if method.eq_ignore_ascii_case("Unsubscribe") {
159            Some(Self::Unsubscribe)
160        } else if method.eq_ignore_ascii_case("Patch") {
161            Some(Self::Patch)
162        } else if method.eq_ignore_ascii_case("Purge") {
163            Some(Self::Purge)
164        } else if method.eq_ignore_ascii_case("MkCalendar") {
165            Some(Self::MkCalendar)
166        } else if method.eq_ignore_ascii_case("Link") {
167            Some(Self::Link)
168        } else if method.eq_ignore_ascii_case("Unlink") {
169            Some(Self::Unlink)
170        } else {
171            None
172        }
173    }
174
175    fn as_str(&self) -> &'static str {
176        match self {
177            Self::Delete => "DELETE",
178            Self::Get => "GET",
179            Self::Head => "HEAD",
180            Self::Post => "POST",
181            Self::Put => "PUT",
182            Self::Connect => "CONNECT",
183            Self::Options => "OPTIONS",
184            Self::Trace => "TRACE",
185            Self::Copy => "COPY",
186            Self::Lock => "LOCK",
187            Self::MkCol => "MKCOL",
188            Self::Move => "MOVE",
189            Self::Propfind => "PROPFIND",
190            Self::Proppatch => "PROPPATCH",
191            Self::Search => "SEARCH",
192            Self::Unlock => "UNLOCK",
193            Self::Bind => "BIND",
194            Self::Rebind => "REBIND",
195            Self::Unbind => "UNBIND",
196            Self::Acl => "ACL",
197            Self::Report => "REPORT",
198            Self::MkActivity => "MKACTIVITY",
199            Self::Checkout => "CHECKOUT",
200            Self::Merge => "MERGE",
201            Self::MSearch => "MSEARCH",
202            Self::Notify => "NOTIFY",
203            Self::Subscribe => "SUBSCRIBE",
204            Self::Unsubscribe => "UNSUBSCRIBE",
205            Self::Patch => "PATCH",
206            Self::Purge => "PURGE",
207            Self::MkCalendar => "MKCALENDAR",
208            Self::Link => "LINK",
209            Self::Unlink => "UNLINK",
210        }
211    }
212}
213
214impl Display for Method {
215    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
216        write!(f, "{}", self.as_str())
217    }
218}
219
220#[cfg(feature = "defmt")]
221impl defmt::Format for Method {
222    fn format(&self, f: defmt::Formatter<'_>) {
223        defmt::write!(f, "{}", self.as_str())
224    }
225}
226
227/// HTTP headers
228#[derive(Debug)]
229pub struct Headers<'b, const N: usize = 64>([httparse::Header<'b>; N]);
230
231impl<'b, const N: usize> Headers<'b, N> {
232    /// Create a new Headers instance
233    #[inline(always)]
234    pub const fn new() -> Self {
235        Self([httparse::EMPTY_HEADER; N])
236    }
237
238    /// Utility method to return the value of the `Content-Length` header, if present
239    pub fn content_len(&self) -> Option<u64> {
240        self.get("Content-Length").map(|content_len_str| {
241            unwrap!(
242                content_len_str.parse::<u64>(),
243                "Invalid Content-Length header"
244            )
245        })
246    }
247
248    /// Utility method to return the value of the `Content-Type` header, if present
249    pub fn content_type(&self) -> Option<&str> {
250        self.get("Content-Type")
251    }
252
253    /// Utility method to return the value of the `Content-Encoding` header, if present
254    pub fn content_encoding(&self) -> Option<&str> {
255        self.get("Content-Encoding")
256    }
257
258    /// Utility method to return the value of the `Transfer-Encoding` header, if present
259    pub fn transfer_encoding(&self) -> Option<&str> {
260        self.get("Transfer-Encoding")
261    }
262
263    /// Utility method to return the value of the `Host` header, if present
264    pub fn host(&self) -> Option<&str> {
265        self.get("Host")
266    }
267
268    /// Utility method to return the value of the `Connection` header, if present
269    pub fn connection(&self) -> Option<&str> {
270        self.get("Connection")
271    }
272
273    /// Utility method to return the value of the `Cache-Control` header, if present
274    pub fn cache_control(&self) -> Option<&str> {
275        self.get("Cache-Control")
276    }
277
278    /// Utility method to return the value of the `Upgrade` header, if present
279    pub fn upgrade(&self) -> Option<&str> {
280        self.get("Upgrade")
281    }
282
283    /// Iterate over all headers
284    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
285        self.iter_raw()
286            .map(|(name, value)| (name, unsafe { str::from_utf8_unchecked(value) }))
287    }
288
289    /// Iterate over all headers, returning the values as raw byte slices
290    pub fn iter_raw(&self) -> impl Iterator<Item = (&str, &[u8])> {
291        self.0
292            .iter()
293            .filter(|header| !header.name.is_empty())
294            .map(|header| (header.name, header.value))
295    }
296
297    /// Get the value of a header by name
298    pub fn get(&self, name: &str) -> Option<&str> {
299        self.iter()
300            .find(|(hname, _)| name.eq_ignore_ascii_case(hname))
301            .map(|(_, value)| value)
302    }
303
304    /// Get the raw value of a header by name, returning the value as a raw byte slice
305    pub fn get_raw(&self, name: &str) -> Option<&[u8]> {
306        self.iter_raw()
307            .find(|(hname, _)| name.eq_ignore_ascii_case(hname))
308            .map(|(_, value)| value)
309    }
310
311    /// Set a header by name and value
312    pub fn set(&mut self, name: &'b str, value: &'b str) -> &mut Self {
313        self.set_raw(name, value.as_bytes())
314    }
315
316    /// Set a header by name and value, using a raw byte slice for the value
317    pub fn set_raw(&mut self, name: &'b str, value: &'b [u8]) -> &mut Self {
318        if !name.is_empty() {
319            for header in &mut self.0 {
320                if header.name.is_empty() || header.name.eq_ignore_ascii_case(name) {
321                    *header = Header { name, value };
322                    return self;
323                }
324            }
325
326            panic!("No space left");
327        } else {
328            self.remove(name)
329        }
330    }
331
332    /// Remove a header by name
333    pub fn remove(&mut self, name: &str) -> &mut Self {
334        let index = self
335            .0
336            .iter()
337            .enumerate()
338            .find(|(_, header)| header.name.eq_ignore_ascii_case(name));
339
340        if let Some((mut index, _)) = index {
341            while index < self.0.len() - 1 {
342                self.0[index] = self.0[index + 1];
343
344                index += 1;
345            }
346
347            self.0[index] = EMPTY_HEADER;
348        }
349
350        self
351    }
352
353    /// A utility method to set the `Content-Length` header
354    pub fn set_content_len(
355        &mut self,
356        content_len: u64,
357        buf: &'b mut heapless::String<20>,
358    ) -> &mut Self {
359        *buf = unwrap!(content_len.try_into());
360
361        self.set("Content-Length", buf.as_str())
362    }
363
364    /// A utility method to set the `Content-Type` header
365    pub fn set_content_type(&mut self, content_type: &'b str) -> &mut Self {
366        self.set("Content-Type", content_type)
367    }
368
369    /// A utility method to set the `Content-Encoding` header
370    pub fn set_content_encoding(&mut self, content_encoding: &'b str) -> &mut Self {
371        self.set("Content-Encoding", content_encoding)
372    }
373
374    /// A utility method to set the `Transfer-Encoding` header
375    pub fn set_transfer_encoding(&mut self, transfer_encoding: &'b str) -> &mut Self {
376        self.set("Transfer-Encoding", transfer_encoding)
377    }
378
379    /// A utility method to set the `Transfer-Encoding: Chunked` header
380    pub fn set_transfer_encoding_chunked(&mut self) -> &mut Self {
381        self.set_transfer_encoding("Chunked")
382    }
383
384    /// A utility method to set the `Host` header
385    pub fn set_host(&mut self, host: &'b str) -> &mut Self {
386        self.set("Host", host)
387    }
388
389    /// A utility method to set the `Connection` header
390    pub fn set_connection(&mut self, connection: &'b str) -> &mut Self {
391        self.set("Connection", connection)
392    }
393
394    /// A utility method to set the `Connection: Close` header
395    pub fn set_connection_close(&mut self) -> &mut Self {
396        self.set_connection("Close")
397    }
398
399    /// A utility method to set the `Connection: Keep-Alive` header
400    pub fn set_connection_keep_alive(&mut self) -> &mut Self {
401        self.set_connection("Keep-Alive")
402    }
403
404    /// A utility method to set the `Connection: Upgrade` header
405    pub fn set_connection_upgrade(&mut self) -> &mut Self {
406        self.set_connection("Upgrade")
407    }
408
409    /// A utility method to set the `Cache-Control` header
410    pub fn set_cache_control(&mut self, cache: &'b str) -> &mut Self {
411        self.set("Cache-Control", cache)
412    }
413
414    /// A utility method to set the `Cache-Control: No-Cache` header
415    pub fn set_cache_control_no_cache(&mut self) -> &mut Self {
416        self.set_cache_control("No-Cache")
417    }
418
419    /// A utility method to set the `Upgrade` header
420    pub fn set_upgrade(&mut self, upgrade: &'b str) -> &mut Self {
421        self.set("Upgrade", upgrade)
422    }
423
424    /// A utility method to set the `Upgrade: websocket` header
425    pub fn set_upgrade_websocket(&mut self) -> &mut Self {
426        self.set_upgrade("websocket")
427    }
428
429    /// A utility method to set all Websocket upgrade request headers,
430    /// including the `Sec-WebSocket-Key` header with the base64-encoded nonce
431    pub fn set_ws_upgrade_request_headers(
432        &mut self,
433        host: Option<&'b str>,
434        origin: Option<&'b str>,
435        version: Option<&'b str>,
436        nonce: &[u8; ws::NONCE_LEN],
437        buf: &'b mut [u8; ws::MAX_BASE64_KEY_LEN],
438    ) -> &mut Self {
439        for (name, value) in ws::upgrade_request_headers(host, origin, version, nonce, buf) {
440            self.set(name, value);
441        }
442
443        self
444    }
445
446    /// A utility method to set all Websocket upgrade response headers
447    /// including the `Sec-WebSocket-Accept` header with the base64-encoded response
448    pub fn set_ws_upgrade_response_headers<'a, H>(
449        &mut self,
450        request_headers: H,
451        version: Option<&'a str>,
452        buf: &'b mut [u8; ws::MAX_BASE64_KEY_RESPONSE_LEN],
453    ) -> Result<&mut Self, ws::UpgradeError>
454    where
455        H: IntoIterator<Item = (&'a str, &'a str)>,
456    {
457        for (name, value) in ws::upgrade_response_headers(request_headers, version, buf)? {
458            self.set(name, value);
459        }
460
461        Ok(self)
462    }
463}
464
465impl<const N: usize> Default for Headers<'_, N> {
466    fn default() -> Self {
467        Self::new()
468    }
469}
470
471/// Connection type
472#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
473pub enum ConnectionType {
474    KeepAlive,
475    Close,
476    Upgrade,
477}
478
479impl ConnectionType {
480    /// Resolve the connection type
481    ///
482    /// Resolution is based on:
483    /// - The connection type found in the headers, if any
484    /// - (if the above is missing) based on the carry-over connection type, if any
485    /// - (if the above is missing) based on the HTTP version
486    ///
487    /// Parameters:
488    /// - `headers_connection_type`: The connection type found in the headers, if any
489    /// - `carry_over_connection_type`: The carry-over connection type
490    ///   (i.e. if this is a response, the `carry_over_connection_type` is the connection type of the request)
491    /// - `http11`: Whether the HTTP protocol is 1.1
492    pub fn resolve(
493        headers_connection_type: Option<ConnectionType>,
494        carry_over_connection_type: Option<ConnectionType>,
495        http11: bool,
496    ) -> Result<Self, HeadersMismatchError> {
497        match headers_connection_type {
498            Some(connection_type) => {
499                if let Some(carry_over_connection_type) = carry_over_connection_type {
500                    if matches!(connection_type, ConnectionType::KeepAlive)
501                        && matches!(carry_over_connection_type, ConnectionType::Close)
502                    {
503                        warn!("Cannot set a Keep-Alive connection when the peer requested Close");
504                        Err(HeadersMismatchError::ResponseConnectionTypeMismatchError)?;
505                    }
506                }
507
508                Ok(connection_type)
509            }
510            None => {
511                if let Some(carry_over_connection_type) = carry_over_connection_type {
512                    Ok(carry_over_connection_type)
513                } else if http11 {
514                    Ok(Self::KeepAlive)
515                } else {
516                    Ok(Self::Close)
517                }
518            }
519        }
520    }
521
522    /// Create a connection type from a header
523    ///
524    /// If the header is not a `Connection` header, this method returns `None`
525    pub fn from_header(name: &str, value: &str) -> Option<Self> {
526        if "Connection".eq_ignore_ascii_case(name) && value.eq_ignore_ascii_case("Close") {
527            Some(Self::Close)
528        } else if "Connection".eq_ignore_ascii_case(name)
529            && value.eq_ignore_ascii_case("Keep-Alive")
530        {
531            Some(Self::KeepAlive)
532        } else if "Connection".eq_ignore_ascii_case(name) && value.eq_ignore_ascii_case("Upgrade") {
533            Some(Self::Upgrade)
534        } else {
535            None
536        }
537    }
538
539    /// Create a connection type from headers
540    ///
541    /// If multiple `Connection` headers are found, this method logs a warning and returns the last one
542    /// If no `Connection` headers are found, this method returns `None`
543    pub fn from_headers<'a, H>(headers: H) -> Option<Self>
544    where
545        H: IntoIterator<Item = (&'a str, &'a str)>,
546    {
547        let mut connection = None;
548
549        for (name, value) in headers {
550            let header_connection = Self::from_header(name, value);
551
552            if let Some(header_connection) = header_connection {
553                if let Some(connection) = connection {
554                    warn!(
555                        "Multiple Connection headers found. Current {} and new {}",
556                        connection, header_connection
557                    );
558                }
559
560                // The last connection header wins
561                connection = Some(header_connection);
562            }
563        }
564
565        connection
566    }
567
568    /// Create a raw header from the connection type
569    pub fn raw_header(&self) -> (&str, &[u8]) {
570        let connection = match self {
571            Self::KeepAlive => "Keep-Alive",
572            Self::Close => "Close",
573            Self::Upgrade => "Upgrade",
574        };
575
576        ("Connection", connection.as_bytes())
577    }
578}
579
580impl Display for ConnectionType {
581    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
582        match self {
583            Self::KeepAlive => write!(f, "Keep-Alive"),
584            Self::Close => write!(f, "Close"),
585            Self::Upgrade => write!(f, "Upgrade"),
586        }
587    }
588}
589
590#[cfg(feature = "defmt")]
591impl defmt::Format for ConnectionType {
592    fn format(&self, f: defmt::Formatter<'_>) {
593        match self {
594            Self::KeepAlive => defmt::write!(f, "Keep-Alive"),
595            Self::Close => defmt::write!(f, "Close"),
596            Self::Upgrade => defmt::write!(f, "Upgrade"),
597        }
598    }
599}
600
601/// Body type
602#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
603pub enum BodyType {
604    /// Chunked body (Transfer-Encoding: Chunked)
605    Chunked,
606    /// Content-length body (Content-Length: {len})
607    ContentLen(u64),
608    /// Raw body - can only be used with responses, where the connection type is `Close`
609    Raw,
610}
611
612impl BodyType {
613    /// Resolve the body type
614    ///
615    /// Resolution is based on:
616    /// - The body type found in the headers (i.e. `Content-Length` and/or `Transfer-Encoding`), if any
617    /// - (if the above is missing) based on the resolved connection type, HTTP protocol and whether we are dealing with a request or a response
618    ///
619    /// Parameters:
620    /// - `headers_body_type`: The body type found in the headers, if any
621    /// - `connection_type`: The resolved connection type
622    /// - `request`: Whether we are dealing with a request or a response
623    /// - `http11`: Whether the HTTP protocol is 1.1
624    /// - `chunked_if_unspecified`: (HTTP1.1 only) Upgrades the body type to Chunked if requested so and if no body was specified in the headers
625    pub fn resolve(
626        headers_body_type: Option<BodyType>,
627        connection_type: ConnectionType,
628        request: bool,
629        http11: bool,
630        chunked_if_unspecified: bool,
631    ) -> Result<Self, HeadersMismatchError> {
632        match headers_body_type {
633            Some(headers_body_type) => {
634                match headers_body_type {
635                    BodyType::Raw => {
636                        if request {
637                            warn!("Raw body in a request. This is not allowed.");
638                            Err(HeadersMismatchError::BodyTypeError(
639                                "Raw body in a request. This is not allowed.",
640                            ))?;
641                        } else if !matches!(connection_type, ConnectionType::Close) {
642                            warn!("Raw body response with a Keep-Alive connection. This is not allowed.");
643                            Err(HeadersMismatchError::BodyTypeError("Raw body response with a Keep-Alive connection. This is not allowed."))?;
644                        }
645                    }
646                    BodyType::Chunked => {
647                        if !http11 {
648                            warn!("Chunked body with an HTTP/1.0 connection. This is not allowed.");
649                            Err(HeadersMismatchError::BodyTypeError(
650                                "Chunked body with an HTTP/1.0 connection. This is not allowed.",
651                            ))?;
652                        }
653                    }
654                    _ => {}
655                }
656
657                Ok(headers_body_type)
658            }
659            None => {
660                if request {
661                    if chunked_if_unspecified && http11 {
662                        // With HTTP1.1 we can safely upgrade the body to a chunked one
663                        Ok(BodyType::Chunked)
664                    } else {
665                        debug!("Unknown body type in a request. Assuming Content-Length=0.");
666                        Ok(BodyType::ContentLen(0))
667                    }
668                } else if matches!(connection_type, ConnectionType::Close) {
669                    Ok(BodyType::Raw)
670                } else if matches!(connection_type, ConnectionType::Upgrade) {
671                    if http11 {
672                        debug!("Unknown body type in response but the Connection is Upgrade. Assuming Content-Length=0.");
673                        Ok(BodyType::ContentLen(0))
674                    } else {
675                        warn!("Connection is set to Upgrade but the HTTP protocol version is not 1.1. This is not allowed.");
676                        Err(HeadersMismatchError::BodyTypeError(
677                            "Connection is set to Upgrade but the HTTP protocol version is not 1.1. This is not allowed.",
678                        ))
679                    }
680                } else if chunked_if_unspecified && http11 {
681                    // With HTTP1.1 we can safely upgrade the body to a chunked one
682                    Ok(BodyType::Chunked)
683                } else {
684                    warn!("Unknown body type in a response with a Keep-Alive connection. This is not allowed.");
685                    Err(HeadersMismatchError::BodyTypeError("Unknown body type in a response with a Keep-Alive connection. This is not allowed."))
686                }
687            }
688        }
689    }
690
691    /// Create a body type from a header
692    ///
693    /// If the header is not a `Content-Length` or `Transfer-Encoding` header, this method returns `None`
694    pub fn from_header(name: &str, value: &str) -> Option<Self> {
695        if "Transfer-Encoding".eq_ignore_ascii_case(name) {
696            if value.eq_ignore_ascii_case("Chunked") {
697                return Some(Self::Chunked);
698            }
699        } else if "Content-Length".eq_ignore_ascii_case(name) {
700            return Some(Self::ContentLen(unwrap!(
701                value.parse::<u64>(),
702                "Invalid Content-Length header"
703            ))); // TODO
704        }
705
706        None
707    }
708
709    /// Create a body type from headers
710    ///
711    /// If multiple body type headers are found, this method logs a warning and returns the last one
712    /// If no body type headers are found, this method returns `None`
713    pub fn from_headers<'a, H>(headers: H) -> Option<Self>
714    where
715        H: IntoIterator<Item = (&'a str, &'a str)>,
716    {
717        let mut body = None;
718
719        for (name, value) in headers {
720            let header_body = Self::from_header(name, value);
721
722            if let Some(header_body) = header_body {
723                if let Some(body) = body {
724                    warn!(
725                        "Multiple body type headers found. Current {} and new {}",
726                        body, header_body
727                    );
728                }
729
730                // The last body header wins
731                body = Some(header_body);
732            }
733        }
734
735        body
736    }
737
738    /// Create a raw header from the body type
739    ///
740    /// If the body type is `Raw`, this method returns `None` as a raw body cannot be
741    /// represented in a header and is rather, a consequence of using connection type `Close`
742    /// with HTTP server responses
743    pub fn raw_header<'a>(&self, buf: &'a mut heapless::String<20>) -> Option<(&str, &'a [u8])> {
744        match self {
745            Self::Chunked => Some(("Transfer-Encoding", "Chunked".as_bytes())),
746            Self::ContentLen(len) => {
747                use core::fmt::Write;
748
749                buf.clear();
750
751                write_unwrap!(buf, "{}", len);
752
753                Some(("Content-Length", buf.as_bytes()))
754            }
755            Self::Raw => None,
756        }
757    }
758}
759
760impl Display for BodyType {
761    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
762        match self {
763            Self::Chunked => write!(f, "Chunked"),
764            Self::ContentLen(len) => write!(f, "Content-Length: {len}"),
765            Self::Raw => write!(f, "Raw"),
766        }
767    }
768}
769
770#[cfg(feature = "defmt")]
771impl defmt::Format for BodyType {
772    fn format(&self, f: defmt::Formatter<'_>) {
773        match self {
774            Self::Chunked => defmt::write!(f, "Chunked"),
775            Self::ContentLen(len) => defmt::write!(f, "Content-Length: {}", len),
776            Self::Raw => defmt::write!(f, "Raw"),
777        }
778    }
779}
780
781/// Request headers including the request line (method, path)
782#[derive(Debug)]
783pub struct RequestHeaders<'b, const N: usize> {
784    /// Whether the request is HTTP/1.1
785    pub http11: bool,
786    /// The HTTP method
787    pub method: Method,
788    /// The request path
789    pub path: &'b str,
790    /// The headers
791    pub headers: Headers<'b, N>,
792}
793
794impl<const N: usize> RequestHeaders<'_, N> {
795    // Create a new RequestHeaders instance, defaults to GET / HTTP/1.1
796    #[inline(always)]
797    pub const fn new() -> Self {
798        Self {
799            http11: true,
800            method: Method::Get,
801            path: "/",
802            headers: Headers::new(),
803        }
804    }
805
806    /// A utility method to check if the request is a Websocket upgrade request
807    pub fn is_ws_upgrade_request(&self) -> bool {
808        is_upgrade_request(self.method, self.headers.iter())
809    }
810}
811
812impl<const N: usize> Default for RequestHeaders<'_, N> {
813    #[inline(always)]
814    fn default() -> Self {
815        Self::new()
816    }
817}
818
819impl<const N: usize> Display for RequestHeaders<'_, N> {
820    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
821        write!(f, "{} ", if self.http11 { "HTTP/1.1" } else { "HTTP/1.0" })?;
822
823        writeln!(f, "{} {}", self.method, self.path)?;
824
825        for (name, value) in self.headers.iter() {
826            if name.is_empty() {
827                break;
828            }
829
830            writeln!(f, "{name}: {value}")?;
831        }
832
833        Ok(())
834    }
835}
836
837#[cfg(feature = "defmt")]
838impl<const N: usize> defmt::Format for RequestHeaders<'_, N> {
839    fn format(&self, f: defmt::Formatter<'_>) {
840        defmt::write!(f, "{} ", if self.http11 { "HTTP/1.1" } else { "HTTP/1.0" });
841
842        defmt::write!(f, "{} {}\n", self.method, self.path);
843
844        for (name, value) in self.headers.iter() {
845            if name.is_empty() {
846                break;
847            }
848
849            defmt::write!(f, "{}: {}\n", name, value);
850        }
851    }
852}
853
854/// Response headers including the response line (HTTP version, status code, reason phrase)
855#[derive(Debug)]
856pub struct ResponseHeaders<'b, const N: usize> {
857    /// Whether the response is HTTP/1.1
858    pub http11: bool,
859    /// The status code
860    pub code: u16,
861    /// The reason phrase, if present
862    pub reason: Option<&'b str>,
863    /// The headers
864    pub headers: Headers<'b, N>,
865}
866
867impl<const N: usize> ResponseHeaders<'_, N> {
868    /// Create a new ResponseHeaders instance, defaults to HTTP/1.1 200 OK
869    #[inline(always)]
870    pub const fn new() -> Self {
871        Self {
872            http11: true,
873            code: 200,
874            reason: None,
875            headers: Headers::new(),
876        }
877    }
878
879    /// A utility method to check if the response is a Websocket upgrade response
880    /// and if the upgrade was accepted
881    pub fn is_ws_upgrade_accepted(
882        &self,
883        nonce: &[u8; NONCE_LEN],
884        buf: &mut [u8; MAX_BASE64_KEY_RESPONSE_LEN],
885    ) -> bool {
886        is_upgrade_accepted(self.code, self.headers.iter(), nonce, buf)
887    }
888}
889
890impl<const N: usize> Default for ResponseHeaders<'_, N> {
891    #[inline(always)]
892    fn default() -> Self {
893        Self::new()
894    }
895}
896
897impl<const N: usize> Display for ResponseHeaders<'_, N> {
898    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
899        write!(f, "{} ", if self.http11 { "HTTP/1.1 " } else { "HTTP/1.0" })?;
900
901        writeln!(f, "{} {}", self.code, self.reason.unwrap_or(""))?;
902
903        for (name, value) in self.headers.iter() {
904            if name.is_empty() {
905                break;
906            }
907
908            writeln!(f, "{name}: {value}")?;
909        }
910
911        Ok(())
912    }
913}
914
915#[cfg(feature = "defmt")]
916impl<const N: usize> defmt::Format for ResponseHeaders<'_, N> {
917    fn format(&self, f: defmt::Formatter<'_>) {
918        defmt::write!(f, "{} ", if self.http11 { "HTTP/1.1 " } else { "HTTP/1.0" });
919
920        defmt::write!(f, "{} {}\n", self.code, self.reason.unwrap_or(""));
921
922        for (name, value) in self.headers.iter() {
923            if name.is_empty() {
924                break;
925            }
926
927            defmt::write!(f, "{}: {}\n", name, value);
928        }
929    }
930}
931
932/// Websocket utilities
933pub mod ws {
934    use crate::Method;
935
936    pub const NONCE_LEN: usize = 16;
937    pub const MAX_BASE64_KEY_LEN: usize = 28;
938    pub const MAX_BASE64_KEY_RESPONSE_LEN: usize = 33;
939
940    pub const UPGRADE_REQUEST_HEADERS_LEN: usize = 7;
941    pub const UPGRADE_RESPONSE_HEADERS_LEN: usize = 4;
942
943    /// Return ready-to-use WS upgrade request headers
944    ///
945    /// Parameters:
946    /// - `host`: The `Host` header, if present
947    /// - `origin`: The `Origin` header, if present
948    /// - `version`: The `Sec-WebSocket-Version` header, if present; otherwise version "13" is assumed
949    /// - `nonce`: The nonce to use for the `Sec-WebSocket-Key` header
950    /// - `buf`: A buffer to use for base64 encoding the nonce
951    pub fn upgrade_request_headers<'a>(
952        host: Option<&'a str>,
953        origin: Option<&'a str>,
954        version: Option<&'a str>,
955        nonce: &[u8; NONCE_LEN],
956        buf: &'a mut [u8; MAX_BASE64_KEY_LEN],
957    ) -> [(&'a str, &'a str); UPGRADE_REQUEST_HEADERS_LEN] {
958        let host = host.map(|host| ("Host", host)).unwrap_or(("", ""));
959        let origin = origin.map(|origin| ("Origin", origin)).unwrap_or(("", ""));
960
961        [
962            host,
963            origin,
964            ("Content-Length", "0"),
965            ("Connection", "Upgrade"),
966            ("Upgrade", "websocket"),
967            ("Sec-WebSocket-Version", version.unwrap_or("13")),
968            ("Sec-WebSocket-Key", sec_key_encode(nonce, buf)),
969        ]
970    }
971
972    /// Check if the request is a Websocket upgrade request
973    pub fn is_upgrade_request<'a, H>(method: Method, request_headers: H) -> bool
974    where
975        H: IntoIterator<Item = (&'a str, &'a str)>,
976    {
977        if method != Method::Get {
978            return false;
979        }
980
981        let mut connection = false;
982        let mut upgrade = false;
983
984        for (name, value) in request_headers {
985            if name.eq_ignore_ascii_case("Connection") {
986                connection = value.eq_ignore_ascii_case("Upgrade");
987            } else if name.eq_ignore_ascii_case("Upgrade") {
988                upgrade = value.eq_ignore_ascii_case("websocket");
989            }
990        }
991
992        connection && upgrade
993    }
994
995    /// Websocket upgrade errors
996    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
997    pub enum UpgradeError {
998        /// No `Sec-WebSocket-Version` header
999        NoVersion,
1000        /// No `Sec-WebSocket-Key` header
1001        NoSecKey,
1002        /// Unsupported `Sec-WebSocket-Version`
1003        UnsupportedVersion,
1004    }
1005
1006    impl core::fmt::Display for UpgradeError {
1007        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1008            match self {
1009                Self::NoVersion => write!(f, "No Sec-WebSocket-Version header"),
1010                Self::NoSecKey => write!(f, "No Sec-WebSocket-Key header"),
1011                Self::UnsupportedVersion => write!(f, "Unsupported Sec-WebSocket-Version"),
1012            }
1013        }
1014    }
1015
1016    #[cfg(feature = "defmt")]
1017    impl defmt::Format for UpgradeError {
1018        fn format(&self, f: defmt::Formatter<'_>) {
1019            match self {
1020                Self::NoVersion => defmt::write!(f, "No Sec-WebSocket-Version header"),
1021                Self::NoSecKey => defmt::write!(f, "No Sec-WebSocket-Key header"),
1022                Self::UnsupportedVersion => defmt::write!(f, "Unsupported Sec-WebSocket-Version"),
1023            }
1024        }
1025    }
1026
1027    #[cfg(feature = "std")]
1028    impl std::error::Error for UpgradeError {}
1029
1030    /// Return ready-to-use WS upgrade response headers
1031    ///
1032    /// Parameters:
1033    /// - `request_headers`: The request headers
1034    /// - `version`: The `Sec-WebSocket-Version` header, if present; otherwise version "13" is assumed
1035    /// - `buf`: A buffer to use for base64 encoding bits and pieces of the response
1036    pub fn upgrade_response_headers<'a, 'b, H>(
1037        request_headers: H,
1038        version: Option<&'a str>,
1039        buf: &'b mut [u8; MAX_BASE64_KEY_RESPONSE_LEN],
1040    ) -> Result<[(&'b str, &'b str); UPGRADE_RESPONSE_HEADERS_LEN], UpgradeError>
1041    where
1042        H: IntoIterator<Item = (&'a str, &'a str)>,
1043    {
1044        let mut version_ok = false;
1045        let mut sec_key_resp_len = None;
1046
1047        for (name, value) in request_headers {
1048            if name.eq_ignore_ascii_case("Sec-WebSocket-Version") {
1049                if !value.eq_ignore_ascii_case(version.unwrap_or("13")) {
1050                    return Err(UpgradeError::NoVersion);
1051                }
1052
1053                version_ok = true;
1054            } else if name.eq_ignore_ascii_case("Sec-WebSocket-Key") {
1055                sec_key_resp_len = Some(sec_key_response(value, buf).len());
1056            }
1057        }
1058
1059        if version_ok {
1060            if let Some(sec_key_resp_len) = sec_key_resp_len {
1061                Ok([
1062                    ("Content-Length", "0"),
1063                    ("Connection", "Upgrade"),
1064                    ("Upgrade", "websocket"),
1065                    ("Sec-WebSocket-Accept", unsafe {
1066                        core::str::from_utf8_unchecked(&buf[..sec_key_resp_len])
1067                    }),
1068                ])
1069            } else {
1070                Err(UpgradeError::NoSecKey)
1071            }
1072        } else {
1073            Err(UpgradeError::NoVersion)
1074        }
1075    }
1076
1077    /// Check if the response is a Websocket upgrade response and if the upgrade was accepted
1078    ///
1079    /// Parameters:
1080    /// - `code`: The status response code
1081    /// - `response_headers`: The response headers
1082    /// - `nonce`: The nonce used for the `Sec-WebSocket-Key` header in the WS upgrade request
1083    /// - `buf`: A buffer to use when performing the check
1084    pub fn is_upgrade_accepted<'a, H>(
1085        code: u16,
1086        response_headers: H,
1087        nonce: &[u8; NONCE_LEN],
1088        buf: &'a mut [u8; MAX_BASE64_KEY_RESPONSE_LEN],
1089    ) -> bool
1090    where
1091        H: IntoIterator<Item = (&'a str, &'a str)>,
1092    {
1093        if code != 101 {
1094            return false;
1095        }
1096
1097        let mut connection = false;
1098        let mut upgrade = false;
1099        let mut sec_key_response = false;
1100
1101        for (name, value) in response_headers {
1102            if name.eq_ignore_ascii_case("Connection") {
1103                connection = value.eq_ignore_ascii_case("Upgrade");
1104            } else if name.eq_ignore_ascii_case("Upgrade") {
1105                upgrade = value.eq_ignore_ascii_case("websocket");
1106            } else if name.eq_ignore_ascii_case("Sec-WebSocket-Accept") {
1107                let sec_key = sec_key_encode(nonce, buf);
1108
1109                let mut sha1 = sha1_smol::Sha1::new();
1110                sha1.update(sec_key.as_bytes());
1111
1112                let sec_key_resp = sec_key_response_finalize(&mut sha1, buf);
1113
1114                sec_key_response = value.eq(sec_key_resp);
1115            }
1116        }
1117
1118        connection && upgrade && sec_key_response
1119    }
1120
1121    fn sec_key_encode<'a>(nonce: &[u8], buf: &'a mut [u8]) -> &'a str {
1122        let nonce_base64_len = base64::encode_config_slice(nonce, base64::STANDARD, buf);
1123
1124        unsafe { core::str::from_utf8_unchecked(&buf[..nonce_base64_len]) }
1125    }
1126
1127    /// Compute the response for a given `Sec-WebSocket-Key`
1128    pub fn sec_key_response<'a>(
1129        sec_key: &str,
1130        buf: &'a mut [u8; MAX_BASE64_KEY_RESPONSE_LEN],
1131    ) -> &'a str {
1132        let mut sha1 = sha1_smol::Sha1::new();
1133
1134        sec_key_response_start(sec_key, &mut sha1);
1135        sec_key_response_finalize(&mut sha1, buf)
1136    }
1137
1138    fn sec_key_response_start(sec_key: &str, sha1: &mut sha1_smol::Sha1) {
1139        debug!("Computing response for key: {}", sec_key);
1140
1141        sha1.update(sec_key.as_bytes());
1142    }
1143
1144    fn sec_key_response_finalize<'a>(sha1: &mut sha1_smol::Sha1, buf: &'a mut [u8]) -> &'a str {
1145        const WS_MAGIC_GUUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
1146
1147        sha1.update(WS_MAGIC_GUUID.as_bytes());
1148
1149        let len = base64::encode_config_slice(sha1.digest().bytes(), base64::STANDARD, buf);
1150
1151        let sec_key_response = unsafe { core::str::from_utf8_unchecked(&buf[..len]) };
1152
1153        debug!("Computed response: {}", sec_key_response);
1154
1155        sec_key_response
1156    }
1157}
1158
1159#[cfg(test)]
1160mod test {
1161    use crate::{
1162        ws::{sec_key_response, MAX_BASE64_KEY_RESPONSE_LEN},
1163        BodyType, ConnectionType,
1164    };
1165
1166    #[test]
1167    fn test_resp() {
1168        let mut buf = [0_u8; MAX_BASE64_KEY_RESPONSE_LEN];
1169        let resp = sec_key_response("dGhlIHNhbXBsZSBub25jZQ==", &mut buf);
1170
1171        assert_eq!(resp, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
1172    }
1173
1174    #[test]
1175    fn test_resolve_conn() {
1176        // Default connection type resolution
1177        assert_eq!(
1178            unwrap!(ConnectionType::resolve(None, None, true)),
1179            ConnectionType::KeepAlive
1180        );
1181        assert_eq!(
1182            unwrap!(ConnectionType::resolve(None, None, false)),
1183            ConnectionType::Close
1184        );
1185
1186        // Connection type resolution based on carry-over (for responses)
1187        assert_eq!(
1188            unwrap!(ConnectionType::resolve(
1189                None,
1190                Some(ConnectionType::KeepAlive),
1191                false
1192            )),
1193            ConnectionType::KeepAlive
1194        );
1195        assert_eq!(
1196            unwrap!(ConnectionType::resolve(
1197                None,
1198                Some(ConnectionType::KeepAlive),
1199                true
1200            )),
1201            ConnectionType::KeepAlive
1202        );
1203
1204        // Connection type resoluton based on the header value
1205        assert_eq!(
1206            unwrap!(ConnectionType::resolve(
1207                Some(ConnectionType::Close),
1208                None,
1209                false
1210            )),
1211            ConnectionType::Close
1212        );
1213        assert_eq!(
1214            unwrap!(ConnectionType::resolve(
1215                Some(ConnectionType::KeepAlive),
1216                None,
1217                false
1218            )),
1219            ConnectionType::KeepAlive
1220        );
1221        assert_eq!(
1222            unwrap!(ConnectionType::resolve(
1223                Some(ConnectionType::Close),
1224                None,
1225                true
1226            )),
1227            ConnectionType::Close
1228        );
1229        assert_eq!(
1230            unwrap!(ConnectionType::resolve(
1231                Some(ConnectionType::KeepAlive),
1232                None,
1233                true
1234            )),
1235            ConnectionType::KeepAlive
1236        );
1237
1238        // Connection type in the headers should aggree with the carry-over one
1239        assert_eq!(
1240            unwrap!(ConnectionType::resolve(
1241                Some(ConnectionType::Close),
1242                Some(ConnectionType::Close),
1243                false
1244            )),
1245            ConnectionType::Close
1246        );
1247        assert_eq!(
1248            unwrap!(ConnectionType::resolve(
1249                Some(ConnectionType::KeepAlive),
1250                Some(ConnectionType::KeepAlive),
1251                false
1252            )),
1253            ConnectionType::KeepAlive
1254        );
1255        assert_eq!(
1256            unwrap!(ConnectionType::resolve(
1257                Some(ConnectionType::Close),
1258                Some(ConnectionType::Close),
1259                true
1260            )),
1261            ConnectionType::Close
1262        );
1263        assert_eq!(
1264            unwrap!(ConnectionType::resolve(
1265                Some(ConnectionType::KeepAlive),
1266                Some(ConnectionType::KeepAlive),
1267                true
1268            )),
1269            ConnectionType::KeepAlive
1270        );
1271        assert_eq!(
1272            unwrap!(ConnectionType::resolve(
1273                Some(ConnectionType::Close),
1274                Some(ConnectionType::KeepAlive),
1275                false
1276            )),
1277            ConnectionType::Close
1278        );
1279        assert!(ConnectionType::resolve(
1280            Some(ConnectionType::KeepAlive),
1281            Some(ConnectionType::Close),
1282            false
1283        )
1284        .is_err());
1285        assert_eq!(
1286            unwrap!(ConnectionType::resolve(
1287                Some(ConnectionType::Close),
1288                Some(ConnectionType::KeepAlive),
1289                true
1290            )),
1291            ConnectionType::Close
1292        );
1293        assert!(ConnectionType::resolve(
1294            Some(ConnectionType::KeepAlive),
1295            Some(ConnectionType::Close),
1296            true
1297        )
1298        .is_err());
1299    }
1300
1301    #[test]
1302    fn test_resolve_body() {
1303        // Request with no body type specified means Content-Length=0
1304        assert_eq!(
1305            unwrap!(BodyType::resolve(
1306                None,
1307                ConnectionType::KeepAlive,
1308                true,
1309                true,
1310                false
1311            )),
1312            BodyType::ContentLen(0)
1313        );
1314        assert_eq!(
1315            unwrap!(BodyType::resolve(
1316                None,
1317                ConnectionType::Close,
1318                true,
1319                true,
1320                false
1321            )),
1322            BodyType::ContentLen(0)
1323        );
1324        assert_eq!(
1325            unwrap!(BodyType::resolve(
1326                None,
1327                ConnectionType::KeepAlive,
1328                true,
1329                false,
1330                false
1331            )),
1332            BodyType::ContentLen(0)
1333        );
1334        assert_eq!(
1335            unwrap!(BodyType::resolve(
1336                None,
1337                ConnectionType::Close,
1338                true,
1339                false,
1340                false
1341            )),
1342            BodyType::ContentLen(0)
1343        );
1344        assert_eq!(
1345            unwrap!(BodyType::resolve(
1346                None,
1347                ConnectionType::Upgrade,
1348                false,
1349                true,
1350                false
1351            )),
1352            BodyType::ContentLen(0)
1353        );
1354
1355        // Receiving a response with no body type after requesting a connection upgrade with
1356        // HTTP1.0 is no allowed.
1357        assert!(BodyType::resolve(None, ConnectionType::Upgrade, false, false, false).is_err());
1358
1359        // Request or response with a chunked body type is invalid for HTTP1.0
1360        assert!(BodyType::resolve(
1361            Some(BodyType::Chunked),
1362            ConnectionType::Close,
1363            true,
1364            false,
1365            false
1366        )
1367        .is_err());
1368        assert!(BodyType::resolve(
1369            Some(BodyType::Chunked),
1370            ConnectionType::KeepAlive,
1371            true,
1372            false,
1373            false
1374        )
1375        .is_err());
1376        assert!(BodyType::resolve(
1377            Some(BodyType::Chunked),
1378            ConnectionType::Close,
1379            false,
1380            false,
1381            false
1382        )
1383        .is_err());
1384        assert!(BodyType::resolve(
1385            Some(BodyType::Chunked),
1386            ConnectionType::KeepAlive,
1387            false,
1388            false,
1389            false
1390        )
1391        .is_err());
1392
1393        // Raw body in a request is not allowed
1394        assert!(BodyType::resolve(
1395            Some(BodyType::Raw),
1396            ConnectionType::Close,
1397            true,
1398            true,
1399            false
1400        )
1401        .is_err());
1402        assert!(BodyType::resolve(
1403            Some(BodyType::Raw),
1404            ConnectionType::KeepAlive,
1405            true,
1406            true,
1407            false
1408        )
1409        .is_err());
1410        assert!(BodyType::resolve(
1411            Some(BodyType::Raw),
1412            ConnectionType::Close,
1413            true,
1414            false,
1415            false
1416        )
1417        .is_err());
1418        assert!(BodyType::resolve(
1419            Some(BodyType::Raw),
1420            ConnectionType::KeepAlive,
1421            true,
1422            false,
1423            false
1424        )
1425        .is_err());
1426
1427        // Raw body in a response with a Keep-Alive connection is not allowed
1428        assert!(BodyType::resolve(
1429            Some(BodyType::Raw),
1430            ConnectionType::KeepAlive,
1431            false,
1432            true,
1433            false
1434        )
1435        .is_err());
1436        assert!(BodyType::resolve(
1437            Some(BodyType::Raw),
1438            ConnectionType::KeepAlive,
1439            false,
1440            false,
1441            false
1442        )
1443        .is_err());
1444
1445        // The same, but with a Close connection IS allowed
1446        assert_eq!(
1447            unwrap!(BodyType::resolve(
1448                Some(BodyType::Raw),
1449                ConnectionType::Close,
1450                false,
1451                true,
1452                false
1453            )),
1454            BodyType::Raw
1455        );
1456        assert_eq!(
1457            unwrap!(BodyType::resolve(
1458                Some(BodyType::Raw),
1459                ConnectionType::Close,
1460                false,
1461                false,
1462                false
1463            )),
1464            BodyType::Raw
1465        );
1466
1467        // Request upgrades to chunked encoding should only work for HTTP1.1, and if there is no body type in the headers
1468        assert_eq!(
1469            unwrap!(BodyType::resolve(
1470                None,
1471                ConnectionType::Close,
1472                true,
1473                true,
1474                true
1475            )),
1476            BodyType::Chunked
1477        );
1478        assert_eq!(
1479            unwrap!(BodyType::resolve(
1480                None,
1481                ConnectionType::KeepAlive,
1482                true,
1483                true,
1484                true
1485            )),
1486            BodyType::Chunked
1487        );
1488        assert_eq!(
1489            unwrap!(BodyType::resolve(
1490                None,
1491                ConnectionType::Close,
1492                true,
1493                false,
1494                true
1495            )),
1496            BodyType::ContentLen(0)
1497        );
1498        assert_eq!(
1499            unwrap!(BodyType::resolve(
1500                None,
1501                ConnectionType::KeepAlive,
1502                true,
1503                false,
1504                true
1505            )),
1506            BodyType::ContentLen(0)
1507        );
1508
1509        // Response upgrades to chunked encoding should only work for HTTP1.1, and if there is no body type in the headers, and if the connection is KeepAlive
1510        assert_eq!(
1511            unwrap!(BodyType::resolve(
1512                None,
1513                ConnectionType::KeepAlive,
1514                false,
1515                true,
1516                true
1517            )),
1518            BodyType::Chunked
1519        );
1520        // Response upgrades should not be honored if the connection is Close
1521        assert_eq!(
1522            unwrap!(BodyType::resolve(
1523                None,
1524                ConnectionType::Close,
1525                false,
1526                true,
1527                true
1528            )),
1529            BodyType::Raw
1530        );
1531    }
1532}
1533
1534#[cfg(feature = "embedded-svc")]
1535mod embedded_svc_compat {
1536    use core::str;
1537
1538    use embedded_svc::http::client::asynch::Method;
1539
1540    impl From<Method> for super::Method {
1541        fn from(method: Method) -> Self {
1542            match method {
1543                Method::Delete => super::Method::Delete,
1544                Method::Get => super::Method::Get,
1545                Method::Head => super::Method::Head,
1546                Method::Post => super::Method::Post,
1547                Method::Put => super::Method::Put,
1548                Method::Connect => super::Method::Connect,
1549                Method::Options => super::Method::Options,
1550                Method::Trace => super::Method::Trace,
1551                Method::Copy => super::Method::Copy,
1552                Method::Lock => super::Method::Lock,
1553                Method::MkCol => super::Method::MkCol,
1554                Method::Move => super::Method::Move,
1555                Method::Propfind => super::Method::Propfind,
1556                Method::Proppatch => super::Method::Proppatch,
1557                Method::Search => super::Method::Search,
1558                Method::Unlock => super::Method::Unlock,
1559                Method::Bind => super::Method::Bind,
1560                Method::Rebind => super::Method::Rebind,
1561                Method::Unbind => super::Method::Unbind,
1562                Method::Acl => super::Method::Acl,
1563                Method::Report => super::Method::Report,
1564                Method::MkActivity => super::Method::MkActivity,
1565                Method::Checkout => super::Method::Checkout,
1566                Method::Merge => super::Method::Merge,
1567                Method::MSearch => super::Method::MSearch,
1568                Method::Notify => super::Method::Notify,
1569                Method::Subscribe => super::Method::Subscribe,
1570                Method::Unsubscribe => super::Method::Unsubscribe,
1571                Method::Patch => super::Method::Patch,
1572                Method::Purge => super::Method::Purge,
1573                Method::MkCalendar => super::Method::MkCalendar,
1574                Method::Link => super::Method::Link,
1575                Method::Unlink => super::Method::Unlink,
1576            }
1577        }
1578    }
1579
1580    impl From<super::Method> for Method {
1581        fn from(method: super::Method) -> Self {
1582            match method {
1583                super::Method::Delete => Method::Delete,
1584                super::Method::Get => Method::Get,
1585                super::Method::Head => Method::Head,
1586                super::Method::Post => Method::Post,
1587                super::Method::Put => Method::Put,
1588                super::Method::Connect => Method::Connect,
1589                super::Method::Options => Method::Options,
1590                super::Method::Trace => Method::Trace,
1591                super::Method::Copy => Method::Copy,
1592                super::Method::Lock => Method::Lock,
1593                super::Method::MkCol => Method::MkCol,
1594                super::Method::Move => Method::Move,
1595                super::Method::Propfind => Method::Propfind,
1596                super::Method::Proppatch => Method::Proppatch,
1597                super::Method::Search => Method::Search,
1598                super::Method::Unlock => Method::Unlock,
1599                super::Method::Bind => Method::Bind,
1600                super::Method::Rebind => Method::Rebind,
1601                super::Method::Unbind => Method::Unbind,
1602                super::Method::Acl => Method::Acl,
1603                super::Method::Report => Method::Report,
1604                super::Method::MkActivity => Method::MkActivity,
1605                super::Method::Checkout => Method::Checkout,
1606                super::Method::Merge => Method::Merge,
1607                super::Method::MSearch => Method::MSearch,
1608                super::Method::Notify => Method::Notify,
1609                super::Method::Subscribe => Method::Subscribe,
1610                super::Method::Unsubscribe => Method::Unsubscribe,
1611                super::Method::Patch => Method::Patch,
1612                super::Method::Purge => Method::Purge,
1613                super::Method::MkCalendar => Method::MkCalendar,
1614                super::Method::Link => Method::Link,
1615                super::Method::Unlink => Method::Unlink,
1616            }
1617        }
1618    }
1619
1620    impl<const N: usize> embedded_svc::http::Query for super::RequestHeaders<'_, N> {
1621        fn uri(&self) -> &'_ str {
1622            self.path
1623        }
1624
1625        fn method(&self) -> Method {
1626            self.method.into()
1627        }
1628    }
1629
1630    impl<const N: usize> embedded_svc::http::Headers for super::RequestHeaders<'_, N> {
1631        fn header(&self, name: &str) -> Option<&'_ str> {
1632            self.headers.get(name)
1633        }
1634    }
1635
1636    impl<const N: usize> embedded_svc::http::Status for super::ResponseHeaders<'_, N> {
1637        fn status(&self) -> u16 {
1638            self.code
1639        }
1640
1641        fn status_message(&self) -> Option<&'_ str> {
1642            self.reason
1643        }
1644    }
1645
1646    impl<const N: usize> embedded_svc::http::Headers for super::ResponseHeaders<'_, N> {
1647        fn header(&self, name: &str) -> Option<&'_ str> {
1648            self.headers.get(name)
1649        }
1650    }
1651
1652    impl<const N: usize> embedded_svc::http::Headers for super::Headers<'_, N> {
1653        fn header(&self, name: &str) -> Option<&'_ str> {
1654            self.get(name)
1655        }
1656    }
1657}