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