Initial codebase structure
- Complete Gemini server implementation with logging - Add comprehensive documentation (README.md, AGENTS.md) - Implement certificate management guidelines - Add .gitignore for security and build artifacts - All unit tests passing (14/14) - Ready for production deployment
This commit is contained in:
commit
1ed443ff2a
10 changed files with 639 additions and 0 deletions
119
src/request.rs
Normal file
119
src/request.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
use path_security::validate_path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn parse_gemini_url(request: &str, expected_host: &str) -> Result<String, ()> {
|
||||
if let Some(url) = request.strip_prefix("gemini://") {
|
||||
let host_end = url.find('/').unwrap_or(url.len());
|
||||
let host = &url[..host_end];
|
||||
if host != expected_host {
|
||||
return Err(()); // Hostname mismatch
|
||||
}
|
||||
let path = if host_end < url.len() { &url[host_end..] } else { "/" };
|
||||
Ok(path.trim().to_string())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_file_path(path: &str, dir: &str) -> Result<PathBuf, ()> {
|
||||
let file_path_str = if path == "/" {
|
||||
"index.gmi".to_string()
|
||||
} else if path.ends_with('/') {
|
||||
format!("{}index.gmi", &path[1..])
|
||||
} else {
|
||||
path[1..].to_string()
|
||||
};
|
||||
|
||||
match validate_path(Path::new(&file_path_str), Path::new(dir)) {
|
||||
Ok(safe_path) => Ok(safe_path),
|
||||
Err(_) => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mime_type(file_path: &Path) -> &str {
|
||||
if let Some(ext) = file_path.extension() {
|
||||
match ext.to_str() {
|
||||
Some("gmi") => "text/gemini",
|
||||
Some("txt") => "text/plain",
|
||||
Some("html") => "text/html",
|
||||
Some("png") => "image/png",
|
||||
Some("jpg") | Some("jpeg") => "image/jpeg",
|
||||
Some("webp") => "image/webp",
|
||||
Some("gif") => "image/gif",
|
||||
_ => "application/octet-stream",
|
||||
}
|
||||
} else {
|
||||
"application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_parse_gemini_url_valid() {
|
||||
assert_eq!(parse_gemini_url("gemini://gemini.jeena.net/", "gemini.jeena.net"), Ok("/".to_string()));
|
||||
assert_eq!(parse_gemini_url("gemini://gemini.jeena.net/posts/test", "gemini.jeena.net"), Ok("/posts/test".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gemini_url_invalid_host() {
|
||||
assert!(parse_gemini_url("gemini://foo.com/", "gemini.jeena.net").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gemini_url_no_prefix() {
|
||||
assert!(parse_gemini_url("http://gemini.jeena.net/", "gemini.jeena.net").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_file_path_root() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
assert!(resolve_file_path("/", temp_dir.path().to_str().unwrap()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_file_path_directory() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
std::fs::create_dir(temp_dir.path().join("test")).unwrap();
|
||||
assert!(resolve_file_path("/test/", temp_dir.path().to_str().unwrap()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_file_path_file() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
assert!(resolve_file_path("/test.gmi", temp_dir.path().to_str().unwrap()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_file_path_traversal() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
assert!(resolve_file_path("/../etc/passwd", temp_dir.path().to_str().unwrap()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_mime_type_gmi() {
|
||||
let path = Path::new("test.gmi");
|
||||
assert_eq!(get_mime_type(path), "text/gemini");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_mime_type_png() {
|
||||
let path = Path::new("test.png");
|
||||
assert_eq!(get_mime_type(path), "image/png");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_mime_type_unknown() {
|
||||
let path = Path::new("test.xyz");
|
||||
assert_eq!(get_mime_type(path), "application/octet-stream");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_mime_type_no_extension() {
|
||||
let path = Path::new("test");
|
||||
assert_eq!(get_mime_type(path), "application/octet-stream");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue