1#![allow(non_snake_case)]
2#![allow(non_camel_case_types)]
3pub mod extra_data;
4pub mod link_info;
5mod link_target_id_list;
6pub mod shell_link_header;
7
8use extra_data::{ExtraData, ExtraDataTypes};
9use getset::Getters;
10use link_info::LinkInfo;
11use link_target_id_list::LinkTargetIDList;
12use serde::{ser::SerializeStruct, Serialize, Serializer};
13use shell_link_header::{LinkFlags, ShellLinkHeader};
14
15use chrono::{DateTime, Utc};
16use std::{
17 collections::HashMap,
18 fs,
19 io::{Cursor, Read, Seek, SeekFrom},
20 time::SystemTime,
21};
22use winparsingtools::{
23 structs::StringData,
24 traits::{Normalize, Path},
25 ReaderError,
26};
27
28#[derive(Debug, Getters, Clone)]
29#[getset(get = "pub with_prefix")]
30pub struct LnkFileMetaData {
31 full_path: String,
32 mtime: DateTime<Utc>,
33 atime: DateTime<Utc>,
34 ctime: DateTime<Utc>,
35}
36
37impl LnkFileMetaData {
38 fn from_path(path: &str) -> Result<Self, ReaderError> {
39 let file_metadata = fs::metadata(path)?;
40 let full_path = match fs::canonicalize(path) {
41 Ok(path_buf) => path_buf
42 .to_str()
43 .ok_or_else(|| {
44 std::io::Error::new(
45 std::io::ErrorKind::Other,
46 format!("Can not Read full_path for '{}'", path),
47 )
48 })?
49 .to_string()
50 .replace("\\\\?\\", ""),
51 Err(_) => path.to_string(),
52 };
53 let mtime: DateTime<Utc> = DateTime::from(match file_metadata.created() {
54 Ok(date) => date,
55 Err(_) => SystemTime::UNIX_EPOCH,
56 });
57 let atime: DateTime<Utc> = DateTime::from(match file_metadata.accessed() {
58 Ok(date) => date,
59 Err(_) => SystemTime::UNIX_EPOCH,
60 });
61 let ctime: DateTime<Utc> = DateTime::from(match file_metadata.modified() {
62 Ok(date) => date,
63 Err(_) => SystemTime::UNIX_EPOCH,
64 });
65 Ok(Self {
66 full_path,
67 mtime,
68 ctime,
69 atime,
70 })
71 }
72}
73
74impl Serialize for LnkFileMetaData {
75 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
76 where
77 S: Serializer,
78 {
79 let mut state = serializer.serialize_struct("LnkFileMetaData", 4)?;
80 state.serialize_field("full_path", &self.full_path)?;
81 state.serialize_field(
82 "mtime",
83 &format!("{}", self.mtime.format("%Y-%m-%dT%H:%M:%SZ")),
84 )?;
85 state.serialize_field(
86 "atime",
87 &format!("{}", self.atime.format("%Y-%m-%dT%H:%M:%SZ")),
88 )?;
89 state.serialize_field(
90 "ctime",
91 &format!("{}", self.ctime.format("%Y-%m-%dT%H:%M:%SZ")),
92 )?;
93 state.end()
94 }
95}
96
97#[derive(Debug, Serialize, Getters)]
99#[getset(get = "pub with_prefix")]
100pub struct LNKParser {
101 #[serde(skip_serializing_if = "Option::is_none")]
102 target_full_path: Option<String>,
103 #[serde(skip_serializing_if = "Option::is_none")]
104 lnk_file_metadata: Option<LnkFileMetaData>,
105 shell_link_header: ShellLinkHeader,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 link_target_id_list: Option<LinkTargetIDList>,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 link_info: Option<LinkInfo>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 name_string: Option<StringData>,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 relative_path: Option<StringData>,
114 #[serde(skip_serializing_if = "Option::is_none")]
115 working_dir: Option<StringData>,
116 #[serde(skip_serializing_if = "Option::is_none")]
117 command_line_arguments: Option<StringData>,
118 #[serde(skip_serializing_if = "Option::is_none")]
119 icon_location: Option<StringData>,
120 #[serde(skip_serializing_if = "Option::is_none")]
121 extra_data: Option<ExtraData>,
122}
123
124#[inline]
125fn seek_string_data<R: Read + Seek>(r: &mut R, flags: &LinkFlags) -> Result<StringData, ReaderError> {
126 let offset = r.seek(SeekFrom::Current(0))?;
127 let seek_pos = SeekFrom::Start(offset);
128
129 if flags.IsUnicode {
130 return Ok(StringData::from_reader(r)?)
131 } else {
132 r.seek(seek_pos)?;
133
134 match StringData::from_reader_cp1252(r) {
135 Ok(s) => {
136 return Ok(s)
137 },
138 Err(_) => {
139 r.seek(seek_pos)?;
140
141 return Ok(StringData::from_reader_utf8(r)?);
142 }
143 }
144 }
145}
146
147impl LNKParser {
148 pub fn from_path(path: &str) -> Result<Self, ReaderError> {
158 let lnk_file_metadata = LnkFileMetaData::from_path(path)?;
159 let mut lnk_file_reader = fs::File::open(path)?;
160 let mut lnk_parser = Self::from_reader(&mut lnk_file_reader)?;
161 lnk_parser.lnk_file_metadata = Some(lnk_file_metadata);
162 Ok(lnk_parser)
163 }
164 pub fn from_buffer(buf: &[u8]) -> Result<Self, ReaderError> {
166 Self::from_reader(&mut Cursor::new(buf))
167 }
168 pub fn from_reader<R: Read + Seek>(r: &mut R) -> Result<Self, ReaderError> {
183 let shell_link_header = ShellLinkHeader::from_reader(r)?;
184 let mut link_target_id_list = None;
185 let mut link_info = None;
186 let mut name_string = None;
187 let mut relative_path = None;
188 let mut working_dir = None;
189 let mut command_line_arguments = None;
190 let mut icon_location = None;
191
192 if shell_link_header.flags.HasLinkTargetIDList {
193 link_target_id_list = Some(LinkTargetIDList::from_reader(r)?);
194 }
195
196 if shell_link_header.flags.HasLinkInfo {
197 link_info = Some(LinkInfo::from_reader(r)?);
198 }
199
200 if shell_link_header.flags.HasName {
201 name_string = Some(seek_string_data(r, &shell_link_header.flags)?);
202 }
203
204 if shell_link_header.flags.HasRelativePath {
205 relative_path = Some(seek_string_data(r, &shell_link_header.flags)?)
206 }
207
208 if shell_link_header.flags.HasWorkingDir {
209 working_dir = Some(seek_string_data(r, &shell_link_header.flags)?)
210 }
211
212 if shell_link_header.flags.HasArguments {
213 command_line_arguments = Some(seek_string_data(r, &shell_link_header.flags)?)
214 }
215
216 if shell_link_header.flags.HasIconLocation {
217 icon_location = Some(seek_string_data(r, &shell_link_header.flags)?)
218 }
219
220 let extra_data = ExtraData::from_reader(r).ok();
221
222 let mut lnk_parser = Self {
223 shell_link_header,
224 link_target_id_list,
225 link_info,
226 name_string,
227 relative_path,
228 working_dir,
229 command_line_arguments,
230 icon_location,
231 extra_data,
232 lnk_file_metadata: None,
233 target_full_path: None,
234 };
235 lnk_parser.target_full_path = lnk_parser.path();
236
237 Ok(lnk_parser)
238 }
239}
240
241impl Path for LNKParser {
242 fn path(&self) -> Option<String> {
243 match &self.link_info {
244 Some(link_info) => match link_info.path() {
245 Some(link_info_path) => Some(link_info_path),
246 None => match &self.link_target_id_list {
247 Some(link_target_id_list) => link_target_id_list.path(),
248 None => None,
249 },
250 },
251 None => match &self.link_target_id_list {
252 Some(link_target_id_list) => link_target_id_list.path(),
253 None => None,
254 },
255 }
256 }
257}
258
259impl Normalize for LNKParser {
260 fn normalize(&self) -> HashMap<String, String> {
261 let mut fields: HashMap<String, String> = HashMap::new();
262 let mut target_hostname = String::new();
263
264 let target_full_path = match &self.path() {
265 Some(path) => path.to_owned(),
266 None => String::new(),
267 };
268
269 let target_modification_time = self.shell_link_header.mtime.to_string();
270 let target_access_time = self.shell_link_header.atime.to_string();
271 let target_creation_time = self.shell_link_header.ctime.to_string();
272
273 let target_size = self.shell_link_header.file_size.to_string();
274
275 match &self.extra_data {
276 Some(extra_data) => {
277 extra_data.extra_data_blocks.iter().find(|&edb| match edb {
278 ExtraDataTypes::Tracker(tracker) => {
279 target_hostname = tracker.machine_id.to_owned();
280 true
281 }
282 });
283 }
284 None => {}
285 };
286
287 let lnk_full_path = match &self.lnk_file_metadata {
288 Some(lnk_file_metadata) => lnk_file_metadata.full_path.to_owned(),
289 None => String::new(),
290 };
291
292 let lnk_modification_time = match &self.lnk_file_metadata {
293 Some(lnk_file_metadata) => lnk_file_metadata
294 .mtime
295 .format("%Y-%m-%dT%H:%M:%SZ")
296 .to_string(),
297 None => String::new(),
298 };
299
300 let lnk_access_time = match &self.lnk_file_metadata {
301 Some(lnk_file_metadata) => lnk_file_metadata
302 .atime
303 .format("%Y-%m-%dT%H:%M:%SZ")
304 .to_string(),
305 None => String::new(),
306 };
307
308 let lnk_creation_time = match &self.lnk_file_metadata {
309 Some(lnk_file_metadata) => lnk_file_metadata
310 .ctime
311 .format("%Y-%m-%dT%H:%M:%SZ")
312 .to_string(),
313 None => String::new(),
314 };
315
316 fields.insert("target_full_path".to_string(), target_full_path);
317 fields.insert(
318 "target_modification_time".to_string(),
319 target_modification_time,
320 );
321
322 let mac_address = match &self.extra_data {
323 Some(ed) => match &ed.extra_data_blocks.get(0) {
324 Some(item) => match item {
325 ExtraDataTypes::Tracker(t) => t.get_mac_address(),
326 },
327 None => String::from("00:00:00:00:00:00"),
328 },
329 None => String::from("00:00:00:00:00:00"),
330 };
331 fields.insert("target_access_time".to_string(), target_access_time);
332 fields.insert("target_creation_time".to_string(), target_creation_time);
333 fields.insert("target_size".to_string(), target_size);
334 fields.insert("target_hostname".to_string(), target_hostname);
335 fields.insert("lnk_full_path".to_string(), lnk_full_path);
336 fields.insert("lnk_modification_time".to_string(), lnk_modification_time);
337 fields.insert("lnk_access_time".to_string(), lnk_access_time);
338 fields.insert("lnk_creation_time".to_string(), lnk_creation_time);
339 fields.insert("mac_address".to_string(), mac_address);
340 fields
341 }
342}