Skip to main content

tauri_plugin_widgets/
group.rs

1//! App Group / widget group identifier validation.
2//!
3//! Fail-closed at the Tauri command boundary so path joins (macOS Containers,
4//! Linux store files) never see traversal characters.
5
6use crate::error::{Error, Result};
7
8/// Validate a widget `group` string before any store / transport work.
9///
10/// - Non-empty
11/// - Charset: ASCII alphanumeric, `.`, `_`, `-` only
12/// - Rejects `.` / `..` (path-segment traps)
13/// - On Apple targets: must start with `group.` and have a non-trap suffix
14pub fn validate_group(group: &str) -> Result<&str> {
15    if group.is_empty() {
16        return Err(Error::new("group must not be empty"));
17    }
18    if !group
19        .chars()
20        .all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-'))
21    {
22        return Err(Error::new("group contains invalid characters"));
23    }
24    if group == "." || group == ".." {
25        return Err(Error::new("group is not a valid identifier"));
26    }
27    #[cfg(any(target_os = "macos", target_os = "ios"))]
28    {
29        const PREFIX: &str = "group.";
30        if !group.starts_with(PREFIX) {
31            return Err(Error::new(
32                "group must be an App Group id starting with 'group.'",
33            ));
34        }
35        let rest = &group[PREFIX.len()..];
36        if rest.is_empty() || rest == "." || rest == ".." {
37            return Err(Error::new("group App Group suffix is invalid"));
38        }
39    }
40    Ok(group)
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn accepts_reverse_dns() {
49        assert!(validate_group("group.com.example.app").is_ok());
50        #[cfg(not(any(target_os = "macos", target_os = "ios")))]
51        assert!(validate_group("com.example.app").is_ok());
52    }
53
54    #[test]
55    fn rejects_path_chars() {
56        assert!(validate_group("group.com/../../tmp").is_err());
57        assert!(validate_group("group.com\\evil").is_err());
58        assert!(validate_group("").is_err());
59        assert!(validate_group("..").is_err());
60    }
61
62    #[test]
63    #[cfg(any(target_os = "macos", target_os = "ios"))]
64    fn apple_requires_group_prefix() {
65        assert!(validate_group("com.example.app").is_err());
66        assert!(validate_group("group.").is_err());
67        assert!(validate_group("group..").is_err());
68        assert!(validate_group("group...").is_err());
69    }
70}