diff --git a/common/Common.hpp b/common/Common.hpp index 4eb5cf75cfb1f..1862003100773 100644 --- a/common/Common.hpp +++ b/common/Common.hpp @@ -49,6 +49,8 @@ constexpr const char FORKIT_URI[] = "/coolws/forkit"; constexpr const char CAPABILITIES_END_POINT[] = "/hosting/capabilities"; +constexpr const char CHECK_END_POINT[] = "/hosting/check"; + /// The file suffix used to mark the file slated for uploading. constexpr const char TO_UPLOAD_SUFFIX[] = ".upload"; /// The file suffix used to mark the file being uploaded. diff --git a/wsd/COOLWSD.cpp b/wsd/COOLWSD.cpp index a9e33cd1f65e7..9298748367d62 100644 --- a/wsd/COOLWSD.cpp +++ b/wsd/COOLWSD.cpp @@ -3093,6 +3093,9 @@ class ClientRequestDispatcher final : public SimpleSocketHandler requestDetails.isGet("/hosting/discovery/")) handleWopiDiscoveryRequest(requestDetails, socket); + else if (requestDetails.isPost(CHECK_END_POINT)) + handleCheckRequest(request, socket, message); + else if (requestDetails.isGet(CAPABILITIES_END_POINT)) handleCapabilitiesRequest(request, socket); @@ -3274,6 +3277,103 @@ class ClientRequestDispatcher final : public SimpleSocketHandler LOG_INF("Sent capabilities.json successfully."); } + void handleCheckRequest(const Poco::Net::HTTPRequest& request, + const std::shared_ptr& socket, + Poco::MemoryInputStream& message) + { + assert(socket && "Must have a valid socket"); + + LOG_DBG("Check request: " << request.getURI()); + + bool validWopiConfig = false; + std::string connectMessage; + bool validConnectBack = false; + + ConvertToPartHandler handler; + HTMLForm form(request, message, handler); + + std::string url = (form.has("url") ? form.get("url") : ""); + Poco::URI remoteServerURI(url); + + // Validate allowed WOPI host + std::string addressToCheck = remoteServerURI.getHost(); + LOG_DBG("Checking remote host: " << addressToCheck); + try + { + if (!StorageBase::allowedWopiHost(addressToCheck) && !net::isLocalhost(addressToCheck)) + { + // FIXME: does not automatically detect that 127.0.0.1 matches localhost in coolwsd.xml + const auto hostAddresses(Poco::Net::DNS::resolve(addressToCheck)); + for (auto &address : hostAddresses.addresses()) + { + LOG_DBG("Checking resolved remote host: " << address.toString()); + if (StorageBase::allowedWopiHost(address.toString())) + { + validWopiConfig = true; + break; + } + LOG_DBG("Invalid resolved remote host: " << address.toString()); + } + } else { + validWopiConfig = true; + } + + } + catch (const Poco::Exception& exc) + { + LOG_ERR("Poco::Net::DNS::resolve(\"" << addressToCheck << "\") failed: " << exc.displayText()); + // We can't find out the hostname, and it already failed the IP check + validWopiConfig = false; + } + + // Validate connecting back to WOPI host + try + { + std::shared_ptr httpSession( + StorageBase::getHttpSession(remoteServerURI)); + http::Request wopiRequest(remoteServerURI.getPathAndQuery()); + + const std::shared_ptr httpResponse = + httpSession->syncRequest(wopiRequest); + + unsigned int statusCode = httpResponse->statusLine().statusCode(); + + if (statusCode == Poco::Net::HTTPResponse::HTTP_OK) + { + validConnectBack = true; + connectMessage = "OK"; + + std::string body = httpResponse->getBody(); + } + else + { + connectMessage = "Collabora received unexpected status code from the WOPI host: " + + std::to_string(statusCode); + } + } + catch (...) + { + connectMessage = "Failed to fetch"; + } + + + Poco::JSON::Object::Ptr check = new Poco::JSON::Object; + check->set("status", validWopiConfig && validConnectBack); + check->set("status_config", validWopiConfig); + check->set("status_connect", validConnectBack); + check->set("status_connect_message", connectMessage); + check->set("message", "Validated configuration and connectivity"); + std::ostringstream ostrJSON; + check->stringify(ostrJSON); + + http::Response httpResponse(http::StatusLine(200)); + httpResponse.set("Last-Modified", Util::getHttpTimeNow()); + httpResponse.setBody(ostrJSON.str(), "application/json"); + httpResponse.set("X-Content-Type-Options", "nosniff"); + socket->sendAndShutdown(httpResponse); + LOG_INF("Sent check results successfully."); + } + static void handleClipboardRequest(const Poco::Net::HTTPRequest& request, Poco::MemoryInputStream& message, SocketDisposition &disposition, diff --git a/wsd/RequestDetails.cpp b/wsd/RequestDetails.cpp index 0d779221c116a..99c2d72654501 100644 --- a/wsd/RequestDetails.cpp +++ b/wsd/RequestDetails.cpp @@ -78,6 +78,7 @@ RequestDetails::RequestDetails(Poco::Net::HTTPRequest &request, const std::strin const std::string &method = request.getMethod(); _isGet = method == "GET"; _isHead = method == "HEAD"; + _isPost = method == "POST"; auto it = request.find("ProxyPrefix"); _isProxy = it != request.end(); if (_isProxy) @@ -96,6 +97,7 @@ RequestDetails::RequestDetails(Poco::Net::HTTPRequest &request, const std::strin RequestDetails::RequestDetails(const std::string &mobileURI) : _isGet(true) , _isHead(false) + , _isPost(false) , _isProxy(false) , _isWebSocket(false) { diff --git a/wsd/RequestDetails.hpp b/wsd/RequestDetails.hpp index 9da5de29f3cf1..32c6cb2487796 100644 --- a/wsd/RequestDetails.hpp +++ b/wsd/RequestDetails.hpp @@ -110,6 +110,7 @@ class RequestDetails bool _isGet : 1; bool _isHead : 1; + bool _isPost : 1; bool _isProxy : 1; bool _isWebSocket : 1; bool _isMobile : 1; @@ -183,6 +184,10 @@ class RequestDetails { return _isGet && _uriString == path; } + bool isPost(const char *path) const + { + return _isPost && _uriString == path; + } bool isGetOrHead(const char *path) const { return (_isGet || _isHead) && _uriString == path;