1 /*
2 * SessionAsyncDownloadFile.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 <session/SessionAsyncDownloadFile.hpp>
17
18 #include <boost/format.hpp>
19
20 #include <shared_core/Error.hpp>
21 #include <core/StringUtils.hpp>
22 #include <core/json/JsonRpc.hpp>
23
24 #include <core/system/Process.hpp>
25
26 #include <session/SessionAsyncRProcess.hpp>
27
28 using namespace rstudio::core;
29
30 namespace rstudio {
31 namespace session {
32 namespace {
33
34 class AsyncDownloadFile : public async_r::AsyncRProcess
35 {
36 public:
AsyncDownloadFile(const boost::function<void (const core::system::ProcessResult &)> & onCompleted)37 AsyncDownloadFile(const boost::function<void(const core::system::ProcessResult&)>& onCompleted)
38 : onCompleted_(onCompleted)
39 {
40 }
41
onStdout(const std::string & output)42 virtual void onStdout(const std::string& output)
43 {
44 output_ += output;
45 }
46
onStderr(const std::string & output)47 virtual void onStderr(const std::string& output)
48 {
49 error_ += output;
50 }
51
onCompleted(int exitStatus)52 virtual void onCompleted(int exitStatus)
53 {
54 core::system::ProcessResult result;
55 result.exitStatus = exitStatus;
56 result.stdOut = output_;
57 result.stdErr = error_;
58 onCompleted_(result);
59 }
60
61 std::string output_;
62 std::string error_;
63 boost::function<void(const core::system::ProcessResult&)> onCompleted_;
64 };
65
66
67
readJson(const std::string & output,json::Value * pValue,json::JsonRpcResponse * pResponse)68 bool readJson(const std::string& output, json::Value* pValue, json::JsonRpcResponse* pResponse)
69 {
70 Error error = pValue->parse(output);
71 if (error)
72 {
73 Error parseError(boost::system::errc::state_not_recoverable,
74 errorMessage(error),
75 ERROR_LOCATION);
76 setErrorResponse(parseError, pResponse);
77 return false;
78 }
79 else
80 {
81 return true;
82 }
83 }
84
85
endJsonRpcRequest(const json::JsonRpcFunctionContinuation & cont,const JsonRpcResponseHandler & handler,const core::system::ProcessResult & result)86 void endJsonRpcRequest(const json::JsonRpcFunctionContinuation& cont,
87 const JsonRpcResponseHandler& handler,
88 const core::system::ProcessResult& result)
89 {
90 json::JsonRpcResponse response;
91 if (result.exitStatus == EXIT_SUCCESS)
92 {
93 json::Value jsonValue;
94 if (readJson(result.stdOut, &jsonValue, &response))
95 handler(jsonValue, &response);
96 }
97 else
98 {
99 setProcessErrorResponse(result, ERROR_LOCATION, &response);
100 }
101 cont(Success(), &response);
102 }
103
104
105 } // anonymous namespace
106
107
asyncDownloadFile(const std::string & url,const boost::function<void (const core::system::ProcessResult &)> & onCompleted)108 void asyncDownloadFile(const std::string& url,
109 const boost::function<void(const core::system::ProcessResult&)>& onCompleted)
110 {
111 http::Fields headers;
112 asyncDownloadFile(url, "", headers, onCompleted);
113 }
114
asyncDownloadFile(const std::string & url,const http::Fields & headers,const boost::function<void (const core::system::ProcessResult &)> & onCompleted)115 void asyncDownloadFile(const std::string& url,
116 const http::Fields& headers,
117 const boost::function<void(const core::system::ProcessResult&)>& onCompleted)
118 {
119 asyncDownloadFile(url, "", headers, onCompleted);
120 }
121
asyncDownloadFile(const std::string & url,const std::string & userAgent,const http::Fields & headers,const boost::function<void (const core::system::ProcessResult &)> & onCompleted)122 void asyncDownloadFile(const std::string& url,
123 const std::string& userAgent,
124 const http::Fields& headers,
125 const boost::function<void(const core::system::ProcessResult&)>& onCompleted)
126 {
127 // create headers if we have them
128 std::vector<std::string> headerList;
129 std::transform(headers.begin(), headers.end(), std::back_inserter(headerList), [](const http::Field& field) {
130 return "`" + field.first + "` = '" + field.second + "'";
131 });
132 std::string headersStr;
133 if (headerList.size() > 0)
134 headersStr = ", headers = c(" + boost::algorithm::join(headerList, ", ") + ")";
135
136 // build the command
137 std::string cmd("{ ");
138 if (!userAgent.empty())
139 cmd += "options(HTTPUserAgent = '" + string_utils::singleQuotedStrEscape(userAgent) + "'); ";
140 cmd += "tmp <- tempfile(); ";
141 cmd += "download.file('" + url +"'" + headersStr + ", destfile = tmp, quiet = TRUE); ";
142 cmd += "cat(readLines(tmp, warn = FALSE)); ";
143 cmd += "} ";
144
145 // kickoff the process
146 boost::shared_ptr<AsyncDownloadFile> pDownload(new AsyncDownloadFile(onCompleted));
147 pDownload->start(cmd.c_str(), FilePath(), async_r::R_PROCESS_NO_RDATA);
148 }
149
asyncJsonRpcRequest(const std::string & url,const JsonRpcResponseHandler & handler,const core::json::JsonRpcFunctionContinuation & cont)150 void asyncJsonRpcRequest(const std::string& url,
151 const JsonRpcResponseHandler& handler,
152 const core::json::JsonRpcFunctionContinuation& cont)
153 {
154 http::Fields headers;
155 asyncJsonRpcRequest(url, "", headers, handler, cont);
156 }
157
asyncJsonRpcRequest(const std::string & url,const std::string & userAgent,const http::Fields & headers,const JsonRpcResponseHandler & handler,const core::json::JsonRpcFunctionContinuation & cont)158 void asyncJsonRpcRequest(const std::string& url,
159 const std::string& userAgent,
160 const http::Fields& headers,
161 const JsonRpcResponseHandler& handler,
162 const core::json::JsonRpcFunctionContinuation& cont)
163 {
164 asyncDownloadFile(url, userAgent, headers, boost::bind(endJsonRpcRequest, cont, handler, _1));
165 }
166
is404Error(const std::string & stdErr)167 bool is404Error(const std::string& stdErr)
168 {
169 return boost::algorithm::contains(stdErr, "404 Not Found");
170 }
171
isHostError(const std::string & stdErr)172 bool isHostError(const std::string& stdErr)
173 {
174 return boost::algorithm::contains(stdErr, "resolve host name");
175 }
176
177
178 } // namespace session
179 } // namespace rstudio
180
181