#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum StatusCode {
Input(u8),
Success(u8),
Redirect(u8),
TemporaryFailure(u8),
PermanentFailure(u8),
ClientCertRequired(u8),
Unknown(u8),
}
impl From<u8> for StatusCode {
fn from(i: u8) -> Self {
if i > 99 {
return Self::Unknown(i);
}
let first_digit = i % 10;
let second_digit = i / 10;
match second_digit {
1 => Self::Input(first_digit),
2 => Self::Success(first_digit),
3 => Self::Redirect(first_digit),
4 => Self::TemporaryFailure(first_digit),
5 => Self::PermanentFailure(first_digit),
6 => Self::ClientCertRequired(first_digit),
_ => Self::Unknown(i),
}
}
}
impl From<StatusCode> for u8 {
fn from(s: StatusCode) -> Self {
match s {
StatusCode::Input(i) => 10 + i,
StatusCode::Success(i) => 20 + i,
StatusCode::Redirect(i) => 30 + i,
StatusCode::TemporaryFailure(i) => 40 + i,
StatusCode::PermanentFailure(i) => 50 + i,
StatusCode::ClientCertRequired(i) => 60 + i,
StatusCode::Unknown(i) => i,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ResponseParseError {
EmptyResponse,
InvalidResponseHeader,
}
impl core::fmt::Display for ResponseParseError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ResponseParseError::EmptyResponse => {
write!(f, "Error parsing response! The response was empty!")
},
ResponseParseError::InvalidResponseHeader => {
write!(f, "Error parsing response! The response's header was invalid")
},
}
}
}
impl std::error::Error for ResponseParseError {}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Response {
pub status: StatusCode,
pub meta: String,
pub data: Vec<u8>,
}
impl core::convert::TryFrom<&[u8]> for Response {
type Error = ResponseParseError;
fn try_from(raw_response: &[u8]) -> Result<Self, ResponseParseError> {
if raw_response.len() == 0 {
return Err(ResponseParseError::EmptyResponse);
}
let mut first_lf = 0;
for (i, b) in raw_response.iter().enumerate() {
if *b == b'\n' {
first_lf = i;
break;
}
}
if first_lf == 0 {
return Err(ResponseParseError::InvalidResponseHeader);
}
let response_header: &str = match core::str::from_utf8(&raw_response[..first_lf]) {
Ok(s) => s,
Err(_) => return Err(ResponseParseError::InvalidResponseHeader),
};
let (status_code, meta) = match response_header.split_once(' ') {
None => return Err(ResponseParseError::InvalidResponseHeader),
Some(r) => r,
};
let meta = meta.trim();
if meta.len() > 1024 {
return Err(ResponseParseError::InvalidResponseHeader);
}
let status_code = match status_code.parse::<u8>() {
Ok(s) => s,
Err(_) => return Err(ResponseParseError::InvalidResponseHeader),
};
let status = StatusCode::from(status_code);
let data = Vec::from(&raw_response[first_lf + 1..]);
Ok(Self {
status,
meta: String::from(meta),
data,
})
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct Request {
pub raw_string: String,
}
impl From<&super::url::Url> for Request {
fn from(url: &super::url::Url) -> Self {
let mut raw_string = url.to_string();
raw_string.push_str("\r\n");
Self {
raw_string
}
}
}
impl core::fmt::Display for Request {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.raw_string)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryFrom;
#[test]
fn status_code_from_u8_input() {
assert_eq!(StatusCode::from(18), StatusCode::Input(8));
}
#[test]
fn status_code_to_u8() {
assert_eq!(u8::from(StatusCode::Input(8)), 18);
}
#[test]
fn response_parse_slice() {
let raw_response = "20 text/gemini\r\n# Hello!";
let parsed_response = Response::try_from(raw_response.as_bytes()).unwrap();
assert_eq!(parsed_response.status, StatusCode::Success(0));
assert_eq!(parsed_response.meta, "text/gemini");
assert_eq!(parsed_response.data, "# Hello!".as_bytes());
}
#[test]
fn response_parse_slice_error_empty() {
let raw_response = "";
let parsed_response = Response::try_from(raw_response.as_bytes()).unwrap_err();
assert_eq!(parsed_response, ResponseParseError::EmptyResponse);
}
#[test]
fn response_parse_slice_error_invalid_header_missing_space() {
let raw_response = "20text/gemini\r\n#Hello!";
let parsed_response = Response::try_from(raw_response.as_bytes()).unwrap_err();
assert_eq!(parsed_response, ResponseParseError::InvalidResponseHeader);
}
#[test]
fn response_parse_slice_error_invalid_header_missing_space_and_meta() {
let raw_response = "20\r\n# Hello!";
let parsed_response = Response::try_from(raw_response.as_bytes()).unwrap_err();
assert_eq!(parsed_response, ResponseParseError::InvalidResponseHeader);
}
#[test]
fn response_parse_slice_error_invalid_header_meta_long() {
let mut raw_response: String = String::from("20 ");
for _ in 0..2048 {
raw_response.push('a');
}
raw_response.push_str("\r\n# Hello!");
let parsed_response = Response::try_from(raw_response.as_bytes()).unwrap_err();
assert_eq!(parsed_response, ResponseParseError::InvalidResponseHeader);
}
#[test]
fn response_parse_slice_empty_body() {
let raw_response = "20 text/gemini\r\n";
let parsed_response = Response::try_from(raw_response.as_bytes()).unwrap(); assert_eq!(parsed_response.status, StatusCode::Success(0)); assert_eq!(parsed_response.meta, "text/gemini"); assert_eq!(parsed_response.data, []);
}
#[test]
fn response_parse_slice_empty_meta() {
let raw_response = "20 \r\n";
let parsed_response = Response::try_from(raw_response.as_bytes()).unwrap();
assert_eq!(parsed_response.status, StatusCode::Success(0));
assert_eq!(parsed_response.meta, "");
assert_eq!(parsed_response.data, []);
}
}