Skip to main content

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    for part in iter {
32        let part_length = part.len();
33
34        let destination_buffer = buffer.get_mut(position..position + part_length)?;
35        destination_buffer.copy_from_slice(part);
36        position += part_length;
37    }
38
39    // Add line ending
40    let line_ending = LINE_ENDING.as_bytes();
41    let destination_buffer = buffer.get_mut(position..position + line_ending.len())?;
42    destination_buffer.copy_from_slice(line_ending);
43    position += line_ending.len();
44
45    Some(position)
46}
47
48pub fn parse_header(buffer: &[u8]) -> Option<(&str, &str)> {
49    let mut parts = buffer.splitn(2, |&b| b == b':');
50
51    let name = parts.next()?;
52    let value = parts.next()?;
53
54    let name = core::str::from_utf8(name).ok()?.trim();
55    let value = core::str::from_utf8(value).ok()?.trim();
56
57    Some((name, value))
58}
59
60pub fn split_lines(buffer: &[u8]) -> impl Iterator<Item = &[u8]> + '_ + Clone {
61    buffer.split(|&b| b == b'\n').map(|line| {
62        if line.ends_with(b"\r") {
63            &line[..line.len() - 1]
64        } else {
65            line
66        }
67    })
68}
69
70pub fn get_body(buffer: &[u8]) -> Option<&[u8]> {
71    // Find the end of headers where 2 consecutive line endings occur, DO NOT SPLIT LINES
72    let headers_end = buffer
73        .windows(4)
74        .position(|window| window == b"\r\n\r\n")
75        .map(|pos| pos + 4)?; // Move past the header ending
76
77    buffer.get(headers_end..)
78}
79
80pub fn format_url<'a>(
81    scheme: &str,
82    host: &str,
83    query_parameters: impl Iterator<Item = (&'a str, &'a str)> + Clone,
84) -> String {
85    let total_query_length: usize = query_parameters
86        .clone()
87        .map(|(key, value)| key.len() + value.len() + 2) // +2 for '=' and '&'
88        .sum();
89
90    let mut url = String::with_capacity(
91        scheme.len() + 3 + host.len() + 1 + total_query_length.saturating_sub(1),
92    ); // 3 for "://", 1 for "?",
93
94    url.push_str(scheme);
95    url.push_str("://");
96    url.push_str(host);
97    if total_query_length > 0 {
98        url.push('?');
99
100        for (i, (key, value)) in query_parameters.enumerate() {
101            url.push_str(key);
102            url.push('=');
103            url.push_str(value);
104
105            if i < total_query_length - 1 {
106                url.push('&');
107            }
108        }
109    }
110
111    url
112}