mirror of
https://github.com/YGGverse/agate.git
synced 2026-04-08 20:45:29 +00:00
parent
4ae9cd5826
commit
8fbbec2b4b
2 changed files with 31 additions and 15 deletions
|
|
@ -89,6 +89,14 @@ gone.gmi:52 This file is no longer here, sorry.
|
||||||
|
|
||||||
Agate uses the `env_logger` crate and allows you to set the logging verbosity by setting the default `RUST_LOG` environment variable. For more information, please see the [documentation of `env_logger`].
|
Agate uses the `env_logger` crate and allows you to set the logging verbosity by setting the default `RUST_LOG` environment variable. For more information, please see the [documentation of `env_logger`].
|
||||||
|
|
||||||
|
### Virtual Hosts
|
||||||
|
|
||||||
|
Agate has basic support for virtual hosts. If you specify multiple `--hostname`s, Agate will look in a directory with the respective hostname within the content root directory.
|
||||||
|
For example if one of the hostnames is `example.com`, and the content root directory is set to the default `./content`, and `gemini://example.com/file.gmi` is requested, then Agate will look for `./content/example.com/file.gmi`. This behaviour is only enabled if multiple `--hostname`s are specified.
|
||||||
|
Agate does not support different certificates for different hostnames, you will have to use a single certificate for all domains (multi domain certificate).
|
||||||
|
|
||||||
|
If you want to serve the same content for multiple domains, you can instead disable the hostname check by not specifying `--hostname`. In this case Agate will disregard a request's hostname apart from checking that there is one.
|
||||||
|
|
||||||
[Gemini]: https://gemini.circumlunar.space/
|
[Gemini]: https://gemini.circumlunar.space/
|
||||||
[Rust]: https://www.rust-lang.org/
|
[Rust]: https://www.rust-lang.org/
|
||||||
[home]: gemini://gem.limpet.net/agate/
|
[home]: gemini://gem.limpet.net/agate/
|
||||||
|
|
|
||||||
38
src/main.rs
38
src/main.rs
|
|
@ -69,7 +69,7 @@ struct Args {
|
||||||
content_dir: String,
|
content_dir: String,
|
||||||
cert_file: String,
|
cert_file: String,
|
||||||
key_file: String,
|
key_file: String,
|
||||||
hostname: Option<Host>,
|
hostnames: Vec<Host>,
|
||||||
language: Option<String>,
|
language: Option<String>,
|
||||||
silent: bool,
|
silent: bool,
|
||||||
serve_secret: bool,
|
serve_secret: bool,
|
||||||
|
|
@ -103,10 +103,10 @@ fn args() -> Result<Args> {
|
||||||
"Address to listen on (multiple occurences possible, default 0.0.0.0:1965 and [::]:1965)",
|
"Address to listen on (multiple occurences possible, default 0.0.0.0:1965 and [::]:1965)",
|
||||||
"IP:PORT",
|
"IP:PORT",
|
||||||
);
|
);
|
||||||
opts.optopt(
|
opts.optmulti(
|
||||||
"",
|
"",
|
||||||
"hostname",
|
"hostname",
|
||||||
"Domain name of this Gemini server (optional)",
|
"Domain name of this Gemini server (optional; simple vhosts if there are multiple occurences)",
|
||||||
"NAME",
|
"NAME",
|
||||||
);
|
);
|
||||||
opts.optopt(
|
opts.optopt(
|
||||||
|
|
@ -127,12 +127,12 @@ fn args() -> Result<Args> {
|
||||||
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]));
|
let usage = opts.usage(&format!("Usage: {} [options]", &args[0]));
|
||||||
return Err(usage.into())
|
return Err(usage.into());
|
||||||
|
}
|
||||||
|
let mut hostnames = vec![];
|
||||||
|
for s in matches.opt_strs("hostname") {
|
||||||
|
hostnames.push(Host::parse(&s)?);
|
||||||
}
|
}
|
||||||
let hostname = match matches.opt_str("hostname") {
|
|
||||||
Some(s) => Some(Host::parse(&s)?),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let mut addrs = vec![];
|
let mut addrs = vec![];
|
||||||
for i in matches.opt_strs("addr") {
|
for i in matches.opt_strs("addr") {
|
||||||
addrs.push(i.parse()?);
|
addrs.push(i.parse()?);
|
||||||
|
|
@ -148,7 +148,7 @@ fn args() -> Result<Args> {
|
||||||
content_dir: check_path(matches.opt_get_default("content", "content".into())?)?,
|
content_dir: check_path(matches.opt_get_default("content", "content".into())?)?,
|
||||||
cert_file: check_path(matches.opt_get_default("cert", "cert.pem".into())?)?,
|
cert_file: check_path(matches.opt_get_default("cert", "cert.pem".into())?)?,
|
||||||
key_file: check_path(matches.opt_get_default("key", "key.rsa".into())?)?,
|
key_file: check_path(matches.opt_get_default("key", "key.rsa".into())?)?,
|
||||||
hostname,
|
hostnames,
|
||||||
language: matches.opt_str("lang"),
|
language: matches.opt_str("lang"),
|
||||||
silent: matches.opt_present("s"),
|
silent: matches.opt_present("s"),
|
||||||
serve_secret: matches.opt_present("serve-secret"),
|
serve_secret: matches.opt_present("serve-secret"),
|
||||||
|
|
@ -268,14 +268,16 @@ impl RequestHandle {
|
||||||
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
|
|
||||||
if let (Some(Host::Domain(expected)), Some(Host::Domain(host))) =
|
if let Some(host) = url.host() {
|
||||||
(url.host(), &ARGS.hostname)
|
// TODO: to_owned can be removed in next version of url https://github.com/servo/rust-url/pull/651
|
||||||
{
|
if !ARGS.hostnames.is_empty() && !ARGS.hostnames.contains(&host.to_owned()) {
|
||||||
if host != expected {
|
|
||||||
return Err((53, "Proxy request refused"));
|
return Err((53, "Proxy request refused"));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return Err((59, "URL does not contain a host"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(port) = url.port() {
|
if let Some(port) = url.port() {
|
||||||
// Validate that the port in the URL is the same as for the stream this request came in on.
|
// Validate that the port in the URL is the same as for the stream this request came in on.
|
||||||
if port != self.stream.get_ref().0.local_addr().unwrap().port() {
|
if port != self.stream.get_ref().0.local_addr().unwrap().port() {
|
||||||
|
|
@ -288,6 +290,12 @@ impl RequestHandle {
|
||||||
/// Send the client the file located at the requested URL.
|
/// Send the client the file located at the requested URL.
|
||||||
async fn send_response(&mut self, url: Url) -> Result {
|
async fn send_response(&mut self, url: Url) -> Result {
|
||||||
let mut path = std::path::PathBuf::from(&ARGS.content_dir);
|
let mut path = std::path::PathBuf::from(&ARGS.content_dir);
|
||||||
|
|
||||||
|
if ARGS.hostnames.len() > 1 {
|
||||||
|
// basic vhosts, existence of host_str was checked by parse_request already
|
||||||
|
path.push(url.host_str().expect("no hostname"));
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(segments) = url.path_segments() {
|
if let Some(segments) = url.path_segments() {
|
||||||
for segment in segments {
|
for segment in segments {
|
||||||
if !ARGS.serve_secret && segment.starts_with('.') {
|
if !ARGS.serve_secret && segment.starts_with('.') {
|
||||||
|
|
@ -332,7 +340,7 @@ impl RequestHandle {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.send_header(51, "Not found, sorry.").await?;
|
self.send_header(51, "Not found, sorry.").await?;
|
||||||
return Err(e.into())
|
return Err(e.into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue