Unverified Commit 1f918772 authored by Sebastian Schüpbach's avatar Sebastian Schüpbach
Browse files

provide better checks for request path

parent 29e56ee9
......@@ -325,7 +325,9 @@ dependencies = [
"env_logger",
"futures",
"hyper",
"lazy_static",
"log",
"regex",
"serde",
"tokio",
"toml",
......
......@@ -15,9 +15,11 @@ license-file = "LICENSE"
[dependencies]
anyhow = "1.0"
env_logger = "0.8"
lazy_static = "1.4"
log = "0.4"
futures = "0.3.9"
hyper = { version = "0.14.2", features = ["full"] }
regex = "1.4"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
toml = "0.5"
......
/*
* Media File Distributor
* Copyright (C) 2021 Memoriav
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::endpoints::Endpoint::*;
use anyhow::{anyhow, Result};
use regex::Regex;
/// Provided endpoints
pub enum Endpoint<'a> {
/// `/collections` endpoint: Provides a list of available collections
Collections,
/// `/media` endpoint: Returns a file if a valid id is provided
Media(&'a str),
/// `/health` endpoint: Always returns ok
Health,
/// `/refresh` endpoint: Refreshes the entire cache
Refresh,
/// `/summary` endpoint: Provides some numbers about a collection or the whole collection tree
Summary(Option<&'a str>),
/// `/thumbnail` endpoint: Returns a file if a valid id is provided
Thumbnail(&'a str),
/// If endpoint is unknown
Unknown(&'a str),
/// If something is wrong with the request
Error(String),
}
impl<'a> Endpoint<'a> {
pub fn new(path: &'a str) -> Endpoint<'a> {
let endpoint = path.split('/').nth(1);
match endpoint {
Some("collections") => Collections,
Some("media") => match Self::extract_id(path) {
Ok(id) => Media(id),
Err(e) => Error(e.to_string()),
},
Some("health") => Health,
Some("refresh") => Refresh,
Some("summary") => match Self::extract_collection(path) {
Ok(collection) => Summary(collection),
Err(e) => Error(e.to_string()),
},
Some("thumbnail") => match Self::extract_id(path) {
Ok(id) => Thumbnail(id),
Err(e) => Error(e.to_string()),
},
Some(p) => Unknown(p),
_ => Unknown(""),
}
}
fn extract_id(path: &str) -> Result<&str> {
lazy_static! {
static ref ID_PATTERN: Regex = Regex::new(r"^[[:alpha:]]{3}-\d{3}-.+").unwrap();
}
if let Some(id) = path.split('/').nth(2) {
if ID_PATTERN.is_match(id) {
Ok(id)
} else {
Err(anyhow!("Record id syntactically invalid"))
}
} else {
Err(anyhow!("No record id indicated"))
}
}
fn extract_collection(path: &str) -> Result<Option<&str>> {
lazy_static! {
static ref COLLECTION_PATTERN: Regex = Regex::new(r"^[[:alpha:]]{3}-\d{3}$").unwrap();
}
if let Some(id) = path.split('/').nth(2) {
if COLLECTION_PATTERN.is_match(id) {
Ok(Some(id))
} else {
Err(anyhow!("Collection id syntactically invalid"))
}
} else {
Ok(None)
}
}
}
......@@ -18,9 +18,13 @@
//! A general comment
mod endpoints;
mod media_folder_utils;
mod service;
#[macro_use]
extern crate lazy_static;
use crate::media_folder_utils::MediaFileCache;
use crate::service::Svc;
use anyhow::{Context, Result};
......
......@@ -142,8 +142,11 @@ impl Collection {
/// Updates the media index for a certain directory
pub fn update_media_index(&mut self, dir: &Path) -> Result<()> {
if !dir.exists() {
debug!("{} does not exist therefore skipping indexing of it", dir.to_str().unwrap_or(""));
return Ok(())
debug!(
"{} does not exist therefore skipping indexing of it",
dir.to_str().unwrap_or("")
);
return Ok(());
}
if dir.ends_with("media") {
self.dissemination_copies =
......
......@@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::endpoints::Endpoint;
use crate::media_folder_utils::MediaFileCache;
use anyhow::{anyhow, Context as AContext, Result};
use hyper::http::header;
......@@ -60,7 +61,10 @@ impl Svc {
fn refresh_partial_cache_if_outdated(&mut self, collection_id: &str) -> Result<()> {
if let Ok(mut x) = self.media_cache.lock() {
if x.collection_is_outdated(collection_id, &self.partial_cache_timeout) {
info!("Partial media cache for {} is outdated! Regenerating", collection_id);
info!(
"Partial media cache for {} is outdated! Regenerating",
collection_id
);
x.refresh_collection(collection_id)
} else {
Ok(())
......@@ -126,46 +130,18 @@ impl Service<Request<Body>> for Svc {
type Response = Response<Body>;
type Error = anyhow::Error;
#[allow(clippy::type_complexity)]
type Future = Pin<Box<dyn Future<Output=Result<Self::Response, Self::Error>> + Send>>;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
let res: Result<Response<Body>> = match req
.uri()
.path()
.split('/')
.skip(1)
.take(1)
.collect::<String>()
.as_str()
{
"health" => {
ok()
}
"media" => {
let path = req
.uri()
.path()
.split('/')
.skip(2)
.take(1)
.collect::<String>();
self.fetch_file(path, "media", true)
}
"thumbnail" => {
let path = req
.uri()
.path()
.split('/')
.skip(2)
.take(1)
.collect::<String>();
self.fetch_file(path, "thumbnail", true)
}
"refresh" => {
let res = match Endpoint::new(req.uri().path()) {
Endpoint::Health => ok(),
Endpoint::Media(id) => self.fetch_file(id.to_owned(), "media", true),
Endpoint::Thumbnail(id) => self.fetch_file(id.to_owned(), "thumbnail", true),
Endpoint::Refresh => {
if let Ok(mut l) = self.media_cache.lock() {
match l.refresh() {
Ok(_) => {
......@@ -182,10 +158,11 @@ impl Service<Request<Body>> for Svc {
internal_server_error()
}
}
"summary" => {
if let Some(collection_id) = req.uri().path().split('/').nth(2) {
Endpoint::Summary(id) => {
if let Some(collection_id) = id {
if let Ok(x) = self.media_cache.lock() {
if let Some(metadata_count) = x.collection_metadata_file_size(collection_id) {
if let Some(metadata_count) = x.collection_metadata_file_size(collection_id)
{
let collection_dissemination_copies_size =
x.collection_dissemination_copies_size(collection_id);
let collection_thumbnails_size =
......@@ -200,18 +177,16 @@ impl Service<Request<Body>> for Svc {
error!("Couldn't lock cache mutex!");
internal_server_error()
}
} else if let Ok(x) = self.media_cache.lock() {
let body = format!("{{\"metadata_files\": {}, \"dissemination_copies\": {}, \"thumbnails\": {}}}",
x.metadata_file_size(), x.dissemination_copies_size(), x.thumbnails_size());
json_response(body)
} else {
if let Ok(x) = self.media_cache.lock() {
let body = format!("{{\"metadata_files\": {}, \"dissemination_copies\": {}, \"thumbnails\": {}}}",
x.metadata_file_size(), x.dissemination_copies_size(), x.thumbnails_size());
json_response(body)
} else {
error!("Couldn't lock cache mutex!");
internal_server_error()
}
error!("Couldn't lock cache mutex!");
internal_server_error()
}
}
"collections" => {
Endpoint::Collections => {
if let Ok(l) = self.media_cache.lock() {
let body = format!("[\"{}\"]", l.get_collections().join("\",\""));
json_response(body)
......@@ -220,10 +195,14 @@ impl Service<Request<Body>> for Svc {
internal_server_error()
}
}
p => {
Endpoint::Unknown(p) => {
info!("No route on /{}", p);
bad_request()
}
Endpoint::Error(err) => {
warn!("Bad request {}", &err);
bad_request()
}
};
Box::pin(async { res })
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment