~garritfra/taurus

9c83ef849b60c954e5c1ff9104622b78776c3c5c — Alexey Yerin 2 years ago c9e07b8
Extract I/O related functions to "io" module

Signed-off-by: Alexey Yerin <yerinalexey98fd@gmail.com>
3 files changed, 84 insertions(+), 45 deletions(-)

M src/gemini.rs
A src/io.rs
M src/main.rs
M src/gemini.rs => src/gemini.rs +25 -0
@@ 1,5 1,7 @@
use crate::error::{TaurusError, TaurusResult};
use crate::logger;
use native_tls::TlsStream;
use std::path;
use std::{io::Write, net::TcpStream, str::FromStr};
use url::Url;



@@ 83,6 85,29 @@ impl GeminiResponse {
        }
    }

    pub fn from_file(path: &str) -> TaurusResult<GeminiResponse> {
        let extension = path::Path::new(path)
            .extension()
            .unwrap_or_else(|| std::ffi::OsStr::new(""));

        let mime_type = match &*extension.to_string_lossy() {
            "gmi" => "text/gemini; charset=utf-8",
            ext => mime_guess::from_ext(ext)
                .first_raw()
                .unwrap_or("text/plain"),
        };

        match crate::io::read_file(path) {
            Ok(buf) => Ok(GeminiResponse::success(buf, mime_type)),
            Err(err) => {
                // Cannot read file or it doesn't exist
                logger::error(format!("{}: {}", path, err));

                Ok(GeminiResponse::not_found())
            }
        }
    }

    pub fn send(&self, mut stream: TlsStream<TcpStream>) -> TaurusResult<usize> {
        let mut buf: Vec<u8> = Vec::new();


A src/io.rs => src/io.rs +54 -0
@@ 0,0 1,54 @@
use crate::error::{TaurusError, TaurusResult};
use native_tls::Identity;
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;

/// Read a file into Vec
pub fn read_file(file_path: &str) -> Result<Vec<u8>, io::Error> {
    let mut file = File::open(file_path)?;
    let mut buf = Vec::new();

    file.read_to_end(&mut buf)?;

    Ok(buf)
}

/// Read certificate file
pub fn load_cert(cert_file: &str, password: &str) -> TaurusResult<Identity> {
    let identity = read_file(&cert_file).map_err(TaurusError::NoIdentity)?;
    let identity = Identity::from_pkcs12(&identity, &password)?;

    Ok(identity)
}

/// Resolve path to a file, returning index.gmi if a subdirectory is encountered
///
/// If path points to a file, it is returned.
/// If path points to a directory, `./index.gmi` is returned
pub fn resolve_path(path: &Path) -> String {
    if path.is_dir() {
        path.join("index.gmi").to_string_lossy().into_owned()
    } else {
        path.to_string_lossy().into_owned()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn resolve_path_file() {
        let path = Path::new("./file.gmi");

        assert_eq!(resolve_path(&path), String::from("./file.gmi"));
    }

    #[test]
    fn resolve_path_dir() {
        let path = Path::new("./");

        assert_eq!(resolve_path(&path), String::from("./index.gmi"));
    }
}

M src/main.rs => src/main.rs +5 -45
@@ 4,14 4,14 @@ extern crate url;
mod config;
mod error;
mod gemini;
mod io;
mod logger;

use error::{TaurusError, TaurusResult};
use gemini::{GeminiRequest, GeminiResponse};
use native_tls::{Identity, TlsAcceptor, TlsStream};
use native_tls::{TlsAcceptor, TlsStream};
use std::{
    fs::File,
    io::{self, Read},
    io::Read,
    net::{TcpListener, TcpStream},
    path,
    sync::Arc,


@@ 58,9 58,7 @@ fn run() -> TaurusResult<()> {
        .unwrap_or_else(|| "/var/www/gemini".to_owned());

    // Read certificate
    let identity = read_file(&cert_file).map_err(TaurusError::NoIdentity)?;

    let identity = Identity::from_pkcs12(&identity, &config.certificate_password)?;
    let identity = crate::io::load_cert(&cert_file, &config.certificate_password)?;

    let address = format!("0.0.0.0:{}", port);
    let listener = TcpListener::bind(address).map_err(TaurusError::BindFailed)?;


@@ 93,40 91,7 @@ fn run() -> TaurusResult<()> {
    Ok(())
}

/// Helper function to read a file into Vec
fn read_file(file_path: &str) -> Result<Vec<u8>, io::Error> {
    let mut file = File::open(file_path)?;
    let mut buf = Vec::new();

    file.read_to_end(&mut buf)?;

    Ok(buf)
}

/// Send file as a response
fn write_file(path: &str) -> TaurusResult<GeminiResponse> {
    let extension = path::Path::new(path)
        .extension()
        .unwrap_or_else(|| std::ffi::OsStr::new(""));

    let mime_type = match &*extension.to_string_lossy() {
        "gmi" => "text/gemini; charset=utf-8",
        ext => mime_guess::from_ext(ext)
            .first_raw()
            .unwrap_or("text/plain"),
    };

    match read_file(path) {
        Ok(buf) => Ok(GeminiResponse::success(buf, mime_type)),
        Err(err) => {
            // Cannot read file or it doesn't exist
            logger::error(format!("{}: {}", path, err));

            Ok(GeminiResponse::not_found())
        }
    }
}

fn handle_client(mut stream: TlsStream<TcpStream>, static_root: &str) -> TaurusResult<usize> {
    let mut buffer = [0; 1024];



@@ 151,12 116,7 @@ fn handle_client(mut stream: TlsStream<TcpStream>, static_root: &str) -> TaurusR

        // Check if file/dir exists
        if path.exists() {
            // If it's a directory, try to find index.gmi
            if path.is_dir() {
                write_file(&path.join("index.gmi").to_string_lossy())?.send(stream)
            } else {
                write_file(&path.to_string_lossy())?.send(stream)
            }
            GeminiResponse::from_file(&crate::io::resolve_path(&path))?.send(stream)
        } else {
            GeminiResponse::not_found().send(stream)
        }