forked from cadey/maj
spec compliance
This commit is contained in:
parent
112c5969d8
commit
49190b32ff
|
@ -21,6 +21,7 @@ thiserror = "1"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.4"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["client"]
|
default = ["client", "server"]
|
||||||
|
|
||||||
client = ["rustls", "webpki", "webpki-roots"]
|
client = ["rustls", "webpki", "webpki-roots"]
|
||||||
|
server = ["rustls", "webpki", "webpki-roots"]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Response, StatusCode};
|
use crate::{Response};
|
||||||
use rustls::{ClientConfig, ClientSession, Stream, TLSError};
|
use rustls::{ClientConfig, ClientSession, Stream, TLSError};
|
||||||
use std::{io::prelude::*, net::TcpStream, sync::Arc};
|
use std::{io::prelude::*, net::TcpStream, sync::Arc};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -50,6 +50,8 @@ pub fn get(u: String) -> Result<crate::Response, Error> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gemini_homepage() -> Result<(), Error> {
|
fn gemini_homepage() -> Result<(), Error> {
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
|
@ -8,3 +8,8 @@ pub use status_code::StatusCode;
|
||||||
mod client;
|
mod client;
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
pub use client::{get, Error as ClientError};
|
pub use client::{get, Error as ClientError};
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
mod server;
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub use server::{Handler, serve, serve_plain};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::StatusCode;
|
use crate::StatusCode;
|
||||||
use num::FromPrimitive;
|
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).
|
/// A Gemini response as specified in [the spec](https://gemini.circumlunar.space/docs/specification.html).
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -19,7 +19,7 @@ enum State {
|
||||||
ReadBody { data: Vec<u8> },
|
ReadBody { data: Vec<u8> },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Response parsing error.
|
/// Response error.
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("unexpected end of file found while parsing response")]
|
#[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")]
|
#[error("None found when none should not be found")]
|
||||||
NoneFound,
|
NoneFound,
|
||||||
|
|
||||||
#[error("TLS error: {0:?}")]
|
#[error("Response meta is too long")]
|
||||||
TLS(#[from] rustls::TLSError),
|
ResponseMetaTooLong,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
|
@ -60,7 +60,6 @@ impl Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log::trace!("buf: {:?}: {:?}", buf, buf[0] as char);
|
log::trace!("buf: {:?}: {:?}", buf, buf[0] as char);
|
||||||
log::trace!("n: {}", n);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(why) => {
|
Err(why) => {
|
||||||
|
@ -75,8 +74,6 @@ impl Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// log::trace!("state: {:?}", state);
|
|
||||||
|
|
||||||
match &mut state {
|
match &mut state {
|
||||||
State::ReadStatusCode { data } => match buf[0] as char {
|
State::ReadStatusCode { data } => match buf[0] as char {
|
||||||
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => {
|
'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();
|
result.meta = std::str::from_utf8(data)?.to_string();
|
||||||
state = State::ReadBody { data: vec![] };
|
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]),
|
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)]
|
#[cfg(test)]
|
||||||
|
@ -151,4 +160,19 @@ mod tests {
|
||||||
|
|
||||||
Ok(())
|
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")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
use crate::Response;
|
||||||
|
use std::{error::Error, io};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
pub trait Handler {
|
||||||
|
fn handle(u: Url) -> Result<Response, Box<dyn Error + Sync + Send>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serve(_h: impl Handler, _port: u16) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serve_plain(_h: impl Handler, _port: u16) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -118,6 +118,13 @@ pub enum StatusCode {
|
||||||
CertificateNotValid = 62,
|
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 {
|
impl Default for StatusCode {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
StatusCode::Success
|
StatusCode::Success
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
20 fGeQX5GJ9Tu1fk8VkEVqVqvAujalD2z0snURmdmwai1eYPvJEbtdFKW0SAPlWuuaVFkq8pDNmqAyiDNOorsgw8OGBlOLW3A02XdnsB4MUjiH3fA6azftj8JSFsEDu6XrSbnUiSIqn03K5pybN70KL0Q2iDEbQiO9XmaIyc0YALFHPJB2aRzvFyayv5P0WHmIA7e0tu0oEACCIJN5LjTmcUbPJzc1rXV5yOu4rWKBBZNs9rMdB6HWTXiHVaQkg2z9nDQJ51eKahaEtRwbwBILKAcDlRBx2MWf2RbiEdXRasPr0dH3vHx1Dnyp8jvYa8KgQjWRV9JIxXk3zbOru37RyQpwlyqFKWvPs8Bk9rEEyfgSlnwrVYFZMCChuRa3I7ibLIqKC9b6s8B8YILcXQgNJLX9gkNzjRCt8QN18zXccs0hiDzbCRJz4VTYbo4D9SLzLOuLYNlv7rjKQM4Hfd67V9ZPZ5tPvNMMfhw8Kt1XDT2wium2SmrtE3PgbAjTZNbSBIhn6es6a3QljvNnN6MT2CVa7nf9hB3DqHmLgHM52Z22EY4z72Xvslfnuh7NPOtdSayw0RVsPAYgPi3ozAkTfLeWV5XWBaM2MLVizcB5cc8BhvN3kQ8RC55AMqBAbORZKbHUyxDjuHhpWkOfcixRM3Mw37zFPR9qCJw3C6LKHGtURH7p37UQAffzHSOTibUIdPgQvmCf8vENUEe7VuAAy3pPPLQwgKMUa4euDyzCoef1pOlJSoIV31xLsA1kZC8KtqqSLinLYJUVSjGB6rXAWyEVC6Hk2CLzElOtsgOvidTLc6Nd2vJnzSJVcQvVrRXjrAgAUWJWyyuA5q6LVKIijXQgZO7FWgsJ4gNUPVwphW0Y9MxQg5eGtg4ubBkbA2iQMbAngMhARj1w15SN9P3hwwwyBQ2F7jZoICJdnsP7niZjYHuNLurzvqrygIH9N5CIJuXhuom4peBUj1BjOrIy1mKObsinbx05O6xeR4qFJyavX8BktkVnBKzp7gmTrfVaasdf
|
Loading…
Reference in New Issue