parselnk/
lib.rs

1//! Parse windows .lnk files using only safe rust. Windows lnk files
2//! describe links to data objects as defined by
3//! [this specification](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-shllink/16cb4ca1-9339-4d0c-a68d-bf1d6cc0f943).
4//!
5//! # Examples
6//!
7//! You can process the `Lnk` data from a memory buffer that implements
8//! `std::io::Read`.
9//!
10//! ```no_run
11//! use parselnk::Lnk;
12//! use std::convert::TryFrom;
13//!
14//! let mut lnk_data: Vec<u8> = Vec::new();
15//! // read your link into `lnk_data` here...
16//! let lnk = Lnk::try_from(lnk_data);
17//! ```
18//!
19//! Or you can process any `Lnk` on disk.
20//! ```no_run
21//! use parselnk::Lnk;
22//! use std::convert::TryFrom;
23//!
24//! let path = std::path::Path::new("c:\\users\\me\\shortcut.lnk");
25//!
26//! let lnk = Lnk::try_from(path).unwrap();
27//! ```
28
29#![warn(missing_docs)]
30
31pub mod error;
32pub mod extra_data;
33pub mod header;
34pub mod link_info;
35pub mod link_target_id_list;
36pub mod string_data;
37
38pub use extra_data::*;
39pub use header::*;
40pub use link_info::*;
41pub use link_target_id_list::*;
42use std::convert::TryFrom;
43use std::path::PathBuf;
44pub use string_data::*;
45
46/// Result type wrapping around `parselnk::error::Error`
47pub type Result<T> = std::result::Result<T, error::Error>;
48
49/// Represents a windows .lnk file
50#[derive(Clone, Debug)]
51pub struct Lnk {
52    /// Path to the `.lnk` file
53    path: Option<PathBuf>,
54
55    /// The ShellLinkHeader structure contains identification information, timestamps, and flags that specify the presence of optional structures, including LinkTargetIDList (section 2.2), LinkInfo (section 2.3), and StringData (section 2.4).
56    pub header: ShellLinkHeader,
57
58    /// StringData refers to a set of structures that convey user interface and path identification information. The presence of these optional structures is controlled by LinkFlags (section 2.1.1) in the ShellLinkHeader (section 2.1).
59    pub string_data: StringData,
60
61    /// The LinkTargetIDList structure specifies the target of the link. The presence of this optional structure is specified by the HasLinkTargetIDList bit (LinkFlags section 2.1.1) in the ShellLinkHeader (section 2.1).
62    pub link_target_id_list: LinkTargetIdList,
63
64    /// The LinkInfo structure specifies information necessary to resolve a link target if it is not found in its original location. This includes information about the volume that the target was stored on, the mapped drive letter, and a Universal Naming Convention (UNC) form of the path if one existed when the link was created. For more details about UNC paths, see [MS-DFSNM] section 2.2.1.4.:w
65    pub link_info: LinkInfo,
66
67    /// ExtraData refers to a set of structures that convey additional information about a link target. These optional structures can be present in an extra data section that is appended to the basic Shell Link Binary File Format.
68    pub extra_data: ExtraData,
69}
70
71impl Lnk {
72    /// Creates a new `Lnk` from a `Read` source.
73    ///
74    /// # Example
75    ///
76    /// ```no_run
77    /// use parselnk::Lnk;
78    /// use std::fs::File;
79    ///
80    /// let mut file = File::open(r"c:\users\me\desktop\firefox.lnk").unwrap();
81    /// let lnk = Lnk::new(&mut file);
82    /// ```
83    ///
84    pub fn new<S: std::io::Read>(reader: &mut S) -> Result<Lnk> {
85        let mut data_buf = Vec::new();
86        reader
87            .read_to_end(&mut data_buf)
88            .map_err(error::HeaderError::Read)?;
89
90        let mut cursor = std::io::Cursor::new(data_buf);
91
92        let header = ShellLinkHeader::try_from(&mut cursor)?;
93        let link_target_id_list = LinkTargetIdList::new(&mut cursor, &header)?;
94        let link_info = LinkInfo::new(&mut cursor, &header)?;
95        let string_data = StringData::new(&mut cursor, &header)?;
96        let extra_data = ExtraData::new(&mut cursor, &header)?;
97
98        Ok(Lnk {
99            path: None,
100            header,
101            string_data,
102            link_target_id_list,
103            link_info,
104            extra_data,
105        })
106    }
107
108    /// The command line arguments supplied via the `Lnk`
109    pub fn arguments(&self) -> Option<String> {
110        self.string_data.command_line_arguments.clone()
111    }
112
113    /// The relative path to the resource of the `Lnk``
114    pub fn relative_path(&self) -> Option<PathBuf> {
115        self.string_data.relative_path.clone()
116    }
117
118    /// The working directory of the `Lnk`
119    pub fn working_dir(&self) -> Option<PathBuf> {
120        self.string_data.working_dir.clone()
121    }
122
123    /// The description of the `Lnk`
124    pub fn description(&self) -> Option<String> {
125        self.string_data.name_string.clone()
126    }
127
128    /// The creation `FileTime` as a u64
129    pub fn creation_time(&self) -> u64 {
130        self.header.creation_time
131    }
132
133    /// The access `FileTime` as a u64
134    pub fn access_time(&self) -> u64 {
135        self.header.access_time
136    }
137
138    /// The write `FileTime` as a u64
139    pub fn write_time(&self) -> u64 {
140        self.header.write_time
141    }
142
143    /// The creation `FileTime` as a `DateTime`
144    #[cfg(feature = "chrono")]
145    pub fn created_on(&self) -> Option<chrono::DateTime<chrono::Utc>> {
146        self.header.created_on
147    }
148
149    /// The access `FileTime` as a `DateTime`
150    #[cfg(feature = "chrono")]
151    pub fn accessed_on(&self) -> Option<chrono::DateTime<chrono::Utc>> {
152        self.header.accessed_on
153    }
154
155    /// The write `FileTime` as a `DateTime`
156    #[cfg(feature = "chrono")]
157    pub fn modified_on(&self) -> Option<chrono::DateTime<chrono::Utc>> {
158        self.header.modified_on
159    }
160}
161
162impl TryFrom<&std::path::Path> for Lnk {
163    type Error = crate::error::Error;
164
165    fn try_from(p: &std::path::Path) -> std::result::Result<Self, Self::Error> {
166        let mut f = std::fs::File::open(p).map_err(crate::error::Error::from)?;
167        Lnk::new(&mut f).map(|mut lnk| {
168            lnk.path = Some(p.to_path_buf());
169            lnk
170        })
171    }
172}
173
174impl TryFrom<&[u8]> for Lnk {
175    type Error = crate::error::Error;
176
177    fn try_from(mut p: &[u8]) -> std::result::Result<Self, Self::Error> {
178        Lnk::new(&mut p)
179    }
180}
181
182impl TryFrom<Vec<u8>> for Lnk {
183    type Error = crate::error::Error;
184
185    fn try_from(p: Vec<u8>) -> std::result::Result<Self, Self::Error> {
186        Lnk::new(&mut &p[0..])
187    }
188}
189
190impl TryFrom<&Vec<u8>> for Lnk {
191    type Error = crate::error::Error;
192
193    fn try_from(p: &Vec<u8>) -> std::result::Result<Self, Self::Error> {
194        Lnk::new(&mut &p[0..])
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use crate::Lnk;
201    use std::convert::TryFrom;
202    use std::path::Path;
203
204    #[test]
205    fn firefox() {
206        let path = Path::new("./test_data/firefox.lnk");
207        assert!(Lnk::try_from(path).is_ok());
208    }
209
210    #[test]
211    fn commander() {
212        let path = Path::new("./test_data/commander.lnk");
213        assert!(Lnk::try_from(path).is_ok());
214    }
215
216    #[test]
217    fn notepad() {
218        let path = Path::new("./test_data/notepad.lnk");
219        assert!(Lnk::try_from(path).is_ok());
220    }
221
222    #[test]
223    fn xp_outlook_express() {
224        let path = Path::new("./test_data/outlook_express.lnk");
225        assert!(Lnk::try_from(path).is_ok());
226    }
227}