shared/http/
mod.rs

1mod request;
2mod response;
3mod url;
4
5use alloc::string::String;
6pub use request::*;
7pub use response::*;
8pub use url::*;
9
10pub const LINE_ENDING: &str = "\r\n";
11pub const HEADER_SEPARATOR: &str = ": ";
12
13pub fn add_line(buffer: &mut [u8], position: usize, line: &[u8]) -> Option<usize> {
14    let destination_buffer = buffer.get_mut(position..position + line.len())?;
15    destination_buffer.copy_from_slice(line);
16    let mut new_position = position + line.len();
17
18    // Add line ending
19    let line_ending = LINE_ENDING.as_bytes();
20    let destination_buffer = buffer.get_mut(new_position..new_position + line_ending.len())?;
21    destination_buffer.copy_from_slice(line_ending);
22    new_position += line_ending.len();
23
24    Some(new_position)
25}
26
27pub fn add_line_iterator<'a, I>(buffer: &mut [u8], mut position: usize, iter: I) -> Option<usize>
28where
29    I: Iterator<Item = &'a [u8]>,
30{
31    log::information!(
32        "Adding line iterator to buffer at position {} to buffer of length {}",
33        position,
34        buffer.len()
35    );
36    for part in iter {
37        log::information!("Adding part: {:?}", core::str::from_utf8(part).ok());
38        let part_length = part.len();
39
40        log::information!("Part length: {}", part_length);
41
42        let destination_buffer = buffer.get_mut(position..position + part_length)?;
43
44        log::information!("Copying part to buffer at position {}", position);
45        destination_buffer.copy_from_slice(part);
46        position += part_length;
47    }
48
49    log::information!(
50        "All parts added, final position before line ending: {}",
51        position
52    );
53    // Add line ending
54    let line_ending = LINE_ENDING.as_bytes();
55    let destination_buffer = buffer.get_mut(position..position + line_ending.len())?;
56    destination_buffer.copy_from_slice(line_ending);
57    position += line_ending.len();
58
59    Some(position)
60}
61
62pub fn parse_header(buffer: &[u8]) -> Option<(&str, &str)> {
63    let mut parts = buffer.splitn(2, |&b| b == b':');
64
65    let name = parts.next()?;
66    let value = parts.next()?;
67
68    let name = core::str::from_utf8(name).ok()?.trim();
69    let value = core::str::from_utf8(value).ok()?.trim();
70
71    Some((name, value))
72}
73
74pub fn split_lines(buffer: &[u8]) -> impl Iterator<Item = &[u8]> + '_ + Clone {
75    buffer.split(|&b| b == b'\n').map(|line| {
76        if line.ends_with(b"\r") {
77            &line[..line.len() - 1]
78        } else {
79            line
80        }
81    })
82}
83
84pub fn get_body(buffer: &[u8]) -> Option<&[u8]> {
85    // Find the end of headers where 2 consecutive line endings occur, DO NOT SPLIT LINES
86    let headers_end = buffer
87        .windows(4)
88        .position(|window| window == b"\r\n\r\n")
89        .map(|pos| pos + 4)?; // Move past the header ending
90
91    buffer.get(headers_end..)
92}
93
94pub fn format_url<'a>(
95    scheme: &str,
96    host: &str,
97    query_parameters: impl Iterator<Item = (&'a str, &'a str)> + Clone,
98) -> String {
99    let total_query_length: usize = query_parameters
100        .clone()
101        .map(|(key, value)| key.len() + value.len() + 2) // +2 for '=' and '&'
102        .sum();
103
104    let mut url = String::with_capacity(
105        scheme.len() + 3 + host.len() + 1 + total_query_length.saturating_sub(1),
106    ); // 3 for "://", 1 for "?",
107
108    url.push_str(scheme);
109    url.push_str("://");
110    url.push_str(host);
111    if total_query_length > 0 {
112        url.push('?');
113
114        for (i, (key, value)) in query_parameters.enumerate() {
115            url.push_str(key);
116            url.push('=');
117            url.push_str(value);
118
119            if i < total_query_length - 1 {
120                url.push('&');
121            }
122        }
123    }
124
125    url
126}