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