1 /*
2  * SessionUpdates.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 "SessionUpdates.hpp"
17 
18 #include <shared_core/Error.hpp>
19 #include <core/Exec.hpp>
20 #include <core/system/Process.hpp>
21 #include <core/system/Environment.hpp>
22 
23 #include <boost/bind/bind.hpp>
24 
25 #include <session/SessionModuleContext.hpp>
26 
27 #include <session/prefs/UserPrefs.hpp>
28 
29 #include <string>
30 
31 #include "session-config.h"
32 
33 using namespace rstudio::core;
34 using namespace boost::placeholders;
35 
36 namespace rstudio {
37 namespace session {
38 namespace modules {
39 namespace updates {
40 namespace {
41 
jsonFromProcessResult(const core::system::ProcessResult & result)42 json::Object jsonFromProcessResult(const core::system::ProcessResult& result)
43 {
44    json::Object obj;
45    std::stringstream output(result.stdOut);
46    // The output looks like:
47    // key1=value1
48    // key2=value2
49    // ...
50    for (std::string line; std::getline(output, line); )
51    {
52       size_t pos = line.find('=');
53       if (pos > 0)
54       {
55          obj[line.substr(0, pos)] = line.substr(pos + 1,
56                                                 line.length() - (pos + 1));
57       }
58    }
59    return obj;
60 }
61 
beginUpdateCheck(bool manual,const boost::function<void (const core::system::ProcessResult &)> & onCompleted)62 void beginUpdateCheck(bool manual,
63    const boost::function<void(const core::system::ProcessResult&)>& onCompleted)
64 {
65    using namespace module_context;
66 
67    // Find the path to R
68    FilePath rProgramPath;
69    Error error = module_context::rScriptPath(&rProgramPath);
70    if (error)
71    {
72       return;
73    }
74 
75    // Find the path to the script we need to source
76    FilePath modulesPath = session::options().modulesRSourcePath();;
77    std::string scriptPath = core::string_utils::utf8ToSystem(
78       modulesPath.completePath("SessionUpdates.R").getAbsolutePath());
79 
80    // Arguments
81    std::vector<std::string> args;
82    args.push_back("--vanilla");
83 
84 #if defined(_WIN32)
85    if (prefs::userPrefs().useInternet2())
86    {
87       args.push_back("--internet2");
88    }
89 #endif
90 
91    args.push_back("-s");
92    args.push_back("-e");
93 
94    // Build the command to send to R
95    std::string cmd;
96    cmd.append("source('");
97    cmd.append(string_utils::jsLiteralEscape(scriptPath));
98    cmd.append("'); downloadUpdateInfo('");
99    cmd.append(http::util::urlEncode(RSTUDIO_VERSION));
100    cmd.append("', '");
101 #if defined(_WIN32)
102    cmd.append("windows");
103 #elif defined(__APPLE__)
104    cmd.append("mac");
105 #else
106    cmd.append("linux");
107 #endif
108    cmd.append("', ");
109    cmd.append(manual ? "TRUE" : "FALSE");
110    cmd.append(", ");
111    cmd.append(haveSecureDownloadFileMethod() ? "TRUE" : "FALSE");
112    cmd.append(", '");
113    cmd.append(downloadFileMethod("auto"));
114    cmd.append("'");
115    cmd.append(")");
116    args.push_back(cmd);
117 
118    LOG_DEBUG_MESSAGE("Checking for updates with command: " + cmd);
119 
120    // Set options
121    core::system::ProcessOptions options;
122    options.terminateChildren = true;
123 
124    module_context::processSupervisor().runProgram(
125       rProgramPath.getAbsolutePath(),
126       args,
127       std::string(),
128       options,
129       onCompleted);
130 }
131 
endRPCUpdateCheck(const json::JsonRpcFunctionContinuation & cont,const core::system::ProcessResult & result)132 void endRPCUpdateCheck(const json::JsonRpcFunctionContinuation& cont,
133                        const core::system::ProcessResult& result)
134 {
135    json::JsonRpcResponse response;
136    response.setResult(jsonFromProcessResult(result));
137    cont(Success(), &response);
138 }
139 
checkForUpdates(const json::JsonRpcRequest & request,const json::JsonRpcFunctionContinuation & cont)140 void checkForUpdates(const json::JsonRpcRequest& request,
141                      const json::JsonRpcFunctionContinuation& cont)
142 {
143    bool manual = false;
144    Error error = json::readParam(request.params, 0, &manual);
145    if (error)
146    {
147       json::JsonRpcResponse response;
148       cont(error, &response);
149       return;
150    }
151    beginUpdateCheck(manual, boost::bind(endRPCUpdateCheck, cont, _1));
152 }
153 
154 } // anonymous namespace
155 
initialize()156 Error initialize()
157 {
158    using boost::bind;
159    using namespace module_context;
160 
161    ExecBlock initBlock;
162    initBlock.addFunctions()
163       (bind(registerAsyncRpcMethod, "check_for_updates", checkForUpdates))
164    ;
165    return initBlock.execute();
166 }
167 
168 } // namespace updates
169 } // namespace modules
170 } // namespace session
171 } // namespace rstudio
172