refactor(core): simplify and document push_pattern (#11065)

This commit is contained in:
Amr Bashir 2024-09-20 14:21:30 +03:00 committed by GitHub
parent 9014a3f176
commit 8ab2e3cc3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -77,74 +77,85 @@ fn push_pattern<P: AsRef<Path>, F: Fn(&str) -> Result<Pattern, glob::PatternErro
pattern: P,
f: F,
) -> crate::Result<()> {
let mut path: PathBuf = dunce::simplified(pattern.as_ref()).components().collect();
// Reconstruct pattern path components with appropraite separator
// so `some\path/to/dir/**\*` would be `some/path/to/dir/**/*` on Unix
// and `some\path\to\dir\**\*` on Windows.
let path: PathBuf = pattern.as_ref().components().collect();
if cfg!(windows) {
// Canonicalize disk-relative paths before inserting into the list
use std::path::{Component, Prefix};
let mut components = path.components();
if let Some(Component::Prefix(prefix)) = components.next() {
if matches!(prefix.kind(), Prefix::Disk(_) | Prefix::VerbatimDisk(_))
&& !matches!(components.next(), Some(Component::RootDir))
// Add pattern as is to be matched with paths as is
let path_str = path.to_string_lossy();
list.insert(f(&path_str)?);
// On Windows, if path starts with a Prefix, try to strip it if possible
// so `\\?\C:\\SomeDir` would result in a scope of:
// - `\\?\C:\\SomeDir`
// - `C:\\SomeDir`
#[cfg(windows)]
{
path = dunce::simplified(&path.canonicalize()?).to_path_buf();
use std::path::Component;
if matches!(path.components().next(), Some(Component::Prefix(_))) {
let simplified = dunce::simplified(&path);
let simplified_str = simplified.to_string_lossy();
if simplified_str != path_str {
list.insert(f(&simplified_str)?);
}
}
}
list.insert(f(&path.to_string_lossy())?);
// Add canonicalized version of the pattern or canonicalized version of its parents
// so `/data/user/0/appid/assets/*` would be canonicalized to `/data/data/appid/assets/*`
// and can then be matched against any of them.
if let Some(p) = canonicalize_parent(path) {
list.insert(f(&p.to_string_lossy())?);
}
let mut checked_path = None;
Ok(())
}
// attempt to canonicalize parents in case we have a path like `/data/user/0/appid/**`
// where `**` obviously does not exist but we need to canonicalize the parent
//
// example: given the `/data/user/0/appid/assets/*` path,
// it's a glob pattern so it won't exist (canonicalize() fails);
//
// the second iteration needs to check `/data/user/0/appid/assets` and save the `*` component to append later.
//
// if it also does not exist, a third iteration is required to check `/data/user/0/appid`
// with `assets/*` as the cached value (`checked_path` variable)
// on Android that gets canonicalized to `/data/data/appid` so the final value will be `/data/data/appid/assets/*`
// which is the value we want to check when we execute the `is_allowed` function
let canonicalized = loop {
/// Attempt to canonicalize path or its parents in case we have a path like `/data/user/0/appid/**`
/// where `**` obviously does not exist but we need to canonicalize the parent.
///
/// example: given the `/data/user/0/appid/assets/*` path,
/// it's a glob pattern so it won't exist (std::fs::canonicalize() fails);
///
/// the second iteration needs to check `/data/user/0/appid/assets` and save the `*` component to append later.
///
/// if it also does not exist, a third iteration is required to check `/data/user/0/appid`
/// with `assets/*` as the cached value (`checked_path` variable)
/// on Android that gets canonicalized to `/data/data/appid` so the final value will be `/data/data/appid/assets/*`
/// which is the value we want to check when we execute the `Scope::is_allowed` function
fn canonicalize_parent(mut path: PathBuf) -> Option<PathBuf> {
let mut failed_components = None;
loop {
if let Ok(path) = path.canonicalize() {
break Some(if let Some(p) = checked_path {
break Some(if let Some(p) = failed_components {
path.join(p)
} else {
path
});
}
// get the last component of the path as an OsStr
let last = path.iter().next_back().map(PathBuf::from);
if let Some(mut p) = last {
// remove the last component of the path
// so the next iteration checks its parent
// grap the last component of the path
if let Some(mut last) = path.iter().next_back().map(PathBuf::from) {
// remove the last component of the path so the next iteration checks its parent
// if there is no more parent components, we failed to canonicalize
if !path.pop() {
break None;
}
// append the already checked path to the last component
if let Some(checked_path) = &checked_path {
p.push(checked_path);
// to construct `<last>/<checked_path>` and saved it for next iteration
if let Some(failed_components) = &failed_components {
last.push(failed_components);
}
// replace the checked path with the current value
checked_path.replace(p);
failed_components.replace(last);
} else {
break None;
}
};
if let Some(p) = canonicalized {
list.insert(f(&p.to_string_lossy())?);
} else if cfg!(windows) && !path.to_string_lossy().starts_with("\\\\") {
list.insert(f(&format!("\\\\?\\{}", path.display()))?);
}
Ok(())
}
impl Scope {
/// Creates a new scope from a [`FsScope`] configuration.
pub fn new<R: crate::Runtime, M: crate::Manager<R>>(