correctly adhere to spec in most request cases
This commit is contained in:
parent
aa041cc4a6
commit
2ffc8ff0cc
5 changed files with 47 additions and 11 deletions
13
README.md
13
README.md
|
@ -63,6 +63,19 @@ Note: This sets the user to `nobody` and the group to `nobody` as well. This
|
|||
naming scheme is not consistent for all Unix systems... Try changing the group
|
||||
name to `nogroup` if the software fails to start.
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
As you may have spotted, I did not get around to write a test suite for this.
|
||||
The server's behavior can be tested using the
|
||||
[gemini-diagnostics](https://github.com/michael-lazar/gemini-diagnostics) suite
|
||||
by michael-lazar. It passes all "important" tests (some malformed requests
|
||||
are still handled). Most importantly: the URLDotEscape tests fails. This does
|
||||
not mean you can successfully a URL escape attack against this, rather the URL
|
||||
library I use already parses out any superfluous ..'s.
|
||||
e.g. "localhost/../../../etc/passwd" already became "localhost/etc/passwd" once
|
||||
I receive the parsed URL from the library.
|
||||
|
||||
Why "Sheldon Director"?
|
||||
-----------------------
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ default_host = localhost
|
|||
gem_root = ./doc
|
||||
|
||||
# you can define as many of these as you like
|
||||
listen = [::1]:1965
|
||||
listen = 127.0.0.1:1965
|
||||
listen = 0.0.0.0:1965
|
||||
listen = [::]:1965
|
||||
|
||||
# privilege level for the server to drop to after initializing
|
||||
user = nobody
|
||||
|
|
|
@ -20,11 +20,10 @@ fn send_header(stream: &mut SslStream<TcpStream>, header: &response::Header) {
|
|||
}
|
||||
|
||||
pub fn handle_request(config: &ServerConfig, mut stream: SslStream<TcpStream>) {
|
||||
let mut buffer = [0; 1026];
|
||||
let mut buffer = [0; 1024];
|
||||
match stream.ssl_read(&mut buffer) {
|
||||
Ok(s) => {
|
||||
if s == 0 {
|
||||
println!("received empty request buffer");
|
||||
if s == 0 || s > 1025 {
|
||||
send_header(&mut stream, &response::bad_request());
|
||||
return;
|
||||
}
|
||||
|
@ -47,7 +46,11 @@ pub fn handle_request(config: &ServerConfig, mut stream: SslStream<TcpStream>) {
|
|||
|
||||
let location = match Url::parse(&request) {
|
||||
Ok(url) => url,
|
||||
Err(_) => config.default_host.join(&request).unwrap(),
|
||||
Err(_) => {
|
||||
println!("received invalid request url");
|
||||
send_header(&mut stream, &response::bad_request());
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
handle_response(config, location, &mut stream);
|
||||
|
@ -69,25 +72,39 @@ fn write_line<T: Write>(line: &[u8], stream: &mut BufWriter<T>) -> Result<(), Er
|
|||
fn handle_response(config: &ServerConfig, url: Url, mut stream: &mut SslStream<TcpStream>) {
|
||||
println!("responding for: {}", url);
|
||||
|
||||
if url.scheme() != "gemini" {
|
||||
send_header(&mut stream, &response::permanent_failure());
|
||||
// url scheme must be either "gemini://" or "//" (empty)
|
||||
if url.scheme() != "gemini" && url.scheme() != "" {
|
||||
send_header(&mut stream, &response::proxy_request_refused());
|
||||
return;
|
||||
}
|
||||
|
||||
// url request host must match
|
||||
if url.host() != config.default_host.host() {
|
||||
send_header(&mut stream, &response::proxy_request_refused());
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: also drop on incorrect port in request
|
||||
|
||||
let rel_path = match Path::new(url.path()).strip_prefix("/") {
|
||||
Ok(path) => path,
|
||||
Err(_) => {
|
||||
Path::new("")
|
||||
// empty path, gemini spec says to redirect client to root
|
||||
send_header(&mut stream, &response::redirect_permanent(
|
||||
config.default_host.as_str(),
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let path = gen_path_index(&config.gem_root.join(rel_path));
|
||||
|
||||
// make sure we can't escape gem_root
|
||||
if !path.starts_with(&config.gem_root) {
|
||||
send_header(&mut stream, &response::bad_request());
|
||||
return;
|
||||
}
|
||||
|
||||
let file = match File::open(&path) {
|
||||
Ok(file) => file,
|
||||
Err(_) => {
|
||||
|
|
|
@ -24,7 +24,7 @@ pub struct ServerConfig {
|
|||
impl ServerConfig {
|
||||
pub fn new() -> ServerConfig {
|
||||
ServerConfig {
|
||||
default_host: Url::parse("gemini://localhost").unwrap(),
|
||||
default_host: Url::parse("gemini://localhost/").unwrap(),
|
||||
gem_root: PathBuf::from(""),
|
||||
addrs: Vec::new(),
|
||||
user: unistd::getuid(),
|
||||
|
@ -35,7 +35,7 @@ impl ServerConfig {
|
|||
}
|
||||
|
||||
pub fn set_default_host(&mut self, default_host: String) {
|
||||
let mut url = Url::parse("gemini://default").unwrap();
|
||||
let mut url = Url::parse("gemini://default/").unwrap();
|
||||
|
||||
match url.set_host(Some(default_host.as_str())) {
|
||||
Ok(_) => {}
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::vec::Vec;
|
|||
#[derive(Copy, Clone)]
|
||||
pub enum Status {
|
||||
Success = 20,
|
||||
RedirectPermanent = 31,
|
||||
PermanentFailure = 50,
|
||||
NotFound = 51,
|
||||
ProxyRequestRefused = 53,
|
||||
|
@ -31,6 +32,10 @@ impl Header {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn redirect_permanent(meta: &str) -> Header {
|
||||
Header::new(Status::RedirectPermanent, meta)
|
||||
}
|
||||
|
||||
pub fn permanent_failure() -> Header {
|
||||
Header::new(Status::PermanentFailure, "permanent failure")
|
||||
}
|
||||
|
@ -46,3 +51,4 @@ pub fn proxy_request_refused() -> Header {
|
|||
pub fn bad_request() -> Header {
|
||||
Header::new(Status::BadRequest, "bad request")
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue