Skip to content

Commit

Permalink
Add an endpoint to check connectivity to WOPI server
Browse files Browse the repository at this point in the history
The endpoint is accessible at /hosting/wopiAccessCheck

You send it a POST with input json:
{
	"callbackUrl: "<url-to-my-wopi-host>"
}

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 <meven.car@collabora.com>
Change-Id: Ibdef0bbf0d2fd98e3d293a06dfcfc79478d618e5
  • Loading branch information
meven committed Jun 5, 2024
1 parent 11682f1 commit 85b67fb
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 0 deletions.
164 changes: 164 additions & 0 deletions wsd/ClientRequestDispatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/Net/WebSocket.h>
#include <config.h>
#include <config_version.h>

#include <ClientRequestDispatcher.hpp>
#include <cstdio>
#include <istream>

#if ENABLE_FEATURE_LOCK
#include "CommandControl.hpp"
Expand Down Expand Up @@ -49,9 +54,15 @@
#include <Poco/MemoryStream.h>
#include <Poco/Net/HTMLForm.h>
#include <Poco/Net/NetException.h>
#include <Poco/Net/DNS.h>
#include <Poco/Net/PartHandler.h>
#if !MOBILEAPP
#include <Poco/Net/SSLException.h>
#endif // !MOBILEAPP
#include <Poco/Net/HTTPSClientSession.h>
#include <Poco/SAX/InputSource.h>
#include <Poco/StreamCopier.h>
#include <Poco/Timespan.h>

#include <map>
#include <memory>
Expand Down Expand Up @@ -797,6 +808,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);
Expand Down Expand Up @@ -990,6 +1003,157 @@ void ClientRequestDispatcher::handleWopiDiscoveryRequest(
LOG_INF("Sent discovery.xml successfully.");
}

void ClientRequestDispatcher::handleWopiAccessCheckRequest(const Poco::Net::HTTPRequest& request,
Poco::MemoryInputStream& message,
const std::shared_ptr<StreamSocket>& 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<char>(message), {});
try
{
parsingResult = jsonParser.parse(text);
object = parsingResult.extract<Poco::JSON::Object::Ptr>();

auto callbackUrlStr = object->getValue<std::string>("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,
};
std::map<CheckStatus, std::string> checkStatusNames = {
{ 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& exception)
{
result = CheckStatus::ConnectionRefused;
}
#if !MOBILEAPP
catch (Poco::Net::InvalidCertificateException& invalidCertification)
{
result = CheckStatus::InvalidCertificate;
}
catch (Poco::Net::CertificateValidationException& certificateValidation)
{
result = CheckStatus::CertificateValidation;
}
#endif // MOBILEAPP
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", checkStatusNames[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,
Expand Down
4 changes: 4 additions & 0 deletions wsd/ClientRequestDispatcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ class ClientRequestDispatcher final : public SimpleSocketHandler
void handleCapabilitiesRequest(const Poco::Net::HTTPRequest& request,
const std::shared_ptr<StreamSocket>& socket);

void handleWopiAccessCheckRequest(const Poco::Net::HTTPRequest& request,
Poco::MemoryInputStream& message,
const std::shared_ptr<StreamSocket>& socket);

static void handleClipboardRequest(const Poco::Net::HTTPRequest& request,
Poco::MemoryInputStream& message,
SocketDisposition& disposition,
Expand Down

0 comments on commit 85b67fb

Please sign in to comment.