1 /*
2  * SessionHttpMethods.hpp
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 // We need LogLevel::DEBUG so don't #define DEBUG for this file
17 #define RSTUDIO_DEBUG_MACROS_DISABLED
18 
19 #include "SessionHttpMethods.hpp"
20 #include "SessionConsoleInput.hpp"
21 #include "SessionMainProcess.hpp"
22 #include "SessionSuspend.hpp"
23 #include "SessionClientInit.hpp"
24 #include "SessionInit.hpp"
25 #include "SessionUriHandlers.hpp"
26 #include "SessionDirs.hpp"
27 #include "SessionRpc.hpp"
28 #include "http/SessionTcpIpHttpConnectionListener.hpp"
29 
30 #include "session-config.h"
31 
32 #include <boost/algorithm/string.hpp>
33 
34 #include <core/gwt/GwtLogHandler.hpp>
35 #include <core/gwt/GwtFileHandler.hpp>
36 
37 #include <shared_core/json/Json.hpp>
38 #include <shared_core/Logger.hpp>
39 #include <core/json/JsonRpc.hpp>
40 
41 #include <core/system/Crypto.hpp>
42 
43 #include <core/text/TemplateFilter.hpp>
44 
45 #include <core/http/CSRFToken.hpp>
46 
47 #include <r/RExec.hpp>
48 #include <r/session/RSession.hpp>
49 #include <r/session/REventLoop.hpp>
50 
51 #include <session/RVersionSettings.hpp>
52 #include <session/SessionHttpConnection.hpp>
53 #include <session/SessionHttpConnectionListener.hpp>
54 #include <session/SessionModuleContext.hpp>
55 #include <session/SessionOptions.hpp>
56 #include <session/SessionPersistentState.hpp>
57 #include <session/SessionScopes.hpp>
58 #include <session/projects/SessionProjects.hpp>
59 #include <session/prefs/UserPrefs.hpp>
60 
61 #ifdef RSTUDIO_SERVER
62 #include <server_core/sessions/SessionSignature.hpp>
63 #endif
64 
65 #include "SessionAsyncRpcConnection.hpp"
66 
67 using namespace rstudio::core;
68 
69 namespace rstudio {
70 namespace session {
71 namespace {
72 
73 core::http::UriHandlerFunction s_defaultUriHandler;
74 
75 // version of the executable -- this is the legacy version designator. we
76 // set it to double max so that it always invalidates legacy clients
77 double s_version = std::numeric_limits<double>::max();
78 
79 // names of waitForMethod handlers (used to screen out of bkgnd processing)
80 std::vector<std::string> s_waitForMethodNames;
81 
82 // url for next session
83 std::string s_nextSessionUrl;
84 
85 bool s_protocolDebugEnabled = false;
86 bool s_sessionDebugLogCreated = false;
87 
timeoutTimeFromNow()88 boost::posix_time::ptime timeoutTimeFromNow()
89 {
90    int timeoutMinutes = options().timeoutMinutes();
91    if (timeoutMinutes > 0)
92    {
93       return boost::posix_time::second_clock::universal_time() +
94              boost::posix_time::minutes(options().timeoutMinutes());
95    }
96    else
97    {
98       return boost::posix_time::ptime(boost::posix_time::not_a_date_time);
99    }
100 }
101 
processEvents()102 void processEvents()
103 {
104    if (!r::exec::isMainThread())
105    {
106       LOG_ERROR_MESSAGE("processEvents() called from non-main thread");
107       return;
108    }
109     // execute safely since this can call arbitrary R code (and
110     // (can also cause jump_to_top if an interrupt is pending)
111     Error error = rstudio::r::exec::executeSafely(
112                 rstudio::r::session::event_loop::processEvents);
113     if (error)
114         LOG_ERROR(error);
115 }
116 
parseAndValidateJsonRpcConnection(boost::shared_ptr<HttpConnection> ptrConnection,json::JsonRpcRequest * pJsonRpcRequest)117 bool parseAndValidateJsonRpcConnection(
118          boost::shared_ptr<HttpConnection> ptrConnection,
119          json::JsonRpcRequest* pJsonRpcRequest)
120 {
121    // Async rpc requests will have already parsed the request so just copy it over here
122    if (ptrConnection->isAsyncRpc())
123    {
124       boost::shared_ptr<rpc::AsyncRpcConnection> asyncConnection =
125               boost::static_pointer_cast<rpc::AsyncRpcConnection>(ptrConnection);
126       *pJsonRpcRequest = asyncConnection->jsonRpcRequest();
127       return true;
128    }
129 
130    // attempt to parse the request into a json-rpc request
131    Error error = json::parseJsonRpcRequest(ptrConnection->request().body(),
132                                            pJsonRpcRequest);
133    if (error)
134    {
135       ptrConnection->sendJsonRpcError(error);
136       return false;
137    }
138 
139    // check for valid CSRF headers in server mode
140    if (options().programMode() == kSessionProgramModeServer &&
141        !core::http::validateCSRFHeaders(ptrConnection->request()))
142    {
143       ptrConnection->sendJsonRpcError(Error(json::errc::Unauthorized, ERROR_LOCATION));
144       return false;
145    }
146 
147    // check for invalid client id
148    if (pJsonRpcRequest->clientId != persistentState().activeClientId())
149    {
150       Error error(json::errc::InvalidClientId, ERROR_LOCATION);
151       ptrConnection->sendJsonRpcError(error);
152       return false;
153    }
154 
155    // check for legacy client version (need to invalidate any client using
156    // the old version field)
157    if ( (pJsonRpcRequest->version > 0) &&
158         (s_version > pJsonRpcRequest->version) )
159    {
160       Error error(json::errc::InvalidClientVersion, ERROR_LOCATION);
161       ptrConnection->sendJsonRpcError(error);
162       return false;
163    }
164 
165    // check for client version
166    if (!pJsonRpcRequest->clientVersion.empty() &&
167        http_methods::clientVersion() != pJsonRpcRequest->clientVersion)
168    {
169       Error error(json::errc::InvalidClientVersion, ERROR_LOCATION);
170       ptrConnection->sendJsonRpcError(error);
171       return false;
172    }
173 
174    // got through all of the validation, return true
175    return true;
176 }
177 
endHandleConnection(boost::shared_ptr<HttpConnection> ptrConnection,http_methods::ConnectionType connectionType,core::http::Response * pResponse)178 void endHandleConnection(boost::shared_ptr<HttpConnection> ptrConnection,
179                          http_methods::ConnectionType connectionType,
180                          core::http::Response* pResponse)
181 {
182    ptrConnection->sendResponse(*pResponse);
183    if (!console_input::executing())
184       module_context::events().onDetectChanges(module_context::ChangeSourceURI);
185 }
186 
isMethod(const std::string & uri,const std::string & method)187 bool isMethod(const std::string& uri, const std::string& method)
188 {
189    return boost::algorithm::ends_with(uri, method);
190 }
191 
isMethod(boost::shared_ptr<HttpConnection> ptrConnection,const std::string & method)192 bool isMethod(boost::shared_ptr<HttpConnection> ptrConnection,
193               const std::string& method)
194 {
195    return isMethod(ptrConnection->request().uri(), method);
196 }
197 
startHttpConnectionListener()198 Error startHttpConnectionListener()
199 {
200    initializeHttpConnectionListener();
201    Error error = httpConnectionListener().start();
202    if (error)
203       return error;
204 
205    if (options().standalone())
206    {
207       // log the endpoint to which we have bound to so other services can discover us
208       TcpIpHttpConnectionListener& listener =
209             static_cast<TcpIpHttpConnectionListener&>(httpConnectionListener());
210 
211       boost::asio::ip::tcp::endpoint endpoint = listener.getLocalEndpoint();
212       std::cout << "Listener bound to address " << endpoint.address().to_string()
213                 << " port " << endpoint.port() << std::endl;
214 
215       // set the standalone port so rpostback and others know how to
216       // connect back into the session process
217       std::string port = safe_convert::numberToString(endpoint.port());
218       core::system::setenv(kRSessionStandalonePortNumber, port);
219 
220       // save the standalone port for possible session relaunches - launcher sessions
221       // need to rebind to the same port
222       persistentState().setReusedStandalonePort(port);
223    }
224 
225    return Success();
226 }
227 
isTimedOut(const boost::posix_time::ptime & timeoutTime)228 bool isTimedOut(const boost::posix_time::ptime& timeoutTime)
229 {
230    using namespace boost::posix_time;
231 
232    // never time out in desktop mode
233    if (options().programMode() == kSessionProgramModeDesktop)
234       return false;
235 
236    // check for an client disconnection based timeout
237    int disconnectedTimeoutMinutes = options().disconnectedTimeoutMinutes();
238    if (disconnectedTimeoutMinutes > 0)
239    {
240       ptime lastEventConnection =
241          httpConnectionListener().eventsConnectionQueue().lastConnectionTime();
242       if (!lastEventConnection.is_not_a_date_time())
243       {
244          if ( (lastEventConnection + minutes(disconnectedTimeoutMinutes)
245                < second_clock::universal_time()) )
246          {
247             return true;
248          }
249       }
250    }
251 
252    // check for a foreground inactivity based timeout
253    if (timeoutTime.is_not_a_date_time())
254       return false;
255    else
256       return second_clock::universal_time() > timeoutTime;
257 }
258 
isWaitForMethodUri(const std::string & uri)259 bool isWaitForMethodUri(const std::string& uri)
260 {
261    for (const std::string& methodName : s_waitForMethodNames)
262    {
263       if (isMethod(uri, methodName))
264          return true;
265    }
266 
267    return false;
268 }
269 
polledEventHandler()270 void polledEventHandler()
271 {
272    // if R is getting called after a fork this is likely multicore or
273    // some other parallel computing package that uses fork. in this
274    // case be defensive by shutting down as many things as we can
275    // which might cause mischief in the child process
276    if (main_process::wasForked())
277    {
278       // no more polled events
279       rstudio::r::session::event_loop::permanentlyDisablePolledEventHandler();
280 
281       // done
282       return;
283    }
284 
285    if (!r::exec::isMainThread())
286    {
287       LOG_ERROR_MESSAGE("polledEventHandler called from thread other than main");
288       return;
289    }
290 
291    // static lastPerformed value used for throttling
292    using namespace boost::posix_time;
293    static ptime s_lastPerformed;
294    if (s_lastPerformed.is_not_a_date_time())
295       s_lastPerformed = microsec_clock::universal_time();
296 
297    // throttle to no more than once every 50ms
298    static time_duration s_intervalMs = milliseconds(50);
299    if (microsec_clock::universal_time() <= (s_lastPerformed + s_intervalMs))
300       return;
301 
302    // notify modules
303    module_context::onBackgroundProcessing(false);
304 
305    // set last performed (should be set after calling onBackgroundProcessing so
306    // that long running background processing handlers can't overflow the 50ms
307    // interval between background processing invocations)
308    s_lastPerformed = microsec_clock::universal_time();
309 
310    // check for a pending connections only while R is processing
311    // (otherwise we'll handle them directly in waitForMethod)
312    if (console_input::executing())
313    {
314       // check the uri of the next connection
315       std::string nextConnectionUri =
316        httpConnectionListener().mainConnectionQueue().peekNextConnectionUri();
317 
318       // if the uri is empty or if it one of our special waitForMethod calls
319       // then bails so that the waitForMethod logic can handle it
320       if (nextConnectionUri.empty() || isWaitForMethodUri(nextConnectionUri))
321          return;
322 
323       // attempt to deque a connection and handle it. for now we just handle
324       // a single connection at a time (we'll be called back again if processing
325       // continues)
326       boost::shared_ptr<HttpConnection> ptrConnection =
327             httpConnectionListener().mainConnectionQueue().dequeConnection();
328       if (ptrConnection)
329       {
330          if ( isMethod(ptrConnection, kClientInit) )
331          {
332             if (ptrConnection->isAsyncRpc())
333             {
334                BOOST_ASSERT(false); // The listener thread skips client_init when converting RPC to async
335             }
336             // client_init means the user is attempting to reload the browser
337             // in the middle of a computation. process client_init and post
338             // a busy event as our initFunction
339             using namespace session::module_context;
340             ClientEvent busyEvent(client_events::kBusy, true);
341             client_init::handleClientInit(
342                   boost::bind(enqueClientEvent, busyEvent), ptrConnection);
343          }
344          else
345          {
346             if (s_protocolDebugEnabled)
347             {
348                std::chrono::duration<double> duration =
349                        std::chrono::steady_clock::now() - ptrConnection->receivedTime();
350                LOG_DEBUG_MESSAGE("Handle background: " + ptrConnection->request().uri() +
351                                  " after: " + string_utils::formatDouble(duration.count(), 2));
352             }
353             handleConnection(ptrConnection, http_methods::BackgroundConnection);
354          }
355       }
356    }
357 }
358 
registeredWaitForMethod(const std::string & method,const ClientEvent & event,core::json::JsonRpcRequest * pRequest)359 bool registeredWaitForMethod(const std::string& method,
360                              const ClientEvent& event,
361                              core::json::JsonRpcRequest* pRequest)
362 {
363    // enque the event which notifies the client we want input
364    module_context::enqueClientEvent(event);
365 
366    // wait for method
367    return http_methods::waitForMethod(method,
368                         boost::bind(http_methods::waitForMethodInitFunction,
369                                     event),
370                         suspend::disallowSuspend,
371                         pRequest);
372 }
373 
verifyRequestSignature(const core::http::Request & request)374 bool verifyRequestSignature(const core::http::Request& request)
375 {
376 #ifndef RSTUDIO_SERVER
377    // signatures only supported in server mode - requires a dependency
378    // on server_core, which is only available to server platforms
379    return true;
380 #else
381    // only verify signatures if we are in standalone mode and
382    // we are explicitly configured to verify signatures
383    if (!options().verifySignatures() || !options().standalone())
384       return true;
385 
386    // determine which signing key pair to use
387    // we use an automatically generated key for postback requests
388    // (since we do not have access to the private key of rserver)
389    std::string signingKey = options().signingKey();
390    if (request.headerValue("X-Session-Postback") == "1")
391       signingKey = options().sessionRsaPublicKey();
392 
393    // ensure specified signature is valid
394    // note: we do not validate the signing username if we are running in a root container
395    Error error = !core::system::effectiveUserIsRoot() ?
396                     server_core::sessions::verifyRequestSignature(signingKey, core::system::username(), request) :
397                     server_core::sessions::verifyRequestSignature(signingKey, request);
398 
399    if (error)
400    {
401       LOG_ERROR(error);
402       return false;
403    }
404 
405    return true;
406 #endif
407 }
408 
409 
410 } // anonymous namespace
411 
412 namespace module_context
413 {
registerWaitForMethod(const std::string & methodName)414 module_context::WaitForMethodFunction registerWaitForMethod(
415       const std::string& methodName)
416 {
417    s_waitForMethodNames.push_back(methodName);
418    return boost::bind(registeredWaitForMethod, methodName, _2, _1);
419 }
420 
421 } // namespace module_context
422 
423 namespace http_methods {
424 
425 // client version -- this is determined by the git revision hash. the client
426 // and the server can diverge if a new version of the server was installed
427 // underneath a previously rendered client. if versions diverge then a reload
428 // of the client is forced
clientVersion()429 std::string clientVersion()
430 {
431    // never return a version in desktop mode
432    if (options().programMode() == kSessionProgramModeDesktop)
433       return std::string();
434 
435    // never return a version in standalone mode
436    if (options().standalone())
437       return std::string();
438 
439    // clientVersion is the git revision hash
440    return RSTUDIO_VERSION;
441 }
442 
protocolDebugEnabled()443 bool protocolDebugEnabled()
444 {
445    return s_protocolDebugEnabled;
446 }
447 
waitForMethodInitFunction(const ClientEvent & initEvent)448 void waitForMethodInitFunction(const ClientEvent& initEvent)
449 {
450    module_context::enqueClientEvent(initEvent);
451 
452    if (console_input::executing())
453    {
454       ClientEvent busyEvent(client_events::kBusy, true);
455       module_context::enqueClientEvent(busyEvent);
456    }
457    else
458    {
459       console_input::reissueLastConsolePrompt();
460    }
461 }
462 
463 // wait for the specified method. will either:
464 //   - return true and the method request in pRequest
465 //   - return false indicating failure (e.g. called after fork in child)
466 //   - suspend or quit the process
467 // exit the process as a result of suspend or quit)
waitForMethod(const std::string & method,const boost::function<void ()> & initFunction,const boost::function<bool ()> & allowSuspend,core::json::JsonRpcRequest * pRequest)468 bool waitForMethod(const std::string& method,
469                    const boost::function<void()>& initFunction,
470                    const boost::function<bool()>& allowSuspend,
471                    core::json::JsonRpcRequest* pRequest)
472 {
473    if (main_process::wasForked())
474    {
475       LOG_ERROR_MESSAGE("Waiting for method " + method + " after fork");
476       return false;
477    }
478    if (!r::exec::isMainThread())
479    {
480       LOG_ERROR_MESSAGE("waitForMethod: " + method + " called from thread other than main");
481       return false;
482    }
483 
484    // establish timeouts
485    boost::posix_time::ptime timeoutTime = timeoutTimeFromNow();
486    boost::posix_time::time_duration connectionQueueTimeout =
487                                    boost::posix_time::milliseconds(50);
488 
489    // wait until we get the method we are looking for
490    while (true)
491    {
492       // suspend if necessary (does not return if a suspend occurs)
493       suspend::suspendIfRequested(allowSuspend);
494 
495       // check for timeout
496       if (isTimedOut(timeoutTime))
497       {
498          if (allowSuspend())
499          {
500             // note that we timed out
501             suspend::setSuspendedFromTimeout(true);
502 
503             if (!options().timeoutSuspend())
504             {
505                // configuration dictates that we should quit the
506                // session instead of suspending when timeout occurs
507                //
508                // the conditions for the quit must be the same as those
509                // for a regular suspend
510                rstudio::r::session::quit(false, EXIT_SUCCESS); // does not return
511                return false;
512             }
513 
514             // attempt to suspend (does not return if it succeeds)
515             if (!suspend::suspendSession(false))
516             {
517                // reset timeout flag
518                suspend::setSuspendedFromTimeout(false);
519 
520                // if it fails then reset the timeout timer so we don't keep
521                // hammering away on the failure case
522                timeoutTime = timeoutTimeFromNow();
523             }
524          }
525       }
526 
527       // if we have at least one async process running then this counts
528       // as "activity" and resets the timeout timer
529       if (main_process::haveActiveChildren())
530          timeoutTime = timeoutTimeFromNow();
531 
532       // look for a connection (waiting for the specified interval)
533       boost::shared_ptr<HttpConnection> ptrConnection =
534           httpConnectionListener().mainConnectionQueue().dequeConnection(
535                                             connectionQueueTimeout);
536 
537 
538       // perform background processing (true for isIdle)
539       module_context::onBackgroundProcessing(true);
540 
541       // process pending events
542       processEvents();
543 
544       if (ptrConnection)
545       {
546          // ensure request signature is valid
547          if (!verifyRequestSignature(ptrConnection->request()))
548          {
549             LOG_ERROR_MESSAGE("Invalid signature for request URI " + ptrConnection->request().uri());
550             core::http::Response response;
551             response.setError(http::status::Unauthorized, "Invalid message signature");
552             ptrConnection->sendResponse(response);
553             continue;
554          }
555 
556          // check for client_init
557          if (isMethod(ptrConnection, kClientInit))
558          {
559             client_init::handleClientInit(initFunction, ptrConnection);
560          }
561 
562          // check for the method we are waiting on
563          else if (isMethod(ptrConnection, method))
564          {
565             // parse and validate request then proceed
566             if (parseAndValidateJsonRpcConnection(ptrConnection, pRequest))
567             {
568                // respond to the method
569                ptrConnection->sendJsonRpcResponse();
570 
571                // ensure initialized
572                init::ensureSessionInitialized();
573 
574                if (s_protocolDebugEnabled && method != kConsoleInput)
575                {
576                   LOG_DEBUG_MESSAGE("Handle wait for:     " + ptrConnection->request().uri());
577                }
578 
579                break; // got the method, we are out of here!
580             }
581          }
582 
583          // another connection type, dispatch it
584          else
585          {
586             if (s_protocolDebugEnabled)
587             {
588                std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
589                std::chrono::duration<double> beforeTime = now - ptrConnection->receivedTime();
590                LOG_DEBUG_MESSAGE("- Handle foreground: " + ptrConnection->request().uri() +
591                                  " after: " + string_utils::formatDouble(beforeTime.count(), 2));
592                handleConnection(ptrConnection, ForegroundConnection);
593                std::chrono::duration<double> afterTime = std::chrono::steady_clock::now() - now;
594                LOG_DEBUG_MESSAGE("--- complete:        " + ptrConnection->request().uri() +
595                                  " in: " + string_utils::formatDouble(afterTime.count(), 2));
596             }
597             else
598                handleConnection(ptrConnection, ForegroundConnection);
599          }
600 
601          // since we got a connection we can reset the timeout time
602          timeoutTime = timeoutTimeFromNow();
603 
604          // after we've processed at least one waitForMethod it is now safe to
605          // initialize the polledEventHandler (which is used to maintain rsession
606          // responsiveness even when R is executing code received at the console).
607          // we defer this to make sure that the FIRST request is always handled
608          // by the logic above. if we didn't do this then client_init or
609          // console_input (often the first request) could go directly to
610          // handleConnection which wouldn't know what to do with them
611          if (!rstudio::r::session::event_loop::polledEventHandlerInitialized())
612             rstudio::r::session::event_loop::initializePolledEventHandler(
613                                                      polledEventHandler);
614       }
615    }
616 
617    // satisfied the request
618    return true;
619 }
620 
621 
622 // wait for the specified method (will either return the method or
623 // exit the process as a result of suspend or quit)
waitForMethod(const std::string & method,const ClientEvent & initEvent,const boost::function<bool ()> & allowSuspend,core::json::JsonRpcRequest * pRequest)624 bool waitForMethod(const std::string& method,
625                    const ClientEvent& initEvent,
626                    const boost::function<bool()>& allowSuspend,
627                    core::json::JsonRpcRequest* pRequest)
628 {
629    return waitForMethod(method,
630                         boost::bind(module_context::enqueClientEvent,
631                                     initEvent),
632                         allowSuspend,
633                         pRequest);
634 }
635 
isJsonRpcRequest(boost::shared_ptr<HttpConnection> ptrConnection)636 bool isJsonRpcRequest(boost::shared_ptr<HttpConnection> ptrConnection)
637 {
638     return boost::algorithm::starts_with(ptrConnection->request().uri(),
639                                          "/rpc/");
640 }
641 
isAsyncJsonRpcRequest(boost::shared_ptr<HttpConnection> ptrConnection)642 bool isAsyncJsonRpcRequest(boost::shared_ptr<HttpConnection> ptrConnection)
643 {
644    std::string uri = ptrConnection->request().uri();
645    // Excluding the client_init operation because it's one of those special cases that will
646    // require more code + testing. Since its only made once per browser process it won't pile up
647    return boost::algorithm::starts_with(uri, "/rpc/") && uri != "/rpc/client_init";
648 }
649 
handleConnection(boost::shared_ptr<HttpConnection> ptrConnection,ConnectionType connectionType)650 void handleConnection(boost::shared_ptr<HttpConnection> ptrConnection,
651                       ConnectionType connectionType)
652 {
653    // check for a uri handler registered by a module
654    const core::http::Request& request = ptrConnection->request();
655    std::string uri = request.uri();
656    boost::optional<core::http::UriAsyncHandlerFunctionVariant> uriHandler =
657      uri_handlers::handlers().handlerFor(uri);
658 
659    if (uriHandler) // uri handler
660    {
661       if (ptrConnection->isAsyncRpc())
662       {
663          BOOST_ASSERT(false); // should not get here for uri handlers
664       }
665       core::http::visitHandler(uriHandler.get(),
666                                request,
667                                boost::bind(endHandleConnection,
668                                            ptrConnection,
669                                            connectionType,
670                                            _1));
671 
672       // r code may execute - ensure session is initialized
673       init::ensureSessionInitialized();
674    }
675    else if (isJsonRpcRequest(ptrConnection)) // check for json-rpc
676    {
677       // r code may execute - ensure session is initialized
678       init::ensureSessionInitialized();
679 
680       // attempt to parse & validate
681       json::JsonRpcRequest jsonRpcRequest;
682       if (parseAndValidateJsonRpcConnection(ptrConnection, &jsonRpcRequest))
683       {
684          // quit_session: exit process
685          if (jsonRpcRequest.method == kQuitSession)
686          {
687 #ifdef _WIN32
688             // if we are on windows then we can't quit while the browser
689             // context is active
690             if (rstudio::r::session::browserContextActive())
691             {
692                module_context::consoleWriteError(
693                         "Error: unable to quit when browser is active\n");
694                json::JsonRpcResponse response;
695                response.setResult(false);
696                ptrConnection->sendJsonRpcResponse(response);
697                return;
698             }
699 #endif
700 
701             // see whether we should save the workspace
702             bool saveWorkspace = true;
703             std::string switchToProject, hostPageUrl;
704             json::Value switchToVersionJson;
705             Error error = json::readParams(jsonRpcRequest.params,
706                                            &saveWorkspace,
707                                            &switchToProject,
708                                            &switchToVersionJson,
709                                            &hostPageUrl);
710             if (error)
711                LOG_ERROR(error);
712 
713             // note switch to project
714             if (!switchToProject.empty())
715             {
716                // if we don't have a project file then create it (can
717                // occur when e.g. opening a project from a directory for
718                // which we don't yet have a .Rproj file)
719                if (switchToProject != kProjectNone)
720                {
721                   FilePath projFile = module_context::resolveAliasedPath(switchToProject);
722                   if (projFile.getParent().exists() && !projFile.exists())
723                   {
724                      Error error = r_util::writeProjectFile(
725                               projFile,
726                               projects::ProjectContext::buildDefaults(),
727                               projects::ProjectContext::defaultConfig());
728                      if (error)
729                         LOG_ERROR(error);
730                   }
731                }
732 
733                // update project and working dir
734                using namespace module_context;
735                if (switchToProject == kProjectNone)
736                {
737                   // update the project and working dir
738                   activeSession().setProject(kProjectNone);
739                   activeSession().setWorkingDir(
740                     createAliasedPath(dirs::getDefaultWorkingDirectory()));
741                }
742                else
743                {
744                   FilePath projFile = module_context::resolveAliasedPath(switchToProject);
745                   std::string projDir = createAliasedPath(projFile.getParent());
746                   activeSession().setProject(projDir);
747                   activeSession().setWorkingDir(projDir);
748                }
749 
750                if (options().switchProjectsWithUrl())
751                {
752                   r_util::SessionScope scope;
753                   if (switchToProject == kProjectNone)
754                   {
755                      scope = r_util::SessionScope::projectNone(
756                                           options().sessionScope().id());
757                   }
758                   else
759                   {
760                      // extract the directory (aliased)
761                      using namespace module_context;
762                      FilePath projFile = module_context::resolveAliasedPath(switchToProject);
763                      std::string projDir = createAliasedPath(projFile.getParent());
764                      scope = r_util::SessionScope::fromProject(
765                               projDir,
766                               options().sessionScope().id(),
767                               filePathToProjectId(options().userScratchPath(),
768                                    FilePath(options().getOverlayOption(
769                                                kSessionSharedStoragePath))));
770                   }
771 
772                   // set next session url
773                   s_nextSessionUrl = r_util::createSessionUrl(hostPageUrl,
774                                                               scope);
775                }
776                else
777                {
778                   projects::ProjectsSettings(options().userScratchPath()).
779                                     setSwitchToProjectPath(switchToProject);
780                }
781 
782                // note switch to R version if requested
783                if (json::isType<json::Object>(switchToVersionJson))
784                {
785                   using namespace module_context;
786                   std::string version, rHome, label;
787                   Error error = json::readObject(
788                                             switchToVersionJson.getObject(),
789                                             "version", version,
790                                             "r_home", rHome,
791                                             "label", label);
792                   if (!error)
793                   {
794                      // set version for active session
795                      activeSession().setRVersion(version, rHome, label);
796 
797                      // if we had a project directory as well then
798                      // set it's version (this is necessary because
799                      // project versions override session versions)
800                      if (switchToProject != kProjectNone)
801                      {
802                         FilePath projFile = resolveAliasedPath(switchToProject);
803                         std::string projDir = createAliasedPath(projFile.getParent());
804                         RVersionSettings verSettings(
805                                             options().userScratchPath(),
806                                             FilePath(options().getOverlayOption(
807                                                   kSessionSharedStoragePath)));
808                         verSettings.setProjectLastRVersion(projDir,
809                                                            module_context::sharedProjectScratchPath(),
810                                                            version,
811                                                            rHome,
812                                                            label);
813                      }
814                  }
815                  else
816                     LOG_ERROR(error);
817                }
818             }
819 
820             // exit status
821             int status = switchToProject.empty() ? EXIT_SUCCESS : EX_CONTINUE;
822 
823             // acknowledge request & quit session
824             json::JsonRpcResponse response;
825             response.setResult(true);
826             ptrConnection->sendJsonRpcResponse(response);
827             rstudio::r::session::quit(saveWorkspace, status); // does not return
828          }
829          else if (jsonRpcRequest.method == kSuspendSession)
830          {
831             // check for force
832             bool force = true;
833             Error error = json::readParams(jsonRpcRequest.params, &force);
834             if (error)
835                LOG_ERROR(error);
836 
837             // acknowledge request and set flags to suspend session
838             ptrConnection->sendJsonRpcResponse();
839             if (force)
840                suspend::handleUSR2(0);
841             else
842                suspend::handleUSR1(0);
843          }
844 
845          // interrupt
846          else if ( jsonRpcRequest.method == kInterrupt )
847          {
848             // Discard any buffered input
849             console_input::clearConsoleInputBuffer();
850 
851             // aknowledge request
852             ptrConnection->sendJsonRpcResponse();
853 
854             // only accept interrupts while R is processing input
855             if (console_input::executing())
856                rstudio::r::exec::setInterruptsPending(true);
857          }
858 
859          // other rpc method, handle it
860          else
861          {
862             jsonRpcRequest.isBackgroundConnection =
863                   (connectionType == BackgroundConnection);
864             rpc::handleRpcRequest(jsonRpcRequest, ptrConnection, connectionType);
865          }
866       }
867    }
868    else if (s_defaultUriHandler)
869    {
870       core::http::Response response;
871        s_defaultUriHandler(request, &response);
872        ptrConnection->sendResponse(response);
873    }
874    else
875    {
876       core::http::Response response;
877       response.setNotFoundError(request);
878       ptrConnection->sendResponse(response);
879    }
880 }
881 
handleAsyncRpc(boost::shared_ptr<HttpConnection> ptrConnection)882 boost::shared_ptr<HttpConnection> handleAsyncRpc(boost::shared_ptr<HttpConnection> ptrConnection)
883 {
884    json::JsonRpcRequest jsonRpcRequest;
885    boost::shared_ptr<HttpConnection> res;
886    if (parseAndValidateJsonRpcConnection(ptrConnection, &jsonRpcRequest))
887    {
888       std::string asyncHandle = core::system::generateUuid(true);
889       // This will eventually call sendResponse and close ptrConnection
890       rpc::sendJsonAsyncPendingResponse(jsonRpcRequest, ptrConnection, asyncHandle);
891 
892       res.reset(new rpc::AsyncRpcConnection(ptrConnection, jsonRpcRequest, asyncHandle));
893    }
894    // else - an invalid request - an http error response has already been sent so return nothing
895    return res;
896 }
897 
startHttpConnectionListenerWithTimeout()898 WaitResult startHttpConnectionListenerWithTimeout()
899 {
900    Error error = startHttpConnectionListener();
901 
902    // When the rsession restarts, it may take a few ms for the port to become
903    // available; therefore, retry connection, but only for address_in_use error
904    if (!error)
905        return WaitResult(WaitSuccess, Success());
906    else if (error != boost::system::error_code(boost::system::errc::address_in_use, boost::system::generic_category()))
907       return WaitResult(WaitError, error);
908    else
909       return WaitResult(WaitContinue, error);
910 }
911 
912 
registerGwtHandlers()913 void registerGwtHandlers()
914 {
915    // alias options
916    session::Options& options = session::options();
917 
918    // establish logging handler
919    module_context::registerUriHandler(
920          "/log",
921          boost::bind(gwt::handleLogRequest, options.userIdentity(), _1, _2));
922 
923    // establish progress handler
924    FilePath wwwPath(options.wwwLocalPath());
925    FilePath progressPagePath = wwwPath.completePath("progress.htm");
926    module_context::registerUriHandler(
927          "/progress",
928           boost::bind(text::handleTemplateRequest, progressPagePath, _1, _2));
929 
930    // initialize gwt symbol maps
931    gwt::initializeSymbolMaps(options.wwwSymbolMapsPath());
932 
933    // declare program mode in JS init block (for GWT boot time access)
934    std::string initJs = "window.program_mode = \"" + options.programMode() + "\";\n";
935 
936    // set default handler
937    s_defaultUriHandler = gwt::fileHandlerFunction(options.wwwLocalPath(),
938                                                   "/",
939                                                   http::UriFilterFunction(),
940                                                   initJs);
941 }
942 
943 namespace {
944 
945 /**
946  * When a session debug logging preference is enabled, either create a new FileLog or update an existing one
947  * to have DEBUG so these messages show up. This prevents the user from needing to configure logging.conf
948  * for the session just to turn on debug logging.
949  */
initSessionDebugLog()950 void initSessionDebugLog()
951 {
952    if (s_sessionDebugLogCreated)
953       return;
954    s_sessionDebugLogCreated = true;
955 
956    system::initFileLogDestination(log::LogLevel::DEBUG, core::system::xdg::userDataDir().completePath("log"));
957 }
958 
onUserPrefsChanged(const std::string & layer,const std::string & pref)959 void onUserPrefsChanged(const std::string& layer, const std::string& pref)
960 {
961    if (pref == kSessionProtocolDebug)
962    {
963       bool newVal = prefs::userPrefs().sessionProtocolDebug();
964       if (newVal != s_protocolDebugEnabled)
965       {
966          s_protocolDebugEnabled = newVal;
967          if (newVal)
968              initSessionDebugLog();
969       }
970    }
971 }
972 
973 
974 }
975 
initialize()976 core::Error initialize()
977 {
978    s_protocolDebugEnabled = prefs::userPrefs().sessionProtocolDebug();
979    prefs::userPrefs().onChanged.connect(onUserPrefsChanged);
980    if (s_protocolDebugEnabled)
981       initSessionDebugLog();
982    return Success();
983 }
984 
nextSessionUrl()985 std::string nextSessionUrl()
986 {
987    return s_nextSessionUrl;
988 }
989 
990 } // namespace http_methods
991 } // namespace session
992 } // namespace rstudio
993 
994