rebecca-core 0.3.0

Core planning, safety, scanning, and history models for Rebecca.
Documentation
pub(crate) fn default_home_suffix(key: &str) -> Option<&'static [&'static str]> {
    match key {
        "MACOS_CACHE_HOME" => Some(&["Library", "Caches"]),
        "MACOS_APPLICATION_SUPPORT_HOME" => Some(&["Library", "Application Support"]),
        "MACOS_LOG_HOME" => Some(&["Library", "Logs"]),
        "MACOS_CONTAINER_HOME" => Some(&["Library", "Containers"]),
        "MACOS_GROUP_CONTAINER_HOME" => Some(&["Library", "Group Containers"]),
        _ => None,
    }
}

pub(crate) fn cache_home_tail_start(segments: &[&str]) -> Option<usize> {
    find_segment(segments, "%macos_cache_home%")
        .map(|index| index + 1)
        .or_else(|| user_library_tail_start(segments, &["caches"]))
}

pub(crate) fn application_support_home_tail_start(segments: &[&str]) -> Option<usize> {
    find_segment(segments, "%macos_application_support_home%")
        .map(|index| index + 1)
        .or_else(|| user_library_tail_start(segments, &["application support"]))
}

pub(crate) fn log_home_tail_start(segments: &[&str]) -> Option<usize> {
    find_segment(segments, "%macos_log_home%")
        .map(|index| index + 1)
        .or_else(|| user_library_tail_start(segments, &["logs"]))
}

pub(crate) fn chromium_profile_tail_start(segments: &[&str]) -> Option<usize> {
    let start = application_support_home_tail_start(segments)?;
    let tail = segments.get(start..)?;

    if tail.starts_with(&["google", "chrome"]) {
        return Some(start + 2);
    }
    if tail.starts_with(&["bravesoftware", "brave-browser"]) {
        return Some(start + 2);
    }
    if tail
        .first()
        .is_some_and(|app| matches!(*app, "chromium" | "microsoft edge"))
    {
        return Some(start + 1);
    }

    None
}

pub(crate) fn is_durable_data_path(segments: &[&str]) -> bool {
    has_sequence(segments, &["library", "application support"])
        || has_sequence(segments, &["library", "caches"])
        || has_sequence(segments, &["library", "containers"])
        || has_sequence(segments, &["library", "group containers"])
        || has_sequence(segments, &["library", "keychains"])
        || has_sequence(segments, &["library", "mail"])
        || has_sequence(segments, &["library", "messages"])
        || has_sequence(segments, &["library", "photos"])
        || has_sequence(segments, &["library", "preferences"])
        || has_sequence(segments, &["library", "safari"])
        || find_segment(segments, "%macos_cache_home%").is_some()
        || find_segment(segments, "%macos_application_support_home%").is_some()
        || find_segment(segments, "%macos_container_home%").is_some()
        || find_segment(segments, "%macos_group_container_home%").is_some()
}

fn user_library_tail_start(segments: &[&str], library_child: &[&str]) -> Option<usize> {
    if library_child.is_empty() {
        return None;
    }

    let root_required_len = 3 + library_child.len();
    if segments.len() < root_required_len {
        return None;
    }

    if let Some(index) = segments.windows(root_required_len).position(|window| {
        window[0] == "users"
            && !window[1].is_empty()
            && window[1] != "shared"
            && window[2] == "library"
            && &window[3..] == library_child
    }) {
        return Some(index + root_required_len);
    }

    let library_required_len = 1 + library_child.len();
    segments
        .windows(library_required_len)
        .enumerate()
        .find(|(index, window)| {
            *index > 0
                && segments.get(index - 1) != Some(&"system")
                && window[0] == "library"
                && &window[1..] == library_child
        })
        .map(|(index, _)| index + library_required_len)
}

fn has_sequence(segments: &[&str], sequence: &[&str]) -> bool {
    if sequence.is_empty() || segments.len() < sequence.len() {
        return false;
    }

    segments
        .windows(sequence.len())
        .any(|window| window == sequence)
}

fn find_segment(segments: &[&str], needle: &str) -> Option<usize> {
    segments.iter().position(|segment| *segment == needle)
}