use crate::enums::char_set::CharSet;
use crate::enums::header::http_header::HttpHeader;
use crate::enums::http_body::HttpBody;
use crate::enums::http_error::{HttpError, HttpErrorKind};
use crate::structures::header::header_list::HttpHeaderList;
use crate::utils::parse_util;
use async_regex::CRLF;
use core::fmt;
use futures::{AsyncBufRead, AsyncBufReadExt};
use std::fmt::Write;
pub struct HttpMessage {
pub start_line: String,
pub headers: HttpHeaderList,
pub body: HttpBody,
}
impl HttpMessage {
pub fn new<T: ToString>(start_line: T, headers: HttpHeaderList, body: HttpBody) -> HttpMessage {
HttpMessage {
start_line: start_line.to_string(),
headers,
body,
}
}
pub async fn read<R: AsyncBufRead + Unpin>(mut reader: R) -> Result<HttpMessage, HttpError> {
let mut start_line = String::new();
reader.read_line(&mut start_line).await?;
let mut headers = HttpHeaderList::read(&mut reader).await?;
let body = HttpBody::read(reader, &mut headers).await?;
Ok(HttpMessage {
start_line: start_line.trim_end().to_string(),
headers,
body,
})
}
pub fn get_header(&self, key: &str) -> Option<&HttpHeader> {
self.headers.get(key)
}
pub fn as_bytes(&self, add_content_length: bool) -> Box<[u8]> {
let body = self.body.as_bytes();
let mut headers_str = String::with_capacity(256);
let mut first = true;
for (_, header) in self.headers.iter() {
if !first {
headers_str.push_str("\r\n");
}
let _ = write!(headers_str, "{}", header);
first = false;
}
if add_content_length {
if !first {
headers_str.push_str("\r\n");
}
let _ = write!(
headers_str,
"{}:{}",
crate::utils::http_header_field_name::CONTENT_LENGTH,
body.len()
);
}
let headers = headers_str.as_bytes();
let mut result = Vec::with_capacity(self.start_line.len() + headers.len() + body.len() + 6);
result.extend_from_slice(self.start_line.as_bytes());
result.extend_from_slice(CRLF);
result.extend_from_slice(headers);
result.extend_from_slice(CRLF);
result.extend_from_slice(CRLF);
result.extend_from_slice(&body);
result.into_boxed_slice()
}
}
impl fmt::Display for HttpMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let charset = self.headers.get_charset(CharSet::Iso88591);
let bytes = self.as_bytes(false);
let str = match parse_util::bytes_to_string(&bytes, charset) {
Ok(str) => str,
Err(error) if matches!(error.kind, HttpErrorKind::UnsupportedCharset) => {
parse_util::bytes_to_string(&bytes, CharSet::Iso88591).unwrap()
}
_ => unreachable!(),
};
write!(f, "{}", str)
}
}
impl fmt::Debug for HttpMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[cfg(test)]
mod tests {
use futures::executor::block_on;
use std::str::from_utf8;
use super::*;
#[test]
fn test_read_content_msg() -> Result<(), HttpError> {
let msg = r###"POST /test HTTP/1.1
Host: foo.example
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
field1=value1&field2=value2"###;
let orig_msg = msg.replace("\n", from_utf8(&CRLF)?);
let msg = block_on(async { HttpMessage::read(&mut orig_msg.as_bytes()).await })?;
assert!(matches!(msg.body, HttpBody::Content(_)));
assert_eq!(msg.body.get_content().len(), 27);
Ok(())
}
#[test]
fn test_read_mulipart_form_body() -> Result<(), HttpError> {
let msg = r###"POST /test HTTP/1.1
Host: foo.example
Content-Type: multipart/form-data;boundary="simple boundary"
This is the preamble. It is to be ignored, though it
is a handy place for mail composers to include an
explanatory note to non-MIME compliant readers.
--simple boundary
This is 1 implicitly typed plain ASCII text.
It does NOT end with a linebreak.
--simple boundary
Content-type: text/plain; charset=us-ascii
This is 2 explicitly typed plain ASCII text.
It DOES end with a linebreak.
--simple boundary--
This is the epilogue. It is also to be ignored."###;
let msg = msg.replace("\n", from_utf8(&CRLF)?);
let msg = block_on(async { HttpMessage::read(&mut msg.as_bytes()).await })?;
assert!(matches!(msg.body, HttpBody::Multipart(_)));
let multipart = msg.body.get_multipart().unwrap();
assert_eq!(multipart.parts.len(), 2);
assert_eq!(multipart.parts[0].headers.len(), 0);
assert_eq!(multipart.parts[1].headers.len(), 1);
Ok(())
}
}