1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
use hyper::{Body, Request}; use std::path::{Component, Path, PathBuf}; use walkdir::WalkDir; use crate::Error; use crate::{config::CounterfeitConfig, Dispatcher}; pub struct DefaultDirDispatcher { config: CounterfeitConfig, } impl DefaultDirDispatcher { pub fn new(config: CounterfeitConfig) -> Self { Self { config } } } impl Dispatcher for DefaultDirDispatcher { fn dispatch( &self, _base_directory: impl AsRef<Path>, request: &Request<Body>, ) -> Result<PathBuf, Error> { let path = PathBuf::from(format!( "{}{}", &self.config.base_path, request.uri().path() )); if path.exists() { return Ok(path); } let all_paths = list_dirs_recursive(&self.config.base_path); let matching_path = all_paths .iter() .filter_map(|potential_path| PathMatch::get_match(&path, potential_path, &self.config)) .max_by(|p1, p2| p1.num_exact_matches.cmp(&p2.num_exact_matches)); match matching_path { Some(p) => Ok(PathBuf::from(p)), None => Ok(path), } } } struct PathMatch { path: PathBuf, num_exact_matches: usize, } impl From<PathMatch> for PathBuf { fn from(path_match: PathMatch) -> PathBuf { path_match.path } } impl PathMatch { pub fn get_match<T: AsRef<Path>, P: AsRef<Path>>( target: &T, potential: &P, config: &CounterfeitConfig, ) -> Option<Self> { let target = target.as_ref(); let potential = potential.as_ref(); if target.components().count() != potential.components().count() { return None; } let (exact_count, param_count) = target.components().zip(potential.components()).fold( (0, 0), |(exact_acc, param_acc), (tc, pc)| { if tc == pc { (exact_acc + 1, param_acc) } else if is_param(&pc, config) { (exact_acc, param_acc + 1) } else { (exact_acc, param_acc) } }, ); if exact_count + param_count == target.components().count() { let result = PathMatch { path: PathBuf::from(potential), num_exact_matches: exact_count, }; Some(result) } else { None } } } fn is_param(component: &Component, config: &CounterfeitConfig) -> bool { match component.as_os_str().to_str() { Some(s) => s.starts_with(&config.prefix) && s.ends_with(&config.postfix), None => false, } } fn list_dirs_recursive<P: AsRef<Path>>(path: P) -> Vec<PathBuf> { WalkDir::new(path) .into_iter() .filter_map(Result::ok) .map(|entry| PathBuf::from(entry.path())) .filter(|path| path.is_dir()) .collect() }