1 /*
2 * Win32StringUtils.cpp
3 *
4 * Copyright (C) 2021 by RStudio, PBC
5 *
6 * Unless you have received this program directly from RStudio pursuant
7 * to the terms of a commercial license agreement with RStudio, then
8 * this program is licensed to you under the terms of version 3 of the
9 * GNU Affero General Public License. This program is distributed WITHOUT
10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13 *
14 */
15
16 #include <shared_core/system/User.hpp>
17
18 #include <shlobj.h>
19
20 #include <boost/algorithm/string.hpp>
21 #include <boost/bind/bind.hpp>
22
23 #include <shared_core/FilePath.hpp>
24 #include <shared_core/Logger.hpp>
25 #include <shared_core/SafeConvert.hpp>
26 #include <shared_core/system/Win32StringUtils.hpp>
27
28 using namespace boost::placeholders;
29
30 namespace rstudio {
31 namespace core {
32 namespace system {
33
34 // home path strategies
35 namespace {
36
environmentHomePath(std::string envVariables)37 FilePath environmentHomePath(std::string envVariables)
38 {
39 using namespace boost::algorithm;
40
41 // use environment override if specified
42 if (!envVariables.empty())
43 {
44 for (split_iterator<std::string::iterator> it =
45 make_split_iterator(envVariables, first_finder("|", is_iequal()));
46 it != split_iterator<std::string::iterator>();
47 ++it)
48 {
49 std::string envHomePath =
50 detail::getenv(boost::copy_range<std::string>(*it));
51 if (!envHomePath.empty())
52 {
53 FilePath userHomePath(envHomePath);
54 if (userHomePath.exists())
55 return userHomePath;
56 }
57 }
58 }
59
60 // no override
61 return FilePath();
62 }
63
currentCSIDLPersonalHomePath()64 FilePath currentCSIDLPersonalHomePath()
65 {
66 // query for My Documents directory
67 const DWORD SHGFP_TYPE_CURRENT = 0;
68 wchar_t homePath[MAX_PATH];
69 HRESULT hr = ::SHGetFolderPathW(nullptr,
70 CSIDL_PERSONAL,
71 nullptr,
72 SHGFP_TYPE_CURRENT,
73 homePath);
74 if (SUCCEEDED(hr))
75 {
76 return FilePath(homePath);
77 }
78 else
79 {
80 log::logWarningMessage("Unable to retreive user home path. HRESULT: " +
81 safe_convert::numberToString(hr));
82 return FilePath();
83 }
84 }
85
defaultCSIDLPersonalHomePath()86 FilePath defaultCSIDLPersonalHomePath()
87 {
88 // query for default and force creation (works around situations
89 // where redirected path is not available)
90 const DWORD SHGFP_TYPE_DEFAULT = 1;
91 wchar_t homePath[MAX_PATH];
92 HRESULT hr = ::SHGetFolderPathW(nullptr,
93 CSIDL_PERSONAL|CSIDL_FLAG_CREATE,
94 nullptr,
95 SHGFP_TYPE_DEFAULT,
96 homePath);
97 if (SUCCEEDED(hr))
98 {
99 return FilePath(homePath);
100 }
101 else
102 {
103 log::logWarningMessage("Unable to retreive user home path. HRESULT: " +
104 safe_convert::numberToString(hr));
105 return FilePath();
106 }
107 }
108
homepathHomePath()109 FilePath homepathHomePath()
110 {
111 std::string homeDrive = detail::getenv("HOMEDRIVE");
112 std::string homePath = detail::getenv("HOMEPATH");
113 if (!homeDrive.empty() && !homePath.empty())
114 return FilePath(homeDrive + homePath);
115 else
116 return FilePath();
117 }
118
homedriveHomePath()119 FilePath homedriveHomePath()
120 {
121 std::string homeDrive = detail::getenv("HOMEDRIVE");
122 if (homeDrive.empty())
123 homeDrive = "C:";
124 return FilePath(homeDrive);
125 }
126
127 typedef std::pair<std::string,boost::function<FilePath()> > HomePathSource;
128
129 } // anonymous namespace
130
131 namespace detail {
132
getenv(const std::string & name)133 std::string getenv(const std::string& name)
134 {
135 std::wstring nameWide(name.begin(), name.end());
136
137 // get the variable
138 DWORD nSize = 256;
139 std::vector<wchar_t> buffer(nSize);
140 DWORD result = ::GetEnvironmentVariableW(nameWide.c_str(), &(buffer[0]), nSize);
141 if (result == 0) // not found
142 {
143 return std::string();
144 }
145 if (result > nSize) // not enough space in buffer
146 {
147 nSize = result;
148 buffer.resize(nSize);
149 result = ::GetEnvironmentVariableW(nameWide.c_str(), &(buffer[0]), nSize);
150 if (result == 0 || result > nSize)
151 return std::string(); // VERY unexpected failure case
152 }
153
154 // return it
155 return string_utils::wideToUtf8(&(buffer[0]));
156 }
157
158 } // namespace detail
159
160
getUserHomePath(const std::string & in_envOverride)161 FilePath User::getUserHomePath(const std::string& in_envOverride)
162 {
163 using boost::bind;
164 std::vector<HomePathSource> sources;
165 sources.push_back(std::make_pair("R_USER|HOME",
166 bind(environmentHomePath, in_envOverride)));
167 sources.push_back(std::make_pair("SHGFP_TYPE_CURRENT",
168 currentCSIDLPersonalHomePath));
169 sources.push_back(std::make_pair("SHGFP_TYPE_DEFAULT",
170 defaultCSIDLPersonalHomePath));
171 std::string envFallback = "USERPROFILE";
172 sources.push_back(std::make_pair(envFallback,
173 bind(environmentHomePath, envFallback)));
174 sources.push_back(std::make_pair("HOMEPATH",
175 homepathHomePath));
176 sources.push_back(std::make_pair("HOMEDRIVE",
177 homedriveHomePath));
178
179 for (const HomePathSource& source : sources)
180 {
181 FilePath homePath = source.second();
182 if (!homePath.isEmpty())
183 {
184 // return if we found one that exists
185 if (homePath.exists())
186 {
187 std::string path = homePath.getAbsolutePath();
188
189 // standardize drive letter capitalization if in X:/y/z format
190 if (path.length() > 1 && path[1] == ':')
191 {
192 path[0] = toupper(path[0]);
193 homePath = FilePath(path);
194 }
195
196 return homePath;
197 }
198
199 // otherwise warn that we got a value that didn't exist
200 log::logWarningMessage("Home path returned by " + source.first + " (" +
201 homePath.getAbsolutePath() + ") does not exist.");
202 }
203 }
204
205 // no luck!
206 log::logErrorMessage("No valid home path found for user");
207 return FilePath();
208 }
209
210
211 } // namesapce system
212 } // namespace core
213 } // namespace rstudio
214