libmagic_rs/parser/
format.rs1use crate::error::ParseError;
10use std::io::Read;
11use std::path::Path;
12
13const MGC_MAGIC: u32 = 0xF11E_041C;
14
15pub(super) fn has_binary_magic_header(bytes: &[u8]) -> bool {
16 bytes.starts_with(&MGC_MAGIC.to_le_bytes()) || bytes.starts_with(&MGC_MAGIC.to_be_bytes())
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum MagicFileFormat {
22 Text,
24 Directory,
26 Binary,
28}
29
30pub fn detect_format(path: &Path) -> Result<MagicFileFormat, ParseError> {
66 let metadata = std::fs::metadata(path)?;
68
69 if metadata.is_dir() {
71 return Ok(MagicFileFormat::Directory);
72 }
73
74 let mut file = std::fs::File::open(path)?;
76
77 let mut magic_bytes = [0u8; 4];
78
79 match file.read_exact(&mut magic_bytes) {
80 Ok(()) => {
81 if has_binary_magic_header(&magic_bytes) {
82 return Ok(MagicFileFormat::Binary);
83 }
84 Ok(MagicFileFormat::Text)
86 }
87 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
88 Ok(MagicFileFormat::Text)
90 }
91 Err(e) => Err(ParseError::IoError(e)),
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use std::fs;
99 use std::io::Write;
100
101 #[test]
102 fn test_detect_format_text_file() {
103 let temp_dir = std::env::temp_dir();
104 let text_file = temp_dir.join("test_text_magic.txt");
105 fs::write(&text_file, "# Magic file\n0 string test Test").unwrap();
106
107 let format = detect_format(&text_file).unwrap();
108 assert_eq!(format, MagicFileFormat::Text);
109
110 fs::remove_file(&text_file).unwrap();
111 }
112
113 #[test]
114 fn test_detect_format_directory() {
115 let temp_dir = std::env::temp_dir().join("test_magic_dir");
116 fs::create_dir_all(&temp_dir).unwrap();
117
118 let format = detect_format(&temp_dir).unwrap();
119 assert_eq!(format, MagicFileFormat::Directory);
120
121 fs::remove_dir_all(&temp_dir).unwrap();
122 }
123
124 #[test]
125 fn test_detect_format_binary_mgc() {
126 let temp_dir = std::env::temp_dir();
127 let headers = [
128 ("little-endian", MGC_MAGIC.to_le_bytes()),
129 ("big-endian", MGC_MAGIC.to_be_bytes()),
130 ];
131
132 for (byte_order, header) in headers {
133 let binary_file = temp_dir.join(format!("test_binary_{byte_order}.mgc"));
134 let mut file = fs::File::create(&binary_file).unwrap();
135 file.write_all(&header).unwrap();
136 file.write_all(b"additional binary data").unwrap();
137
138 let result = detect_format(&binary_file);
139 assert!(result.is_ok(), "failed to detect {byte_order} header");
140
141 match result.unwrap() {
142 MagicFileFormat::Binary => {}
143 other => panic!("Expected Binary format for {byte_order}, got {other:?}"),
144 }
145
146 fs::remove_file(&binary_file).unwrap();
147 }
148 }
149
150 #[test]
151 fn test_detect_format_nonexistent_path() {
152 let nonexistent = std::env::temp_dir().join("nonexistent_magic_file.txt");
153
154 let result = detect_format(&nonexistent);
155 assert!(result.is_err());
156
157 match result.unwrap_err() {
158 ParseError::IoError(e) => {
159 assert_eq!(e.kind(), std::io::ErrorKind::NotFound);
160 }
161 other => panic!("Expected IoError, got: {other:?}"),
162 }
163 }
164
165 #[test]
166 fn test_detect_format_empty_file() {
167 let temp_dir = std::env::temp_dir();
168 let empty_file = temp_dir.join("test_empty_magic.txt");
169 fs::write(&empty_file, "").unwrap();
170
171 let format = detect_format(&empty_file).unwrap();
173 assert_eq!(format, MagicFileFormat::Text);
174
175 fs::remove_file(&empty_file).unwrap();
176 }
177
178 #[test]
179 fn test_detect_format_small_file() {
180 let temp_dir = std::env::temp_dir();
181 let small_file = temp_dir.join("test_small_magic.txt");
182 fs::write(&small_file, "ab").unwrap(); let format = detect_format(&small_file).unwrap();
186 assert_eq!(format, MagicFileFormat::Text);
187
188 fs::remove_file(&small_file).unwrap();
189 }
190
191 #[test]
192 fn test_detect_format_text_with_binary_content() {
193 let temp_dir = std::env::temp_dir();
194 let binary_text_file = temp_dir.join("test_binary_text.txt");
195
196 let mut file = fs::File::create(&binary_text_file).unwrap();
198 file.write_all(&[0xFF, 0xFE, 0xFD, 0xFC]).unwrap();
199 file.write_all(b"some text").unwrap();
200
201 let format = detect_format(&binary_text_file).unwrap();
203 assert_eq!(format, MagicFileFormat::Text);
204
205 fs::remove_file(&binary_text_file).unwrap();
206 }
207
208 #[test]
209 fn test_magic_file_format_enum_equality() {
210 assert_eq!(MagicFileFormat::Text, MagicFileFormat::Text);
211 assert_eq!(MagicFileFormat::Directory, MagicFileFormat::Directory);
212 assert_eq!(MagicFileFormat::Binary, MagicFileFormat::Binary);
213
214 assert_ne!(MagicFileFormat::Text, MagicFileFormat::Directory);
215 assert_ne!(MagicFileFormat::Text, MagicFileFormat::Binary);
216 assert_ne!(MagicFileFormat::Directory, MagicFileFormat::Binary);
217 }
218
219 #[test]
220 fn test_magic_file_format_debug() {
221 let text_format = MagicFileFormat::Text;
222 let debug_str = format!("{text_format:?}");
223 assert!(debug_str.contains("Text"));
224 }
225
226 #[test]
227 fn test_magic_file_format_clone() {
228 let original = MagicFileFormat::Directory;
229 let cloned = original;
230 assert_eq!(original, cloned);
231 }
232
233 #[test]
234 fn test_magic_file_format_copy() {
235 let original = MagicFileFormat::Binary;
236 let copied = original; assert_eq!(original, copied);
238 }
239}