feat: #3 add a target and http version validator
This commit is contained in:
parent
9f0dc6e6c5
commit
5fc2a07bce
34
Cargo.lock
generated
34
Cargo.lock
generated
@ -2,6 +2,15 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-attributes"
|
name = "async-attributes"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@ -289,6 +298,12 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.132"
|
version = "0.2.132"
|
||||||
@ -367,12 +382,31 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simple-auth"
|
name = "simple-auth"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"json",
|
"json",
|
||||||
|
"lazy_static",
|
||||||
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@ -7,6 +7,8 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
json = "0.12.4"
|
json = "0.12.4"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
regex = "1"
|
||||||
|
|
||||||
[dependencies.async-std]
|
[dependencies.async-std]
|
||||||
version = "1.6"
|
version = "1.6"
|
||||||
|
|||||||
@ -4,15 +4,23 @@
|
|||||||
//! NOTE: only few parts of the specification has been implemented
|
//! NOTE: only few parts of the specification has been implemented
|
||||||
|
|
||||||
use json;
|
use json;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
type RequestParts = (String, String, String);
|
type RequestParts = (String, String, String);
|
||||||
|
|
||||||
// only two methods accepted
|
// TODO: put this const in a conf file ?
|
||||||
const HTTP_METHODS: [&'static str; 2] = ["POST", "GET"];
|
const HTTP_METHODS: [&'static str; 1] = ["POST"];
|
||||||
|
const HTTP_TARGETS: [&'static str; 3] = ["/validate/", "/get/", "/refresh/"];
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref HTTP_VERSION_REGEX: Regex = Regex::new("^HTTP/(1.1|2)$").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum HTTPVersion {
|
pub enum HTTPVersion {
|
||||||
Http1,
|
Http1,
|
||||||
|
Http2,
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,11 +28,23 @@ impl Into<String> for HTTPVersion {
|
|||||||
fn into(self) -> String {
|
fn into(self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::Http1 => "HTTP/1.1".to_string(),
|
Self::Http1 => "HTTP/1.1".to_string(),
|
||||||
|
Self::Http2 => "HTTP/2".to_string(),
|
||||||
Self::Unknown => "UNKNOWN".to_string(),
|
Self::Unknown => "UNKNOWN".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: not really satifying... could accept `String` too
|
||||||
|
impl From<&String> for HTTPVersion {
|
||||||
|
fn from(http_version: &String) -> Self {
|
||||||
|
match http_version.as_str() {
|
||||||
|
"HTTP/1.1" => Self::Http1,
|
||||||
|
"HTTP/2" => Self::Http2,
|
||||||
|
_ => Self::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HTTPStartLine {
|
pub struct HTTPStartLine {
|
||||||
pub method: String,
|
pub method: String,
|
||||||
@ -50,28 +70,55 @@ impl HTTPStartLine {
|
|||||||
return Err("unable to parse the start correctly");
|
return Err("unable to parse the start correctly");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !Self::check_method(parts[0].to_string()) {
|
let method = parts[0].to_string();
|
||||||
|
let target = parts[1].to_string();
|
||||||
|
let version = parts[2].to_string();
|
||||||
|
|
||||||
|
if !Self::check_method(&method) {
|
||||||
return Err("method validation failed, bad method");
|
return Err("method validation failed, bad method");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !Self::check_target(&target) {
|
||||||
|
return Err("target validation failed, unvalid target");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Self::check_version(&version) {
|
||||||
|
return Err("http version validation failed, unknown version");
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: parse correctly the different parts (using regex ?)
|
// TODO: parse correctly the different parts (using regex ?)
|
||||||
Ok(HTTPStartLine::new(
|
Ok(HTTPStartLine::new(
|
||||||
parts[0].to_string(),
|
method,
|
||||||
parts[1].to_string(),
|
target,
|
||||||
HTTPVersion::Http1,
|
HTTPVersion::from(&version),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// check_method checks if the start_line method is in a predefined HTTP method list
|
/// check_method checks if the start_line method is in a predefined HTTP method list
|
||||||
fn check_method(method: String) -> bool {
|
fn check_method(method: &String) -> bool {
|
||||||
for m in HTTP_METHODS.iter() {
|
for m in HTTP_METHODS.iter() {
|
||||||
if m.to_string() == method {
|
if m.to_string() == *method {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// check_target checks if the start_line target is in a predefined HTTP target whitelist
|
||||||
|
fn check_target(target: &String) -> bool {
|
||||||
|
for t in HTTP_TARGETS.iter() {
|
||||||
|
if t.to_string() == *target {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_version(version: &String) -> bool {
|
||||||
|
println!("version : {}", version);
|
||||||
|
HTTP_VERSION_REGEX.is_match(version)
|
||||||
|
}
|
||||||
|
|
||||||
fn is_valid(&self) -> bool {
|
fn is_valid(&self) -> bool {
|
||||||
return self.method != "" && self.target != "";
|
return self.method != "" && self.target != "";
|
||||||
}
|
}
|
||||||
@ -133,48 +180,22 @@ impl HTTPRequest {
|
|||||||
HTTPRequest { start_line, body }
|
HTTPRequest { start_line, body }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get mandatory request informations :
|
|
||||||
/// * start_line
|
|
||||||
/// * headers
|
|
||||||
fn get_request_mandats(request_parts: Vec<&str>) -> Result<(String, String), String> {
|
|
||||||
let headers_sline: Vec<&str> = request_parts[0].split("\r\n").collect();
|
|
||||||
match headers_sline.len() {
|
|
||||||
0 => return Err("request does not contain start_line or headers".to_string()),
|
|
||||||
1 => return Ok((headers_sline[0].to_string(), "".to_string())),
|
|
||||||
// TODO: check if in the spec it must be 2 or 3 !
|
|
||||||
2 | 3 => return Ok((headers_sline[0].to_string(), headers_sline[1].to_string())),
|
|
||||||
_ => return Err("bad start_line headers parsing".to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// split correctly the incoming request in order to get :
|
/// split correctly the incoming request in order to get :
|
||||||
/// * start_line
|
/// * start_line
|
||||||
/// * headers
|
/// * headers
|
||||||
/// * data (if exists)
|
/// * data (if exists)
|
||||||
fn get_request_parts(request: &str) -> Result<RequestParts, String> {
|
fn get_request_parts(request: &str) -> Result<RequestParts, String> {
|
||||||
// separate the body part from the start_line and the headers
|
// separate the body part from the start_line and the headers
|
||||||
let request_parts: Vec<&str> = request.split("\r\n\r\n").collect();
|
let request_parts: Vec<&str> = request.split("\r\n").collect();
|
||||||
if request_parts.len() == 0 {
|
println!("request_parts : {:?}", request_parts);
|
||||||
|
if request_parts.len() < 3 {
|
||||||
return Err("request has no enough informations to be correctly parsed".to_string());
|
return Err("request has no enough informations to be correctly parsed".to_string());
|
||||||
}
|
}
|
||||||
|
Ok((
|
||||||
match request_parts.len() {
|
request_parts[0].to_string(),
|
||||||
0 => {
|
request_parts[1].to_string(),
|
||||||
return Err("request has no enough informations to be correctly parsed".to_string())
|
request_parts[2].to_string(),
|
||||||
}
|
))
|
||||||
1 => match HTTPRequest::get_request_mandats(request_parts) {
|
|
||||||
Ok(v) => return Ok((v.0, v.1, "".to_string())),
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
},
|
|
||||||
2 => {
|
|
||||||
let body = request_parts[1];
|
|
||||||
match HTTPRequest::get_request_mandats(request_parts) {
|
|
||||||
Ok(v) => return Ok((v.0, v.1, body.to_string())),
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return Err("bad incoming request, impossible to parse".to_string()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// parse parses the request by spliting the incoming request with the separator `\r\n`
|
/// parse parses the request by spliting the incoming request with the separator `\r\n`
|
||||||
@ -244,18 +265,34 @@ fn test_handle_request() {
|
|||||||
is_valid: bool,
|
is_valid: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
let test_cases: [(&str, Expect); 7] = [
|
let test_cases: [(String, Expect); 10] = [
|
||||||
(
|
(
|
||||||
"GET / HTTP/1.1\r\n\r\n",
|
"POST /get/ HTTP/1.1\r\n\r\n".to_string(),
|
||||||
Expect {
|
Expect {
|
||||||
start_line: "GET / HTTP/1.1".to_string(),
|
start_line: "POST /get/ HTTP/1.1".to_string(),
|
||||||
body: None,
|
body: None,
|
||||||
is_valid: true,
|
is_valid: true,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"POST /refresh/ HTTP/2\r\n\r\n".to_string(),
|
||||||
|
Expect {
|
||||||
|
start_line: "POST /refresh/ HTTP/2".to_string(),
|
||||||
|
body: None,
|
||||||
|
is_valid: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"GET / HTTP/1.1\r\n\r\n".to_string(),
|
||||||
|
Expect {
|
||||||
|
start_line: " UNKNOWN".to_string(),
|
||||||
|
body: None,
|
||||||
|
is_valid: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
// intentionally add HTTP with no version number
|
// intentionally add HTTP with no version number
|
||||||
(
|
(
|
||||||
"OPTIONS /admin/2 HTTP/\r\nContent-Type: application/json\r\n",
|
"OPTIONS /admin/2 HTTP/\r\nContent-Type: application/json\r\n".to_string(),
|
||||||
Expect {
|
Expect {
|
||||||
start_line: " UNKNOWN".to_string(),
|
start_line: " UNKNOWN".to_string(),
|
||||||
body: None,
|
body: None,
|
||||||
@ -263,7 +300,7 @@ fn test_handle_request() {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"POST HTTP",
|
"POST HTTP".to_string(),
|
||||||
Expect {
|
Expect {
|
||||||
start_line: " UNKNOWN".to_string(),
|
start_line: " UNKNOWN".to_string(),
|
||||||
body: None,
|
body: None,
|
||||||
@ -271,7 +308,7 @@ fn test_handle_request() {
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"",
|
"".to_string(),
|
||||||
Expect {
|
Expect {
|
||||||
start_line: " UNKNOWN".to_string(),
|
start_line: " UNKNOWN".to_string(),
|
||||||
body: None,
|
body: None,
|
||||||
@ -279,7 +316,7 @@ fn test_handle_request() {
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"fjlqskjd /oks?id=65 HTTP/2\r\n\r\n",
|
"fjlqskjd /oks?id=65 HTTP/2\r\n\r\n".to_string(),
|
||||||
Expect {
|
Expect {
|
||||||
start_line: " UNKNOWN".to_string(),
|
start_line: " UNKNOWN".to_string(),
|
||||||
body: None,
|
body: None,
|
||||||
@ -287,7 +324,7 @@ fn test_handle_request() {
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
" ",
|
" ".to_string(),
|
||||||
Expect {
|
Expect {
|
||||||
start_line: " UNKNOWN".to_string(),
|
start_line: " UNKNOWN".to_string(),
|
||||||
body: None,
|
body: None,
|
||||||
@ -295,17 +332,25 @@ fn test_handle_request() {
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r#"lm //// skkss\r\ndkldklkdl\r\n"{"access_token":"AAAAAAAAAAAA.BBBBBBBBBB.CCCCCCCCCC","refresh_token": "DDDDDDDDDDD.EEEEEEEEEEE.FFFFF"}""#,
|
r#"lm //// skkss\r\ndkldklkdl\r\n"{"access_token":"AAAAAAAAAAAA.BBBBBBBBBB.CCCCCCCCCC","refresh_token": "DDDDDDDDDDD.EEEEEEEEEEE.FFFFF"}""#.to_string(),
|
||||||
Expect {
|
Expect {
|
||||||
start_line: " UNKNOWN".to_string(),
|
start_line: " UNKNOWN".to_string(),
|
||||||
body: Some(r#"{"access_token":"AAAAAAAAAAAA.BBBBBBBBBB.CCCCCCCCCC","refresh_token": "DDDDDDDDDDD.EEEEEEEEEEE.FFFFF"}"#.to_string()),
|
body: Some(r#"{"access_token":"AAAAAAAAAAAA.BBBBBBBBBB.CCCCCCCCCC","refresh_token": "DDDDDDDDDDD.EEEEEEEEEEE.FFFFF"}"#.to_string()),
|
||||||
is_valid: false,
|
is_valid: false,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
format!("{}\r\nuselessheaders\r\n{}", "POST /refresh/ HTTP/1.1", r#"{"access_token": "toto", "refresh_token": "tutu"}"#),
|
||||||
|
Expect {
|
||||||
|
start_line: "POST /refresh/ HTTP/1.1".to_string(),
|
||||||
|
body: Some(r#"{"access_token":"toto","refresh_token":"tutu"}"#.to_string()),
|
||||||
|
is_valid: true,
|
||||||
|
}
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (request, expect) in test_cases {
|
for (request, expect) in test_cases {
|
||||||
let http_request = HTTPRequest::from(request);
|
let http_request = HTTPRequest::from(request.as_str());
|
||||||
println!("{:?}", http_request);
|
println!("{:?}", http_request);
|
||||||
assert_eq!(expect.is_valid, http_request.is_valid());
|
assert_eq!(expect.is_valid, http_request.is_valid());
|
||||||
|
|
||||||
@ -314,7 +359,7 @@ fn test_handle_request() {
|
|||||||
|
|
||||||
match http_request.body {
|
match http_request.body {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
assert_eq!(expect.body.unwrap(), v.data)
|
assert_eq!(expect.body.unwrap(), v.data.dump())
|
||||||
}
|
}
|
||||||
None => continue,
|
None => continue,
|
||||||
}
|
}
|
||||||
@ -345,13 +390,13 @@ fn test_http_method() {
|
|||||||
let test_cases: Vec<(String, bool)> = vec![
|
let test_cases: Vec<(String, bool)> = vec![
|
||||||
("POST".to_string(), true),
|
("POST".to_string(), true),
|
||||||
("POST ".to_string(), false),
|
("POST ".to_string(), false),
|
||||||
("GET".to_string(), true),
|
("GET".to_string(), false),
|
||||||
("get".to_string(), false),
|
("get".to_string(), false),
|
||||||
("qsdqsfqsf/".to_string(), false),
|
("qsdqsfqsf/".to_string(), false),
|
||||||
("OPTIONS".to_string(), false),
|
("OPTIONS".to_string(), false),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (method, is_valid) in test_cases {
|
for (method, is_valid) in test_cases {
|
||||||
assert_eq!(is_valid, HTTPStartLine::check_method(method));
|
assert_eq!(is_valid, HTTPStartLine::check_method(&method));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user