1 /*
2  * SessionPostback.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 /* Postback Handlers
17 
18 Postback handlers are generally used for situations where R options call for
19 an external executable rather than a function. For example, the R action
20 for viewing PDFs, options("pdfviewer"), is handle using a postback handler
21 named 'pdfviewer'.
22 
23 To create a new postback handler for an action 'foo' do the following:
24 
25 1) Create a shell script named 'rpostback-foo' (based on rpostback-pdfviewer)
26 
27 2) Ensure that the shell script is included in the installation
28 
29 3) Call module_context::registerPostbackHandler with 'foo' as the name param
30    and the function you want called during the postback. The function will
31    be passed a single parameter corresponding to the first command line
32    argument passed by R to the shell script.
33 
34 4) The registration function uses the pShellCommand out param to provide you
35    with the shell command which you in turn provide to R.
36 
37 */
38 
39 #include <string>
40 #include <map>
41 
42 #include <boost/function.hpp>
43 
44 #include <shared_core/Error.hpp>
45 #include <shared_core/SafeConvert.hpp>
46 
47 #include <core/http/Request.hpp>
48 #include <core/http/Response.hpp>
49 
50 #include <session/SessionModuleContext.hpp>
51 
52 #include <session/SessionOptions.hpp>
53 
54 using namespace rstudio::core;
55 
56 namespace rstudio {
57 namespace session {
58 namespace module_context {
59 
60 namespace {
61 
62 std::map<std::string,
63          module_context::PostbackHandlerFunction> s_postbackHandlers;
64 
endHandlePostback(const http::UriHandlerFunctionContinuation & cont,int exitCode,const std::string & output)65 void endHandlePostback(const http::UriHandlerFunctionContinuation& cont,
66                        int exitCode,
67                        const std::string& output)
68 {
69    http::Response response;
70    // send basic response
71    response.setStatusCode(http::status::Ok);
72    response.setContentType("text/plain");
73    response.setHeader(kPostbackExitCodeHeader,
74                         safe_convert::numberToString(exitCode));
75    response.setBody(output);
76    cont(&response);
77 }
78 
79 // UriHandlerFunction wrapper for simple postbacks
handlePostback(const PostbackHandlerFunction & handlerFunction,const http::Request & request,const http::UriHandlerFunctionContinuation & cont)80 void handlePostback(const PostbackHandlerFunction& handlerFunction,
81                     const http::Request& request,
82                     const http::UriHandlerFunctionContinuation& cont)
83 {
84    // pass the body to the postback function
85    handlerFunction(request.body(), boost::bind(endHandlePostback, cont, _1, _2));
86 }
87 
88 } // anonymous namespace
89 
90 
registerPostbackHandler(const std::string & name,const PostbackHandlerFunction & handlerFunction,std::string * pShellCommand)91 Error registerPostbackHandler(const std::string& name,
92                               const PostbackHandlerFunction& handlerFunction,
93                               std::string* pShellCommand)
94 {
95    // form postback uri fragment
96    std::string postback = kPostbackUriScope + name;
97 
98    // register a uri handler for this prefix
99    Error error = module_context::registerAsyncLocalUriHandler(
100                     postback,
101                     boost::bind(handlePostback, handlerFunction, _1, _2));
102    if (error)
103       return error;
104 
105    // compute the shell command required to invoke this handler and return it
106    Options& options = session::options();
107    *pShellCommand = options.rpostbackPath().getAbsolutePath() + "-" + name;
108 
109    // return success
110    return Success();
111 }
112 
113 
114 
115 } // namespace module_context
116 } // namespace session
117 } // namespace rstudio
118