postnummer-app/rs-backend/src/parse/client.rs

137 lines
3.9 KiB
Rust

fn read_until_whitespace(
stream_buffer: &mut [u8],
stream_initial_index: usize,
) -> super::Result<String> {
let buffer = String::from_utf8(
(stream_initial_index..stream_buffer.len())
.map_while(|stream_index| {
let byte = stream_buffer[stream_index];
if byte == b' ' || byte == b'\r' && stream_buffer[stream_index + 1] == b'\n' {
return None;
}
Some(byte)
})
.collect(),
)?;
Ok(buffer)
}
pub struct HttpStart {
pub method: String,
pub path: String,
pub version: String,
pub total_length: usize,
}
pub fn http_start(stream_buffer: &mut [u8]) -> super::Result<HttpStart> {
let mut total_length = 0;
let method = read_until_whitespace(stream_buffer, total_length)?;
total_length += method.len() + 1;
let path = read_until_whitespace(stream_buffer, total_length)?;
total_length += path.len() + 1;
let version = read_until_whitespace(stream_buffer, total_length)?;
total_length += version.len() + 2; // CRLF
Ok(HttpStart {
method,
path,
version,
total_length,
})
}
pub struct HttpHeader {
pub key: String,
pub content: String,
pub total_length: usize,
}
pub fn http_header(
stream_buffer: &mut [u8],
stream_initial_index: usize,
) -> super::Result<HttpHeader> {
if read_until_whitespace(stream_buffer, stream_initial_index)?.is_empty() {
return Err(super::Error::InvalidHeader);
}
let key = String::from_utf8(
(stream_initial_index..stream_buffer.len())
.map_while(|stream_index| {
let byte = stream_buffer[stream_index];
if byte == b':' {
None
} else {
Some(byte)
}
})
.collect(),
)?;
let content = String::from_utf8(
(stream_initial_index + key.len() + 2..stream_buffer.len())
.map_while(|stream_index| {
let byte = stream_buffer[stream_index];
if byte == b'\r' && stream_buffer[stream_index + 1] == b'\n' {
None
} else {
Some(byte)
}
})
.collect(),
)?;
let total_length = key.len() + content.len() + 4; // ': ' + '\r\n'
Ok(HttpHeader {
key,
content,
total_length,
})
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn valid_http_header() {
let mut buffer = b"Content-Type: application/json\r\n".to_owned();
let len = buffer.len();
let header = http_header(&mut buffer, 0).expect("should not fail with valid input");
assert_eq!(header.total_length, len, "total length should be {len}");
assert_eq!(
&header.content, "application/json",
"header content should be application/json"
);
assert_eq!(
&header.key, "Content-Type",
"header key should be Content-Type"
);
}
#[test]
fn invalid_http_header() {
let mut buffer = b"\r\n".to_owned();
let result = http_header(&mut buffer, 0)
.err()
.expect("should fail with invalid input");
assert_eq!(
result,
crate::parse::Error::InvalidHeader,
"should return ParseError::InvalidHeader on invalid header"
);
}
#[test]
fn valid_http_start() {
let mut buffer = b"GET /resource HTTP/1.1\r\n".to_owned();
let len = buffer.len();
let start = http_start(&mut buffer).expect("should not fail with valid input");
assert_eq!(start.total_length, len, "total length should be {len}");
assert_eq!(&start.method, "GET", "method should be GET");
assert_eq!(&start.path, "/resource", "path should be /resource");
assert_eq!(&start.version, "HTTP/1.1", "version should be HTTP/1.1");
}
}