rama_utils/include_dir/
mod.rs1mod dir;
39mod dir_entry;
40mod file;
41mod metadata;
42
43pub use self::{dir::Dir, dir_entry::DirEntry, file::File, metadata::Metadata};
44
45#[doc(inline)]
46pub use ::rama_macros::include_dir;
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn test_relative_dir() {
54 static ASSETS: Dir = include_dir!("../../../test-files");
55
56 let entry = ASSETS.get_entry("index.html").unwrap();
57 let file = entry.as_file().unwrap();
58
59 assert!(file.contents_utf8().unwrap().contains("<b>HTML!</b>"));
60
61 _ = file.metadata().unwrap();
62 }
63
64 #[test]
65 fn test_absolute_dir() {
66 static ASSETS: Dir = include_dir!("$CARGO_MANIFEST_DIR/src");
67
68 let entry = ASSETS.get_entry("include_dir/dir.rs").unwrap();
69 let file = entry.as_file().unwrap();
70 assert!(file.contents_utf8().unwrap().contains("fn get_entry"));
71
72 _ = file.metadata().unwrap();
73
74 let entry = ASSETS.get_entry("macros").unwrap();
75 _ = entry.as_dir().unwrap();
76 }
77
78 #[test]
79 fn test_absolute_with_relative_dir() {
80 static ASSETS: Dir = include_dir!("$CARGO_MANIFEST_DIR/../test-files");
81
82 let entry = ASSETS.get_entry("index.html").unwrap();
83 let file = entry.as_file().unwrap();
84
85 assert!(file.contents_utf8().unwrap().contains("<b>HTML!</b>"));
86
87 _ = file.metadata().unwrap();
88 }
89
90 #[test]
91 fn test_extract_rejects_absolute_paths() {
92 let malicious_file = File::new("/etc/passwd", b"malicious content");
93 let malicious_entry = DirEntry::File(malicious_file);
94 let entries = [malicious_entry];
95 let malicious_dir = Dir::new("test", &entries);
96
97 let temp_dir = std::env::temp_dir().join("test_extract_absolute");
98 let result = malicious_dir.extract(&temp_dir);
99
100 assert!(result.is_err());
101 let err = result.unwrap_err();
102 assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
103 }
104
105 #[test]
106 fn test_extract_rejects_parent_traversal() {
107 let malicious_file = File::new("../../../etc/passwd", b"malicious content");
108 let malicious_entry = DirEntry::File(malicious_file);
109 let entries = [malicious_entry];
110 let malicious_dir = Dir::new("test", &entries);
111
112 let temp_dir = std::env::temp_dir().join("test_extract_traversal");
113 let result = malicious_dir.extract(&temp_dir);
114
115 assert!(result.is_err());
116 let err = result.unwrap_err();
117 assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
118 }
119
120 #[test]
121 fn test_extract_allows_safe_paths() {
122 let safe_file = File::new("subdir/safe.txt", b"safe content");
123 let safe_entry = DirEntry::File(safe_file);
124 let entries = [safe_entry];
125 let safe_dir = Dir::new("test", &entries);
126
127 let temp_dir = tempfile::tempdir().unwrap();
128 safe_dir.extract(temp_dir.path()).unwrap();
129 }
130
131 #[test]
132 fn test_extract_rejects_mixed_traversal() {
133 let malicious_file = File::new("subdir/../../etc/passwd", b"malicious content");
134 let malicious_entry = DirEntry::File(malicious_file);
135 let entries = [malicious_entry];
136 let malicious_dir = Dir::new("test", &entries);
137
138 let temp_dir = std::env::temp_dir().join("test_extract_mixed");
139 let result = malicious_dir.extract(&temp_dir);
140
141 assert!(result.is_err());
142 let err = result.unwrap_err();
143 assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
144 }
145
146 #[cfg(unix)]
147 #[test]
148 fn test_extract_rejects_symlink_escape() {
149 let temp_dir = tempfile::tempdir().unwrap();
150 let root = temp_dir.path().join("root");
151 let outside = temp_dir.path().join("outside");
152 std::fs::create_dir(&root).unwrap();
153 std::fs::create_dir(&outside).unwrap();
154 std::os::unix::fs::symlink(outside.join("escaped.txt"), root.join("link.txt")).unwrap();
155
156 let malicious_file = File::new("link.txt", b"malicious content");
157 let malicious_entry = DirEntry::File(malicious_file);
158 let entries = [malicious_entry];
159 let malicious_dir = Dir::new("test", &entries);
160
161 let result = malicious_dir.extract(&root);
162
163 assert!(result.is_err());
164 let err = result.unwrap_err();
165 assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
166 assert!(!outside.join("escaped.txt").exists());
167 }
168}