diff --git a/Cargo.toml b/Cargo.toml index f7e747e..37e5ac4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ thiserror = "1" pretty_env_logger = "0.4" [features] -default = ["client"] +default = ["client", "server"] client = ["rustls", "webpki", "webpki-roots"] +server = ["rustls", "webpki", "webpki-roots"] diff --git a/src/client.rs b/src/client.rs index 4cba5e5..a9b05c7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,4 +1,4 @@ -use crate::{Response, StatusCode}; +use crate::{Response}; use rustls::{ClientConfig, ClientSession, Stream, TLSError}; use std::{io::prelude::*, net::TcpStream, sync::Arc}; use url::Url; @@ -50,6 +50,8 @@ pub fn get(u: String) -> Result { #[cfg(test)] mod tests { use super::*; + use crate::*; + #[test] fn gemini_homepage() -> Result<(), Error> { let _ = pretty_env_logger::try_init(); diff --git a/src/lib.rs b/src/lib.rs index dddc4b7..80e605f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,3 +8,8 @@ pub use status_code::StatusCode; mod client; #[cfg(feature = "client")] pub use client::{get, Error as ClientError}; + +#[cfg(feature = "server")] +mod server; +#[cfg(feature = "server")] +pub use server::{Handler, serve, serve_plain}; diff --git a/src/response.rs b/src/response.rs index c55c2da..e8cde59 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,6 +1,6 @@ use crate::StatusCode; use num::FromPrimitive; -use std::io::{prelude::*, ErrorKind}; +use std::io::{prelude::*, ErrorKind, self}; /// A Gemini response as specified in [the spec](https://gemini.circumlunar.space/docs/specification.html). #[derive(Default)] @@ -19,7 +19,7 @@ enum State { ReadBody { data: Vec }, } -/// Response parsing error. +/// Response error. #[derive(thiserror::Error, Debug)] pub enum Error { #[error("unexpected end of file found while parsing response")] @@ -40,8 +40,8 @@ pub enum Error { #[error("None found when none should not be found")] NoneFound, - #[error("TLS error: {0:?}")] - TLS(#[from] rustls::TLSError), + #[error("Response meta is too long")] + ResponseMetaTooLong, } impl Response { @@ -60,7 +60,6 @@ impl Response { } } log::trace!("buf: {:?}: {:?}", buf, buf[0] as char); - log::trace!("n: {}", n); } Err(why) => { @@ -75,8 +74,6 @@ impl Response { } } - // log::trace!("state: {:?}", state); - match &mut state { State::ReadStatusCode { data } => match buf[0] as char { '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => { @@ -104,13 +101,25 @@ impl Response { result.meta = std::str::from_utf8(data)?.to_string(); state = State::ReadBody { data: vec![] }; } - _ => data.push(buf[0]), + _ => { + if data.len() == 1024 { + return Err(Error::ResponseMetaTooLong) + } + data.push(buf[0]); + } }, State::ReadBody { data } => data.push(buf[0]), } } } + + pub fn write(self, out: &mut impl Write) -> io::Result<()> { + write!(out, "{} {}\r\n", self.status.num(), self.meta)?; + out.write(&self.body)?; + + Ok(()) + } } #[cfg(test)] @@ -151,4 +160,19 @@ mod tests { Ok(()) } + + #[test] + fn meta_too_long() { + let _ = pretty_env_logger::try_init(); + let mut fin = std::fs::File::open("./testdata/meta_too_long.txt").unwrap(); + + match Response::parse(&mut fin) { + Ok(_) => panic!("wanted error but didn't get one"), + Err(why) => if let ResponseError::ResponseMetaTooLong = why { + println!("ok"); + } else { + panic!("wanted ResponseError::ResponseMetaTooLong") + }, + } + } } diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..4965a12 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,15 @@ +use crate::Response; +use std::{error::Error, io}; +use url::Url; + +pub trait Handler { + fn handle(u: Url) -> Result>; +} + +pub fn serve(_h: impl Handler, _port: u16) -> io::Result<()> { + Ok(()) +} + +pub fn serve_plain(_h: impl Handler, _port: u16) -> io::Result<()> { + Ok(()) +} diff --git a/src/status_code.rs b/src/status_code.rs index 6a7cf8c..359d7a4 100644 --- a/src/status_code.rs +++ b/src/status_code.rs @@ -118,6 +118,13 @@ pub enum StatusCode { CertificateNotValid = 62, } +impl StatusCode { + // Converts this StatusCode to its numerical representation, consuming it. + pub fn num(self) -> u8 { + self as u8 + } +} + impl Default for StatusCode { fn default() -> Self { StatusCode::Success diff --git a/testdata/meta_too_long.txt b/testdata/meta_too_long.txt new file mode 100644 index 0000000..6e5f18a --- /dev/null +++ b/testdata/meta_too_long.txt @@ -0,0 +1 @@ +20 fGeQX5GJ9Tu1fk8VkEVqVqvAujalD2z0snURmdmwai1eYPvJEbtdFKW0SAPlWuuaVFkq8pDNmqAyiDNOorsgw8OGBlOLW3A02XdnsB4MUjiH3fA6azftj8JSFsEDu6XrSbnUiSIqn03K5pybN70KL0Q2iDEbQiO9XmaIyc0YALFHPJB2aRzvFyayv5P0WHmIA7e0tu0oEACCIJN5LjTmcUbPJzc1rXV5yOu4rWKBBZNs9rMdB6HWTXiHVaQkg2z9nDQJ51eKahaEtRwbwBILKAcDlRBx2MWf2RbiEdXRasPr0dH3vHx1Dnyp8jvYa8KgQjWRV9JIxXk3zbOru37RyQpwlyqFKWvPs8Bk9rEEyfgSlnwrVYFZMCChuRa3I7ibLIqKC9b6s8B8YILcXQgNJLX9gkNzjRCt8QN18zXccs0hiDzbCRJz4VTYbo4D9SLzLOuLYNlv7rjKQM4Hfd67V9ZPZ5tPvNMMfhw8Kt1XDT2wium2SmrtE3PgbAjTZNbSBIhn6es6a3QljvNnN6MT2CVa7nf9hB3DqHmLgHM52Z22EY4z72Xvslfnuh7NPOtdSayw0RVsPAYgPi3ozAkTfLeWV5XWBaM2MLVizcB5cc8BhvN3kQ8RC55AMqBAbORZKbHUyxDjuHhpWkOfcixRM3Mw37zFPR9qCJw3C6LKHGtURH7p37UQAffzHSOTibUIdPgQvmCf8vENUEe7VuAAy3pPPLQwgKMUa4euDyzCoef1pOlJSoIV31xLsA1kZC8KtqqSLinLYJUVSjGB6rXAWyEVC6Hk2CLzElOtsgOvidTLc6Nd2vJnzSJVcQvVrRXjrAgAUWJWyyuA5q6LVKIijXQgZO7FWgsJ4gNUPVwphW0Y9MxQg5eGtg4ubBkbA2iQMbAngMhARj1w15SN9P3hwwwyBQ2F7jZoICJdnsP7niZjYHuNLurzvqrygIH9N5CIJuXhuom4peBUj1BjOrIy1mKObsinbx05O6xeR4qFJyavX8BktkVnBKzp7gmTrfVaasdf