From 0c46f27f89ab5a11032a453379a40581e0e5d907 Mon Sep 17 00:00:00 2001 From: Skylar Hill Date: Sun, 23 Jun 2024 03:02:27 -0500 Subject: [PATCH] Add basic URI parsing It works with Lagrange, but gemget still causes the server to throw "Operation would block" --- src/main.rs | 3 ++- src/protocol.rs | 27 ++++++++++++++++++--------- src/server.rs | 35 ++++++++++++++++++++++++++--------- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index 517710d..4ee165b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,8 +18,9 @@ fn main() -> Result<(), Box> { let config = rustls::ServerConfig::builder() .with_no_client_auth() .with_single_cert(certs, private_key)?; + let listener = TcpListener::bind(format!("[::]:{}", 1965)).unwrap(); let srv = server::Server { - listener: TcpListener::bind(format!("[::]:{}", 1965)).unwrap(), + listener, tls_config: config, }; match srv.listen() { diff --git a/src/protocol.rs b/src/protocol.rs index 9339b1d..a19a987 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -81,21 +81,39 @@ pub struct Request { #[derive(Debug)] pub enum RequestParseError { + NotTerminated, + Error(Box), UserInfoGiven, TooLarge, FragmentGiven, BadScheme, + BadUrl(url::ParseError), +} + +impl From for RequestParseError { + fn from(value: url::ParseError) -> Self { + RequestParseError::BadUrl(value) + } +} + +impl From for RequestParseError { + fn from(value: std::io::Error) -> Self { + RequestParseError::Error(Box::new(value)) + } } impl std::fmt::Display for RequestParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + RequestParseError::NotTerminated => write!(f, "Request not terminated with CRLF"), + RequestParseError::Error(e) => write!(f, "Request read error: {}", e), RequestParseError::UserInfoGiven => write!(f, "URI userinfo not permitted"), RequestParseError::TooLarge => write!(f, "URI larger than permitted 1024 bytes"), RequestParseError::FragmentGiven => write!(f, "URI fragments not permitted"), RequestParseError::BadScheme => { write!(f, "URI schemes besides 'gemini' not supported") } + RequestParseError::BadUrl(e) => e.fmt(f), } } } @@ -200,12 +218,3 @@ impl From for ErrorResponse { } } } - -impl From for ErrorResponse { - fn from(value: url::ParseError) -> Self { - ErrorResponse { - code: ErrorStatus::BadRequest, - error: value.to_string(), - } - } -} diff --git a/src/server.rs b/src/server.rs index 37ef6c7..5b790d1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,6 @@ -use crate::protocol::{self, Response}; +use crate::protocol::*; use mime::Mime; -use std::io::{Read, Write}; +use std::io::{self, BufRead, Read, Write}; use std::{net, str::FromStr, sync::Arc}; use url::Url; @@ -12,24 +12,41 @@ pub struct Server { impl Server { pub fn listen(&self) -> Result<(), Box> { - let (mut stream, _) = self.listener.accept().expect("15"); - let mut conn = - rustls::ServerConnection::new(Arc::new(self.tls_config.clone())).expect("17"); + let (mut stream, _) = self.listener.accept()?; + let mut conn = rustls::ServerConnection::new(Arc::new(self.tls_config.clone()))?; - let response = protocol::Request::try_from(Url::from_str("gemini://localhost")?)?.process(); + conn.complete_io(&mut stream)?; + let response = read_request(conn.reader())?.process(); conn.writer() .write_all(response.serialize().as_slice()) .expect("23"); - conn.complete_io(&mut stream).expect("24"); + conn.complete_io(&mut stream)?; Ok(()) } } -impl protocol::Request { +fn read_request(stream: rustls::Reader) -> Result { + let mut reader = io::BufReader::new(stream); + let mut buf = String::new(); + // The URI can be 1024 bytes long, plus \r\n + if reader.read_line(&mut buf)? > 1026 { + return Err(RequestParseError::TooLarge); + } + match buf.char_indices().rev().nth(1) { + Some((i, _)) if &buf[i..] != "\r\n" => Err(RequestParseError::NotTerminated), + None => Err(RequestParseError::NotTerminated), + Some(_) => Ok(()), + }?; + + let url = Url::from_str(buf.trim())?; + Request::try_from(url) +} + +impl Request { fn process(&self) -> impl Response { - protocol::SuccessResponse { + SuccessResponse { mime: Mime::from_str("text/gemini").expect("Hardcoded mime value failed??"), body: b"Hiiiiii im gay :>".to_vec(), }