Browse Source

Return TaurusError when failing to parse GeminiRequest

This patch adds TaurusError::InvalidRequest` and updates GeminiRequest
to impl FromStr and expose Gemini::parse that will attempt to parse a
string and return an instance of TaurusError::InvalidRequest if an error
occurs - such as the request being malformed, or the url being invalid.

Signed-off-by: Josh Leeb-du Toit <mail@joshleeb.com>
master
Josh Leeb-du Toit 4 years ago committed by Garrit Franke
parent
commit
dc61ecb6af
  1. 3
      src/error.rs
  2. 72
      src/gemini.rs
  3. 25
      src/main.rs

3
src/error.rs

@ -14,6 +14,9 @@ pub enum TaurusError {
#[error("failed parse certificate: {0:#?}")]
InvalidCertificate(#[from] native_tls::Error),
#[error("invalid request")]
InvalidRequest(String),
#[error("failed to bind: {0}")]
BindFailed(io::Error),

72
src/gemini.rs

@ -1,35 +1,41 @@
use crate::error::{TaurusError, TaurusResult};
use native_tls::TlsStream;
use std::io::Write;
use std::net::TcpStream;
use std::{io::Write, net::TcpStream, str::FromStr};
use url::Url;
#[derive(Debug, PartialEq, Eq)]
pub struct GeminiRequest {
path: Url,
url: Url,
}
impl GeminiRequest {
pub fn from_string(request: &str) -> Result<Self, String> {
let gemini_request = GeminiRequest {
path: Url::parse(&parse_path(request).ok_or("Invalid path")?.to_string())
.map_err(|e| e.to_string())?,
};
Ok(gemini_request)
pub fn parse(request: &str) -> TaurusResult<Self> {
Self::from_str(request)
}
/// Get file path
pub fn file_path(&self) -> &str {
self.path
self.url
.path()
.chars()
.next()
.map_or("", |c| &self.path.path()[c.len_utf8()..])
.map_or("", |c| &self.url.path()[c.len_utf8()..])
}
}
fn parse_path(req: &str) -> Option<&str> {
req.split("\r\n").next()
impl FromStr for GeminiRequest {
type Err = TaurusError;
fn from_str(s: &str) -> TaurusResult<Self> {
// Extract and parse the url from the request.
let raw = s
.strip_suffix("\r\n")
.ok_or_else(|| TaurusError::InvalidRequest("malformed request".into()))?;
let url = Url::parse(&raw)
.map_err(|e| TaurusError::InvalidRequest(format!("invalid url: {}", e)))?;
Ok(Self { url })
}
}
pub struct GeminiResponse {
@ -76,3 +82,41 @@ impl GeminiResponse {
stream.write(&buf).map_err(TaurusError::StreamWriteFailed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_request() {
let raw = "gemini://example.space\r\n";
let req = GeminiRequest::parse(raw).unwrap();
assert_eq!(
req,
GeminiRequest {
url: Url::parse("gemini://example.space").unwrap()
}
);
}
#[test]
fn parse_malformed_request() {
let raw = "gemini://example.space";
match GeminiRequest::parse(raw) {
Err(TaurusError::InvalidRequest(_)) => {}
x => panic!("expected TaurusError::InvalidRequest, got: {:?}", x),
}
}
#[test]
fn parse_invalid_request_url() {
let raw = "foobar@example.com\r\n";
match GeminiRequest::parse(raw) {
Err(TaurusError::InvalidRequest(_)) => {}
x => panic!("expected TaurusError::InvalidRequest, got: {:?}", x),
}
}
}

25
src/main.rs

@ -5,16 +5,17 @@ mod config;
mod error;
mod gemini;
use gemini::GeminiResponse;
use error::{TaurusError, TaurusResult};
use gemini::{GeminiRequest, GeminiResponse};
use native_tls::{Identity, TlsAcceptor, TlsStream};
use std::fs::File;
use std::io::{self, Read};
use std::net::{TcpListener, TcpStream};
use std::path;
use std::sync::Arc;
use std::thread;
use std::{
fs::File,
io::{self, Read},
net::{TcpListener, TcpStream},
path,
sync::Arc,
thread,
};
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
@ -133,14 +134,12 @@ fn handle_client(mut stream: TlsStream<TcpStream>, static_root: &str) -> TaurusR
.read(&mut buffer)
.map_err(TaurusError::StreamReadFailed)?;
let mut raw_request = String::from_utf8_lossy(&buffer[..]).to_mut().to_owned();
// TODO: Redundantly converted to owned and later referenced again
let mut raw_request = String::from_utf8_lossy(&buffer[..]).into_owned();
if !raw_request.starts_with("gemini://") {
raw_request = "gemini://".to_owned() + &raw_request;
raw_request = format!("gemini://{}", raw_request);
}
let request = gemini::GeminiRequest::from_string(&raw_request).unwrap();
let request = GeminiRequest::parse(&raw_request)?;
let url_path = request.file_path();
let file_path = path::Path::new(url_path);

Loading…
Cancel
Save