1 //! Windows provides three different ways to get the paths to roaming and local
2 //! app data: environment variables, KNOWNFOLDERID, and CSIDL. From the CSIDL
3 //! documentation:
4 //!
5 //! *"These values supersede the use of environment variables for this purpose.
6 //! They are in turn superseded in Windows Vista and later by the KNOWNFOLDERID
7 //! values."*
8 //! - https://msdn.microsoft.com/en-us/library/windows/desktop/bb762494.aspx
9 //!
10 //! -_-
11 
12 // The function get_folder_path was adapted from:
13 // https://github.com/AndyBarron/preferences-rs/blob/f03c7/src/lib.rs#L211-L296
14 //
15 // Credit for the above code goes to Connorcpu (https://github.com/Connorcpu).
16 
17 extern crate ole32;
18 extern crate shell32;
19 extern crate winapi;
20 use AppDataType::*;
21 use common::*;
22 use self::shell32::SHGetKnownFolderPath;
23 use self::winapi::{GUID, PWSTR};
24 use std::ffi::OsString;
25 use std::os::windows::ffi::OsStringExt;
26 use std::path::PathBuf;
27 use std::ptr;
28 use std::slice;
29 
30 pub const USE_AUTHOR: bool = true;
31 
get_app_dir(t: AppDataType) -> Result<PathBuf, AppDirsError>32 pub fn get_app_dir(t: AppDataType) -> Result<PathBuf, AppDirsError> {
33     let folder_id = match t {
34         UserConfig => &FOLDERID_RoamingAppData,
35         SharedConfig | SharedData => &FOLDERID_ProgramData,
36         UserCache | UserData => &FOLDERID_LocalAppData,
37     };
38     get_folder_path(folder_id).map(|os_str| os_str.into())
39 }
40 
41 /// https://msdn.microsoft.com/en-us/library/dd378457.aspx#FOLDERID_RoamingAppData
42 #[allow(non_upper_case_globals)]
43 static FOLDERID_RoamingAppData: GUID = GUID {
44     Data1: 0x3EB685DB,
45     Data2: 0x65F9,
46     Data3: 0x4CF6,
47     Data4: [0xA0, 0x3A, 0xE3, 0xEF, 0x65, 0x72, 0x9F, 0x3D],
48 };
49 
50 /// https://msdn.microsoft.com/en-us/library/dd378457.aspx#FOLDERID_LocalAppData
51 #[allow(non_upper_case_globals)]
52 static FOLDERID_LocalAppData: GUID = GUID {
53     Data1: 0xF1B32785,
54     Data2: 0x6FBA,
55     Data3: 0x4FCF,
56     Data4: [0x9D, 0x55, 0x7B, 0x8E, 0x7F, 0x15, 0x70, 0x91],
57 };
58 
59 /// https://msdn.microsoft.com/en-us/library/dd378457.aspx#FOLDERID_ProgramData
60 #[allow(non_upper_case_globals)]
61 static FOLDERID_ProgramData: GUID = GUID {
62     Data1: 0x62AB5D82,
63     Data2: 0xFDC1,
64     Data3: 0x4DC3,
65     Data4: [0xA9, 0xDD, 0x07, 0x0D, 0x1D, 0x49, 0x5D, 0x97],
66 };
67 
68 /// Wrapper around `winapi::PWSTR` to automatically free the string pointer.
69 /// This ensures the memory is freed when `get_folder_path` scope is left,
70 /// regardless of whether the call succeeded or failed/panicked.
71 struct SafePwstr(PWSTR);
72 impl Drop for SafePwstr {
drop(&mut self)73     fn drop(&mut self) {
74         unsafe { ole32::CoTaskMemFree(self.0 as *mut _) }
75     }
76 }
77 
get_folder_path(folder_id: &GUID) -> Result<OsString, AppDirsError>78 fn get_folder_path(folder_id: &GUID) -> Result<OsString, AppDirsError> {
79     unsafe {
80         // Wide C string to be allocated by SHGetKnownFolderPath.
81         // We are responsible for freeing this!
82         let mut raw_path: PWSTR = ptr::null_mut();
83 
84         // SHGetKnownFolderPath arguments:
85         // 1. reference to KNOWNFOLDERID
86         // 2. no flags
87         // 3. null handle -> current user
88         // 4. output location
89         let result = SHGetKnownFolderPath(folder_id, 0, ptr::null_mut(), &mut raw_path);
90 
91         // SHGetKnownFolderPath shouldn't ever fail, but if it does,
92         // it will return a negative HRESULT.
93         if result < 0 {
94             return Err(AppDirsError::NotSupported);
95         }
96 
97         // Ensures that the PWSTR is free when we leave this scope through
98         // normal execution or a thread panic.
99         let _cleanup = SafePwstr(raw_path);
100 
101         // Manually calculate length of wide C string.
102         let mut length = 0;
103         for i in 0.. {
104             if *raw_path.offset(i) == 0 {
105                 length = i as usize;
106                 break;
107             }
108         }
109 
110         let wpath: &[u16] = slice::from_raw_parts(raw_path, length);
111         let path: OsString = OsStringExt::from_wide(wpath);
112         Ok(path)
113         // _cleanup is deallocated, so raw_path is freed
114     }
115 }
116