lnk_parser/
lib.rs

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/// Reads LNK file and determine its parts then parses them
98#[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    /// Parse LNK file from path.
149    /// # Example
150    /// ```
151    ///# use lnk_parser::LNKParser;
152    /// fn main(){
153    ///     let lnk_file = LNKParser::from_path("sample.lnk");
154    ///     println!("{:?}", lnk_file);
155    /// }
156    /// ```
157    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    /// Parse the LNK file data from buffer
165    pub fn from_buffer(buf: &[u8]) -> Result<Self, ReaderError> {
166        Self::from_reader(&mut Cursor::new(buf))
167    }
168    /// Parse LNK file from an instance that implement `Read` & `Seek` traits.
169    /// # Example
170    /// ```
171    ///# use lnk_parser::LNKParser;
172    /// use std::fs::File;
173    /// fn main(){
174    ///     // Open the LNK file
175    ///     let mut file = File::open("samples/WIN7/6.1_7601/network_share.lnk").unwrap();
176    ///     // Pass the `File` instance to `from_reader` function.
177    ///     // `std::fs::File` implements `Read` & `Seek` traits.
178    ///     let lnk_file = LNKParser::from_reader(&mut file);
179    ///     println!("{:?}", lnk_file);
180    /// }
181    /// ```
182    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}