Refactor error handling and logging

This commit is contained in:
Matt Brubeck 2020-12-31 16:20:57 -08:00
parent 847434d844
commit edf2ebffdc

View file

@ -73,9 +73,9 @@ fn args() -> Result<Args> {
opts.optflag("s", "silent", "Disable logging output"); opts.optflag("s", "silent", "Disable logging output");
opts.optflag("h", "help", "Print this help menu"); opts.optflag("h", "help", "Print this help menu");
let usage = opts.usage(&format!("Usage: {} FILE [options]", &args[0]));
let matches = opts.parse(&args[1..]).map_err(|f| f.to_string())?; let matches = opts.parse(&args[1..]).map_err(|f| f.to_string())?;
if matches.opt_present("h") { if matches.opt_present("h") {
let usage = opts.usage(&format!("Usage: {} [options]", &args[0]));
Err(usage)?; Err(usage)?;
} }
let hostname = match matches.opt_str("hostname") { let hostname = match matches.opt_str("hostname") {
@ -107,18 +107,10 @@ async fn handle_request(stream: TcpStream) -> Result {
static TLS: Lazy<TlsAcceptor> = Lazy::new(|| acceptor().unwrap()); static TLS: Lazy<TlsAcceptor> = Lazy::new(|| acceptor().unwrap());
let stream = &mut TLS.accept(stream).await?; let stream = &mut TLS.accept(stream).await?;
let url = match parse_request(stream).await { match parse_request(stream).await {
Ok(url) => url, Ok(url) => send_response(url, stream).await,
Err((status, msg)) => { Err((status, msg)) => send_header(stream, status, &[msg]).await,
send_header(stream, status, &[&msg]).await?;
Err(msg)?
}
};
if let Err(e) = send_response(url, stream).await {
send_header(stream, 51, &["Not found, sorry."]).await?;
Err(e)?
} }
Ok(())
} }
/// TLS configuration. /// TLS configuration.
@ -147,10 +139,7 @@ async fn parse_request<R: Read + Unpin>(
// Read until CRLF, end-of-stream, or there's no buffer space left. // Read until CRLF, end-of-stream, or there's no buffer space left.
loop { loop {
let bytes_read = stream let bytes_read = stream.read(buf).await.or(Err((59, "Request ended unexpectedly")))?;
.read(buf)
.await
.map_err(|_| (59, "Request ended unexpectedly"))?;
len += bytes_read; len += bytes_read;
if request[..len].ends_with(b"\r\n") { if request[..len].ends_with(b"\r\n") {
break; break;
@ -159,24 +148,24 @@ async fn parse_request<R: Read + Unpin>(
} }
buf = &mut request[len..]; buf = &mut request[len..];
} }
let request = std::str::from_utf8(&request[..len - 2]).map_err(|_| (59, "Invalid URL"))?; let request = std::str::from_utf8(&request[..len - 2]).or(Err((59, "Invalid URL")))?;
log::info!("Got request for {:?}", request); log::info!("Got request for {:?}", request);
// Handle scheme-relative URLs. // Handle scheme-relative URLs.
let url = if request.starts_with("//") { let url = if request.starts_with("//") {
Url::parse(&format!("gemini:{}", request)).map_err(|_| (59, "Invalid URL"))? Url::parse(&format!("gemini:{}", request))
} else { } else {
Url::parse(request).map_err(|_| (59, "Invalid URL"))? Url::parse(request)
}; }.or(Err((59, "Invalid URL")))?;
// Validate the URL, host and port. // Validate the URL, host and port.
if url.scheme() != "gemini" { if url.scheme() != "gemini" {
return Err((53, "unsupported URL scheme")); return Err((53, "Unsupported URL scheme"));
} }
// TODO: Can be simplified by https://github.com/servo/rust-url/pull/651 // TODO: Can be simplified by https://github.com/servo/rust-url/pull/651
if let (Some(Host::Domain(expected)), Some(Host::Domain(host))) = (url.host(), &ARGS.hostname) { if let (Some(Host::Domain(expected)), Some(Host::Domain(host))) = (url.host(), &ARGS.hostname) {
if host != expected { if host != expected {
return Err((53, "proxy request refused")); return Err((53, "Proxy request refused"));
} }
} }
if let Some(port) = url.port() { if let Some(port) = url.port() {
@ -195,23 +184,31 @@ async fn send_response<W: Write + Unpin>(url: Url, stream: &mut W) -> Result {
path.push(&*percent_decode_str(segment).decode_utf8()?); path.push(&*percent_decode_str(segment).decode_utf8()?);
} }
} }
if async_std::fs::metadata(&path).await?.is_dir() { if let Ok(metadata) = async_std::fs::metadata(&path).await {
if url.path().ends_with('/') || url.path().is_empty() { if metadata.is_dir() {
// if the path ends with a slash or the path is empty, the links will work the same if url.path().ends_with('/') || url.path().is_empty() {
// without a redirect // if the path ends with a slash or the path is empty, the links will work the same
path.push("index.gmi"); // without a redirect
if !path.exists() && path.with_file_name(".directory-listing-ok").exists() { path.push("index.gmi");
path.pop(); if !path.exists() && path.with_file_name(".directory-listing-ok").exists() {
return list_directory(stream, &path).await; path.pop();
return list_directory(stream, &path).await;
}
} else {
// if client is not redirected, links may not work as expected without trailing slash
return send_header(stream, 31, &[url.as_str(), "/"]).await;
} }
} else {
// if client is not redirected, links may not work as expected without trailing slash
return send_header(stream, 31, &[url.as_str(), "/"]).await;
} }
} }
// Make sure the file opens successfully before sending the success header. // Make sure the file opens successfully before sending the success header.
let mut file = async_std::fs::File::open(&path).await?; let mut file = match async_std::fs::File::open(&path).await {
Ok(file) => file,
Err(e) => {
send_header(stream, 51, &["Not found, sorry."]).await?;
Err(e)?
}
};
// Send header. // Send header.
if path.extension() == Some(OsStr::new("gmi")) { if path.extension() == Some(OsStr::new("gmi")) {