ntfs_reader/
file.rs

1// Copyright (c) 2022, Matteo Bernacchia <dev@kikijiki.com>. All rights reserved.
2// This project is dual licensed under the Apache License 2.0 and the MIT license.
3// See the LICENSE files in the project root for details.
4
5use std::mem::size_of;
6
7use crate::{api::*, attribute::NtfsAttribute, mft::Mft};
8
9pub struct NtfsFile<'a> {
10    pub number: u64,
11    pub header: &'a NtfsFileRecordHeader,
12    pub data: &'a [u8],
13}
14
15impl<'a> NtfsFile<'a> {
16    pub fn new(number: u64, data: &'a [u8]) -> Self {
17        assert!(
18            data.len() >= size_of::<NtfsFileRecordHeader>(),
19            "NTFS file record too small",
20        );
21        let header = unsafe { &*(data.as_ptr() as *const NtfsFileRecordHeader) };
22        NtfsFile {
23            number,
24            header,
25            data,
26        }
27    }
28
29    pub fn number(&self) -> u64 {
30        self.number
31    }
32
33    pub fn reference_number(&self) -> u64 {
34        let seq = self.header.sequence_value as u64;
35        (seq << 48) | (self.number & 0x0000_FFFF_FFFF_FFFF)
36    }
37
38    pub fn get_file_id(&self) -> FileId {
39        FileId::Normal(self.reference_number())
40    }
41
42    pub fn is_valid(data: &[u8]) -> bool {
43        if data.len() < size_of::<NtfsFileRecordHeader>() {
44            return false;
45        }
46        let header = unsafe { &*(data.as_ptr() as *const NtfsFileRecordHeader) };
47        if &header.signature != FILE_RECORD_SIGNATURE {
48            return false;
49        }
50
51        if header.update_sequence_length == 0 {
52            return false;
53        }
54
55        if header.used_size as usize > data.len() {
56            return false;
57        }
58
59        let usa_end =
60            header.update_sequence_offset as usize + header.update_sequence_length as usize * 2;
61
62        let usa_num = header.update_sequence_length as usize - 1;
63        let sector_num = data.len() / SECTOR_SIZE;
64
65        if usa_end > data.len() || usa_num > sector_num {
66            return false;
67        }
68
69        if header.attributes_offset as usize >= header.used_size as usize {
70            return false;
71        }
72
73        true
74    }
75
76    pub fn attributes<F>(&self, mut f: F)
77    where
78        F: FnMut(&NtfsAttribute),
79    {
80        let mut offset = self.header.attributes_offset as usize;
81        let used = usize::min(self.header.used_size as usize, self.data.len());
82
83        while offset < used {
84            let slice = &self.data[offset..used];
85            let attr = match NtfsAttribute::new(slice) {
86                Some(attr) => attr,
87                None => break,
88            };
89
90            if attr.header.type_id == NtfsAttributeType::End as u32 {
91                break;
92            }
93
94            f(&attr);
95
96            let attr_len = attr.len();
97            if attr_len == 0 {
98                break;
99            }
100            offset = match offset.checked_add(attr_len) {
101                Some(next) if next <= used => next,
102                _ => break,
103            };
104        }
105    }
106
107    pub fn get_attribute(&self, attribute_type: NtfsAttributeType) -> Option<NtfsAttribute<'_>> {
108        let mut offset = self.header.attributes_offset as usize;
109        let used = usize::min(self.header.used_size as usize, self.data.len());
110
111        while offset < used {
112            let slice = &self.data[offset..used];
113            let attr = match NtfsAttribute::new(slice) {
114                Some(attr) => attr,
115                None => break,
116            };
117
118            if attr.header.type_id == NtfsAttributeType::End as u32 {
119                break;
120            }
121            if attr.header.type_id == attribute_type as u32 {
122                return Some(attr);
123            }
124
125            let attr_len = attr.len();
126            if attr_len == 0 {
127                break;
128            }
129            offset = match offset.checked_add(attr_len) {
130                Some(next) if next <= used => next,
131                _ => break,
132            };
133        }
134        None
135    }
136
137    pub fn get_best_file_name(&self, mft: &Mft) -> Option<NtfsFileName> {
138        let mut offset = self.header.attributes_offset as usize;
139        let used = usize::min(self.header.used_size as usize, self.data.len());
140        let mut best = None;
141
142        while offset < used {
143            let slice = &self.data[offset..used];
144            let attr = match NtfsAttribute::new(slice) {
145                Some(attr) => attr,
146                None => break,
147            };
148
149            if attr.header.type_id == NtfsAttributeType::End as u32 {
150                break;
151            }
152
153            if attr.header.type_id == NtfsAttributeType::FileName as u32 {
154                if let Some(name) = attr.as_name() {
155                    if !name.is_reparse_point() {
156                        if name.header.namespace == NtfsFileNamespace::Win32 as u8
157                            || name.header.namespace == NtfsFileNamespace::Win32AndDos as u8
158                        {
159                            return Some(name);
160                        } else {
161                            best = Some(name);
162                        }
163                    }
164                }
165            }
166
167            if attr.header.type_id == NtfsAttributeType::AttributeList as u32 {
168                if attr.header.is_non_resident != 0 {
169                    // We do not support non-resident attribute lists here.
170                    break;
171                }
172                let header = match attr.resident_header() {
173                    Some(header) => header,
174                    None => break,
175                };
176                let value_offset = header.value_offset as usize;
177                let value_length = header.value_length as usize;
178                let value_end = match value_offset.checked_add(value_length) {
179                    Some(end) if end <= attr.data().len() => end,
180                    _ => break,
181                };
182                let attr_slice = attr.data();
183                let att_data = &attr_slice[value_offset..value_end];
184
185                let mut att_offset = 0usize;
186                while att_offset < att_data.len() {
187                    let entry_slice = &att_data[att_offset..];
188                    let entry = match parse_attribute_list_entry(entry_slice) {
189                        Some(entry) => entry,
190                        None => break,
191                    };
192                    let entry_len = entry.length as usize;
193                    if entry.type_id == NtfsAttributeType::FileName as u32 {
194                        let rec = mft.get_record(entry.reference())?;
195                        let att = rec.get_attribute(NtfsAttributeType::FileName)?;
196
197                        if let Some(name) = att.as_name() {
198                            if !name.is_reparse_point() {
199                                if name.header.namespace == NtfsFileNamespace::Win32 as u8
200                                    || name.header.namespace == NtfsFileNamespace::Win32AndDos as u8
201                                {
202                                    return Some(name);
203                                } else {
204                                    best = Some(name);
205                                    break;
206                                }
207                            }
208                        }
209                    }
210
211                    if entry_len == 0 {
212                        break;
213                    }
214                    att_offset = match att_offset.checked_add(entry_len) {
215                        Some(next) if next <= att_data.len() => next,
216                        _ => break,
217                    };
218                    let align = (8 - (att_offset % 8)) % 8;
219                    att_offset = match att_offset.checked_add(align) {
220                        Some(next) if next <= att_data.len() => next,
221                        _ => break,
222                    };
223                }
224            }
225
226            let attr_len = attr.len();
227            if attr_len == 0 {
228                break;
229            }
230            offset = match offset.checked_add(attr_len) {
231                Some(next) if next <= used => next,
232                _ => break,
233            };
234        }
235
236        best
237    }
238
239    // This cannot read nonresident data!
240    pub fn read_data(&self) -> Option<&[u8]> {
241        if let Some(att) = self.get_attribute(NtfsAttributeType::Data) {
242            if att.header.is_non_resident == 0 {
243                return att.as_resident_data();
244            }
245        }
246        None
247    }
248
249    pub fn is_used(&self) -> bool {
250        self.header.flags & NtfsFileFlags::InUse as u16 != 0
251    }
252
253    pub fn is_directory(&self) -> bool {
254        self.header.flags & NtfsFileFlags::IsDirectory as u16 != 0
255    }
256}
257
258fn parse_attribute_list_entry(data: &[u8]) -> Option<&NtfsAttributeListEntry> {
259    if data.len() < size_of::<NtfsAttributeListEntry>() {
260        return None;
261    }
262    let entry = unsafe { &*(data.as_ptr() as *const NtfsAttributeListEntry) };
263    let length = entry.length as usize;
264    if length < size_of::<NtfsAttributeListEntry>() || length > data.len() {
265        return None;
266    }
267    Some(entry)
268}