1
use crate::{ Error, Result };
2
use regex::Regex;
3
use std::ffi::{ OsString, OsStr };
4
use std::path::{ PathBuf, Path };
5

            
6
use super::Fetcher;
7

            
8
lazy_static::lazy_static! {
9
    static ref URI_REGEX: Regex = Regex::new(r"^[a-z]+(\+[a-z]+)?://").unwrap();
10
}
11

            
12
pub struct Builder<'a> {
13
    env:	&'a crate::Environment,
14
}
15

            
16
11
fn normalize_path(p: &std::path::Path) -> Result<std::path::PathBuf>
17
11
{
18
11
    let mut res = std::path::PathBuf::new();
19

            
20
39
    for c in p.components() {
21
	use std::path::Component as C;
22

            
23
39
	match c {
24
8
	    C::RootDir		=> {},
25
1
	    C::CurDir		=> {},
26
29
	    C::Normal(p)	=> res.push(p),
27
	    // TODO: audit log this error
28
1
	    _			=> return Err(Error::InvalidPathName),
29
	}
30
    }
31

            
32
10
    assert!(!res.is_absolute());
33

            
34
10
    Ok(res)
35
11
}
36

            
37
6
#[derive(PartialEq, Debug)]
38
enum LookupResult {
39
    Path(PathBuf),
40
    Uri(http::uri::Uri),
41
}
42

            
43
//#[instrument(level = "trace", skip_all, ret)]
44
6
fn lookup_path<A, B, C>(root: A, p: B, fallback: Option<C>) -> Result<LookupResult>
45
6
where
46
6
    A: AsRef<Path>,
47
6
    B: AsRef<Path>,
48
6
    C: AsRef<OsStr>,
49
6
{
50
6
    use std::os::unix::ffi::OsStrExt;
51
6

            
52
6
    let mut uri: Option<OsString> = None;
53
6
    let mut dir = root.as_ref().to_path_buf();
54
6
    let path_norm = normalize_path(p.as_ref())?;
55
6
    let mut is_dangling = false;
56

            
57
15
    for c in path_norm.components() {
58
13
	uri = match uri {
59
2
	    Some(mut u)		=> {
60
2
		let tmp = u.as_bytes();
61
2

            
62
2
		#[allow(clippy::len_zero)]
63
2
		if tmp.len() > 0 && tmp[tmp.len() - 1] != b'/' {
64
2
		    u.push(OsStr::from_bytes(b"/"));
65
2
		}
66

            
67
2
		u.push(c);
68
2
		Some(u)
69
	    },
70

            
71
	    None if is_dangling	=> {
72
		dir = dir.join(c);
73
		None
74
	    },
75

            
76
	    None		=> {
77
13
		let p = dir.join(c);
78
13
		let meta = p.symlink_metadata();
79
13

            
80
13
		is_dangling = meta.is_err();
81

            
82
13
		let uri_raw = if is_dangling || !meta.unwrap().is_symlink() {
83
8
		    None
84
		} else {
85
5
		    let data = std::fs::read_link(&p)?.into_os_string();
86
5
		    let data_str = data.to_str();
87

            
88
5
		    match data_str {
89
5
			Some(d) if URI_REGEX.is_match(d)	=> Some(data),
90
2
			_					=> None,
91
		    }
92
		};
93

            
94
13
		if uri_raw.is_none() {
95
10
		    dir = p;
96
10
		}
97

            
98
13
		uri_raw
99
	    }
100
	};
101
    }
102

            
103
    #[allow(clippy::unnecessary_unwrap)]
104
6
    if uri.is_none() && fallback.is_some() && !dir.exists() {
105
	let fallback = fallback.unwrap();
106
	let mut tmp: OsString = fallback.as_ref().to_os_string();
107

            
108
	tmp.push(path_norm.as_os_str());
109

            
110
	match fallback.as_ref().to_str() {
111
	    Some(d) if URI_REGEX.is_match(d)	=> uri = Some(tmp),
112
	    _					=> dir = tmp.into(),
113
	}
114
6
    }
115

            
116
6
    match uri.map(|u| u.to_str().map(|u| u.parse::<http::Uri>())) {
117
3
	None			=> Ok(LookupResult::Path(dir)),
118
	Some(None)		=> Err(Error::StringConversion),
119
	Some(Some(Err(_)))	=> Err(Error::UriParse),
120
3
	Some(Some(Ok(u)))	=> Ok(LookupResult::Uri(u)),
121
    }
122
6
}
123

            
124
impl <'a> Builder<'a> {
125
    pub fn new(env: &'a crate::Environment) -> Self {
126
	Self {
127
	    env:	env,
128
	}
129
    }
130

            
131
    #[instrument(level = "trace", skip(self), ret)]
132
    pub fn instanciate(&'a self, p: &std::path::Path) -> Result<super::Fetcher> {
133
	match lookup_path(&self.env.dir, p, self.env.fallback_uri.as_ref())? {
134
	    LookupResult::Path(p)	=> Ok(Fetcher::new_file(&p)),
135
	    _ => unreachable!(),
136
	}
137
    }
138
}
139

            
140
#[cfg(test)]
141
mod test {
142
    use super::*;
143

            
144
1
    #[test]
145
1
    fn test_normalize() {
146
1
	use std::path::Path;
147
1

            
148
1
	assert_eq!(normalize_path(Path::new("/a/b/c")).unwrap(),
149
1
		   Path::new("a/b/c"));
150

            
151
1
	assert_eq!(normalize_path(Path::new("////a/b/c")).unwrap(),
152
1
		   Path::new("a/b/c"));
153

            
154
1
	assert_eq!(normalize_path(Path::new("a/b///c")).unwrap(),
155
1
		   Path::new("a/b/c"));
156

            
157
1
	assert_eq!(normalize_path(Path::new("./a/b/.//c")).unwrap(),
158
1
		   Path::new("a/b/c"));
159

            
160
1
	assert!(normalize_path(Path::new("a/b/../c")).is_err());
161
1
    }
162

            
163
1
    #[test]
164

            
165
1
    fn test_lookup() {
166
1
	use tempdir::TempDir;
167
1
	use std::os::unix::fs::symlink;
168
1

            
169
1
	let tmp_dir = TempDir::new("test_lookup").unwrap();
170
1
	let tmp_path = tmp_dir.path();
171
1

            
172
1
	std::fs::create_dir(tmp_path.join("a")).unwrap();
173
1
	std::fs::create_dir(tmp_path.join("b")).unwrap();
174
1

            
175
1
	std::fs::File::create(tmp_path.join("b/foo")).unwrap();
176
1

            
177
1
	symlink("http://test.example.com/foo",          tmp_path.join("a/link-0")).unwrap();
178
1
	symlink("http://test.example.com/bar/",         tmp_path.join("a/link-1")).unwrap();
179
1
	symlink("https://test.example.com/foo",         tmp_path.join("a/link-2")).unwrap();
180
1
	symlink("https+nocache://test.example.com/foo", tmp_path.join("a/link-3")).unwrap();
181
1
	symlink("./http://test.example.com/foo",        tmp_path.join("a/nolink-0")).unwrap();
182
1

            
183
1
	let fb_none = None::<OsString>;
184
1
	let _fb_some = Some::<OsString>("http://fb.example.com/redir/".into());
185
1

            
186
1

            
187
1
	assert_eq!(lookup_path(tmp_path, "/b/foo", fb_none.clone()).unwrap(),
188
1
		   LookupResult::Path(tmp_path.join("b/foo")));
189
1
	assert_eq!(lookup_path(tmp_path, "/a/link-0", fb_none.clone()).unwrap(),
190
1
		   LookupResult::Uri("http://test.example.com/foo".parse().unwrap()));
191
1
	assert_eq!(lookup_path(tmp_path, "/a/link-0/test", fb_none.clone()).unwrap(),
192
1
		   LookupResult::Uri("http://test.example.com/foo/test".parse().unwrap()));
193
1
	assert_eq!(lookup_path(tmp_path, "/a/link-3/test", fb_none.clone()).unwrap(),
194
1
		   LookupResult::Uri("https+nocache://test.example.com/foo/test".parse().unwrap()));
195
1
	assert_eq!(lookup_path(tmp_path, "/a/nolink-0", fb_none.clone()).unwrap(),
196
1
		   LookupResult::Path(tmp_path.join("a/nolink-0")));
197
1
	assert_eq!(lookup_path(tmp_path, "/a/nolink-0/file", fb_none.clone()).unwrap(),
198
1
		   LookupResult::Path(tmp_path.join("a/nolink-0/file")));
199
1
    }
200
}