terminal-mcp 0.1.6

Model Context Protocol (MCP) server for long-lived shell execution.
#![allow(unused)]
use anyhow::{anyhow, Result};
use std::net::IpAddr;
use std::path::Path;
use tree_sitter::Node;
use crate::lazy_regex;

lazy_regex!(pub NAME_CLEAN_REGEX = r"[_.].*$");

/// 从 AST 节点中提取文本
pub fn node_extract_text<'a>(node: &Node, source: &'a [u8]) -> Option<&'a str> {
    node.utf8_text(source).ok()
}


/// 对 Shell/程序路径名称进行归一化处理
pub fn name_normalize(shell: &str) -> Result<String> {
    let name = Path::new(shell)
        .file_name()
        .and_then(|s| s.to_str())
        .ok_or_else(|| anyhow!("invalid shell path: {}", shell))?
        .to_lowercase();

    Ok(NAME_CLEAN_REGEX.replace(&name, "").into_owned())
}

/// 判断名称是否为 Unix Shell
pub fn shell_is_unix(name: &str) -> bool {
    if let Ok(sh) = name_normalize(name) {
        return matches!(sh.as_str(), "bash" | "sh" | "zsh" | "fish" | "ksh");
    }
    false
}

/// 判断名称是否为 Windows Shell
pub fn shell_is_win(name: &str) -> bool {
    if let Ok(sh) = name_normalize(name) {
        return matches!(sh.as_str(), "powershell" | "pwsh" | "cmd");
    }
    false
}

/// 判断名称是否为广义的 Shell / 脚本解释器
pub fn shell_is_valid(name: &str) -> bool {
    if shell_is_unix(name) || shell_is_win(name) {
        return true;
    }
    if let Ok(sh) = name_normalize(name) {
        return matches!(sh.as_str(), "node" | "python");
    }
    false
}

/// 判断名称是否为下载工具
pub fn process_is_downloader(name: &str) -> bool {
    if let Ok(p) = name_normalize(name) {
        return matches!(p.as_str(), "curl" | "wget" | "fetch");
    }
    false
}

pub enum UrlAnalysisResult {
    Https,
    HttpDomain,
    HttpIp,
}

/// 分析 URL 类型
pub fn url_analyze(url: &str) -> Result<UrlAnalysisResult> {
    let url = url::Url::parse(url)?;

    match url.scheme() {
        "https" => Ok(UrlAnalysisResult::Https),
        "http" => match url.host() {
            Some(url::Host::Ipv4(_)) | Some(url::Host::Ipv6(_)) => Ok(UrlAnalysisResult::HttpIp),
            Some(url::Host::Domain(_)) => Ok(UrlAnalysisResult::HttpDomain),
            None => Err(anyhow!("missing host")),
        },
        s => Err(anyhow!("unsupported scheme: {}", s)),
    }
}

/// 归一化路径:去首尾空白/引号、去尾部多余斜杠。
pub fn normalize_target(t: &str) -> String {
    let mut s = t.trim().trim_matches(|c| c == '\'' || c == '"').to_string();
    while s.len() > 1 && s.ends_with('/') {
        s.pop();
    }
    s
}

/// 归一化后路径是否包含任一标记(用于敏感文件 / 持久化路径匹配)。
pub fn path_has_marker(p: &str, markers: &[&str]) -> bool {
    let s = normalize_target(p);
    markers.iter().any(|m| s.contains(m))
}

/// 短选项簇中是否包含指定标志字符(跳过开头的 '-')。
pub fn cluster_has_flag(arg: &str, flag: char) -> bool {
    arg.chars().skip(1).any(|c| c == flag)
}

/// 命令名归一化为"小写 basename",用于精确命令匹配。
/// 与 `name_normalize` 不同:不做版本号剥离,
/// 以保留 `base64`、`hexdump` 这类含数字的命令名。
pub fn command_basename(name: &str) -> String {
    Path::new(name)
        .file_name()
        .and_then(|s| s.to_str())
        .unwrap_or(name)
        .to_lowercase()
}

/// 是否为块设备或危险内核设备路径(/dev/sd*、/dev/nvme*、/dev/mem 等)。
/// Unix 语义,供 bash 规则使用。
pub fn is_block_device(p: &str) -> bool {
    let s = normalize_target(p);
    if !s.starts_with("/dev/") {
        return false;
    }
    let first = s
        .trim_start_matches("/dev/")
        .split('/')
        .next()
        .unwrap_or("");
    if first.is_empty() {
        return false;
    }
    // 无害伪设备:写入这些不会造成磁盘破坏
    if matches!(
        first,
        "null" | "zero" | "random" | "urandom" | "full" | "tty" | "ptmx" | "pts"
    ) {
        return false;
    }
    // 内核内存 / IO 端口
    if matches!(first, "mem" | "kmem" | "port") {
        return true;
    }
    for prefix in [
        "sd", "hd", "vd", "xvd", "nvme", "mmcblk", "loop", "dm-", "md", "sr", "ram", "fd", "ubd",
    ] {
        if let Some(rest) = first.strip_prefix(prefix) {
            let ok = match prefix {
                // sdX / sdX1...:字母后可带分区号
                "sd" | "hd" | "vd" | "xvd" => {
                    let mut chars = rest.chars();
                    matches!(chars.next(), Some(c) if c.is_ascii_lowercase())
                        && chars.all(|c| c.is_ascii_alphanumeric())
                }
                // nvme0n1 / mmcblk0p1:首字符必须为数字
                "nvme" | "mmcblk" => rest.chars().next().is_some_and(|c| c.is_ascii_digit()),
                _ => rest.chars().all(|c| c.is_ascii_digit()),
            };
            if ok {
                return true;
            }
        }
    }
    false
}

/// 是否为 Windows 物理磁盘/卷设备路径(\\.\PhysicalDriveN、\\.\C: 等)。
/// Windows 语义,供 PowerShell 规则使用。
pub fn is_ps_physical_device(p: &str) -> bool {
    let s = p.trim().trim_matches(|c| c == '\'' || c == '"');
    let lower = s.to_lowercase();
    if let Some(rest) = lower.strip_prefix(r"\\.\") {
        return rest.starts_with("physicaldrive") || rest.len() == 2 && rest.ends_with(':');
    }
    false
}