Refactoring
This commit is contained in:
334
src/con_handler.rs
Normal file
334
src/con_handler.rs
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
use new_mime_guess;
|
||||||
|
use std::fs;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, BufRead, BufReader};
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::util;
|
||||||
|
use crate::conn;
|
||||||
|
use crate::status::Status;
|
||||||
|
use crate::cgi;
|
||||||
|
use crate::logger;
|
||||||
|
use crate::revproxy;
|
||||||
|
|
||||||
|
type Result<T=()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||||
|
|
||||||
|
fn get_mime(path: &PathBuf) -> String {
|
||||||
|
let mut mime = "text/gemini".to_string();
|
||||||
|
if path.is_dir() {
|
||||||
|
return mime;
|
||||||
|
}
|
||||||
|
let ext = match path.extension() {
|
||||||
|
Some(p) => p.to_str().unwrap(),
|
||||||
|
None => return "text/plain".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mime = match new_mime_guess::from_ext(ext).first() {
|
||||||
|
Some(m) => m.essence_str().to_string(),
|
||||||
|
None => "text/plain".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return mime;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_binary(mut con: conn::Connection, path: PathBuf, meta: String) -> io::Result<()> {
|
||||||
|
let fd = File::open(path)?;
|
||||||
|
let mut reader = BufReader::with_capacity(1024 * 1024, fd);
|
||||||
|
con.send_status(Status::Success, Some(&meta))
|
||||||
|
.await?;
|
||||||
|
loop {
|
||||||
|
let len = {
|
||||||
|
let buf = reader.fill_buf()?;
|
||||||
|
con.send_raw(buf).await?;
|
||||||
|
buf.len()
|
||||||
|
};
|
||||||
|
if len == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reader.consume(len);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_content(path: PathBuf, u: &url::Url) -> Result<String> {
|
||||||
|
let meta = tokio::fs::metadata(&path).await?;
|
||||||
|
if meta.is_file() {
|
||||||
|
return Ok(tokio::fs::read_to_string(path).await?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dirs: Vec<String> = Vec::new();
|
||||||
|
let mut files: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
// needs work
|
||||||
|
for file in fs::read_dir(&path)? {
|
||||||
|
if let Ok(file) = file {
|
||||||
|
let m = file.metadata()?;
|
||||||
|
let perm = m.permissions();
|
||||||
|
if perm.mode() & 0o0444 != 0o0444 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let file = file.path();
|
||||||
|
let p = file.strip_prefix(&path).unwrap();
|
||||||
|
let ps = match p.to_str() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
let ep = match u.join(ps) {
|
||||||
|
Ok(p) => p,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
if m.is_dir() {
|
||||||
|
dirs.push(format!("=> {}/ {}/\r\n", ep, p.display()));
|
||||||
|
} else {
|
||||||
|
files.push(format!("=> {} {}\r\n", ep, p.display()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dirs.sort();
|
||||||
|
files.sort();
|
||||||
|
|
||||||
|
let mut list = String::from("# Directory Listing\r\n\r\n");
|
||||||
|
list.push_str(&format!("Path: {}\r\n\r\n", u.path()));
|
||||||
|
|
||||||
|
for dir in dirs {
|
||||||
|
list.push_str(&dir);
|
||||||
|
}
|
||||||
|
for file in files {
|
||||||
|
list.push_str(&file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle CGI and return Ok(true), or indicate this request wasn't for CGI with Ok(false)
|
||||||
|
#[cfg(feature = "cgi")]
|
||||||
|
async fn handle_cgi(
|
||||||
|
con: &mut conn::Connection,
|
||||||
|
request: &str,
|
||||||
|
url: &Url,
|
||||||
|
full_path: &PathBuf,
|
||||||
|
) -> Result<bool> {
|
||||||
|
if con.srv.server.cgi.unwrap_or(false) {
|
||||||
|
let mut path = full_path.clone();
|
||||||
|
let mut segments = url.path_segments().unwrap();
|
||||||
|
let mut path_info = "".to_string();
|
||||||
|
|
||||||
|
// Find an ancestor url that matches a file
|
||||||
|
while !path.exists() {
|
||||||
|
if let Some(segment) = segments.next_back() {
|
||||||
|
path.pop();
|
||||||
|
path_info = format!("/{}{}", &segment, path_info);
|
||||||
|
} else {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let script_name = format!("/{}", segments.collect::<Vec<_>>().join("/"));
|
||||||
|
|
||||||
|
let meta = tokio::fs::metadata(&path).await?;
|
||||||
|
let perm = meta.permissions();
|
||||||
|
|
||||||
|
match &con.srv.server.cgipath {
|
||||||
|
Some(c) => {
|
||||||
|
if path.starts_with(c) {
|
||||||
|
if perm.mode() & 0o0111 == 0o0111 {
|
||||||
|
cgi::cgi(con, path, url, script_name, path_info).await?;
|
||||||
|
return Ok(true);
|
||||||
|
} else {
|
||||||
|
logger::logger(con.peer_addr, Status::CGIError, request);
|
||||||
|
con.send_status(Status::CGIError, None).await?;
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
if meta.is_file() && perm.mode() & 0o0111 == 0o0111 {
|
||||||
|
cgi::cgi(con, path, url, script_name, path_info).await?;
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Rewrite this monster.
|
||||||
|
pub async fn handle_connection(
|
||||||
|
mut con: conn::Connection,
|
||||||
|
url: url::Url
|
||||||
|
) -> Result {
|
||||||
|
let index = match &con.srv.server.index {
|
||||||
|
Some(i) => i.clone(),
|
||||||
|
None => "index.gemini".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match &con.srv.server.redirect.to_owned() {
|
||||||
|
Some(re) => {
|
||||||
|
let u = match url.path() {
|
||||||
|
"/" => "/",
|
||||||
|
_ => url.path().trim_end_matches("/"),
|
||||||
|
};
|
||||||
|
match re.get(u) {
|
||||||
|
Some(r) => {
|
||||||
|
logger::logger(con.peer_addr, Status::RedirectTemporary, &url.as_str());
|
||||||
|
con.send_status(Status::RedirectTemporary, Some(r)).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "proxy")]
|
||||||
|
if let Some(pr) = con.srv.server.proxy_all.to_owned() {
|
||||||
|
let host_port: Vec<&str> = pr.splitn(2, ':').collect();
|
||||||
|
let host = host_port[0];
|
||||||
|
let port: Option<u16>;
|
||||||
|
if host_port.len() == 2 {
|
||||||
|
port = host_port[1].parse().ok();
|
||||||
|
} else {
|
||||||
|
port = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut upstream_url = url.clone();
|
||||||
|
upstream_url.set_host(Some(host)).unwrap();
|
||||||
|
upstream_url.set_port(port).unwrap();
|
||||||
|
|
||||||
|
revproxy::proxy_all(pr.as_str(), upstream_url, con).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "proxy")]
|
||||||
|
match &con.srv.server.proxy {
|
||||||
|
Some(pr) => match url.path_segments().map(|c| c.collect::<Vec<_>>()) {
|
||||||
|
Some(s) => match pr.get(s[0]) {
|
||||||
|
Some(p) => {
|
||||||
|
revproxy::proxy(p.to_string(), url, con).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "scgi")]
|
||||||
|
match &con.srv.server.scgi {
|
||||||
|
Some(sc) => {
|
||||||
|
let u = match url.path() {
|
||||||
|
"/" => "/",
|
||||||
|
_ => url.path().trim_end_matches("/"),
|
||||||
|
};
|
||||||
|
match sc.get(u) {
|
||||||
|
Some(r) => {
|
||||||
|
cgi::scgi(r.to_string(), url, con).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let mut path = PathBuf::new();
|
||||||
|
|
||||||
|
if url.path().starts_with("/~") && con.srv.server.usrdir.unwrap_or(false) {
|
||||||
|
let usr = url.path().trim_start_matches("/~");
|
||||||
|
let usr: Vec<&str> = usr.splitn(2, "/").collect();
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
path.push("/Users/");
|
||||||
|
} else {
|
||||||
|
path.push("/home/");
|
||||||
|
}
|
||||||
|
if usr.len() == 2 {
|
||||||
|
path.push(format!("{}/{}/{}", usr[0], "public_gemini", util::url_decode(usr[1].as_bytes())));
|
||||||
|
} else {
|
||||||
|
path.push(format!("{}/{}/", usr[0], "public_gemini"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path.push(&con.srv.server.dir);
|
||||||
|
if url.path() != "" || url.path() != "/" {
|
||||||
|
let decoded = util::url_decode(url.path().trim_start_matches("/").as_bytes());
|
||||||
|
path.push(decoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !path.exists() {
|
||||||
|
// See if it's a subpath of a CGI script before returning NotFound
|
||||||
|
#[cfg(feature = "cgi")]
|
||||||
|
if handle_cgi(&mut con, &url.as_str(), &url, &path).await? {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger::logger(con.peer_addr, Status::NotFound, &url.as_str());
|
||||||
|
con.send_status(Status::NotFound, None).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut meta = tokio::fs::metadata(&path).await?;
|
||||||
|
let mut perm = meta.permissions();
|
||||||
|
|
||||||
|
// TODO fix me
|
||||||
|
// This block is terrible
|
||||||
|
if meta.is_dir() {
|
||||||
|
if !url.path().ends_with("/") {
|
||||||
|
logger::logger(con.peer_addr, Status::RedirectPermanent, &url.as_str());
|
||||||
|
con.send_status(
|
||||||
|
Status::RedirectPermanent,
|
||||||
|
Some(format!("{}/", url).as_str()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if path.join(&index).exists() {
|
||||||
|
path.push(index);
|
||||||
|
meta = tokio::fs::metadata(&path).await?;
|
||||||
|
perm = meta.permissions();
|
||||||
|
if perm.mode() & 0o0444 != 0o444 {
|
||||||
|
let mut p = path.clone();
|
||||||
|
p.pop();
|
||||||
|
path.push(format!("{}/", p.display()));
|
||||||
|
meta = tokio::fs::metadata(&path).await?;
|
||||||
|
perm = meta.permissions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cgi")]
|
||||||
|
if handle_cgi(&mut con, &url.as_str(), &url, &path).await? {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.is_file() && perm.mode() & 0o0111 == 0o0111 {
|
||||||
|
logger::logger(con.peer_addr, Status::NotFound, &url.as_str());
|
||||||
|
con.send_status(Status::NotFound, None).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if perm.mode() & 0o0444 != 0o0444 {
|
||||||
|
logger::logger(con.peer_addr, Status::NotFound, &url.as_str());
|
||||||
|
con.send_status(Status::NotFound, None).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut mime = get_mime(&path);
|
||||||
|
if mime == "text/gemini" && con.srv.server.lang.is_some() {
|
||||||
|
mime += &("; lang=".to_string() + &con.srv.server.lang.to_owned().unwrap());
|
||||||
|
}
|
||||||
|
if !mime.starts_with("text/") {
|
||||||
|
logger::logger(con.peer_addr, Status::Success, &url.as_str());
|
||||||
|
get_binary(con, path, mime).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let content = get_content(path, &url).await?;
|
||||||
|
con.send_body(Status::Success, Some(&mime), Some(content))
|
||||||
|
.await?;
|
||||||
|
logger::logger(con.peer_addr, Status::Success, &url.as_str());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
13
src/lib/errors.rs
Normal file
13
src/lib/errors.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GemError(pub String);
|
||||||
|
|
||||||
|
impl fmt::Display for GemError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for GemError {}
|
||||||
@@ -2,3 +2,5 @@ pub mod util;
|
|||||||
pub mod status;
|
pub mod status;
|
||||||
pub mod conn;
|
pub mod conn;
|
||||||
pub mod tls;
|
pub mod tls;
|
||||||
|
pub mod server;
|
||||||
|
pub mod errors;
|
||||||
|
|||||||
164
src/lib/server.rs
Normal file
164
src/lib/server.rs
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
use tokio::net::TcpListener;
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
use openssl::ssl::SslAcceptor;
|
||||||
|
use openssl::error::ErrorStack;
|
||||||
|
use openssl::ssl::NameType;
|
||||||
|
//use futures_util::future::TryFutureExt;
|
||||||
|
use url::Url;
|
||||||
|
use std::io;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::future::Future;
|
||||||
|
//use std::sync::Arc;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use crate::config;
|
||||||
|
use crate::conn;
|
||||||
|
use crate::logger;
|
||||||
|
use crate::status::Status;
|
||||||
|
use crate::errors;
|
||||||
|
|
||||||
|
type Result<T=()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||||
|
|
||||||
|
pub trait Handler: FnMut(conn::Connection, url::Url) -> Pin<Box<dyn Future<Output = Result> + Send>> + Send + Sync + Copy {}
|
||||||
|
impl<T> Handler for T
|
||||||
|
where T: FnMut(conn::Connection, url::Url) -> Pin<Box<dyn Future<Output = Result> + Send>> + Send + Sync + Copy
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn force_boxed<T>(f: fn(conn::Connection, url::Url) -> T) -> impl Handler
|
||||||
|
where
|
||||||
|
T: Future<Output = Result> + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
move |a, b| Box::pin(f(a, b)) as _
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Server {
|
||||||
|
pub listener: TcpListener,
|
||||||
|
pub acceptor: SslAcceptor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
pub async fn bind(addr: String,
|
||||||
|
acceptor: fn(config::Config) -> std::result::Result<SslAcceptor, ErrorStack>,
|
||||||
|
cfg: config::Config) -> io::Result<Server>
|
||||||
|
{
|
||||||
|
Ok(Server {
|
||||||
|
listener: TcpListener::bind(addr).await?,
|
||||||
|
acceptor: acceptor(cfg)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn serve(self, cmap: HashMap<String, config::ServerCfg>, default: String,
|
||||||
|
handler: impl Handler + 'static + Copy) -> Result
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
let (stream, peer_addr) = self.listener.accept().await?;
|
||||||
|
let acceptor = self.acceptor.clone();
|
||||||
|
let cmap = cmap.clone();
|
||||||
|
let default = default.clone();
|
||||||
|
let mut handler = handler.clone();
|
||||||
|
|
||||||
|
let ssl = openssl::ssl::Ssl::new(acceptor.context()).unwrap();
|
||||||
|
let mut stream = tokio_openssl::SslStream::new(ssl, stream).unwrap();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
match Pin::new(&mut stream).accept().await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Error: {}",e);
|
||||||
|
return Ok(());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let srv = match stream.ssl().servername(NameType::HOST_NAME) {
|
||||||
|
Some(s) => match cmap.get(s) {
|
||||||
|
Some(ss) => ss,
|
||||||
|
None => cmap.get(&default).unwrap(),
|
||||||
|
},
|
||||||
|
None => cmap.get(&default).unwrap(),
|
||||||
|
}.to_owned();
|
||||||
|
|
||||||
|
let con = conn::Connection { stream, peer_addr, srv };
|
||||||
|
let (con, url) = match get_request(con).await {
|
||||||
|
Ok((c, u)) => (c, u),
|
||||||
|
Err(_) => return Ok(()) as io::Result<()>,
|
||||||
|
};
|
||||||
|
|
||||||
|
match handler(con, url).await {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(_) => return Ok(()) as io::Result<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(()) as io::Result<()>
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_request(mut con: conn::Connection) -> Result<(conn::Connection, url::Url)> {
|
||||||
|
let mut buffer = [0; 1024];
|
||||||
|
let len = match tokio::time::timeout(tokio::time::Duration::from_secs(5), con.stream.read(&mut buffer)).await {
|
||||||
|
Ok(result) => result.unwrap(),
|
||||||
|
Err(e) => {
|
||||||
|
logger::logger(con.peer_addr, Status::BadRequest, "");
|
||||||
|
con.send_status(Status::BadRequest, None).await.map_err(|e| e.to_string())?;
|
||||||
|
return Err(Box::new(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut request = match String::from_utf8(buffer[..len].to_vec()) {
|
||||||
|
Ok(request) => request,
|
||||||
|
Err(e) => {
|
||||||
|
logger::logger(con.peer_addr, Status::BadRequest, "");
|
||||||
|
con.send_status(Status::BadRequest, None).await.map_err(|e| e.to_string())?;
|
||||||
|
return Err(Box::new(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if request.starts_with("//") {
|
||||||
|
request = request.replacen("//", "gemini://", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.ends_with("\n") {
|
||||||
|
request.pop();
|
||||||
|
if request.ends_with("\r") {
|
||||||
|
request.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = match Url::parse(&request) {
|
||||||
|
Ok(url) => url,
|
||||||
|
Err(e) => {
|
||||||
|
logger::logger(con.peer_addr, Status::BadRequest, &request);
|
||||||
|
con.send_status(Status::BadRequest, None).await.map_err(|e| e.to_string())?;
|
||||||
|
return Err(Box::new(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match url.host_str() {
|
||||||
|
Some(h) => {
|
||||||
|
if con.srv.server.hostname.as_str() != h.to_lowercase() {
|
||||||
|
logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str());
|
||||||
|
con.send_status(Status::ProxyRequestRefused, None).await.map_err(|e| e.to_string())?;
|
||||||
|
return Err(Box::new(errors::GemError("Wrong host".into())));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
match url.port() {
|
||||||
|
Some(p) => {
|
||||||
|
if p != con.srv.port {
|
||||||
|
logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str());
|
||||||
|
con.send_status(Status::ProxyRequestRefused, None)
|
||||||
|
.await.map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if url.scheme() != "gemini" {
|
||||||
|
logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str());
|
||||||
|
con.send_status(Status::ProxyRequestRefused, None).await.map_err(|e| e.to_string())?;
|
||||||
|
return Err(Box::new(errors::GemError("scheme not gemini".into())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok((con, url))
|
||||||
|
}
|
||||||
@@ -2,6 +2,24 @@ use crate::status;
|
|||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
pub fn init(loglev: &Option<String>) {
|
||||||
|
let loglev = match loglev {
|
||||||
|
None => log::Level::Info,
|
||||||
|
Some(l) => {
|
||||||
|
match l.as_str() {
|
||||||
|
"error" => log::Level::Error,
|
||||||
|
"warn" => log::Level::Warn,
|
||||||
|
"info" => log::Level::Info,
|
||||||
|
_ => {
|
||||||
|
eprintln!("Incorrect log level in config file.");
|
||||||
|
std::process::exit(1);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
simple_logger::init_with_level(loglev).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn logger(addr: SocketAddr, stat: status::Status, req: &str) {
|
pub fn logger(addr: SocketAddr, stat: status::Status, req: &str) {
|
||||||
match stat as u8 {
|
match stat as u8 {
|
||||||
20..=29 => info!("remote={} status={} request={}", addr, stat as u8, req),
|
20..=29 => info!("remote={} status={} request={}", addr, stat as u8, req),
|
||||||
|
|||||||
483
src/main.rs
483
src/main.rs
@@ -1,422 +1,29 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
use futures_util::future::TryFutureExt;
|
|
||||||
use new_mime_guess;
|
|
||||||
use openssl::ssl::NameType;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::io;
|
||||||
use std::pin::Pin;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, BufRead, BufReader};
|
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::path::Path;
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use tokio::net::TcpListener;
|
|
||||||
use tokio::io::AsyncReadExt;
|
|
||||||
use tokio::runtime;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
mod lib;
|
mod lib;
|
||||||
mod cgi;
|
mod cgi;
|
||||||
mod config;
|
mod config;
|
||||||
mod logger;
|
mod logger;
|
||||||
mod revproxy;
|
mod revproxy;
|
||||||
|
mod con_handler;
|
||||||
|
|
||||||
use lib::util;
|
use lib::util;
|
||||||
use lib::conn;
|
use lib::conn;
|
||||||
use lib::status;
|
use lib::status;
|
||||||
use status::Status;
|
|
||||||
use lib::tls;
|
use lib::tls;
|
||||||
|
use lib::server;
|
||||||
|
use lib::errors;
|
||||||
|
|
||||||
fn get_mime(path: &PathBuf) -> String {
|
type Result<T=()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||||
let mut mime = "text/gemini".to_string();
|
|
||||||
if path.is_dir() {
|
|
||||||
return mime;
|
|
||||||
}
|
|
||||||
let ext = match path.extension() {
|
|
||||||
Some(p) => p.to_str().unwrap(),
|
|
||||||
None => return "text/plain".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
mime = match new_mime_guess::from_ext(ext).first() {
|
#[tokio::main]
|
||||||
Some(m) => m.essence_str().to_string(),
|
async fn main() -> Result {
|
||||||
None => "text/plain".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return mime;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_binary(mut con: conn::Connection, path: PathBuf, meta: String) -> io::Result<()> {
|
|
||||||
let fd = File::open(path)?;
|
|
||||||
let mut reader = BufReader::with_capacity(1024 * 1024, fd);
|
|
||||||
con.send_status(status::Status::Success, Some(&meta))
|
|
||||||
.await?;
|
|
||||||
loop {
|
|
||||||
let len = {
|
|
||||||
let buf = reader.fill_buf()?;
|
|
||||||
con.send_raw(buf).await?;
|
|
||||||
buf.len()
|
|
||||||
};
|
|
||||||
if len == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
reader.consume(len);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_content(path: PathBuf, u: &url::Url) -> Result<String, io::Error> {
|
|
||||||
let meta = tokio::fs::metadata(&path).await?;
|
|
||||||
if meta.is_file() {
|
|
||||||
return Ok(tokio::fs::read_to_string(path).await?);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dirs: Vec<String> = Vec::new();
|
|
||||||
let mut files: Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
// needs work
|
|
||||||
for file in fs::read_dir(&path)? {
|
|
||||||
if let Ok(file) = file {
|
|
||||||
let m = file.metadata()?;
|
|
||||||
let perm = m.permissions();
|
|
||||||
if perm.mode() & 0o0444 != 0o0444 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let file = file.path();
|
|
||||||
let p = file.strip_prefix(&path).unwrap();
|
|
||||||
let ps = match p.to_str() {
|
|
||||||
Some(s) => s,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
let ep = match u.join(ps) {
|
|
||||||
Ok(p) => p,
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
if m.is_dir() {
|
|
||||||
dirs.push(format!("=> {}/ {}/\r\n", ep, p.display()));
|
|
||||||
} else {
|
|
||||||
files.push(format!("=> {} {}\r\n", ep, p.display()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dirs.sort();
|
|
||||||
files.sort();
|
|
||||||
|
|
||||||
let mut list = String::from("# Directory Listing\r\n\r\n");
|
|
||||||
list.push_str(&format!("Path: {}\r\n\r\n", u.path()));
|
|
||||||
|
|
||||||
for dir in dirs {
|
|
||||||
list.push_str(&dir);
|
|
||||||
}
|
|
||||||
for file in files {
|
|
||||||
list.push_str(&file);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle CGI and return Ok(true), or indicate this request wasn't for CGI with Ok(false)
|
|
||||||
#[cfg(feature = "cgi")]
|
|
||||||
async fn handle_cgi(
|
|
||||||
con: &mut conn::Connection,
|
|
||||||
request: &str,
|
|
||||||
url: &Url,
|
|
||||||
full_path: &PathBuf,
|
|
||||||
) -> Result<bool, io::Error> {
|
|
||||||
if con.srv.server.cgi.unwrap_or(false) {
|
|
||||||
let mut path = full_path.clone();
|
|
||||||
let mut segments = url.path_segments().unwrap();
|
|
||||||
let mut path_info = "".to_string();
|
|
||||||
|
|
||||||
// Find an ancestor url that matches a file
|
|
||||||
while !path.exists() {
|
|
||||||
if let Some(segment) = segments.next_back() {
|
|
||||||
path.pop();
|
|
||||||
path_info = format!("/{}{}", &segment, path_info);
|
|
||||||
} else {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let script_name = format!("/{}", segments.collect::<Vec<_>>().join("/"));
|
|
||||||
|
|
||||||
let meta = tokio::fs::metadata(&path).await?;
|
|
||||||
let perm = meta.permissions();
|
|
||||||
|
|
||||||
match &con.srv.server.cgipath {
|
|
||||||
Some(c) => {
|
|
||||||
if path.starts_with(c) {
|
|
||||||
if perm.mode() & 0o0111 == 0o0111 {
|
|
||||||
cgi::cgi(con, path, url, script_name, path_info).await?;
|
|
||||||
return Ok(true);
|
|
||||||
} else {
|
|
||||||
logger::logger(con.peer_addr, Status::CGIError, request);
|
|
||||||
con.send_status(Status::CGIError, None).await?;
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
if meta.is_file() && perm.mode() & 0o0111 == 0o0111 {
|
|
||||||
cgi::cgi(con, path, url, script_name, path_info).await?;
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_request(mut con: conn::Connection) -> Result<(conn::Connection, url::Url), String> {
|
|
||||||
let mut buffer = [0; 1024];
|
|
||||||
let len = match tokio::time::timeout(tokio::time::Duration::from_secs(5), con.stream.read(&mut buffer)).await {
|
|
||||||
Ok(result) => result.unwrap(),
|
|
||||||
Err(e) => {
|
|
||||||
logger::logger(con.peer_addr, Status::BadRequest, "");
|
|
||||||
con.send_status(Status::BadRequest, None).await.map_err(|e| e.to_string())?;
|
|
||||||
return Err(e.to_string());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut request = match String::from_utf8(buffer[..len].to_vec()) {
|
|
||||||
Ok(request) => request,
|
|
||||||
Err(e) => {
|
|
||||||
logger::logger(con.peer_addr, Status::BadRequest, "");
|
|
||||||
con.send_status(Status::BadRequest, None).await.map_err(|e| e.to_string())?;
|
|
||||||
return Err(e.to_string());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if request.starts_with("//") {
|
|
||||||
request = request.replacen("//", "gemini://", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if request.ends_with("\n") {
|
|
||||||
request.pop();
|
|
||||||
if request.ends_with("\r") {
|
|
||||||
request.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let url = match Url::parse(&request) {
|
|
||||||
Ok(url) => url,
|
|
||||||
Err(_) => {
|
|
||||||
logger::logger(con.peer_addr, Status::BadRequest, &request);
|
|
||||||
con.send_status(Status::BadRequest, None).await.map_err(|e| e.to_string())?;
|
|
||||||
return Err("Bad url".to_string());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match url.host_str() {
|
|
||||||
Some(h) => {
|
|
||||||
if con.srv.server.hostname.as_str() != h.to_lowercase() {
|
|
||||||
logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str());
|
|
||||||
con.send_status(Status::ProxyRequestRefused, None).await.map_err(|e| e.to_string())?;
|
|
||||||
return Err("Wrong host".to_string());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
match url.port() {
|
|
||||||
Some(p) => {
|
|
||||||
if p != con.srv.port {
|
|
||||||
logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str());
|
|
||||||
con.send_status(status::Status::ProxyRequestRefused, None)
|
|
||||||
.await.map_err(|e| e.to_string())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if url.scheme() != "gemini" {
|
|
||||||
logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str());
|
|
||||||
con.send_status(Status::ProxyRequestRefused, None).await.map_err(|e| e.to_string())?;
|
|
||||||
return Err("scheme not gemini".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok((con, url))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Rewrite this monster.
|
|
||||||
async fn handle_connection(
|
|
||||||
mut con: conn::Connection,
|
|
||||||
url: url::Url
|
|
||||||
) -> Result<(), io::Error> {
|
|
||||||
let index = match &con.srv.server.index {
|
|
||||||
Some(i) => i.clone(),
|
|
||||||
None => "index.gemini".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
match &con.srv.server.redirect.to_owned() {
|
|
||||||
Some(re) => {
|
|
||||||
let u = match url.path() {
|
|
||||||
"/" => "/",
|
|
||||||
_ => url.path().trim_end_matches("/"),
|
|
||||||
};
|
|
||||||
match re.get(u) {
|
|
||||||
Some(r) => {
|
|
||||||
logger::logger(con.peer_addr, Status::RedirectTemporary, &url.as_str());
|
|
||||||
con.send_status(Status::RedirectTemporary, Some(r)).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "proxy")]
|
|
||||||
if let Some(pr) = con.srv.server.proxy_all.to_owned() {
|
|
||||||
let host_port: Vec<&str> = pr.splitn(2, ':').collect();
|
|
||||||
let host = host_port[0];
|
|
||||||
let port: Option<u16>;
|
|
||||||
if host_port.len() == 2 {
|
|
||||||
port = host_port[1].parse().ok();
|
|
||||||
} else {
|
|
||||||
port = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut upstream_url = url.clone();
|
|
||||||
upstream_url.set_host(Some(host)).unwrap();
|
|
||||||
upstream_url.set_port(port).unwrap();
|
|
||||||
|
|
||||||
revproxy::proxy_all(pr.as_str(), upstream_url, con).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "proxy")]
|
|
||||||
match &con.srv.server.proxy {
|
|
||||||
Some(pr) => match url.path_segments().map(|c| c.collect::<Vec<_>>()) {
|
|
||||||
Some(s) => match pr.get(s[0]) {
|
|
||||||
Some(p) => {
|
|
||||||
revproxy::proxy(p.to_string(), url, con).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
},
|
|
||||||
None => {}
|
|
||||||
},
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "scgi")]
|
|
||||||
match &con.srv.server.scgi {
|
|
||||||
Some(sc) => {
|
|
||||||
let u = match url.path() {
|
|
||||||
"/" => "/",
|
|
||||||
_ => url.path().trim_end_matches("/"),
|
|
||||||
};
|
|
||||||
match sc.get(u) {
|
|
||||||
Some(r) => {
|
|
||||||
cgi::scgi(r.to_string(), url, con).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let mut path = PathBuf::new();
|
|
||||||
|
|
||||||
if url.path().starts_with("/~") && con.srv.server.usrdir.unwrap_or(false) {
|
|
||||||
let usr = url.path().trim_start_matches("/~");
|
|
||||||
let usr: Vec<&str> = usr.splitn(2, "/").collect();
|
|
||||||
if cfg!(target_os = "macos") {
|
|
||||||
path.push("/Users/");
|
|
||||||
} else {
|
|
||||||
path.push("/home/");
|
|
||||||
}
|
|
||||||
if usr.len() == 2 {
|
|
||||||
path.push(format!("{}/{}/{}", usr[0], "public_gemini", util::url_decode(usr[1].as_bytes())));
|
|
||||||
} else {
|
|
||||||
path.push(format!("{}/{}/", usr[0], "public_gemini"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
path.push(&con.srv.server.dir);
|
|
||||||
if url.path() != "" || url.path() != "/" {
|
|
||||||
let decoded = util::url_decode(url.path().trim_start_matches("/").as_bytes());
|
|
||||||
path.push(decoded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !path.exists() {
|
|
||||||
// See if it's a subpath of a CGI script before returning NotFound
|
|
||||||
#[cfg(feature = "cgi")]
|
|
||||||
if handle_cgi(&mut con, &url.as_str(), &url, &path).await? {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
logger::logger(con.peer_addr, Status::NotFound, &url.as_str());
|
|
||||||
con.send_status(Status::NotFound, None).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut meta = tokio::fs::metadata(&path).await?;
|
|
||||||
let mut perm = meta.permissions();
|
|
||||||
|
|
||||||
// TODO fix me
|
|
||||||
// This block is terrible
|
|
||||||
if meta.is_dir() {
|
|
||||||
if !url.path().ends_with("/") {
|
|
||||||
logger::logger(con.peer_addr, Status::RedirectPermanent, &url.as_str());
|
|
||||||
con.send_status(
|
|
||||||
Status::RedirectPermanent,
|
|
||||||
Some(format!("{}/", url).as_str()),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
if path.join(&index).exists() {
|
|
||||||
path.push(index);
|
|
||||||
meta = tokio::fs::metadata(&path).await?;
|
|
||||||
perm = meta.permissions();
|
|
||||||
if perm.mode() & 0o0444 != 0o444 {
|
|
||||||
let mut p = path.clone();
|
|
||||||
p.pop();
|
|
||||||
path.push(format!("{}/", p.display()));
|
|
||||||
meta = tokio::fs::metadata(&path).await?;
|
|
||||||
perm = meta.permissions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "cgi")]
|
|
||||||
if handle_cgi(&mut con, &url.as_str(), &url, &path).await? {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if meta.is_file() && perm.mode() & 0o0111 == 0o0111 {
|
|
||||||
logger::logger(con.peer_addr, Status::NotFound, &url.as_str());
|
|
||||||
con.send_status(Status::NotFound, None).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if perm.mode() & 0o0444 != 0o0444 {
|
|
||||||
logger::logger(con.peer_addr, Status::NotFound, &url.as_str());
|
|
||||||
con.send_status(Status::NotFound, None).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut mime = get_mime(&path);
|
|
||||||
if mime == "text/gemini" && con.srv.server.lang.is_some() {
|
|
||||||
mime += &("; lang=".to_string() + &con.srv.server.lang.to_owned().unwrap());
|
|
||||||
}
|
|
||||||
if !mime.starts_with("text/") {
|
|
||||||
logger::logger(con.peer_addr, Status::Success, &url.as_str());
|
|
||||||
get_binary(con, path, mime).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let content = get_content(path, &url).await?;
|
|
||||||
con.send_body(status::Status::Success, Some(&mime), Some(content))
|
|
||||||
.await?;
|
|
||||||
logger::logger(con.peer_addr, Status::Success, &url.as_str());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
if args.len() != 2 {
|
if args.len() != 2 {
|
||||||
println!("Please run with the path to the config file.");
|
println!("Please run with the path to the config file.");
|
||||||
@@ -435,21 +42,9 @@ fn main() -> io::Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let loglev = match &cfg.log {
|
|
||||||
None => log::Level::Info,
|
logger::init(&cfg.log);
|
||||||
Some(l) => {
|
|
||||||
match l.as_str() {
|
|
||||||
"error" => log::Level::Error,
|
|
||||||
"warn" => log::Level::Warn,
|
|
||||||
"info" => log::Level::Info,
|
|
||||||
_ => {
|
|
||||||
eprintln!("Incorrect log level in config file.");
|
|
||||||
return Ok(());
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
simple_logger::init_with_level(loglev).unwrap();
|
|
||||||
let cmap = cfg.to_map();
|
let cmap = cfg.to_map();
|
||||||
let default = &cfg.server[0].hostname;
|
let default = &cfg.server[0].hostname;
|
||||||
println!("Serving {} vhosts", cfg.server.len());
|
println!("Serving {} vhosts", cfg.server.len());
|
||||||
@@ -459,57 +54,9 @@ fn main() -> io::Result<()> {
|
|||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?;
|
.ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?;
|
||||||
|
|
||||||
let runtime = runtime::Builder::new_multi_thread()
|
let server = server::Server::bind(addr, tls::acceptor_conf, cfg.clone()).await?;
|
||||||
// .threaded_scheduler()
|
if let Err(e) = server.serve(cmap, default.to_string(), server::force_boxed(con_handler::handle_connection)).await {
|
||||||
.enable_io()
|
return Err(e)
|
||||||
.enable_time()
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let handle = runtime.handle().clone();
|
|
||||||
|
|
||||||
let acceptor = tls::acceptor_conf(cfg.clone())?;
|
|
||||||
|
|
||||||
let fut = async move {
|
|
||||||
let listener = TcpListener::bind(&addr).await?;
|
|
||||||
loop {
|
|
||||||
let (stream, peer_addr) = listener.accept().await?;
|
|
||||||
let acceptor = acceptor.clone();
|
|
||||||
let cmap = cmap.clone();
|
|
||||||
let default = default.clone();
|
|
||||||
|
|
||||||
let ssl = openssl::ssl::Ssl::new(acceptor.context()).unwrap();
|
|
||||||
let mut stream = tokio_openssl::SslStream::new(ssl, stream).unwrap();
|
|
||||||
|
|
||||||
let fut = async move {
|
|
||||||
match Pin::new(&mut stream).accept().await {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Error: {}",e);
|
|
||||||
return Ok(());
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let srv = match stream.ssl().servername(NameType::HOST_NAME) {
|
|
||||||
Some(s) => match cmap.get(s) {
|
|
||||||
Some(ss) => ss,
|
|
||||||
None => cmap.get(&default).unwrap(),
|
|
||||||
},
|
|
||||||
None => cmap.get(&default).unwrap(),
|
|
||||||
}.to_owned();
|
|
||||||
|
|
||||||
let con = conn::Connection { stream, peer_addr, srv };
|
|
||||||
let (con, url) = match get_request(con).await {
|
|
||||||
Ok((c, u)) => (c, u),
|
|
||||||
Err(_) => return Ok(()) as io::Result<()>,
|
|
||||||
};
|
|
||||||
handle_connection(con, url).await?;
|
|
||||||
|
|
||||||
Ok(()) as io::Result<()>
|
|
||||||
};
|
|
||||||
|
|
||||||
handle.spawn(fut.unwrap_or_else(|err| eprintln!("{:?}", err)));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
return Ok(())
|
||||||
runtime.block_on(fut)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user