Compare commits

...

2 commits

Author SHA1 Message Date
2b6150a8e5
fix: allow all status codes with catch_all
10..69 range is still checked, see comment at L52
2024-08-01 12:35:50 +04:00
0a77d0c2e9
docs: add comments (and blank lines) 2024-08-01 12:28:13 +04:00
2 changed files with 41 additions and 2 deletions

View file

@ -46,43 +46,68 @@ impl From<rustls::ClientConfig> for Client {
impl Client { impl Client {
pub async fn request(self: &Self, url_str: &str) -> Result<Response, LibError> { pub async fn request(self: &Self, url_str: &str) -> Result<Response, LibError> {
let url = Url::parse(url_str).map_err(|e| InvalidUrl::ParseError(e))?; let url = Url::parse(url_str).map_err(|e| InvalidUrl::ParseError(e))?;
// for proxying http(s) through gemini server,
// use Client::request_with_host
if url.scheme() != "gemini" { if url.scheme() != "gemini" {
// deny non-Gemini req
return Err(InvalidUrl::SchemeNotGemini.into()); return Err(InvalidUrl::SchemeNotGemini.into());
} }
// userinfo (user:pswd@) is not allowed in Gemini
if !url.username().is_empty() { if !url.username().is_empty() {
return Err(InvalidUrl::UserinfoPresent.into()); return Err(InvalidUrl::UserinfoPresent.into());
} }
let host = url.host_str().ok_or(InvalidUrl::ConvertError)?; let host = url.host_str().ok_or(InvalidUrl::ConvertError)?;
let addr = (host, url.port().unwrap_or(1965)) let addr = (host, url.port().unwrap_or(1965))
.to_socket_addrs()? .to_socket_addrs()?
.next() .next()
.ok_or(InvalidUrl::ConvertError)?; .ok_or(InvalidUrl::ConvertError)?;
let domain = pki_types::ServerName::try_from(host) let domain = pki_types::ServerName::try_from(host)
.map_err(|_| InvalidUrl::ConvertError)? .map_err(|_| InvalidUrl::ConvertError)?
.to_owned(); .to_owned();
let stream = TcpStream::connect(&addr).await?; let stream = TcpStream::connect(&addr).await?;
let mut stream = self.connector.connect(domain, stream).await?; let mut stream = self.connector.connect(domain, stream).await?;
// Write URL, then CRLF
stream.write_all(url_str.as_bytes()).await?; stream.write_all(url_str.as_bytes()).await?;
stream.write_all(b"\r\n").await?; stream.write_all(b"\r\n").await?;
let mut buf: [u8; 3] = [0, 0, 0]; // 2 digits, space let mut buf: [u8; 3] = [0, 0, 0]; // 2 digits, space
stream.read_exact(&mut buf).await?; stream.read_exact(&mut buf).await?;
let status = Status::parse_status(&buf)?; let status = Status::parse_status(&buf)?;
let mut message: Vec<u8> = Vec::new(); let mut message: Vec<u8> = Vec::new();
let mut buf_reader = tokio::io::BufReader::new(&mut stream); let mut buf_reader = tokio::io::BufReader::new(&mut stream);
let mut buf: [u8; 1] = [0]; let mut buf: [u8; 1] = [0]; // buffer for LF (\n)
// reading message after status code
// until CRLF (\r\n)
loop { loop {
// until CR
buf_reader.read_until(b'\r', &mut message).await?; buf_reader.read_until(b'\r', &mut message).await?;
// now read next char...
buf_reader.read_exact(&mut buf).await?; buf_reader.read_exact(&mut buf).await?;
if buf[0] == b'\n' { if buf[0] == b'\n' {
// ...and check if it's LF
break; break;
} else { } else {
// ...otherwise, CR is a part of message, not a CRLF terminator,
// so append that one byte that's supposed to be LF (but not LF)
// to the message buffer
message.push(buf[0].into()); message.push(buf[0].into());
} }
} }
// trim last CR
if message.last().is_some_and(|c| c == &b'\r') { if message.last().is_some_and(|c| c == &b'\r') {
message.pop(); message.pop();
} }
// Vec<u8> -> ASCII or UTF-8 String
let message = String::from_utf8(message)?; let message = String::from_utf8(message)?;
Ok(Response::new(status, message, stream)) Ok(Response::new(status, message, stream))
} }
} }

View file

@ -48,17 +48,31 @@ pub enum StatusCode {
ClientCerts = 60, ClientCerts = 60,
CertNotAuthorized = 61, CertNotAuthorized = 61,
CertNotValid = 62, CertNotValid = 62,
// 1..6 first digit range check is still
// covered by conversion into ReplyType
// (see Status::parse_status)
#[num_enum(catch_all)]
Unknown(u8),
} }
const ASCII_ZERO: u8 = 48; // '0' const ASCII_ZERO: u8 = 48; // '0'
impl Status { impl Status {
pub fn parse_status(buf: &[u8]) -> Result<Self, LibError> { pub fn parse_status(buf: &[u8]) -> Result<Self, LibError> {
// simple decimal digit conversion
// '2' - '0' = 50 - 48 = 2 (from byte '2' to uint 2)
let first = buf[0] - ASCII_ZERO; let first = buf[0] - ASCII_ZERO;
let second = buf[1] - ASCII_ZERO; let second = buf[1] - ASCII_ZERO;
let code = first * 10 + second;
Ok(Status { Ok(Status {
status_code: StatusCode::try_from_primitive(first * 10 + second)?, // get enum item for 2-digit status code
status_code: StatusCode::try_from_primitive(code)?,
// get enum entry for first digit
reply_type: ReplyType::try_from_primitive(first)?, reply_type: ReplyType::try_from_primitive(first)?,
// provide separate field for the 2nd digit
second_digit: second, second_digit: second,
}) })
} }