rust_docs_mcp/cache/
member_utils.rs

1//! Utilities for member path conversion
2//!
3//! This module provides functions to safely convert member paths containing
4//! slashes to dash-separated formats, preventing path traversal attacks
5//! while maintaining compatibility with workspace member paths.
6
7use anyhow::{Result, bail};
8
9/// Convert a member path with slashes to a safe dash-separated format
10pub fn normalize_member_path(member_path: &str) -> String {
11    member_path.replace('/', "-")
12}
13
14/// Check if a path needs normalization
15pub fn needs_normalization(member_path: &str) -> bool {
16    member_path.contains('/')
17}
18
19/// Validate that a member path doesn't contain dangerous sequences
20///
21/// This function ensures the member path is safe to use in file operations
22/// by rejecting absolute paths, path traversal attempts, and backslashes.
23pub fn validate_member_path(member_path: &str) -> Result<()> {
24    // Reject empty paths
25    if member_path.is_empty() {
26        bail!("Invalid member path: empty path not allowed");
27    }
28
29    // Reject absolute paths
30    if member_path.starts_with('/') || member_path.starts_with('\\') {
31        bail!(
32            "Invalid member path '{}': absolute paths not allowed",
33            member_path
34        );
35    }
36
37    // Check for Windows absolute paths (e.g., C:\)
38    if member_path.len() > 2 && member_path.chars().nth(1) == Some(':') {
39        bail!(
40            "Invalid member path '{}': absolute paths not allowed",
41            member_path
42        );
43    }
44
45    // Reject path traversal
46    if member_path.contains("..") {
47        bail!(
48            "Invalid member path '{}': path traversal not allowed",
49            member_path
50        );
51    }
52
53    // Reject backslashes (Windows paths)
54    if member_path.contains('\\') {
55        bail!(
56            "Invalid member path '{}': backslashes not allowed",
57            member_path
58        );
59    }
60
61    Ok(())
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn test_normalize_member_path() {
70        assert_eq!(normalize_member_path("crates/rmcp"), "crates-rmcp");
71        assert_eq!(
72            normalize_member_path("crates/rmcp/submodule"),
73            "crates-rmcp-submodule"
74        );
75        assert_eq!(normalize_member_path("simple"), "simple");
76        assert_eq!(normalize_member_path("already-dashed"), "already-dashed");
77    }
78
79    #[test]
80    fn test_needs_normalization() {
81        assert!(needs_normalization("crates/rmcp"));
82        assert!(needs_normalization("path/to/member"));
83        assert!(!needs_normalization("simple"));
84        assert!(!needs_normalization("already-dashed"));
85    }
86
87    #[test]
88    fn test_validate_member_path() {
89        // Valid paths
90        assert!(validate_member_path("crates/rmcp").is_ok());
91        assert!(validate_member_path("simple").is_ok());
92        assert!(validate_member_path("path/to/member").is_ok());
93
94        // Invalid paths
95        assert!(validate_member_path("").is_err());
96        assert!(validate_member_path("/absolute/path").is_err());
97        assert!(validate_member_path("\\windows\\path").is_err());
98        assert!(validate_member_path("C:\\Windows").is_err());
99        assert!(validate_member_path("../parent").is_err());
100        assert!(validate_member_path("path/../traversal").is_err());
101        assert!(validate_member_path("path\\with\\backslash").is_err());
102    }
103}