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