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