Skip to main content

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 if !http11 => {
649                        warn!("Chunked body with an HTTP/1.0 connection. This is not allowed.");
650                        Err(HeadersMismatchError::BodyTypeError(
651                            "Chunked body with an HTTP/1.0 connection. This is not allowed.",
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 base64::Engine;
935
936    use crate::Method;
937
938    pub const NONCE_LEN: usize = 16;
939    pub const MAX_BASE64_KEY_LEN: usize = 28;
940    pub const MAX_BASE64_KEY_RESPONSE_LEN: usize = 33;
941
942    pub const UPGRADE_REQUEST_HEADERS_LEN: usize = 7;
943    pub const UPGRADE_RESPONSE_HEADERS_LEN: usize = 4;
944
945    /// Return ready-to-use WS upgrade request headers
946    ///
947    /// Parameters:
948    /// - `host`: The `Host` header, if present
949    /// - `origin`: The `Origin` header, if present
950    /// - `version`: The `Sec-WebSocket-Version` header, if present; otherwise version "13" is assumed
951    /// - `nonce`: The nonce to use for the `Sec-WebSocket-Key` header
952    /// - `buf`: A buffer to use for base64 encoding the nonce
953    pub fn upgrade_request_headers<'a>(
954        host: Option<&'a str>,
955        origin: Option<&'a str>,
956        version: Option<&'a str>,
957        nonce: &[u8; NONCE_LEN],
958        buf: &'a mut [u8; MAX_BASE64_KEY_LEN],
959    ) -> [(&'a str, &'a str); UPGRADE_REQUEST_HEADERS_LEN] {
960        let host = host.map(|host| ("Host", host)).unwrap_or(("", ""));
961        let origin = origin.map(|origin| ("Origin", origin)).unwrap_or(("", ""));
962
963        [
964            host,
965            origin,
966            ("Content-Length", "0"),
967            ("Connection", "Upgrade"),
968            ("Upgrade", "websocket"),
969            ("Sec-WebSocket-Version", version.unwrap_or("13")),
970            ("Sec-WebSocket-Key", sec_key_encode(nonce, buf)),
971        ]
972    }
973
974    /// Check if the request is a Websocket upgrade request
975    pub fn is_upgrade_request<'a, H>(method: Method, request_headers: H) -> bool
976    where
977        H: IntoIterator<Item = (&'a str, &'a str)>,
978    {
979        if method != Method::Get {
980            return false;
981        }
982
983        let mut connection = false;
984        let mut upgrade = false;
985
986        for (name, value) in request_headers {
987            if name.eq_ignore_ascii_case("Connection") {
988                connection = value.eq_ignore_ascii_case("Upgrade");
989            } else if name.eq_ignore_ascii_case("Upgrade") {
990                upgrade = value.eq_ignore_ascii_case("websocket");
991            }
992        }
993
994        connection && upgrade
995    }
996
997    /// Websocket upgrade errors
998    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
999    pub enum UpgradeError {
1000        /// No `Sec-WebSocket-Version` header
1001        NoVersion,
1002        /// No `Sec-WebSocket-Key` header
1003        NoSecKey,
1004        /// Unsupported `Sec-WebSocket-Version`
1005        UnsupportedVersion,
1006    }
1007
1008    impl core::fmt::Display for UpgradeError {
1009        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1010            match self {
1011                Self::NoVersion => write!(f, "No Sec-WebSocket-Version header"),
1012                Self::NoSecKey => write!(f, "No Sec-WebSocket-Key header"),
1013                Self::UnsupportedVersion => write!(f, "Unsupported Sec-WebSocket-Version"),
1014            }
1015        }
1016    }
1017
1018    #[cfg(feature = "defmt")]
1019    impl defmt::Format for UpgradeError {
1020        fn format(&self, f: defmt::Formatter<'_>) {
1021            match self {
1022                Self::NoVersion => defmt::write!(f, "No Sec-WebSocket-Version header"),
1023                Self::NoSecKey => defmt::write!(f, "No Sec-WebSocket-Key header"),
1024                Self::UnsupportedVersion => defmt::write!(f, "Unsupported Sec-WebSocket-Version"),
1025            }
1026        }
1027    }
1028
1029    impl core::error::Error for UpgradeError {}
1030
1031    /// Return ready-to-use WS upgrade response headers
1032    ///
1033    /// Parameters:
1034    /// - `request_headers`: The request headers
1035    /// - `version`: The `Sec-WebSocket-Version` header, if present; otherwise version "13" is assumed
1036    /// - `buf`: A buffer to use for base64 encoding bits and pieces of the response
1037    pub fn upgrade_response_headers<'a, 'b, H>(
1038        request_headers: H,
1039        version: Option<&'a str>,
1040        buf: &'b mut [u8; MAX_BASE64_KEY_RESPONSE_LEN],
1041    ) -> Result<[(&'b str, &'b str); UPGRADE_RESPONSE_HEADERS_LEN], UpgradeError>
1042    where
1043        H: IntoIterator<Item = (&'a str, &'a str)>,
1044    {
1045        let mut version_ok = false;
1046        let mut sec_key_resp_len = None;
1047
1048        for (name, value) in request_headers {
1049            if name.eq_ignore_ascii_case("Sec-WebSocket-Version") {
1050                if !value.eq_ignore_ascii_case(version.unwrap_or("13")) {
1051                    return Err(UpgradeError::NoVersion);
1052                }
1053
1054                version_ok = true;
1055            } else if name.eq_ignore_ascii_case("Sec-WebSocket-Key") {
1056                sec_key_resp_len = Some(sec_key_response(value, buf).len());
1057            }
1058        }
1059
1060        if version_ok {
1061            if let Some(sec_key_resp_len) = sec_key_resp_len {
1062                Ok([
1063                    ("Content-Length", "0"),
1064                    ("Connection", "Upgrade"),
1065                    ("Upgrade", "websocket"),
1066                    (
1067                        "Sec-WebSocket-Accept",
1068                        unwrap!(core::str::from_utf8(&buf[..sec_key_resp_len]).map_err(|_| ())),
1069                    ),
1070                ])
1071            } else {
1072                Err(UpgradeError::NoSecKey)
1073            }
1074        } else {
1075            Err(UpgradeError::NoVersion)
1076        }
1077    }
1078
1079    /// Check if the response is a Websocket upgrade response and if the upgrade was accepted
1080    ///
1081    /// Parameters:
1082    /// - `code`: The status response code
1083    /// - `response_headers`: The response headers
1084    /// - `nonce`: The nonce used for the `Sec-WebSocket-Key` header in the WS upgrade request
1085    /// - `buf`: A buffer to use when performing the check
1086    pub fn is_upgrade_accepted<'a, H>(
1087        code: u16,
1088        response_headers: H,
1089        nonce: &[u8; NONCE_LEN],
1090        buf: &'a mut [u8; MAX_BASE64_KEY_RESPONSE_LEN],
1091    ) -> bool
1092    where
1093        H: IntoIterator<Item = (&'a str, &'a str)>,
1094    {
1095        if code != 101 {
1096            return false;
1097        }
1098
1099        let mut connection = false;
1100        let mut upgrade = false;
1101        let mut sec_key_response = false;
1102
1103        for (name, value) in response_headers {
1104            if name.eq_ignore_ascii_case("Connection") {
1105                connection = value.eq_ignore_ascii_case("Upgrade");
1106            } else if name.eq_ignore_ascii_case("Upgrade") {
1107                upgrade = value.eq_ignore_ascii_case("websocket");
1108            } else if name.eq_ignore_ascii_case("Sec-WebSocket-Accept") {
1109                let sec_key = sec_key_encode(nonce, buf);
1110
1111                let mut sha1 = sha1_smol::Sha1::new();
1112                sha1.update(sec_key.as_bytes());
1113
1114                let sec_key_resp = sec_key_response_finalize(&mut sha1, buf);
1115
1116                sec_key_response = value.eq(sec_key_resp);
1117            }
1118        }
1119
1120        connection && upgrade && sec_key_response
1121    }
1122
1123    fn sec_key_encode<'a>(nonce: &[u8], buf: &'a mut [u8]) -> &'a str {
1124        let nonce_base64_len = unwrap!(base64::engine::general_purpose::STANDARD
1125            .encode_slice(nonce, buf)
1126            .map_err(|_| ()));
1127
1128        unwrap!(core::str::from_utf8(&buf[..nonce_base64_len]).map_err(|_| ()))
1129    }
1130
1131    /// Compute the response for a given `Sec-WebSocket-Key`
1132    pub fn sec_key_response<'a>(
1133        sec_key: &str,
1134        buf: &'a mut [u8; MAX_BASE64_KEY_RESPONSE_LEN],
1135    ) -> &'a str {
1136        let mut sha1 = sha1_smol::Sha1::new();
1137
1138        sec_key_response_start(sec_key, &mut sha1);
1139        sec_key_response_finalize(&mut sha1, buf)
1140    }
1141
1142    fn sec_key_response_start(sec_key: &str, sha1: &mut sha1_smol::Sha1) {
1143        debug!("Computing response for key: {}", sec_key);
1144
1145        sha1.update(sec_key.as_bytes());
1146    }
1147
1148    fn sec_key_response_finalize<'a>(sha1: &mut sha1_smol::Sha1, buf: &'a mut [u8]) -> &'a str {
1149        const WS_MAGIC_GUUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
1150
1151        sha1.update(WS_MAGIC_GUUID.as_bytes());
1152
1153        let len = unwrap!(base64::engine::general_purpose::STANDARD
1154            .encode_slice(sha1.digest().bytes(), buf)
1155            .map_err(|_| ()));
1156
1157        let sec_key_response = unwrap!(core::str::from_utf8(&buf[..len]).map_err(|_| ()));
1158
1159        debug!("Computed response: {}", sec_key_response);
1160
1161        sec_key_response
1162    }
1163}
1164
1165#[cfg(test)]
1166mod test {
1167    use crate::{
1168        ws::{sec_key_response, MAX_BASE64_KEY_RESPONSE_LEN},
1169        BodyType, ConnectionType,
1170    };
1171
1172    #[test]
1173    fn test_resp() {
1174        let mut buf = [0_u8; MAX_BASE64_KEY_RESPONSE_LEN];
1175        let resp = sec_key_response("dGhlIHNhbXBsZSBub25jZQ==", &mut buf);
1176
1177        assert_eq!(resp, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
1178    }
1179
1180    #[test]
1181    fn test_resolve_conn() {
1182        // Default connection type resolution
1183        assert_eq!(
1184            unwrap!(ConnectionType::resolve(None, None, true)),
1185            ConnectionType::KeepAlive
1186        );
1187        assert_eq!(
1188            unwrap!(ConnectionType::resolve(None, None, false)),
1189            ConnectionType::Close
1190        );
1191
1192        // Connection type resolution based on carry-over (for responses)
1193        assert_eq!(
1194            unwrap!(ConnectionType::resolve(
1195                None,
1196                Some(ConnectionType::KeepAlive),
1197                false
1198            )),
1199            ConnectionType::KeepAlive
1200        );
1201        assert_eq!(
1202            unwrap!(ConnectionType::resolve(
1203                None,
1204                Some(ConnectionType::KeepAlive),
1205                true
1206            )),
1207            ConnectionType::KeepAlive
1208        );
1209
1210        // Connection type resoluton based on the header value
1211        assert_eq!(
1212            unwrap!(ConnectionType::resolve(
1213                Some(ConnectionType::Close),
1214                None,
1215                false
1216            )),
1217            ConnectionType::Close
1218        );
1219        assert_eq!(
1220            unwrap!(ConnectionType::resolve(
1221                Some(ConnectionType::KeepAlive),
1222                None,
1223                false
1224            )),
1225            ConnectionType::KeepAlive
1226        );
1227        assert_eq!(
1228            unwrap!(ConnectionType::resolve(
1229                Some(ConnectionType::Close),
1230                None,
1231                true
1232            )),
1233            ConnectionType::Close
1234        );
1235        assert_eq!(
1236            unwrap!(ConnectionType::resolve(
1237                Some(ConnectionType::KeepAlive),
1238                None,
1239                true
1240            )),
1241            ConnectionType::KeepAlive
1242        );
1243
1244        // Connection type in the headers should aggree with the carry-over one
1245        assert_eq!(
1246            unwrap!(ConnectionType::resolve(
1247                Some(ConnectionType::Close),
1248                Some(ConnectionType::Close),
1249                false
1250            )),
1251            ConnectionType::Close
1252        );
1253        assert_eq!(
1254            unwrap!(ConnectionType::resolve(
1255                Some(ConnectionType::KeepAlive),
1256                Some(ConnectionType::KeepAlive),
1257                false
1258            )),
1259            ConnectionType::KeepAlive
1260        );
1261        assert_eq!(
1262            unwrap!(ConnectionType::resolve(
1263                Some(ConnectionType::Close),
1264                Some(ConnectionType::Close),
1265                true
1266            )),
1267            ConnectionType::Close
1268        );
1269        assert_eq!(
1270            unwrap!(ConnectionType::resolve(
1271                Some(ConnectionType::KeepAlive),
1272                Some(ConnectionType::KeepAlive),
1273                true
1274            )),
1275            ConnectionType::KeepAlive
1276        );
1277        assert_eq!(
1278            unwrap!(ConnectionType::resolve(
1279                Some(ConnectionType::Close),
1280                Some(ConnectionType::KeepAlive),
1281                false
1282            )),
1283            ConnectionType::Close
1284        );
1285        assert!(ConnectionType::resolve(
1286            Some(ConnectionType::KeepAlive),
1287            Some(ConnectionType::Close),
1288            false
1289        )
1290        .is_err());
1291        assert_eq!(
1292            unwrap!(ConnectionType::resolve(
1293                Some(ConnectionType::Close),
1294                Some(ConnectionType::KeepAlive),
1295                true
1296            )),
1297            ConnectionType::Close
1298        );
1299        assert!(ConnectionType::resolve(
1300            Some(ConnectionType::KeepAlive),
1301            Some(ConnectionType::Close),
1302            true
1303        )
1304        .is_err());
1305    }
1306
1307    #[test]
1308    fn test_resolve_body() {
1309        // Request with no body type specified means Content-Length=0
1310        assert_eq!(
1311            unwrap!(BodyType::resolve(
1312                None,
1313                ConnectionType::KeepAlive,
1314                true,
1315                true,
1316                false
1317            )),
1318            BodyType::ContentLen(0)
1319        );
1320        assert_eq!(
1321            unwrap!(BodyType::resolve(
1322                None,
1323                ConnectionType::Close,
1324                true,
1325                true,
1326                false
1327            )),
1328            BodyType::ContentLen(0)
1329        );
1330        assert_eq!(
1331            unwrap!(BodyType::resolve(
1332                None,
1333                ConnectionType::KeepAlive,
1334                true,
1335                false,
1336                false
1337            )),
1338            BodyType::ContentLen(0)
1339        );
1340        assert_eq!(
1341            unwrap!(BodyType::resolve(
1342                None,
1343                ConnectionType::Close,
1344                true,
1345                false,
1346                false
1347            )),
1348            BodyType::ContentLen(0)
1349        );
1350        assert_eq!(
1351            unwrap!(BodyType::resolve(
1352                None,
1353                ConnectionType::Upgrade,
1354                false,
1355                true,
1356                false
1357            )),
1358            BodyType::ContentLen(0)
1359        );
1360
1361        // Receiving a response with no body type after requesting a connection upgrade with
1362        // HTTP1.0 is no allowed.
1363        assert!(BodyType::resolve(None, ConnectionType::Upgrade, false, false, false).is_err());
1364
1365        // Request or response with a chunked body type is invalid for HTTP1.0
1366        assert!(BodyType::resolve(
1367            Some(BodyType::Chunked),
1368            ConnectionType::Close,
1369            true,
1370            false,
1371            false
1372        )
1373        .is_err());
1374        assert!(BodyType::resolve(
1375            Some(BodyType::Chunked),
1376            ConnectionType::KeepAlive,
1377            true,
1378            false,
1379            false
1380        )
1381        .is_err());
1382        assert!(BodyType::resolve(
1383            Some(BodyType::Chunked),
1384            ConnectionType::Close,
1385            false,
1386            false,
1387            false
1388        )
1389        .is_err());
1390        assert!(BodyType::resolve(
1391            Some(BodyType::Chunked),
1392            ConnectionType::KeepAlive,
1393            false,
1394            false,
1395            false
1396        )
1397        .is_err());
1398
1399        // Raw body in a request is not allowed
1400        assert!(BodyType::resolve(
1401            Some(BodyType::Raw),
1402            ConnectionType::Close,
1403            true,
1404            true,
1405            false
1406        )
1407        .is_err());
1408        assert!(BodyType::resolve(
1409            Some(BodyType::Raw),
1410            ConnectionType::KeepAlive,
1411            true,
1412            true,
1413            false
1414        )
1415        .is_err());
1416        assert!(BodyType::resolve(
1417            Some(BodyType::Raw),
1418            ConnectionType::Close,
1419            true,
1420            false,
1421            false
1422        )
1423        .is_err());
1424        assert!(BodyType::resolve(
1425            Some(BodyType::Raw),
1426            ConnectionType::KeepAlive,
1427            true,
1428            false,
1429            false
1430        )
1431        .is_err());
1432
1433        // Raw body in a response with a Keep-Alive connection is not allowed
1434        assert!(BodyType::resolve(
1435            Some(BodyType::Raw),
1436            ConnectionType::KeepAlive,
1437            false,
1438            true,
1439            false
1440        )
1441        .is_err());
1442        assert!(BodyType::resolve(
1443            Some(BodyType::Raw),
1444            ConnectionType::KeepAlive,
1445            false,
1446            false,
1447            false
1448        )
1449        .is_err());
1450
1451        // The same, but with a Close connection IS allowed
1452        assert_eq!(
1453            unwrap!(BodyType::resolve(
1454                Some(BodyType::Raw),
1455                ConnectionType::Close,
1456                false,
1457                true,
1458                false
1459            )),
1460            BodyType::Raw
1461        );
1462        assert_eq!(
1463            unwrap!(BodyType::resolve(
1464                Some(BodyType::Raw),
1465                ConnectionType::Close,
1466                false,
1467                false,
1468                false
1469            )),
1470            BodyType::Raw
1471        );
1472
1473        // Request upgrades to chunked encoding should only work for HTTP1.1, and if there is no body type in the headers
1474        assert_eq!(
1475            unwrap!(BodyType::resolve(
1476                None,
1477                ConnectionType::Close,
1478                true,
1479                true,
1480                true
1481            )),
1482            BodyType::Chunked
1483        );
1484        assert_eq!(
1485            unwrap!(BodyType::resolve(
1486                None,
1487                ConnectionType::KeepAlive,
1488                true,
1489                true,
1490                true
1491            )),
1492            BodyType::Chunked
1493        );
1494        assert_eq!(
1495            unwrap!(BodyType::resolve(
1496                None,
1497                ConnectionType::Close,
1498                true,
1499                false,
1500                true
1501            )),
1502            BodyType::ContentLen(0)
1503        );
1504        assert_eq!(
1505            unwrap!(BodyType::resolve(
1506                None,
1507                ConnectionType::KeepAlive,
1508                true,
1509                false,
1510                true
1511            )),
1512            BodyType::ContentLen(0)
1513        );
1514
1515        // 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
1516        assert_eq!(
1517            unwrap!(BodyType::resolve(
1518                None,
1519                ConnectionType::KeepAlive,
1520                false,
1521                true,
1522                true
1523            )),
1524            BodyType::Chunked
1525        );
1526        // Response upgrades should not be honored if the connection is Close
1527        assert_eq!(
1528            unwrap!(BodyType::resolve(
1529                None,
1530                ConnectionType::Close,
1531                false,
1532                true,
1533                true
1534            )),
1535            BodyType::Raw
1536        );
1537    }
1538}