impl http request + fix few tests and scope issues

This commit is contained in:
landrigun 2023-02-15 12:18:29 +00:00
parent 6cadabb483
commit 0c7b0bec12
5 changed files with 222 additions and 4 deletions

View File

@ -8,4 +8,5 @@ edition = "2021"
[dependencies] [dependencies]
json = "0.12.4" json = "0.12.4"
lazy_static = "1.4.0" lazy_static = "1.4.0"
log = "0.4.17"
regex = "1" regex = "1"

View File

@ -1,3 +1,5 @@
use json;
const NULL_CHAR: &'static str = "\0"; const NULL_CHAR: &'static str = "\0";
#[derive(Debug)] #[derive(Debug)]

View File

@ -5,9 +5,11 @@
//! NOTE: only few parts of the specification has been implemented //! NOTE: only few parts of the specification has been implemented
mod body; mod body;
mod request;
mod start_line; mod start_line;
mod version; mod version;
pub use body::HTTPBody; pub use body::HTTPBody;
pub use request::HTTPRequest;
pub use start_line::HTTPStartLine; pub use start_line::HTTPStartLine;
pub use version::HTTPVersion; pub use version::HTTPVersion;

View File

@ -1,6 +1,205 @@
use std::collections::VecDeque; use std::collections::VecDeque;
type RequestParts = (String, VecDeque<String>, String); use crate::{HTTPBody, HTTPStartLine};
type RequestParts<'a> = (&'a str, VecDeque<&'a str>, &'a str);
const HTTP_REQUEST_SEPARATOR: &'static str = "\r\n"; const HTTP_REQUEST_SEPARATOR: &'static str = "\r\n";
const NULL_CHAR: &'static str = "\0";
/// HTTPRequest represents an HTTP request (headers are not parsed)
#[derive(Debug)]
pub struct HTTPRequest<'a> {
pub start_line: HTTPStartLine<'a>,
pub body: Option<HTTPBody>,
// includes the client IP + port (should be in the headers)
pub addr: String,
}
impl<'a> HTTPRequest<'a> {
/// get_request_parts splits correctly the incoming request in order to get:
/// * start_line
/// * headers
/// * data (if exists)
fn get_request_parts(request: &str) -> Result<RequestParts, &str> {
let mut request_parts: VecDeque<&str> = request.split(HTTP_REQUEST_SEPARATOR).collect();
if request_parts.len() < 3 {
return Err("request has no enough informations to be correctly parsed");
}
let start_line = request_parts.pop_front().unwrap();
let body = request_parts.pop_back().unwrap();
Ok((start_line, request_parts, body))
}
fn parse(request: &str) -> Result<HTTPRequest, String> {
match HTTPRequest::get_request_parts(&request) {
Ok(rp) => {
let mut request = HTTPRequest::default();
let start_line = HTTPStartLine::parse(rp.0);
match start_line {
Ok(v) => request.start_line = v,
Err(e) => log::error!("while parsing start_line details={}", e),
}
let body = HTTPBody::try_from(rp.2);
match body {
Ok(v) => request.body = Some(v),
Err(e) => log::warn!("{}", e),
}
return Ok(request);
}
Err(e) => Err(e.to_string()),
}
}
}
impl<'a> Default for HTTPRequest<'a> {
fn default() -> Self {
HTTPRequest {
start_line: HTTPStartLine::default(),
body: None,
addr: "".to_string(),
}
}
}
impl<'a> From<&'a str> for HTTPRequest<'a> {
fn from(request: &'a str) -> Self {
match Self::parse(request) {
Ok(v) => v,
Err(e) => {
log::error!("{}", e);
return HTTPRequest::default();
}
}
}
}
#[test]
fn test_request() {
struct Expect<'a> {
start_line: &'a str,
body: Option<&'a str>,
is_valid: bool,
}
let test_cases: [(&str, Expect); 12] = [
(
"POST /get/ HTTP/1.1\r\n\r\n",
Expect {
start_line: "POST /get/ HTTP/1.1",
body: None,
is_valid: true,
},
),
(
"POST /refresh/ HTTP/2\r\n\r\n",
Expect {
start_line: "POST /refresh/ HTTP/2",
body: None,
is_valid: true,
},
),
(
"POST /validate/ HTTP/1.0\r\n\r\n",
Expect {
start_line: "POST /validate/ HTTP/1.0",
body: None,
is_valid: true,
},
),
(
"GET / HTTP/1.1\r\n\r\n",
Expect {
start_line: "GET / HTTP/1.1",
body: None,
is_valid: true,
},
),
// intentionally add HTTP with no version number
(
"OPTIONS /admin/2 HTTP/\r\nContent-Type: application/json\r\n",
Expect {
start_line: " UNKNOWN",
body: None,
is_valid: false,
},
),
(
"POST HTTP",
Expect {
start_line: " UNKNOWN",
body: None,
is_valid: false,
}
),
(
"",
Expect {
start_line: " UNKNOWN",
body: None,
is_valid: false,
}
),
(
"fjlqskjd /oks?id=65 HTTP/2\r\n\r\n",
Expect {
start_line: "fjlqskjd /oks?id=65 HTTP/2",
body: None,
is_valid: true,
}
),
(
" ",
Expect {
start_line: " UNKNOWN",
body: None,
is_valid: false,
}
),
(
r#"lm //// skkss\r\ndkldklkdl\r\n"{"access_token":"AAAAAAAAAAAA.BBBBBBBBBB.CCCCCCCCCC","refresh_token": "DDDDDDDDDDD.EEEEEEEEEEE.FFFFF"}""#,
Expect {
start_line: " UNKNOWN",
body: Some(r#"{"access_token":"AAAAAAAAAAAA.BBBBBBBBBB.CCCCCCCCCC","refresh_token": "DDDDDDDDDDD.EEEEEEEEEEE.FFFFF"}"#),
is_valid: false,
}
),
(
"POST /refresh/ HTTP/1.1\r\nuselessheaders\r\n\"{\"access_token\": \"toto\", \"refresh_token\": \"tutu\"}",
Expect {
start_line: "POST /refresh/ HTTP/1.1",
body: Some(r#"{"access_token":"toto","refresh_token":"tutu"}"#),
is_valid: true,
}
),
(
"POST /get/ HTTP/1.1\r\nuselessheaders\r\n\"{\"token\": \"toto\", \"refresh_token\": \"tutu\"}",
Expect {
start_line: "POST /get/ HTTP/1.1",
body: Some(r#"{"token":"toto","refresh_token":"tutu"}"#),
is_valid: true,
}
),
];
for (request, expect) in test_cases {
let http_request = HTTPRequest::from(request);
println!("{}", request);
assert_eq!(expect.is_valid, http_request.start_line.is_valid());
let start_line: String = http_request.start_line.into();
assert_eq!(expect.start_line, start_line);
match http_request.body {
Some(v) => {
assert_eq!(expect.body.unwrap(), v.get_data().dump())
}
None => continue,
}
}
}

View File

@ -1,7 +1,7 @@
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use crate::version::HTTPVersion; use crate::HTTPVersion;
lazy_static! { lazy_static! {
static ref HTTP_VERSION_REGEX: Regex = Regex::new("^HTTP/(1.1|1.0|2)$").unwrap(); static ref HTTP_VERSION_REGEX: Regex = Regex::new("^HTTP/(1.1|1.0|2)$").unwrap();
@ -23,7 +23,7 @@ impl<'a> HTTPStartLine<'a> {
} }
} }
fn parse(start_line: &'a str) -> Result<Self, &str> { pub fn parse(start_line: &'a str) -> Result<Self, &str> {
let parts: Vec<&str> = start_line.split(" ").collect(); let parts: Vec<&str> = start_line.split(" ").collect();
if parts.len() != 3 { if parts.len() != 3 {
return Err("unable to parse the start correctly"); return Err("unable to parse the start correctly");
@ -42,11 +42,25 @@ impl<'a> HTTPStartLine<'a> {
)) ))
} }
pub fn is_valid(&self) -> bool {
return self.method != "" && self.target != "" && self.version != HTTPVersion::Unknown;
}
fn check_version(version: &str) -> bool { fn check_version(version: &str) -> bool {
HTTP_VERSION_REGEX.is_match(version) HTTP_VERSION_REGEX.is_match(version)
} }
} }
impl<'a> Default for HTTPStartLine<'a> {
fn default() -> Self {
HTTPStartLine {
method: "",
target: "",
version: HTTPVersion::Unknown,
}
}
}
impl<'a> Into<String> for HTTPStartLine<'a> { impl<'a> Into<String> for HTTPStartLine<'a> {
fn into(self) -> String { fn into(self) -> String {
let version: String = self.version.into(); let version: String = self.version.into();