memf_strings/
from_file.rs1use crate::{ClassifiedString, Result, StringEncoding};
8use std::io::BufRead;
9use std::path::Path;
10
11pub fn from_strings_file(path: &Path) -> Result<Vec<ClassifiedString>> {
16 let file = std::fs::File::open(path)?;
17 let reader = std::io::BufReader::new(file);
18 let mut results = Vec::new();
19
20 for (line_num, line) in reader.lines().enumerate() {
21 let line = line?;
22 let trimmed = line.trim_end();
23 if trimmed.is_empty() {
24 continue;
25 }
26
27 let (offset, value) = parse_line(trimmed, line_num as u64);
28 results.push(ClassifiedString {
29 value,
30 physical_offset: offset,
31 encoding: StringEncoding::Ascii,
32 categories: Vec::new(),
33 });
34 }
35
36 Ok(results)
37}
38
39fn parse_line(line: &str, line_num: u64) -> (u64, String) {
41 if let Some(colon_pos) = line.find(": ") {
43 let prefix = &line[..colon_pos];
44 let prefix = prefix.trim();
45 if let Some(offset) = parse_offset(prefix) {
46 let value = line[colon_pos + 2..].to_string();
47 return (offset, value);
48 }
49 }
50 (line_num, line.to_string())
52}
53
54fn parse_offset(s: &str) -> Option<u64> {
55 if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
56 u64::from_str_radix(hex, 16).ok()
57 } else {
58 s.parse::<u64>().ok()
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use std::io::Write;
66
67 fn write_temp_file(content: &str) -> std::path::PathBuf {
68 use std::collections::hash_map::DefaultHasher;
69 use std::hash::{Hash, Hasher};
70 let mut h = DefaultHasher::new();
71 std::thread::current().name().hash(&mut h);
72 content.len().hash(&mut h);
73 let unique = h.finish();
74 let path = std::env::temp_dir().join(format!(
75 "memf_test_strings_{}_{}",
76 std::process::id(),
77 unique
78 ));
79 let mut f = std::fs::File::create(&path).unwrap();
80 f.write_all(content.as_bytes()).unwrap();
81 path
82 }
83
84 #[test]
85 fn raw_format() {
86 let path = write_temp_file("Hello World\n/etc/passwd\nhttps://evil.com\n");
87 let strings = from_strings_file(&path).unwrap();
88 assert_eq!(strings.len(), 3);
89 assert_eq!(strings[0].value, "Hello World");
90 assert_eq!(strings[1].value, "/etc/passwd");
91 assert_eq!(strings[2].value, "https://evil.com");
92 std::fs::remove_file(&path).ok();
93 }
94
95 #[test]
96 fn offset_prefixed_decimal() {
97 let path = write_temp_file("1000: Hello\n2000: World\n");
98 let strings = from_strings_file(&path).unwrap();
99 assert_eq!(strings.len(), 2);
100 assert_eq!(strings[0].physical_offset, 1000);
101 assert_eq!(strings[0].value, "Hello");
102 assert_eq!(strings[1].physical_offset, 2000);
103 std::fs::remove_file(&path).ok();
104 }
105
106 #[test]
107 fn offset_prefixed_hex() {
108 let path = write_temp_file("0x1A2B: hex string\n");
109 let strings = from_strings_file(&path).unwrap();
110 assert_eq!(strings.len(), 1);
111 assert_eq!(strings[0].physical_offset, 0x1A2B);
112 assert_eq!(strings[0].value, "hex string");
113 std::fs::remove_file(&path).ok();
114 }
115
116 #[test]
117 fn skips_empty_lines() {
118 let path = write_temp_file("line1\n\n\nline2\n");
119 let strings = from_strings_file(&path).unwrap();
120 assert_eq!(strings.len(), 2);
121 std::fs::remove_file(&path).ok();
122 }
123
124 #[test]
125 fn string_with_colon_but_no_offset() {
126 let path = write_temp_file("http://example.com:8080/path\n");
127 let strings = from_strings_file(&path).unwrap();
128 assert_eq!(strings.len(), 1);
129 assert_eq!(strings[0].value, "http://example.com:8080/path");
131 std::fs::remove_file(&path).ok();
132 }
133
134 #[test]
135 fn malformed_offset_line() {
136 let path = write_temp_file("notanumber: some text\n");
139 let strings = from_strings_file(&path).unwrap();
140 assert_eq!(strings.len(), 1);
141 assert_eq!(strings[0].value, "notanumber: some text");
143 assert_eq!(strings[0].physical_offset, 0); std::fs::remove_file(&path).ok();
145 }
146
147 #[test]
148 fn offset_prefixed_hex_uppercase() {
149 let path = write_temp_file("0XFF00: uppercase hex\n");
150 let strings = from_strings_file(&path).unwrap();
151 assert_eq!(strings.len(), 1);
152 assert_eq!(strings[0].physical_offset, 0xFF00);
153 assert_eq!(strings[0].value, "uppercase hex");
154 std::fs::remove_file(&path).ok();
155 }
156
157 #[test]
158 fn parse_offset_helper() {
159 assert_eq!(parse_offset("0x1234"), Some(0x1234));
160 assert_eq!(parse_offset("0X1234"), Some(0x1234));
161 assert_eq!(parse_offset("42"), Some(42));
162 assert_eq!(parse_offset("abc"), None);
163 assert_eq!(parse_offset(""), None);
164 }
165}