Garrit Franke
3 years ago
12 changed files with 1146 additions and 23 deletions
@ -0,0 +1,359 @@ |
|||||||
|
# This file is automatically @generated by Cargo. |
||||||
|
# It is not intended for manual editing. |
||||||
|
[[package]] |
||||||
|
name = "autocfg" |
||||||
|
version = "1.0.1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "bitflags" |
||||||
|
version = "1.3.2" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "cc" |
||||||
|
version = "1.0.70" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "cfg-if" |
||||||
|
version = "1.0.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "core-foundation" |
||||||
|
version = "0.9.1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" |
||||||
|
dependencies = [ |
||||||
|
"core-foundation-sys", |
||||||
|
"libc", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "core-foundation-sys" |
||||||
|
version = "0.8.2" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "foreign-types" |
||||||
|
version = "0.3.2" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" |
||||||
|
dependencies = [ |
||||||
|
"foreign-types-shared", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "foreign-types-shared" |
||||||
|
version = "0.1.1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "gempress" |
||||||
|
version = "0.1.0" |
||||||
|
dependencies = [ |
||||||
|
"native-tls", |
||||||
|
"thiserror", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "getrandom" |
||||||
|
version = "0.2.3" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" |
||||||
|
dependencies = [ |
||||||
|
"cfg-if", |
||||||
|
"libc", |
||||||
|
"wasi", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "lazy_static" |
||||||
|
version = "1.4.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "libc" |
||||||
|
version = "0.2.101" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "log" |
||||||
|
version = "0.4.14" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" |
||||||
|
dependencies = [ |
||||||
|
"cfg-if", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "native-tls" |
||||||
|
version = "0.2.8" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" |
||||||
|
dependencies = [ |
||||||
|
"lazy_static", |
||||||
|
"libc", |
||||||
|
"log", |
||||||
|
"openssl", |
||||||
|
"openssl-probe", |
||||||
|
"openssl-sys", |
||||||
|
"schannel", |
||||||
|
"security-framework", |
||||||
|
"security-framework-sys", |
||||||
|
"tempfile", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "once_cell" |
||||||
|
version = "1.8.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "openssl" |
||||||
|
version = "0.10.36" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a" |
||||||
|
dependencies = [ |
||||||
|
"bitflags", |
||||||
|
"cfg-if", |
||||||
|
"foreign-types", |
||||||
|
"libc", |
||||||
|
"once_cell", |
||||||
|
"openssl-sys", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "openssl-probe" |
||||||
|
version = "0.1.4" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "openssl-sys" |
||||||
|
version = "0.9.66" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "1996d2d305e561b70d1ee0c53f1542833f4e1ac6ce9a6708b6ff2738ca67dc82" |
||||||
|
dependencies = [ |
||||||
|
"autocfg", |
||||||
|
"cc", |
||||||
|
"libc", |
||||||
|
"pkg-config", |
||||||
|
"vcpkg", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "pkg-config" |
||||||
|
version = "0.3.19" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "ppv-lite86" |
||||||
|
version = "0.2.10" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "proc-macro2" |
||||||
|
version = "1.0.29" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" |
||||||
|
dependencies = [ |
||||||
|
"unicode-xid", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "quote" |
||||||
|
version = "1.0.9" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" |
||||||
|
dependencies = [ |
||||||
|
"proc-macro2", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "rand" |
||||||
|
version = "0.8.4" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" |
||||||
|
dependencies = [ |
||||||
|
"libc", |
||||||
|
"rand_chacha", |
||||||
|
"rand_core", |
||||||
|
"rand_hc", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "rand_chacha" |
||||||
|
version = "0.3.1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" |
||||||
|
dependencies = [ |
||||||
|
"ppv-lite86", |
||||||
|
"rand_core", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "rand_core" |
||||||
|
version = "0.6.3" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" |
||||||
|
dependencies = [ |
||||||
|
"getrandom", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "rand_hc" |
||||||
|
version = "0.3.1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" |
||||||
|
dependencies = [ |
||||||
|
"rand_core", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "redox_syscall" |
||||||
|
version = "0.2.10" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" |
||||||
|
dependencies = [ |
||||||
|
"bitflags", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "remove_dir_all" |
||||||
|
version = "0.5.3" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" |
||||||
|
dependencies = [ |
||||||
|
"winapi", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "schannel" |
||||||
|
version = "0.1.19" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" |
||||||
|
dependencies = [ |
||||||
|
"lazy_static", |
||||||
|
"winapi", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "security-framework" |
||||||
|
version = "2.4.2" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" |
||||||
|
dependencies = [ |
||||||
|
"bitflags", |
||||||
|
"core-foundation", |
||||||
|
"core-foundation-sys", |
||||||
|
"libc", |
||||||
|
"security-framework-sys", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "security-framework-sys" |
||||||
|
version = "2.4.2" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" |
||||||
|
dependencies = [ |
||||||
|
"core-foundation-sys", |
||||||
|
"libc", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "syn" |
||||||
|
version = "1.0.76" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" |
||||||
|
dependencies = [ |
||||||
|
"proc-macro2", |
||||||
|
"quote", |
||||||
|
"unicode-xid", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "tempfile" |
||||||
|
version = "3.2.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" |
||||||
|
dependencies = [ |
||||||
|
"cfg-if", |
||||||
|
"libc", |
||||||
|
"rand", |
||||||
|
"redox_syscall", |
||||||
|
"remove_dir_all", |
||||||
|
"winapi", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "thiserror" |
||||||
|
version = "1.0.28" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "283d5230e63df9608ac7d9691adc1dfb6e701225436eb64d0b9a7f0a5a04f6ec" |
||||||
|
dependencies = [ |
||||||
|
"thiserror-impl", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "thiserror-impl" |
||||||
|
version = "1.0.28" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "fa3884228611f5cd3608e2d409bf7dce832e4eb3135e3f11addbd7e41bd68e71" |
||||||
|
dependencies = [ |
||||||
|
"proc-macro2", |
||||||
|
"quote", |
||||||
|
"syn", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "unicode-xid" |
||||||
|
version = "0.2.2" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "vcpkg" |
||||||
|
version = "0.2.15" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "wasi" |
||||||
|
version = "0.10.2+wasi-snapshot-preview1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "winapi" |
||||||
|
version = "0.3.9" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" |
||||||
|
dependencies = [ |
||||||
|
"winapi-i686-pc-windows-gnu", |
||||||
|
"winapi-x86_64-pc-windows-gnu", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "winapi-i686-pc-windows-gnu" |
||||||
|
version = "0.4.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "winapi-x86_64-pc-windows-gnu" |
||||||
|
version = "0.4.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" |
@ -1,24 +1,16 @@ |
|||||||
mod gempress; |
extern crate gempress; |
||||||
|
|
||||||
use gempress::{Gempress, Request, Response}; |
use gempress::Gempress; |
||||||
use std::path::Path; |
use std::path::PathBuf; |
||||||
|
|
||||||
pub fn handler(req: &Box<Request>, res: &mut Box<Response>) { |
|
||||||
res.send("Hello World!\n"); |
|
||||||
} |
|
||||||
|
|
||||||
fn main() { |
fn main() { |
||||||
// Run make_cert.sh to generate a certificate
|
// Run make_cert.sh to generate a certificate
|
||||||
let config = Gempress::Config { |
let config = gempress::Config::with_identity(PathBuf::from("identity.pfx"), "qqqq".into()); |
||||||
certPath: Path::new("../cert.pem"); |
|
||||||
keyPath: Path::new("../key.pem"); |
|
||||||
} |
|
||||||
|
|
||||||
let app = Gempress::new(config); |
let app = Gempress::new(config); |
||||||
|
|
||||||
app.on("/", handler); |
|
||||||
|
|
||||||
app.listen(1965, || { |
app.listen(1965, || { |
||||||
println!("Listening on port 1965"); |
println!("Listening on port 1965"); |
||||||
}); |
}).unwrap(); |
||||||
|
} |
||||||
} |
} |
||||||
|
@ -1 +1,7 @@ |
|||||||
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost' |
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost' |
||||||
|
|
||||||
|
openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem |
||||||
|
|
||||||
|
# Cleanup |
||||||
|
rm key.pem cert.pem |
||||||
|
|
||||||
|
@ -0,0 +1,28 @@ |
|||||||
|
use std::io; |
||||||
|
use thiserror::Error; |
||||||
|
|
||||||
|
pub type GempressResult<T> = Result<T, GempressError>; |
||||||
|
|
||||||
|
#[derive(Error, Debug)] |
||||||
|
pub enum GempressError { |
||||||
|
#[error("failed to open identity file: {0}")] |
||||||
|
NoIdentity(io::Error), |
||||||
|
|
||||||
|
#[error("failed parse certificate: {0:#?}")] |
||||||
|
InvalidCertificate(#[from] native_tls::Error), |
||||||
|
|
||||||
|
#[error("invalid request: {0}")] |
||||||
|
InvalidRequest(String), |
||||||
|
|
||||||
|
#[error("invalid Unicode character in the input")] |
||||||
|
InvalidUnicode(#[from] std::string::FromUtf8Error), |
||||||
|
|
||||||
|
#[error("failed to bind: {0}")] |
||||||
|
BindFailed(io::Error), |
||||||
|
|
||||||
|
#[error("could not read the stream")] |
||||||
|
StreamReadFailed(io::Error), |
||||||
|
|
||||||
|
#[error("could not write to the stream")] |
||||||
|
StreamWriteFailed(io::Error), |
||||||
|
} |
@ -0,0 +1,147 @@ |
|||||||
|
use crate::error::{GempressError, GempressResult}; |
||||||
|
use native_tls::TlsStream; |
||||||
|
use std::{io::Write, net::TcpStream, str::FromStr}; |
||||||
|
use url::Url; |
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)] |
||||||
|
pub struct Request { |
||||||
|
url: Url, |
||||||
|
} |
||||||
|
|
||||||
|
impl Request { |
||||||
|
pub fn parse(request: &str) -> GempressResult<Self> { |
||||||
|
Self::from_str(request) |
||||||
|
} |
||||||
|
|
||||||
|
/// Get file path
|
||||||
|
pub fn file_path(&self) -> &str { |
||||||
|
self.url |
||||||
|
.path() |
||||||
|
.chars() |
||||||
|
.next() |
||||||
|
.map_or("", |c| &self.url.path()[c.len_utf8()..]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl FromStr for Request { |
||||||
|
type Err = GempressError; |
||||||
|
|
||||||
|
fn from_str(s: &str) -> GempressResult<Self> { |
||||||
|
let mut s = s.to_string(); |
||||||
|
|
||||||
|
// Add gemini: scheme if not explicitly set
|
||||||
|
if s.starts_with("//") { |
||||||
|
s = format!("gemini:{}", s); |
||||||
|
} |
||||||
|
|
||||||
|
// Check protocol
|
||||||
|
if let Some(proto_end) = s.find("://") { |
||||||
|
// If set, check if it's allowed
|
||||||
|
let protocol = &s[..proto_end]; |
||||||
|
|
||||||
|
if protocol != "gemini" { |
||||||
|
// TODO: return 53 error instead of dropping
|
||||||
|
return Err(GempressError::InvalidRequest("invalid protocol".into())); |
||||||
|
} |
||||||
|
} else { |
||||||
|
// If no protocol is found, gemini: is implied
|
||||||
|
s = format!("gemini://{}", s); |
||||||
|
} |
||||||
|
|
||||||
|
// Extract and parse the url from the request.
|
||||||
|
let raw = s |
||||||
|
.trim_end_matches(0x0 as char) |
||||||
|
.strip_suffix("\r\n") |
||||||
|
.ok_or_else(|| GempressError::InvalidRequest("malformed request".into()))?; |
||||||
|
let url = Url::parse(&raw) |
||||||
|
.map_err(|e| GempressError::InvalidRequest(format!("invalid url: {}", e)))?; |
||||||
|
|
||||||
|
Ok(Self { url }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub struct Response { |
||||||
|
pub status: [u8; 2], |
||||||
|
pub meta: Vec<u8>, |
||||||
|
pub body: Option<Vec<u8>>, |
||||||
|
} |
||||||
|
|
||||||
|
impl Response { |
||||||
|
pub fn success(body: Vec<u8>, mime_type: &str) -> Self { |
||||||
|
Response { |
||||||
|
status: [b'2', b'0'], |
||||||
|
meta: mime_type.as_bytes().to_vec(), |
||||||
|
body: Some(body), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn not_found() -> Self { |
||||||
|
Response { |
||||||
|
status: [b'5', b'1'], |
||||||
|
meta: "Resource not found".into(), |
||||||
|
body: None, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn send(&self, mut stream: TlsStream<TcpStream>) -> GempressResult<usize> { |
||||||
|
let mut buf: Vec<u8> = Vec::new(); |
||||||
|
|
||||||
|
// <Status>
|
||||||
|
buf.extend(&self.status); |
||||||
|
|
||||||
|
// <Space>
|
||||||
|
buf.push(0x20); |
||||||
|
|
||||||
|
// <Meta>
|
||||||
|
buf.extend(&self.meta); |
||||||
|
|
||||||
|
buf.extend(b"\r\n"); |
||||||
|
|
||||||
|
if let Some(body) = &self.body { |
||||||
|
buf.extend(body); |
||||||
|
} |
||||||
|
|
||||||
|
stream.write(&buf).map_err(GempressError::StreamWriteFailed) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use super::*; |
||||||
|
|
||||||
|
fn check_request(raw: &str, expected_url: &str) { |
||||||
|
let req = Request::parse(raw).unwrap(); |
||||||
|
|
||||||
|
assert_eq!( |
||||||
|
req, |
||||||
|
Request { |
||||||
|
url: Url::parse(expected_url).unwrap() |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn parse_request() { |
||||||
|
check_request("gemini://example.space\r\n", "gemini://example.space"); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn parse_without_scheme() { |
||||||
|
check_request("example.space\r\n", "gemini://example.space"); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn parse_without_scheme_double_slash() { |
||||||
|
check_request("//example.space\r\n", "gemini://example.space"); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn parse_malformed_request() { |
||||||
|
let raw = "gemini://example.space"; |
||||||
|
|
||||||
|
match Request::parse(raw) { |
||||||
|
Err(TaurusError::InvalidRequest(_)) => {} |
||||||
|
x => panic!("expected TaurusError::InvalidRequest, got: {:?}", x), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
use crate::error::{GempressError, GempressResult}; |
||||||
|
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) -> GempressResult<Identity> { |
||||||
|
let identity = read_file(&cert_file).map_err(GempressError::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")); |
||||||
|
} |
||||||
|
} |
@ -1,11 +1,95 @@ |
|||||||
pub fn add(a: u32, b: u32) -> u32 { |
extern crate native_tls; |
||||||
return a + b; |
|
||||||
|
mod logger; |
||||||
|
mod io; |
||||||
|
mod error; |
||||||
|
mod gemini; |
||||||
|
|
||||||
|
use std::io::Read; |
||||||
|
use native_tls::Identity; |
||||||
|
use std::sync::Arc; |
||||||
|
use std::path::PathBuf; |
||||||
|
use std::net::{TcpListener, TcpStream}; |
||||||
|
use std::thread; |
||||||
|
use std::path; |
||||||
|
use native_tls::{TlsAcceptor, TlsStream}; |
||||||
|
use error::{GempressResult, GempressError}; |
||||||
|
|
||||||
|
pub struct Config { |
||||||
|
// Path to the identity file
|
||||||
|
identityPath: PathBuf, |
||||||
|
password: String, |
||||||
|
} |
||||||
|
|
||||||
|
impl Config { |
||||||
|
pub fn with_identity(identityPath: PathBuf, password: String) -> Self { |
||||||
|
Self { |
||||||
|
identityPath, |
||||||
|
password, |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
#[cfg(test)] |
pub struct Gempress { |
||||||
mod tests { |
pub config: Config, |
||||||
#[test] |
} |
||||||
fn it_works() { |
|
||||||
assert_eq!(add(1, 1), 2); |
impl Gempress { |
||||||
|
pub fn new(config: Config) -> Self { |
||||||
|
Gempress { |
||||||
|
config, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn listen<F: Fn()>(&self, port: u16, callback: F) -> GempressResult<()> { |
||||||
|
// Read certificate
|
||||||
|
// TODO: Can a password be optional?
|
||||||
|
let identity = io::load_cert(&self.config.identityPath.to_str().unwrap_or(""), &self.config.password)?; |
||||||
|
|
||||||
|
let address = format!("0.0.0.0:{}", port); |
||||||
|
let listener = TcpListener::bind(address).map_err(GempressError::BindFailed)?; |
||||||
|
let acceptor = TlsAcceptor::new(identity).unwrap(); |
||||||
|
let acceptor = Arc::new(acceptor); |
||||||
|
|
||||||
|
logger::info(format!("Listening on port {}", port)); |
||||||
|
|
||||||
|
for stream in listener.incoming() { |
||||||
|
match stream { |
||||||
|
Ok(stream) => { |
||||||
|
let acceptor = acceptor.clone(); |
||||||
|
|
||||||
|
thread::spawn(move || match acceptor.accept(stream) { |
||||||
|
Ok(stream) => { |
||||||
|
if let Err(e) = handle_client(stream) { |
||||||
|
logger::error(format!("Can't handle client: {}", e)); |
||||||
|
} |
||||||
|
} |
||||||
|
Err(e) => { |
||||||
|
logger::error(format!("Can't handle stream: {}", e)); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
Err(err) => logger::error(err), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Ok(())
|
||||||
} |
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
fn handle_client(mut stream: TlsStream<TcpStream>) -> GempressResult<usize> { |
||||||
|
let mut buffer = [0; 1024]; |
||||||
|
|
||||||
|
stream |
||||||
|
.read(&mut buffer) |
||||||
|
.map_err(GempressError::StreamReadFailed)?; |
||||||
|
|
||||||
|
let raw_request = String::from_utf8(buffer.to_vec())?; |
||||||
|
|
||||||
|
let request = gemini::Request::parse(&raw_request)?; |
||||||
|
let url_path = request.file_path(); |
||||||
|
let file_path = path::Path::new(url_path); |
||||||
|
|
||||||
|
gemini::Response::success("It Works!\n".into(), "text/gemini").send(stream) |
||||||
} |
} |
||||||
|
@ -0,0 +1,9 @@ |
|||||||
|
use std::fmt::Display; |
||||||
|
|
||||||
|
pub fn info(message: impl Display) { |
||||||
|
println!("[info] {}", message); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn error(message: impl Display) { |
||||||
|
println!("[err] {}", message); |
||||||
|
} |
@ -1,5 +1,15 @@ |
|||||||
extern crate gempress; |
extern crate gempress; |
||||||
|
|
||||||
|
use gempress::Gempress; |
||||||
|
use std::path::PathBuf; |
||||||
|
|
||||||
fn main() { |
fn main() { |
||||||
println!("{}", gempress::add(1, 1)); |
// Run make_cert.sh to generate a certificate
|
||||||
|
let config = gempress::Config::with_identity(PathBuf::from("identity.pfx"), "qqqq".into()); |
||||||
|
|
||||||
|
let app = Gempress::new(config); |
||||||
|
|
||||||
|
app.listen(1965, || { |
||||||
|
println!("Listening on port 1965"); |
||||||
|
}).unwrap(); |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue