1use std::mem;
2
3use derive_more::TryFrom;
4use widestring::{U16CStr, u16cstr};
5use windows::{Win32::Foundation::FILETIME, core::PCWSTR};
6
7pub const EVERYTHING_IPC_GET_MINOR_VERSION: u32 = 1;
8
9pub const EVERYTHING_IPC_COPYDATA_QUERY2W: u32 = 18;
10
11pub const EVERYTHING_IPC_IS_DB_LOADED: u32 = 401;
12pub const EVERYTHING_IPC_IS_FILE_INFO_INDEXED: u32 = 411;
13
14pub fn register_ipc_created_message() -> u32 {
16 use windows::Win32::UI::WindowsAndMessaging::RegisterWindowMessageW;
17
18 unsafe { RegisterWindowMessageW(PCWSTR(u16cstr!("EVERYTHING_IPC_CREATED").as_ptr())) }
19}
20
21#[repr(transparent)]
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
24pub struct SearchFlags(u32);
25
26bitflags::bitflags! {
27 impl SearchFlags: u32 {
28 const MatchCase = 0x00000001;
29 const MatchWholeWord = 0x00000002;
30 const MatchPath = 0x00000004;
31 const Regex = 0x00000008;
32 const MatchAccents = 0x00000010;
34 }
35}
36
37#[repr(u32)]
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, TryFrom)]
40#[try_from(repr)]
41pub enum Sort {
42 #[default]
44 NameAscending = 1,
45 NameDescending = 2,
46 PathAscending = 3,
47 PathDescending = 4,
48 SizeAscending = 5,
49 SizeDescending = 6,
50 ExtensionAscending = 7,
51 ExtensionDescending = 8,
52 TypeNameAscending = 9,
53 TypeNameDescending = 10,
54 DateCreatedAscending = 11,
55 DateCreatedDescending = 12,
56 DateModifiedAscending = 13,
57 DateModifiedDescending = 14,
58 AttributesAscending = 15,
59 AttributesDescending = 16,
60 FileListFilenameAscending = 17,
61 FileListFilenameDescending = 18,
62 RunCountAscending = 19,
63 RunCountDescending = 20,
64 DateRecentlyChangedAscending = 21,
65 DateRecentlyChangedDescending = 22,
66 DateAccessedAscending = 23,
67 DateAccessedDescending = 24,
68 DateRunAscending = 25,
69 DateRunDescending = 26,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum FileInfo {
75 FileSize = 1,
76 FolderSize = 2,
77 DateCreated = 3,
78 DateModified = 4,
79 DateAccessed = 5,
80 Attributes = 6,
81}
82
83#[repr(transparent)]
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
86pub struct RequestFlags(u32);
87
88bitflags::bitflags! {
89 impl RequestFlags: u32 {
90 const FileName = 0x00000001;
91 const Path = 0x00000002;
92 const FullPathAndFileName = 0x00000004;
93 const Extension = 0x00000008;
94 const Size = 0x00000010;
95 const DateCreated = 0x00000020;
96 const DateModified = 0x00000040;
97 const DateAccessed = 0x00000080;
98 const Attributes = 0x00000100;
99 const FileListFileName = 0x00000200;
100 const RunCount = 0x00000400;
101 const DateRun = 0x00000800;
102 const DateRecentlyChanged = 0x00001000;
103 const HighlightedFileName = 0x00002000;
104 const HighlightedPath = 0x00004000;
105 const HighlightedFullPathAndFileName = 0x00008000;
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum RequestDataType {
112 Str,
113 Size,
114 Date,
115 Dword,
116}
117
118impl RequestFlags {
119 pub fn data_type(self) -> RequestDataType {
121 match self {
122 Self::FileName
123 | Self::Path
124 | Self::FullPathAndFileName
125 | Self::Extension
126 | Self::FileListFileName
127 | Self::HighlightedFileName
128 | Self::HighlightedPath
129 | Self::HighlightedFullPathAndFileName => RequestDataType::Str,
130 Self::Size => RequestDataType::Size,
131 Self::DateCreated
132 | Self::DateModified
133 | Self::DateAccessed
134 | Self::DateRun
135 | Self::DateRecentlyChanged => RequestDataType::Date,
136 Self::Attributes | Self::RunCount => RequestDataType::Dword,
137 _ => panic!("Invalid request flag"),
138 }
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq)]
144pub enum QueryValue<'a> {
145 Str(&'a U16CStr),
147 Size(u64),
149 Time(FILETIME),
151 U32(u32),
153}
154
155#[repr(C)]
158#[derive(Debug, Clone, Copy)]
159pub struct EverythingIpcList2 {
160 pub totitems: u32, pub numitems: u32, pub offset: u32, pub request_flags: u32, pub sort_type: u32, }
166
167#[repr(C)]
170#[derive(Debug, Clone, Copy)]
171pub struct EverythingIpcItem2 {
172 pub flags: u32,
174 pub data_offset: u32,
175}
176
177#[repr(C, packed)]
182pub struct EverythingIpcQuery2 {
183 pub reply_hwnd: u32,
184 pub reply_copydata_message: u32,
185 pub search_flags: u32,
186 pub offset: u32,
187 pub max_results: u32,
188 pub request_flags: u32,
189 pub sort_type: u32,
190 pub search_string: [u16; 1],
192}
193
194impl EverythingIpcQuery2 {
195 pub fn size_for_search(search_len: usize) -> usize {
198 mem::size_of::<Self>() + (search_len * mem::size_of::<u16>())
199 }
200
201 pub fn create(
204 reply_hwnd: u32,
205 reply_copydata_message: u32,
206 search_flags: u32,
207 offset: u32,
208 max_results: u32,
209 request_flags: u32,
210 sort_type: u32,
211 search: &str,
212 ) -> Vec<u8> {
213 let search_len = search.encode_utf16().count();
214 let total_size = Self::size_for_search(search_len);
215
216 let mut buffer = Vec::with_capacity(total_size);
217
218 buffer.extend_from_slice(&reply_hwnd.to_le_bytes());
220 buffer.extend_from_slice(&reply_copydata_message.to_le_bytes());
221 buffer.extend_from_slice(&search_flags.to_le_bytes());
222 buffer.extend_from_slice(&offset.to_le_bytes());
223 buffer.extend_from_slice(&max_results.to_le_bytes());
224 buffer.extend_from_slice(&request_flags.to_le_bytes());
225 buffer.extend_from_slice(&sort_type.to_le_bytes());
226
227 for ch in search.encode_utf16() {
230 buffer.extend_from_slice(&ch.to_le_bytes());
231 }
232 buffer.extend_from_slice(&0u16.to_le_bytes());
234
235 buffer
236 }
237}
238
239pub struct QueryItem<'a> {
250 request: RequestFlags,
251 data: &'a [u8],
252}
253
254impl std::fmt::Debug for QueryItem<'_> {
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 let mut debug = f.debug_struct("QueryItem");
257 debug.field("request", &self.request);
258 for (name, _flag, value) in self.items_with_name() {
259 debug.field(name, &value);
260 }
261 debug.finish()
262 }
263}
264
265impl QueryItem<'_> {
266 pub(crate) fn new<'a>(request: RequestFlags, data: &'a [u8]) -> QueryItem<'a> {
267 QueryItem { request, data }
268 }
269
270 pub fn get_ptr(&self, flag: RequestFlags) -> Option<*const u8> {
272 if (self.request.bits() & flag.bits()) == 0 {
273 return None;
274 }
275
276 let mut offset = 0;
277 for f in [
278 RequestFlags::FileName,
279 RequestFlags::Path,
280 RequestFlags::FullPathAndFileName,
281 RequestFlags::Extension,
282 RequestFlags::Size,
283 RequestFlags::DateCreated,
284 RequestFlags::DateModified,
285 RequestFlags::DateAccessed,
286 RequestFlags::Attributes,
287 RequestFlags::FileListFileName,
288 RequestFlags::RunCount,
289 RequestFlags::DateRun,
290 RequestFlags::DateRecentlyChanged,
291 RequestFlags::HighlightedFileName,
292 RequestFlags::HighlightedPath,
293 RequestFlags::HighlightedFullPathAndFileName,
294 ] {
295 if (self.request.bits() & f.bits()) == 0 {
296 continue;
297 }
298 if f == flag {
299 let ptr = unsafe { self.data.as_ptr().add(offset) };
300 return Some(ptr);
301 }
302 match f.data_type() {
303 RequestDataType::Str => {
304 let len = unsafe {
305 self.data
306 .as_ptr()
307 .add(offset)
308 .cast::<u32>()
309 .read_unaligned()
310 } as usize;
311 offset += mem::size_of::<u32>() + (len + 1) * mem::size_of::<u16>();
312 }
313 RequestDataType::Size => {
314 offset += mem::size_of::<u64>();
315 }
316 RequestDataType::Date => {
317 offset += mem::size_of::<FILETIME>();
318 }
319 RequestDataType::Dword => {
320 offset += mem::size_of::<u32>();
321 }
322 }
323 }
324 None
325 }
326
327 pub fn get_str(&self, flag: RequestFlags) -> Option<&U16CStr> {
329 let ptr = self.get_ptr(flag)?;
330 let len = unsafe { ptr.cast::<u32>().read_unaligned() } as usize;
331 let str_ptr = unsafe { ptr.add(mem::size_of::<u32>()) as *const u16 };
332 Some(unsafe { U16CStr::from_ptr_unchecked(str_ptr, len) })
333 }
334
335 pub fn get_string(&self, flag: RequestFlags) -> Option<String> {
339 self.get_str(flag).map(|s| s.to_string_lossy())
340 }
341
342 pub fn get_size(&self, flag: RequestFlags) -> Option<u64> {
344 let ptr = self.get_ptr(flag)?;
345 Some(unsafe { ptr.cast::<u64>().read_unaligned() })
346 }
347
348 pub fn get_time(&self, flag: RequestFlags) -> Option<FILETIME> {
350 let ptr = self.get_ptr(flag)?;
351 Some(unsafe { ptr.cast::<FILETIME>().read_unaligned() })
352 }
353
354 pub fn get_u32(&self, flag: RequestFlags) -> Option<u32> {
356 let ptr = self.get_ptr(flag)?;
357 Some(unsafe { ptr.cast::<u32>().read_unaligned() })
358 }
359
360 pub fn get(&self, flag: RequestFlags) -> Option<QueryValue<'_>> {
362 match flag.data_type() {
363 RequestDataType::Str => self.get_str(flag).map(QueryValue::Str),
364 RequestDataType::Size => self.get_size(flag).map(QueryValue::Size),
365 RequestDataType::Date => self.get_time(flag).map(QueryValue::Time),
366 RequestDataType::Dword => self.get_u32(flag).map(QueryValue::U32),
367 }
368 }
369
370 pub fn keys(&self) -> impl Iterator<Item = RequestFlags> {
372 self.request.iter()
373 }
374
375 pub fn items(&self) -> impl Iterator<Item = (RequestFlags, QueryValue<'_>)> {
377 self.request.iter().map(|r| (r, self.get(r).unwrap()))
378 }
379
380 pub fn items_with_name(
382 &self,
383 ) -> impl Iterator<Item = (&'static str, RequestFlags, QueryValue<'_>)> {
384 self.request
385 .iter_names()
386 .map(|(n, r)| (n, r, self.get(r).unwrap()))
387 }
388}
389
390pub struct QueryList {
395 id: u32,
396 data: Box<[u8]>,
397 offset: u32,
398 len: u32,
399 total_len: u32,
400 request_flags: RequestFlags,
401 sort: Sort,
402}
403
404impl std::fmt::Debug for QueryList {
405 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
406 let mut debug = f.debug_struct("QueryList");
407 debug
408 .field("id", &self.id)
409 .field("len", &self.len)
410 .field("offset", &self.offset)
411 .field("total_len", &self.total_len)
412 .field("request_flags", &self.request_flags)
413 .field("sort", &self.sort);
414
415 let items: Vec<_> = self.iter().collect();
416 debug.field("items", &items).finish()
417 }
418}
419
420impl QueryList {
421 pub(crate) fn new(id: u32, data: Box<[u8]>) -> Self {
422 let header = unsafe { std::ptr::read(data.as_ptr() as *const EverythingIpcList2) };
424 Self {
425 id,
426 data: data.into(),
427 len: header.numitems,
428 offset: header.offset,
429 total_len: header.totitems,
430 request_flags: RequestFlags::from_bits_truncate(header.request_flags),
431 sort: header.sort_type.try_into().unwrap(),
432 }
433 }
434
435 pub fn id(&self) -> u32 {
437 self.id
438 }
439
440 pub fn is_empty(&self) -> bool {
442 self.len() == 0
443 }
444
445 pub fn offset(&self) -> u32 {
447 self.offset
448 }
449
450 #[doc(alias = "available_num")]
452 pub fn len(&self) -> usize {
453 self.len as usize
454 }
455
456 #[doc(alias = "found_num")]
458 pub fn total_len(&self) -> usize {
459 self.total_len as usize
460 }
461
462 pub fn request_flags(&self) -> RequestFlags {
464 self.request_flags
465 }
466
467 pub fn sort(&self) -> Sort {
469 self.sort
470 }
471
472 pub fn get(&self, index: usize) -> Option<QueryItem<'_>> {
474 if index >= self.len() {
475 return None;
476 }
477
478 let items_offset = mem::size_of::<EverythingIpcList2>();
485 let item_ptr = unsafe { self.data.as_ptr().add(items_offset + index * 8) };
486 let item: EverythingIpcItem2 =
487 unsafe { std::ptr::read(item_ptr as *const EverythingIpcItem2) };
488
489 let data_offset = item.data_offset as usize;
491 let data_ptr = unsafe { self.data.as_ptr().add(data_offset) };
492
493 let data_len = self.data.len() - data_offset;
495
496 let data_slice = unsafe { std::slice::from_raw_parts(data_ptr as *const u8, data_len) };
498
499 Some(QueryItem::new(self.request_flags, data_slice))
500 }
501
502 pub fn iter(&self) -> QueryListIter<'_> {
504 QueryListIter {
505 list: self,
506 index: 0,
507 }
508 }
509}
510
511pub struct QueryListIter<'a> {
513 list: &'a QueryList,
514 index: usize,
515}
516
517impl<'a> Iterator for QueryListIter<'a> {
518 type Item = QueryItem<'a>;
519
520 fn next(&mut self) -> Option<Self::Item> {
521 let item = self.list.get(self.index);
522 self.index += 1;
523 item
524 }
525
526 fn size_hint(&self) -> (usize, Option<usize>) {
527 let size = self.list.len().saturating_sub(self.index);
528 (size, Some(size))
529 }
530}
531
532impl<'a> ExactSizeIterator for QueryListIter<'a> {}
533
534#[cfg(test)]
535mod tests {
536 use super::*;
537
538 #[test]
539 fn search_flags_combinations() {
540 let flags = SearchFlags::MatchCase | SearchFlags::MatchWholeWord;
541 assert_eq!(flags.bits(), 0x00000003);
542 }
543
544 #[test]
545 fn request_flags_combinations() {
546 let flags = RequestFlags::FileName | RequestFlags::Size | RequestFlags::DateModified;
547 assert_eq!(flags.bits(), 0x00000051); }
549
550 #[test]
551 fn request_flags_data_type() {
552 assert_eq!(RequestFlags::FileName.data_type(), RequestDataType::Str);
553 assert_eq!(RequestFlags::Size.data_type(), RequestDataType::Size);
554 assert_eq!(RequestFlags::DateCreated.data_type(), RequestDataType::Date);
555 assert_eq!(RequestFlags::Attributes.data_type(), RequestDataType::Dword);
556 }
557
558 #[test]
559 fn sort_default() {
560 assert_eq!(Sort::default(), Sort::NameAscending);
561 }
562
563 #[test]
564 fn version_default() {
565 let version: crate::Version = Default::default();
566 assert_eq!(version.major, 0);
567 assert_eq!(version.minor, 0);
568 assert_eq!(version.revision, 0);
569 assert_eq!(version.build, 0);
570 }
571
572 #[test]
573 fn search_flags_not() {
574 let flags = !SearchFlags::MatchCase;
575 assert_eq!(flags.bits(), 0x1E);
578 }
579
580 #[test]
581 fn request_flags_not() {
582 let flags = !RequestFlags::FileName;
583 assert_eq!(flags.bits(), 0xFFFE);
586 }
587
588 #[test]
589 fn everything_ipc_query2_size() {
590 assert_eq!(EverythingIpcQuery2::size_for_search(0), 30); let search_size = 30 + 4 * 2; assert_eq!(EverythingIpcQuery2::size_for_search(4), search_size);
599 }
600
601 #[test]
602 fn everything_ipc_query2_create() {
603 let buffer = EverythingIpcQuery2::create(
604 1234,
605 5678,
606 0,
607 0,
608 10,
609 RequestFlags::FileName.bits(),
610 Sort::NameAscending as u32,
611 "test",
612 );
613
614 assert_eq!(buffer.len(), 38);
616
617 assert_eq!(u32::from_le_bytes(buffer[0..4].try_into().unwrap()), 1234);
619 assert_eq!(u32::from_le_bytes(buffer[4..8].try_into().unwrap()), 5678);
620 assert_eq!(u32::from_le_bytes(buffer[8..12].try_into().unwrap()), 0);
621 assert_eq!(u32::from_le_bytes(buffer[12..16].try_into().unwrap()), 0);
622 assert_eq!(u32::from_le_bytes(buffer[16..20].try_into().unwrap()), 10);
623 assert_eq!(
624 u32::from_le_bytes(buffer[20..24].try_into().unwrap()),
625 RequestFlags::FileName.bits()
626 );
627 assert_eq!(
628 u32::from_le_bytes(buffer[24..28].try_into().unwrap()),
629 Sort::NameAscending as u32
630 );
631 }
632
633 fn make_query_data(
637 found_num: u32,
638 available_num: u32,
639 request_flags: u32,
640 sort: Sort,
641 items: &[(u32, u32)],
642 data: &[u8],
643 ) -> Box<[u8]> {
644 let mut result = Vec::new();
645 result.extend_from_slice(&found_num.to_le_bytes());
646 result.extend_from_slice(&available_num.to_le_bytes());
647 result.extend_from_slice(&0u32.to_le_bytes()); result.extend_from_slice(&request_flags.to_le_bytes());
649 result.extend_from_slice(&(sort as u32).to_le_bytes());
650
651 for (flags, offset) in items {
653 result.extend_from_slice(&flags.to_le_bytes());
654 result.extend_from_slice(&offset.to_le_bytes());
655 }
656
657 result.extend_from_slice(data);
659 result.into_boxed_slice()
660 }
661
662 #[test]
663 fn query_list_empty() {
664 let data = make_query_data(0, 0, 0, Sort::default(), &[], b"");
667 let results = QueryList::new(0, data);
668 assert!(results.is_empty());
669 assert_eq!(results.len(), 0);
670 assert_eq!(results.total_len(), 0);
671 assert!(results.get(0).is_none());
672 }
673
674 #[test]
675 fn query_list_with_items() {
676 let data = make_query_data(
682 1,
683 1,
684 RequestFlags::FileName.bits(),
685 Sort::NameAscending,
686 &[(RequestFlags::FileName.bits(), 20)], b"",
688 );
689 let results = QueryList::new(0, data);
690 assert_eq!(results.total_len(), 1);
691 assert_eq!(results.len(), 1);
692 assert_eq!(results.request_flags(), RequestFlags::FileName);
693 }
694
695 #[test]
696 fn query_list_multiple_items() {
697 let data = make_query_data(
698 2,
699 2,
700 RequestFlags::FileName.bits(),
701 Sort::NameAscending,
702 &[
703 (RequestFlags::FileName.bits(), 20), (RequestFlags::FileName.bits(), 28), ],
706 b"",
707 );
708 let results = QueryList::new(0, data);
709 assert_eq!(results.total_len(), 2);
710 assert_eq!(results.len(), 2);
711 }
712
713 #[test]
714 fn query_list_sort_parsing() {
715 for (sort_value, expected_sort) in [
716 (1, Sort::NameAscending),
717 (2, Sort::NameDescending),
718 (3, Sort::PathAscending),
719 (4, Sort::PathDescending),
720 (5, Sort::SizeAscending),
721 (6, Sort::SizeDescending),
722 (7, Sort::ExtensionAscending),
723 (8, Sort::ExtensionDescending),
724 ] {
725 let data = make_query_data(0, 0, 0, expected_sort, &[], b"");
726 let results = QueryList::new(0, data);
727 assert_eq!(results.sort(), expected_sort, "sort value {}", sort_value);
728 }
729 }
730
731 #[test]
732 fn query_item_str() {
733 let string_data: Vec<u8> = vec![4, 0, 0, 0, 116, 0, 101, 0, 115, 0, 116, 0, 0, 0];
741 let data = make_query_data(
742 1,
743 1,
744 RequestFlags::FileName.bits(),
745 Sort::NameAscending,
746 &[(RequestFlags::FileName.bits(), 28)], &string_data,
748 );
749 let results = QueryList::new(0, data);
750
751 if let Some(item) = results.get(0) {
753 if let Some(str_val) = item.get_str(RequestFlags::FileName) {
754 assert_eq!(str_val.to_string_lossy(), "test");
755 } else {
756 panic!("Expected FILE_NAME string");
757 }
758 } else {
759 panic!("Expected item at index 0");
760 }
761 }
762
763 #[test]
764 fn query_list_iter() {
765 let data = make_query_data(
766 2,
767 2,
768 RequestFlags::FileName.bits(),
769 Sort::NameAscending,
770 &[
771 (RequestFlags::FileName.bits(), 20), (RequestFlags::FileName.bits(), 28), ],
774 b"",
775 );
776 let results = QueryList::new(0, data);
777
778 let mut count = 0;
779 for _ in results.iter() {
780 count += 1;
781 }
782 assert_eq!(count, 2);
783 }
784
785 #[test]
786 fn query_item_out_of_bounds() {
787 let data = make_query_data(0, 0, 0, Sort::default(), &[], b"");
788 let results = QueryList::new(0, data);
789 assert!(results.get(0).is_none());
790 }
791
792 #[test]
793 fn filetime_structure() {
794 let ft = FILETIME {
795 dwLowDateTime: 0x12345678,
796 dwHighDateTime: 0x9ABCDEF0,
797 };
798 assert_eq!(ft.dwLowDateTime, 0x12345678);
799 assert_eq!(ft.dwHighDateTime, 0x9ABCDEF0);
800 }
801
802 #[test]
803 fn query_list_get_by_index() {
804 let data = make_query_data(
805 1,
806 1,
807 RequestFlags::FileName.bits(),
808 Sort::NameAscending,
809 &[(RequestFlags::FileName.bits(), 20)], b"",
811 );
812 let results = QueryList::new(0, data);
813
814 assert!(results.get(0).is_some());
815 assert!(results.get(1).is_none()); }
817
818 #[test]
819 fn request_flags_all() {
820 let flags = [
822 RequestFlags::FileName,
823 RequestFlags::Path,
824 RequestFlags::FullPathAndFileName,
825 RequestFlags::Extension,
826 RequestFlags::Size,
827 RequestFlags::DateCreated,
828 RequestFlags::DateModified,
829 RequestFlags::DateAccessed,
830 RequestFlags::Attributes,
831 RequestFlags::FileListFileName,
832 RequestFlags::RunCount,
833 RequestFlags::DateRun,
834 RequestFlags::DateRecentlyChanged,
835 RequestFlags::HighlightedFileName,
836 RequestFlags::HighlightedPath,
837 RequestFlags::HighlightedFullPathAndFileName,
838 ];
839
840 let mut seen = std::collections::HashSet::new();
841 for flag in flags {
842 assert!(
843 !seen.contains(&flag.0),
844 "Duplicate flag value: {:#x}",
845 flag.0
846 );
847 seen.insert(flag.0);
848 }
849 }
850}