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()
}