(root: &Path, path: &str)
| 12 | pub type FileManifest = BTreeMap<String, Option<String>>; |
| 13 | |
| 14 | fn checked_path(root: &Path, path: &str) -> Result<std::path::PathBuf, String> { |
| 15 | if path.is_empty() |
| 16 | || !Path::new(path) |
| 17 | .components() |
| 18 | .all(|c| matches!(c, Component::Normal(_))) |
| 19 | { |
| 20 | return Err(format!("invalid scoped file path: {path:?}")); |
| 21 | } |
| 22 | let root = root.canonicalize().map_err(|e| e.to_string())?; |
| 23 | let full = root.join(path); |
| 24 | let mut parent = full.parent(); |
| 25 | while let Some(p) = parent { |
| 26 | if p.exists() { |
| 27 | if !p |
| 28 | .canonicalize() |
| 29 | .map_err(|e| e.to_string())? |
| 30 | .starts_with(&root) |
| 31 | { |
| 32 | return Err(format!("scoped path escapes repository: {path}")); |
| 33 | } |
| 34 | break; |
| 35 | } |
| 36 | parent = p.parent(); |
| 37 | } |
| 38 | if Path::new(path) |
| 39 | .components() |
| 40 | .any(|c| matches!(c, Component::Normal(n) if n == ".atomic" || n == ".git")) |
| 41 | { |
| 42 | return Err(format!("repository metadata cannot be scoped: {path}")); |
| 43 | } |
| 44 | Ok(full) |
| 45 | } |
| 46 | |
| 47 | /// Fingerprint a file without following its final symlink. `None` means deleted. |
| 48 | /// Reject directories and paths outside the working tree. |
no test coverage detected