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)
}