Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(core): simplify and document push_pattern #11065

Merged
merged 1 commit into from
Sep 20, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 58 additions & 47 deletions crates/tauri/src/scope/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

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))
{
path = dunce::simplified(&path.canonicalize()?).to_path_buf();
// 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();

// 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)]
{
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())?);

let mut checked_path = None;

// 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 {
// 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())?);
}

Ok(())
}

/// 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>>(
Expand Down