smb_msg/
query_dir.rs

1//! Directory-related messages.
2
3#[cfg(feature = "client")]
4use binrw::io::TakeSeekExt;
5use smb_msg_derive::*;
6use std::io::SeekFrom;
7
8use binrw::prelude::*;
9use modular_bitfield::prelude::*;
10
11use smb_dtyp::binrw_util::prelude::*;
12use smb_fscc::*;
13
14use super::FileId;
15
16/// SMB2 QUERY_DIRECTORY Request packet for obtaining directory enumeration.
17///
18/// This request is sent by the client to obtain a directory enumeration on a
19/// directory open. The client specifies the type of information desired and
20/// can optionally provide a search pattern to filter the results.
21///
22/// Reference: MS-SMB2 section 2.2.33, page 10906442-294c-46d3-8515-c277efe1f752
23#[smb_request(size = 33)]
24pub struct QueryDirectoryRequest {
25    /// The file information class describing the format that data must be returned in.
26    /// Specifies which type of directory information structure should be used for each entry.
27    pub file_information_class: QueryDirectoryInfoClass,
28    /// Flags indicating how the query directory operation must be processed.
29    /// Controls behavior such as restarting enumeration or returning single entries.
30    pub flags: QueryDirectoryFlags,
31    /// The byte offset within the directory to resume enumeration from.
32    /// Must be supplied when INDEX_SPECIFIED flag is set, otherwise must be zero.
33    // If SMB2_INDEX_SPECIFIED is set in Flags, this value MUST be supplied.
34    // Otherwise, it MUST be set to zero and the server MUST ignore it.
35    #[bw(assert(flags.index_specified() || *file_index == 0))]
36    pub file_index: u32,
37    /// Identifier of the directory on which to perform the enumeration.
38    /// This is returned from an SMB2 Create Request to open a directory.
39    pub file_id: FileId,
40    /// Offset from the beginning of the SMB2 header to the search pattern.
41    /// Set to zero if no search pattern is provided.
42    #[bw(calc = PosMarker::default())]
43    #[br(temp)]
44    pub file_name_offset: PosMarker<u16>,
45    /// Length in bytes of the search pattern.
46    /// Set to zero if no search pattern is provided.
47    #[bw(try_calc = file_name.size().try_into())]
48    file_name_length: u16, // in bytes.
49    /// The maximum number of bytes the server is allowed to return in the response.
50    pub output_buffer_length: u32,
51    /// Unicode search pattern for the request with wildcards and other conventions.
52    /// Format is specified in MS-CIFS section 2.2.1.1.3.
53    #[br(seek_before = SeekFrom::Start(file_name_offset.value as u64))]
54    // map stream take until eof:
55    #[br(args {size: SizedStringSize::bytes16(file_name_length)})]
56    #[bw(write_with = PosMarker::write_aoff, args(&file_name_offset))]
57    pub file_name: SizedWideString,
58}
59
60/// Flags indicating how the query directory operation must be processed.
61///
62/// These flags control the behavior of directory enumeration, such as whether
63/// to restart the scan from the beginning or return only a single entry.
64///
65/// Reference: MS-SMB2 section 2.2.33
66#[smb_dtyp::mbitfield]
67pub struct QueryDirectoryFlags {
68    /// The server is requested to restart the enumeration from the beginning.
69    pub restart_scans: bool,
70    /// The server is requested to only return the first entry of the search results.
71    pub return_single_entry: bool,
72    /// The server is requested to return entries beginning at the byte number specified by FileIndex.
73    pub index_specified: bool,
74    /// The server is requested to restart the enumeration from the beginning, and the search pattern is to be changed.
75    pub reopen: bool,
76    #[skip]
77    __: B4,
78}
79
80/// SMB2 QUERY_DIRECTORY Response packet containing directory enumeration results.
81///
82/// This response is sent by a server in response to an SMB2 QUERY_DIRECTORY Request.
83/// It contains the directory enumeration data in the format specified by the
84/// FileInformationClass in the request.
85///
86/// Reference: MS-SMB2 section 2.2.34
87#[smb_response(size = 9)]
88pub struct QueryDirectoryResponse {
89    /// Offset in bytes from the beginning of the SMB2 header to the directory enumeration data.
90    #[bw(calc = PosMarker::default())]
91    #[br(temp)]
92    output_buffer_offset: PosMarker<u16>,
93    /// Length in bytes of the directory enumeration being returned.
94    #[bw(try_calc = output_buffer.len().try_into())]
95    #[br(temp)]
96    output_buffer_length: u32,
97    /// Directory enumeration data in the format specified by the FileInformationClass.
98    /// Format is as specified in MS-FSCC section 2.4 for the specific file information class.
99    #[br(seek_before = SeekFrom::Start(output_buffer_offset.value as u64))]
100    #[br(map_stream = |s| s.take_seek(output_buffer_length as u64), parse_with = binrw::helpers::until_eof)]
101    #[bw(write_with = PosMarker::write_aoff, args(&output_buffer_offset))]
102    pub output_buffer: Vec<u8>,
103}
104
105impl QueryDirectoryResponse {
106    /// Reads and parses the output buffer as a vector of directory information entries.
107    ///
108    /// See viable types for conversions in the `smb-dtyp` crate - [`QueryDirectoryInfoValue`] implementations.
109    ///
110    /// This method parses the raw output buffer into strongly-typed directory information
111    /// structures based on the type parameter T, which should match the FileInformationClass
112    /// used in the original request.
113    pub fn read_output<T>(&self) -> BinResult<Vec<T>>
114    where
115        T: QueryDirectoryInfoValue + BinRead + BinWrite,
116        for<'a> <T as BinRead>::Args<'a>: Default,
117        for<'b> <T as BinWrite>::Args<'b>: Default,
118    {
119        let mut cursor = std::io::Cursor::new(&self.output_buffer);
120        Ok(
121            ChainedItemList::<T, { QueryDirectoryInfo::CHAINED_ALIGNMENT }>::read_le(&mut cursor)?
122                .into(),
123        )
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use time::macros::datetime;
130
131    use super::*;
132    #[test]
133    pub fn test_both_directory_information_attribute_parse() {
134        let data = [
135            0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x39, 0x75, 0x91, 0xbf, 0xc8, 0x4b, 0xdb, 0x1,
136            0xe7, 0xb8, 0x48, 0xcd, 0xc8, 0x5d, 0xdb, 0x1, 0xe7, 0x1b, 0xed, 0xd4, 0x6a, 0x58,
137            0xdb, 0x1, 0xe7, 0x1b, 0xed, 0xd4, 0x6a, 0x58, 0xdb, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
138            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0,
139            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
140            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7b,
141            0x80, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x2e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x70, 0x0,
142            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x8, 0x38, 0x96, 0xae, 0x4b, 0xdb, 0x1, 0x10, 0x6a,
143            0x87, 0x4b, 0x49, 0x5d, 0xdb, 0x1, 0x62, 0xc, 0xcd, 0xc1, 0xc8, 0x4b, 0xdb, 0x1, 0x62,
144            0xc, 0xcd, 0xc1, 0xc8, 0x4b, 0xdb, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
145            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0,
146            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
147            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a, 0xe7, 0x1, 0x0,
148            0x0, 0x0, 0x4, 0x0, 0x2e, 0x0, 0x2e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x78, 0x0, 0x0, 0x0, 0x0,
149            0x0, 0x0, 0x0, 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58, 0xdb, 0x1, 0x5b, 0x6c, 0x44, 0xce,
150            0x6a, 0x58, 0xdb, 0x1, 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58, 0xdb, 0x1, 0x5f, 0xd9, 0xd5,
151            0xce, 0x6a, 0x58, 0xdb, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
152            0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
153            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
154            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf0, 0xa4, 0x0, 0x0, 0x0, 0x0,
155            0xa, 0x0, 0x61, 0x0, 0x2e, 0x0, 0x74, 0x0, 0x78, 0x0, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0,
156            0x0, 0x0, 0x78, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd8, 0xce, 0xec, 0xcf, 0x6a, 0x58,
157            0xdb, 0x1, 0x7e, 0xc, 0x17, 0xd9, 0x6a, 0x58, 0xdb, 0x1, 0x7e, 0xc, 0x17, 0xd9, 0x6a,
158            0x58, 0xdb, 0x1, 0x7e, 0xc, 0x17, 0xd9, 0x6a, 0x58, 0xdb, 0x1, 0x6, 0x0, 0x0, 0x0, 0x0,
159            0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0xa, 0x0,
160            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
161            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
162            0xb9, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x62, 0x0, 0x2e, 0x0, 0x74, 0x0, 0x78, 0x0,
163            0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x78, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x57,
164            0x8e, 0x2f, 0xd0, 0x6a, 0x58, 0xdb, 0x1, 0xe2, 0xa8, 0xc1, 0xdd, 0x6a, 0x58, 0xdb, 0x1,
165            0xe2, 0xa8, 0xc1, 0xdd, 0x6a, 0x58, 0xdb, 0x1, 0xe2, 0xa8, 0xc1, 0xdd, 0x6a, 0x58,
166            0xdb, 0x1, 0xe6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe8, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
167            0x0, 0x20, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
168            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
169            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbb, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x63, 0x0,
170            0x2e, 0x0, 0x74, 0x0, 0x78, 0x0, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
171            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x32, 0x66, 0x47, 0xd0, 0x6a, 0x58, 0xdb, 0x1, 0x3, 0xc,
172            0x39, 0x53, 0x49, 0x5d, 0xdb, 0x1, 0x3, 0xc, 0x39, 0x53, 0x49, 0x5d, 0xdb, 0x1, 0x3,
173            0xc, 0x39, 0x53, 0x49, 0x5d, 0xdb, 0x1, 0x26, 0x3e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
174            0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0,
175            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
176            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbc, 0xf8, 0x0, 0x0,
177            0x0, 0x0, 0x4, 0x0, 0x64, 0x0, 0x2e, 0x0, 0x74, 0x0, 0x78, 0x0, 0x74, 0x0,
178        ];
179
180        let res = QueryDirectoryResponse {
181            output_buffer: data.to_vec(),
182        }
183        .read_output::<FileIdBothDirectoryInformation>()
184        .unwrap();
185
186        assert_eq!(
187            vec![
188                FileIdBothDirectoryInformation {
189                    file_index: 0,
190                    creation_time: FileTime::from(datetime!(2024-12-11 12:32:31.7084985)),
191                    last_access_time: FileTime::from(datetime!(2025-01-03 10:18:15.6499175)),
192                    last_write_time: FileTime::from(datetime!(2024-12-27 14:22:59.9648231)),
193                    change_time: FileTime::from(datetime!(2024-12-27 14:22:59.9648231)),
194                    end_of_file: 0,
195                    allocation_size: 0,
196                    file_attributes: FileAttributes::new().with_directory(true),
197                    ea_size: Some(0),
198                    reparse_tag: None,
199                    short_name_length: 0,
200                    short_name: Default::default(),
201                    file_id: 562949953454203,
202                    file_name: ".".into(),
203                },
204                FileIdBothDirectoryInformation {
205                    file_index: 0,
206                    creation_time: FileTime::from(datetime!(2024-12-11 9:25:15.4208828)),
207                    last_access_time: FileTime::from(datetime!(2025-01-02 19:05:31.8723088)),
208                    last_write_time: FileTime::from(datetime!(2024-12-11 12:32:35.4544738)),
209                    change_time: FileTime::from(datetime!(2024-12-11 12:32:35.4544738)),
210                    end_of_file: 0,
211                    allocation_size: 0,
212                    file_attributes: FileAttributes::new().with_directory(true),
213                    ea_size: Some(0),
214                    reparse_tag: None,
215                    short_name_length: 0,
216                    short_name: Default::default(),
217                    file_id: 1125899906967338,
218                    file_name: "..".into(),
219                },
220                FileIdBothDirectoryInformation {
221                    file_index: 0,
222                    creation_time: FileTime::from(datetime!(2024-12-27 14:22:48.7929947)),
223                    last_access_time: FileTime::from(datetime!(2024-12-27 14:22:48.7929947)),
224                    last_write_time: FileTime::from(datetime!(2024-12-27 14:22:48.7929947)),
225                    change_time: FileTime::from(datetime!(2024-12-27 14:22:49.7460575)),
226                    end_of_file: 0,
227                    allocation_size: 0,
228                    file_attributes: FileAttributes::new().with_archive(true),
229                    ea_size: Some(0),
230                    reparse_tag: None,
231                    short_name_length: 0,
232                    short_name: Default::default(),
233                    file_id: 2814749767148784,
234                    file_name: "a.txt".into(),
235                },
236                FileIdBothDirectoryInformation {
237                    file_index: 0,
238                    creation_time: FileTime::from(datetime!(2024-12-27 14:22:51.5742424)),
239                    last_access_time: FileTime::from(datetime!(2024-12-27 14:23:06.9505662)),
240                    last_write_time: FileTime::from(datetime!(2024-12-27 14:23:06.9505662)),
241                    change_time: FileTime::from(datetime!(2024-12-27 14:23:06.9505662)),
242                    end_of_file: 6,
243                    allocation_size: 8,
244                    file_attributes: FileAttributes::new().with_archive(true),
245                    ea_size: Some(0),
246                    reparse_tag: None,
247                    short_name_length: 0,
248                    short_name: Default::default(),
249                    file_id: 1125899906906297,
250                    file_name: "b.txt".into(),
251                },
252                FileIdBothDirectoryInformation {
253                    file_index: 0,
254                    creation_time: FileTime::from(datetime!(2024-12-27 14:22:52.0116823)),
255                    last_access_time: FileTime::from(datetime!(2024-12-27 14:23:14.7795682)),
256                    last_write_time: FileTime::from(datetime!(2024-12-27 14:23:14.7795682)),
257                    change_time: FileTime::from(datetime!(2024-12-27 14:23:14.7795682)),
258                    end_of_file: 486,
259                    allocation_size: 488,
260                    file_attributes: FileAttributes::new().with_archive(true),
261                    ea_size: Some(0),
262                    reparse_tag: None,
263                    short_name_length: 0,
264                    short_name: Default::default(),
265                    file_id: 1125899906906299,
266                    file_name: "c.txt".into(),
267                },
268                FileIdBothDirectoryInformation {
269                    file_index: 0,
270                    creation_time: FileTime::from(datetime!(2024-12-27 14:22:52.167941),),
271                    last_access_time: FileTime::from(datetime!(2025-01-02 19:05:44.7804931),),
272                    last_write_time: FileTime::from(datetime!(2025-01-02 19:05:44.7804931),),
273                    change_time: FileTime::from(datetime!(2025-01-02 19:05:44.7804931),),
274                    end_of_file: 15910,
275                    allocation_size: 16384,
276                    file_attributes: FileAttributes::new().with_archive(true),
277                    ea_size: Some(0),
278                    reparse_tag: None,
279                    short_name_length: 0,
280                    short_name: Default::default(),
281                    file_id: 1125899906906300,
282                    file_name: "d.txt".into(),
283                },
284            ],
285            res
286        );
287    }
288}