use std::collections::HashMap;
use std::fmt::Write;
use crate::error::ProtocolError;
const MAX_HEADER_BYTES: usize = 64 * 1024;
const MAX_BODY_BYTES: usize = 32 * 1024 * 1024;
pub struct HttpRequest {
buffer: Vec<u8>,
method: Option<String>,
url: Option<String>,
headers: HashMap<String, String>,
body: Option<Vec<u8>>,
content_length: Option<usize>,
headers_complete: bool,
complete: bool,
error: Option<String>,
}
impl HttpRequest {
pub fn new() -> Self {
Self {
buffer: Vec::new(),
method: None,
url: None,
headers: HashMap::new(),
body: None,
content_length: None,
headers_complete: false,
complete: false,
error: None,
}
}
pub fn add_data(&mut self, data: &[u8]) -> Result<(), ProtocolError> {
if self.complete {
return Ok(());
}
self.buffer.extend_from_slice(data);
if !self.headers_complete && self.buffer.len() > MAX_HEADER_BYTES {
return Err(ProtocolError::InvalidRtsp("request headers exceed 64 KiB".into()));
}
if !self.headers_complete {
self.try_parse_headers()?;
}
if self.headers_complete {
self.try_complete_body();
}
Ok(())
}
fn try_parse_headers(&mut self) -> Result<(), ProtocolError> {
let mut parse_buf = self.buffer.clone();
if let Some(pos) = parse_buf.windows(8).position(|w| w == b"RTSP/1.0") {
parse_buf[pos..pos + 4].copy_from_slice(b"HTTP");
}
let mut header_buf = [httparse::EMPTY_HEADER; 64];
let mut req = httparse::Request::new(&mut header_buf);
match req.parse(&parse_buf) {
Ok(httparse::Status::Complete(body_offset)) => {
self.method = req.method.map(|m| m.to_string());
self.url = req.path.map(|p| p.to_string());
for h in req.headers.iter() {
self.headers.insert(
h.name.to_ascii_lowercase(),
String::from_utf8_lossy(h.value).to_string(),
);
}
self.content_length = self.headers.get("content-length").and_then(|v| v.parse::<usize>().ok());
if self.content_length.unwrap_or(0) > MAX_BODY_BYTES {
return Err(ProtocolError::InvalidRtsp("request body exceeds 32 MiB".into()));
}
self.headers_complete = true;
let remaining = self.buffer[body_offset..].to_vec();
self.buffer = remaining;
Ok(())
}
Ok(httparse::Status::Partial) => Ok(()),
Err(e) => {
let msg = format!("{e}");
self.error = Some(msg.clone());
Err(ProtocolError::InvalidRtsp(msg))
}
}
}
fn try_complete_body(&mut self) {
let needed = self.content_length.unwrap_or(0);
if self.buffer.len() >= needed {
if needed > 0 {
self.body = Some(self.buffer[..needed].to_vec());
}
self.complete = true;
}
}
pub fn take_leftover(&mut self) -> Vec<u8> {
if !self.complete {
return Vec::new();
}
let needed = self.content_length.unwrap_or(0);
if self.buffer.len() > needed {
self.buffer[needed..].to_vec()
} else {
Vec::new()
}
}
pub fn is_complete(&self) -> bool {
self.complete
}
pub fn headers_complete(&self) -> bool {
self.headers_complete
}
pub fn has_error(&self) -> bool {
self.error.is_some()
}
pub fn error(&self) -> Option<&str> {
self.error.as_deref()
}
pub fn method(&self) -> Option<&str> {
self.method.as_deref()
}
pub fn url(&self) -> Option<&str> {
self.url.as_deref()
}
pub fn header(&self, name: &str) -> Option<&str> {
self.headers.get(&name.to_ascii_lowercase()).map(|s| s.as_str())
}
pub fn data(&self) -> Option<&[u8]> {
self.body.as_deref()
}
}
pub struct HttpResponse {
data: Vec<u8>,
complete: bool,
disconnect: bool,
code: u16,
}
impl HttpResponse {
pub fn new(protocol: &str, code: u16, message: &str) -> Self {
let mut data = Vec::with_capacity(1024);
let status_line = format!("{protocol} {code} {message}\r\n");
data.extend_from_slice(status_line.as_bytes());
Self {
data,
complete: false,
disconnect: false,
code,
}
}
pub fn add_header(&mut self, name: &str, value: &str) {
self.data.extend_from_slice(name.as_bytes());
self.data.extend_from_slice(b": ");
self.data.extend_from_slice(value.as_bytes());
self.data.extend_from_slice(b"\r\n");
}
pub fn finish(&mut self, body: Option<&[u8]>) {
if let Some(body) = body.filter(|b| !b.is_empty()) {
let mut len_str = String::new();
write!(len_str, "{}", body.len()).unwrap();
self.data.extend_from_slice(b"Content-Length: ");
self.data.extend_from_slice(len_str.as_bytes());
self.data.extend_from_slice(b"\r\n\r\n");
self.data.extend_from_slice(body);
} else {
self.data.extend_from_slice(b"\r\n");
}
self.complete = true;
}
pub fn set_disconnect(&mut self, disconnect: bool) {
self.disconnect = disconnect;
}
pub fn status_code(&self) -> u16 {
self.code
}
pub fn get_disconnect(&self) -> bool {
self.disconnect
}
pub fn get_data(&self) -> &[u8] {
&self.data
}
}
impl Default for HttpRequest {
fn default() -> Self {
Self::new()
}
}