agate

Simple gemini server for static files
git clone https://github.com/mbrubeck/agate.git
Log | Files | Refs | README

commit 49813d0c68137b24b70380068cc826742ef3b920
parent bd9ed3255a8c43d51a6d71edc912d17319f43443
Author: Johann150 <johann.galle@protonmail.com>
Date:   Thu, 11 Feb 2021 18:46:20 +0100

serve hidden files if there is a .meta config for them

Diffstat:
MCHANGELOG.md | 1+
MREADME.md | 2+-
Msrc/main.rs | 27++++++++++++++++++---------
Msrc/metadata.rs | 67+++++++++++++++++++++++++++++++++++--------------------------------
4 files changed, 55 insertions(+), 42 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -19,6 +19,7 @@ Thank you to @gegeweb for contributing to this release. * The configuration files are now parsed as YAML. The syntax only changes in that a space is now required behind the colon. * The changelog is now also kept in this file in addition to the GitHub releases. * Certificate chain and key file are now only loaded once at startup, certificate changes need a restart to take effect. +* Hidden files are now served if there is an explicit setting in a `.meta` file for them, regardless of the `--serve-secret` flag. ### Fixed * The Syntax for the IPv6 address in the README has been corrected. diff --git a/README.md b/README.md @@ -48,7 +48,7 @@ agate --content path/to/content/ \ All of the command-line arguments are optional. Run `agate --help` to see the default values used when arguments are omitted. -When a client requests the URL `gemini://example.com/foo/bar`, Agate will respond with the file at `path/to/content/foo/bar`. If any segment of the requested path starts with a dot, agate will respond with a status code 52, whether the file exists or not (this behaviour can be disabled with `--serve-secret`). If there is a directory at that path, Agate will look for a file named `index.gmi` inside that directory. +When a client requests the URL `gemini://example.com/foo/bar`, Agate will respond with the file at `path/to/content/foo/bar`. If any segment of the requested path starts with a dot, agate will respond with a status code 52, whether the file exists or not. This behaviour can be disabled with `--serve-secret` or by an entry for the specific file in the `.meta` configuration file (see Meta-Presets). If there is a directory at that path, Agate will look for a file named `index.gmi` inside that directory. ## Configuration diff --git a/src/main.rs b/src/main.rs @@ -331,15 +331,24 @@ impl RequestHandle { path.push(url.host_str().expect("no hostname")); } - if let Some(segments) = url.path_segments() { - for segment in segments { - if !ARGS.serve_secret && segment.starts_with('.') { - // Do not serve anything that looks like a hidden file. - return self - .send_header(52, "If I told you, it would not be a secret.") - .await; - } - path.push(&*percent_decode_str(segment).decode_utf8()?); + if let Some(mut segments) = url.path_segments() { + // append percent-decoded path segments + path.extend( + segments + .clone() + .map(|segment| Ok(percent_decode_str(segment).decode_utf8()?.into_owned())) + .collect::<Result<Vec<_>>>()?, + ); + // check if hiding files is disabled + if !ARGS.serve_secret + // there is a configuration for this file, assume it should be served + && !self.metadata.lock().await.exists(&path) + // check if file or directory is hidden + && segments.any(|segment| segment.starts_with('.')) + { + return self + .send_header(52, "If I told you, it would not be a secret.") + .await; } } diff --git a/src/metadata.rs b/src/metadata.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::time::SystemTime; use yaml_rust::YamlLoader; @@ -67,21 +67,22 @@ impl FileOptions { } } - /// Checks wether the database for the respective directory is still - /// up to date. - /// Will only return true if the database should be (re)read, i.e. it will - /// return false if there is no database file in the specified directory. - fn check_outdated(&self, db_dir: &PathBuf) -> bool { - let mut db = db_dir.clone(); - db.push(SIDECAR_FILENAME); - let db = db.as_path(); + /// Checks wether the database for the directory of the specified file is + /// still up to date and re-reads it if outdated or not yet read. + fn update(&mut self, dir: &Path) { + let mut dir = if super::ARGS.central_config { + super::ARGS.content_dir.clone() + } else { + dir.parent().expect("no parent directory").to_path_buf() + }; + dir.push(SIDECAR_FILENAME); - if let Ok(metadata) = db.metadata() { + let should_read = if let Ok(metadata) = dir.as_path().metadata() { if !metadata.is_file() { // it exists, but it is a directory false } else if let (Ok(modified), Some(last_read)) = - (metadata.modified(), self.databases_read.get(db_dir)) + (metadata.modified(), self.databases_read.get(&dir)) { // check that it was last modified before the read // if the times are the same, we might have read the old file @@ -95,18 +96,17 @@ impl FileOptions { } else { // the file probably does not exist false + }; + + if should_read { + self.read_database(&dir); } } - /// (Re)reads a specific sidecar file that resides in the specified - /// directory. The function takes a directory to minimize path - /// alterations "on the fly". + /// (Re)reads a specified sidecar file. /// This function will allways try to read the file, even if it is current. - fn read_database(&mut self, db_dir: &PathBuf) { - log::trace!("reading database for {:?}", db_dir); - let mut db = db_dir.clone(); - db.push(SIDECAR_FILENAME); - let db = db.as_path(); + fn read_database(&mut self, db: &PathBuf) { + log::trace!("reading database {:?}", db); if let Ok(contents) = std::fs::read_to_string(db) { let docs = match YamlLoader::load_from_str(&contents) { @@ -137,7 +137,8 @@ impl FileOptions { }; // generate workspace-unique path - let mut path = db_dir.clone(); + let mut path = db.clone(); + path.pop(); path.push(rel_path); // parse the preset @@ -180,8 +181,10 @@ impl FileOptions { log::error!("no YAML document {:?}", db); return; }; - self.databases_read - .insert(db_dir.clone(), SystemTime::now()); + self.databases_read.insert( + db.as_path().parent().unwrap().to_path_buf(), + SystemTime::now(), + ); } else { log::error!("could not read configuration file {:?}", db); } @@ -192,17 +195,17 @@ impl FileOptions { /// The file path should consistenly be either absolute or relative to the /// working/content directory. If inconsisten file paths are used, this can /// lead to loading and storing sidecar files multiple times. - pub fn get(&mut self, file: &PathBuf) -> PresetMeta { - let dir = if super::ARGS.central_config { - super::ARGS.content_dir.clone() - } else { - file.parent().expect("no parent directory").to_path_buf() - }; - - if self.check_outdated(&dir) { - self.read_database(&dir); - } + pub fn get(&mut self, file: &Path) -> PresetMeta { + self.update(file); self.file_meta.get(file).unwrap_or(&self.default).clone() } + + /// Returns true if a configuration exists in a configuration file. + /// Returns false if no or only the default value exists. + pub fn exists(&mut self, file: &Path) -> bool { + self.update(file); + + self.file_meta.contains_key(file) + } }