From 9d0faa2a167b42a2094b6dce86e8b27b9f76a09e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9ven=20Car?= Date: Mon, 27 May 2024 10:31:45 +0200 Subject: [PATCH] Add an endpoint to check connectivity to WOPI server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The endpoint is accessible at /hosting/wopiAccessCheck You send it a POST with input json: { "callbackUrl: "" } For instance: curl -ki https://localhost:9980/hosting/wopiAccessCheck --header "Content-Type: application/json" -d '{"callbackUrl":"https://cool.local"}' Returns json such as: { "status": 0, "details": "OK" } With status and details giving hints to what went wrong if it did. The callbackUrl needs just to be an HTTPS endpoint, it does not need to be the wopi service but can be only a healthcheck url for instance. Signed-off-by: Méven Car Change-Id: Ibdef0bbf0d2fd98e3d293a06dfcfc79478d618e5 --- wsd/ClientRequestDispatcher.cpp | 161 ++++++++++++++++++++++++++++++++ wsd/ClientRequestDispatcher.hpp | 4 + 2 files changed, 165 insertions(+) diff --git a/wsd/ClientRequestDispatcher.cpp b/wsd/ClientRequestDispatcher.cpp index 28fea06db96b8..9e150dc7a5f01 100644 --- a/wsd/ClientRequestDispatcher.cpp +++ b/wsd/ClientRequestDispatcher.cpp @@ -9,10 +9,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include +#include +#include #include #include #include +#include +#include #if ENABLE_FEATURE_LOCK #include "CommandControl.hpp" @@ -49,9 +54,13 @@ #include #include #include +#include #include +#include +#include #include #include +#include #include #include @@ -797,6 +806,8 @@ void ClientRequestDispatcher::handleIncomingMessage(SocketDisposition& dispositi handleWopiDiscoveryRequest(requestDetails, socket); else if (requestDetails.equals(1, "capabilities")) handleCapabilitiesRequest(request, socket); + else if (requestDetails.equals(1, "wopiAccessCheck")) + handleWopiAccessCheckRequest(request, message, socket); } else if (requestDetails.isGet("/robots.txt")) handleRobotsTxtRequest(request, socket); @@ -990,6 +1001,156 @@ void ClientRequestDispatcher::handleWopiDiscoveryRequest( LOG_INF("Sent discovery.xml successfully."); } +void ClientRequestDispatcher::handleWopiAccessCheckRequest(const Poco::Net::HTTPRequest& request, + Poco::MemoryInputStream& message, + const std::shared_ptr& socket) +{ + assert(socket && "Must have a valid socket"); + + LOG_DBG("Wopi Access Check request: " << request.getURI()); + + // Poco::JSON + Poco::JSON::Parser jsonParser; + Poco::Dynamic::Var parsingResult; + Poco::JSON::Object::Ptr object; + Poco::URI uri; + + std::string text(std::istreambuf_iterator(message), {}); + try + { + parsingResult = jsonParser.parse(text); + object = parsingResult.extract(); + + auto callbackUrlStr = object->getValue("callbackUrl"); + + uri = Poco::URI(callbackUrlStr); + } + catch (const std::exception& exception) + { + LOG_ERR_S("Wopi Access Check request error, json object expected got [" + << text << "] on request to URL: " << request.getURI() << exception.what()); + + HttpHelper::sendErrorAndShutdown(http::StatusCode::BadRequest, socket); + return; + } + + enum class CheckStatus + { + Ok = 0, + NotHttpSucess, + HostNotFound, + HostUnReachable, + UnspecifiedError, + ConnectionAborted, + ConnectionRefused, + InvalidCertificate, + CertificateValidation, + NotHttps, + NoScheme, + Timeout, + }; + map colorToString = { + { CheckStatus::Ok, "Ok" }, + { CheckStatus::NotHttpSucess, "NOT_HTTP_SUCESS" }, + { CheckStatus::HostNotFound, "HOST_NOT_FOUND" }, + { CheckStatus::HostUnReachable, "HOST_UNREACHABLE" }, + { CheckStatus::UnspecifiedError, "UNSPECIFIED_ERROR" }, + { CheckStatus::ConnectionAborted, "CONNECTION_ABORTED" }, + { CheckStatus::ConnectionRefused, "CONNECTION_REFUSED" }, + { CheckStatus::InvalidCertificate, "INVALID_CERTIFICATE" }, + { CheckStatus::NotHttps, "NOT_HTTPS" }, + { CheckStatus::NoScheme, "NO_SCHEME" }, + { CheckStatus::Timeout, "TIMEOUT" } + }; + + CheckStatus result = CheckStatus::Ok; + + if (uri.getScheme().empty()) + { + result = CheckStatus::NoScheme; + } + else if (uri.getScheme() != "https") + { + result = CheckStatus::NotHttps; + } + else + { + // request the url + try + { + const auto hostAddress(Poco::Net::DNS::resolve(uri.getHost())); + + Poco::Net::HTTPSClientSession httpSession(uri.getHost(), 443); + httpSession.setConnectTimeout(Poco::Timespan(0, 300)); + Poco::Net::HTTPRequest httpRequest(Poco::Net::HTTPRequest::HTTP_GET, + uri.getPathAndQuery()); + httpSession.sendRequest(httpRequest); + Poco::Net::HTTPResponse response; + std::istream* responseStream = &httpSession.receiveResponse(response); + + std::cout << responseStream->rdbuf(); + if (response.getStatus() != 200) + { + result = CheckStatus::NotHttpSucess; + } + } + catch (Poco::Net::HostNotFoundException& hostNotfound) + { + result = CheckStatus::HostNotFound; + } + catch (Poco::Net::NoAddressFoundException& noAddressFound) + { + result = CheckStatus::HostUnReachable; + } + catch (Poco::Net::ConnectionAbortedException& connectionAborted) + { + result = CheckStatus::ConnectionAborted; + } + catch (Poco::Net::ConnectionRefusedException& connectionRefused) + { + result = CheckStatus::ConnectionRefused; + } + catch (Poco::Net::InvalidCertificateException& invalidCertification) + { + result = CheckStatus::InvalidCertificate; + } + catch (Poco::Net::CertificateValidationException& certificateValidation) + { + result = CheckStatus::CertificateValidation; + } + catch (Poco::TimeoutException& timeout) + { + result = CheckStatus::Timeout; + } + catch (Poco::Exception& exception) + { + LOG_ERR_S("Wopi Access Check request error, query to callback [" + << uri.toString() << "] failed:" << exception.what() << exception.className() + << exception.name() << exception.message()); + + result = CheckStatus::UnspecifiedError; + } + } + + // construct the result + + Poco::JSON::Object::Ptr status = new Poco::JSON::Object; + status->set("status", (int)result); + status->set("details", map[result]); + + std::ostringstream ostrJSON; + status->stringify(ostrJSON); + const auto output = ostrJSON.str(); + + http::Response httpResponse(http::StatusCode::OK); + FileServerRequestHandler::hstsHeaders(httpResponse); + httpResponse.set("Last-Modified", Util::getHttpTimeNow()); + httpResponse.setBody(output, "application/json"); + httpResponse.set("X-Content-Type-Options", "nosniff"); + socket->sendAndShutdown(httpResponse); + LOG_INF("Sent capabilities.json successfully."); +} + void ClientRequestDispatcher::handleClipboardRequest(const Poco::Net::HTTPRequest& request, Poco::MemoryInputStream& message, SocketDisposition& disposition, diff --git a/wsd/ClientRequestDispatcher.hpp b/wsd/ClientRequestDispatcher.hpp index 148b036536ca1..79dbfb24204fd 100644 --- a/wsd/ClientRequestDispatcher.hpp +++ b/wsd/ClientRequestDispatcher.hpp @@ -67,6 +67,10 @@ class ClientRequestDispatcher final : public SimpleSocketHandler void handleCapabilitiesRequest(const Poco::Net::HTTPRequest& request, const std::shared_ptr& socket); + void handleWopiAccessCheckRequest(const Poco::Net::HTTPRequest& request, + Poco::MemoryInputStream& message, + const std::shared_ptr& socket); + static void handleClipboardRequest(const Poco::Net::HTTPRequest& request, Poco::MemoryInputStream& message, SocketDisposition& disposition,