capabilities.json                |    5 ++
 common/Common.hpp                |    2 +
 discovery.xml                    |    4 ++
 test/integration-http-server.cpp |   73 +++++++++++++++++++++++++++++++++++++
 wsd/LOOLWSD.cpp                  |   76 ++++++++++++++++++++++++++++++++++++---
 5 files changed, 156 insertions(+), 4 deletions(-)

New commits:
commit 17d4031a63fbf72a88a0a931e39adf725d75f2cb
Author:     Tamás Zolnai <tamas.zol...@collabora.com>
AuthorDate: Fri Oct 19 18:14:48 2018 +0200
Commit:     Michael Meeks <michael.me...@collabora.com>
CommitDate: Thu Nov 8 13:12:53 2018 +0100

    Add /hosting/capabilities endpoint to advertise online features
    
    Add an entry to discovery.xml with the urlsrc where capabilities end
    point can be found. Use json format to send back the feature list.
    
    (cherry picked from commit 0bb8b7c7a8a05ec95a7668c42b8b53c6f4da07c6)
    
    Change-Id: I390a53d956d53ca79e5a8090aead7f4131ec4ca0
    Reviewed-on: https://gerrit.libreoffice.org/62400
    Reviewed-by: Michael Meeks <michael.me...@collabora.com>
    Tested-by: Michael Meeks <michael.me...@collabora.com>

diff --git a/capabilities.json b/capabilities.json
new file mode 100644
index 000000000..32ed3801f
--- /dev/null
+++ b/capabilities.json
@@ -0,0 +1,5 @@
+{
+  "convert-to": {
+    "available": false
+  }
+}
diff --git a/common/Common.hpp b/common/Common.hpp
index 29008fd24..e55333937 100644
--- a/common/Common.hpp
+++ b/common/Common.hpp
@@ -36,6 +36,8 @@ constexpr const char CHILD_URI[] = "/loolws/child?";
 constexpr const char NEW_CHILD_URI[] = "/loolws/newchild";
 constexpr const char LO_JAIL_SUBPATH[] = "lo";
 
+constexpr const char CAPABILITIES_END_POINT[] = "/hosting/capabilities";
+
 /// The HTTP response User-Agent.
 constexpr auto HTTP_AGENT_STRING = "LOOLWSD HTTP Agent " LOOLWSD_VERSION;
 
diff --git a/discovery.xml b/discovery.xml
index 0a9ca48c7..b2999b8cf 100644
--- a/discovery.xml
+++ b/discovery.xml
@@ -339,5 +339,9 @@
         <app name="application/pdf">
             <action name="view" ext="pdf"/>
         </app>
+
+        <app name="Capabilities">
+            <action name="getinfo" ext=""/>
+        </app>
     </net-zone>
 </wopi-discovery>
diff --git a/test/integration-http-server.cpp b/test/integration-http-server.cpp
index f93874ffa..16d002de6 100644
--- a/test/integration-http-server.cpp
+++ b/test/integration-http-server.cpp
@@ -9,6 +9,11 @@
 
 #include "config.h"
 
+#include <Poco/DOM/AutoPtr.h>
+#include <Poco/DOM/DOMParser.h>
+#include <Poco/DOM/Document.h>
+#include <Poco/DOM/Element.h>
+#include <Poco/DOM/NodeList.h>
 #include <Poco/Net/AcceptCertificateHandler.h>
 #include <Poco/Net/FilePartSource.h>
 #include <Poco/Net/HTMLForm.h>
@@ -40,6 +45,7 @@ class HTTPServerTest : public CPPUNIT_NS::TestFixture
     CPPUNIT_TEST_SUITE(HTTPServerTest);
 
     CPPUNIT_TEST(testDiscovery);
+    CPPUNIT_TEST(testCapabilities);
     CPPUNIT_TEST(testLoleafletGet);
     CPPUNIT_TEST(testLoleafletPost);
     CPPUNIT_TEST(testScriptsAndLinksGet);
@@ -49,6 +55,7 @@ class HTTPServerTest : public CPPUNIT_NS::TestFixture
     CPPUNIT_TEST_SUITE_END();
 
     void testDiscovery();
+    void testCapabilities();
     void testLoleafletGet();
     void testLoleafletPost();
     void testScriptsAndLinksGet();
@@ -100,6 +107,72 @@ void HTTPServerTest::testDiscovery()
     CPPUNIT_ASSERT_EQUAL(std::string("text/xml"), response.getContentType());
 }
 
+
+void HTTPServerTest::testCapabilities()
+{
+    std::unique_ptr<Poco::Net::HTTPClientSession> 
session(helpers::createSession(_uri));
+
+    // Get discovery first and extract the urlsrc of the capabilities end point
+    std::string capabiltiesURI;
+    {
+
+        Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, 
"/hosting/discovery");
+        session->sendRequest(request);
+
+        Poco::Net::HTTPResponse response;
+        std::istream& rs = session->receiveResponse(response);
+        CPPUNIT_ASSERT_EQUAL(Poco::Net::HTTPResponse::HTTP_OK, 
response.getStatus());
+        CPPUNIT_ASSERT_EQUAL(std::string("text/xml"), 
response.getContentType());
+
+        std::string discoveryXML;
+        Poco::StreamCopier::copyToString(rs, discoveryXML);
+
+        Poco::XML::DOMParser parser;
+        Poco::XML::AutoPtr<Poco::XML::Document> docXML = 
parser.parseString(discoveryXML);
+        Poco::XML::AutoPtr<Poco::XML::NodeList> listNodes = 
docXML->getElementsByTagName("action");
+        bool foundCapabilities = false;
+        for (unsigned long index = 0; index < listNodes->length(); ++index)
+        {
+            Poco::XML::Element* elem = 
static_cast<Poco::XML::Element*>(listNodes->item(index));
+            Poco::XML::Element* parent = elem->parentNode() ? 
static_cast<Poco::XML::Element*>(elem->parentNode()) : nullptr;
+            if(parent && parent->getAttribute("name") == "Capabilities")
+            {
+                foundCapabilities = true;
+                capabiltiesURI = elem->getAttribute("urlsrc");
+                break;
+            }
+        }
+
+        CPPUNIT_ASSERT(foundCapabilities);
+        CPPUNIT_ASSERT_EQUAL(_uri.toString() + CAPABILITIES_END_POINT, 
capabiltiesURI);
+    }
+
+    // Then get the capabilities json
+    {
+        Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, 
CAPABILITIES_END_POINT);
+        session->sendRequest(request);
+
+        Poco::Net::HTTPResponse response;
+        std::istream& rs = session->receiveResponse(response);
+        CPPUNIT_ASSERT_EQUAL(Poco::Net::HTTPResponse::HTTP_OK, 
response.getStatus());
+        CPPUNIT_ASSERT_EQUAL(std::string("application/json"), 
response.getContentType());
+
+        std::ostringstream oss;
+        Poco::StreamCopier::copyStream(rs, oss);
+        std::string responseString = oss.str();
+
+        Poco::JSON::Parser parser;
+        Poco::Dynamic::Var jsonFile = parser.parse(responseString);
+        Poco::JSON::Object::Ptr features = 
jsonFile.extract<Poco::JSON::Object::Ptr>();
+        CPPUNIT_ASSERT(features);
+        CPPUNIT_ASSERT(features->has("convert-to"));
+
+        Poco::JSON::Object::Ptr convert_to = 
features->get("convert-to").extract<Poco::JSON::Object::Ptr>();
+        CPPUNIT_ASSERT(convert_to->has("available"));
+    }
+}
+
+
 void HTTPServerTest::testLoleafletGet()
 {
     std::unique_ptr<Poco::Net::HTTPClientSession> 
session(helpers::createSession(_uri));
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index b8571bfb6..c16f33f22 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -1901,6 +1901,10 @@ private:
             {
                 handleWopiDiscoveryRequest(request);
             }
+            else if (request.getMethod() == HTTPRequest::HTTP_GET && 
request.getURI() == CAPABILITIES_END_POINT)
+            {
+                handleCapabilitiesRequest(request);
+            }
             else if (request.getMethod() == HTTPRequest::HTTP_GET && 
request.getURI() == "/robots.txt")
             {
                 handleRobotsTxtRequest(request);
@@ -2042,6 +2046,28 @@ private:
         LOG_INF("Sent discovery.xml successfully.");
     }
 
+    void handleCapabilitiesRequest(const Poco::Net::HTTPRequest& request)
+    {
+        LOG_DBG("Wopi capabilities request: " << request.getURI());
+
+        std::string capabilities = getCapabilitiesJson();
+
+        std::ostringstream oss;
+        oss << "HTTP/1.1 200 OK\r\n"
+            << "Last-Modified: " << 
Poco::DateTimeFormatter::format(Poco::Timestamp(), 
Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n"
+            << "User-Agent: " << WOPI_AGENT_STRING << "\r\n"
+            << "Content-Length: " << capabilities.size() << "\r\n"
+            << "Content-Type: application/json\r\n"
+            << "X-Content-Type-Options: nosniff\r\n"
+            << "\r\n"
+            << capabilities;
+
+        auto socket = _socket.lock();
+        socket->send(oss.str());
+        socket->shutdown();
+        LOG_INF("Sent cpabilities.json successfully.");
+    }
+
     void handleRobotsTxtRequest(const Poco::Net::HTTPRequest& request)
     {
         LOG_DBG("HTTP request: " << request.getURI());
@@ -2502,10 +2528,11 @@ private:
         const std::string urlsrc = "urlsrc";
         const auto& config = Application::instance().config();
         const std::string loleafletHtml = config.getString("loleaflet_html", 
"loleaflet.html");
-        const std::string uriValue = ((LOOLWSD::isSSLEnabled() || 
LOOLWSD::isSSLTermination()) ? "https://"; : "http://";)
+        const std::string rootUriValue = ((LOOLWSD::isSSLEnabled() || 
LOOLWSD::isSSLTermination()) ? "https://"; : "http://";)
                                    + std::string("%SERVER_HOST%")
-                                   + LOOLWSD::ServiceRoot
-                                   + "/loleaflet/" LOOLWSD_VERSION_HASH "/" + 
loleafletHtml + '?';
+                                   + LOOLWSD::ServiceRoot;
+        const std::string uriValue = rootUriValue
+                                     + "/loleaflet/" LOOLWSD_VERSION_HASH "/" 
+ loleafletHtml + '?';
 
         InputSource inputSrc(discoveryPath);
         DOMParser parser;
@@ -2515,7 +2542,15 @@ private:
         for (unsigned long it = 0; it < listNodes->length(); ++it)
         {
             Element* elem = static_cast<Element*>(listNodes->item(it));
-            elem->setAttribute(urlsrc, uriValue);
+            Element* parent = elem->parentNode() ? 
static_cast<Element*>(elem->parentNode()) : nullptr;
+            if(parent && parent->getAttribute("name") == "Capabilities")
+            {
+                elem->setAttribute(urlsrc, rootUriValue + 
CAPABILITIES_END_POINT);
+            }
+            else
+            {
+                elem->setAttribute(urlsrc, uriValue);
+            }
 
             // Set the View extensions cache as well.
             if (elem->getAttribute("name") == "edit")
@@ -2528,6 +2563,39 @@ private:
         return ostrXML.str();
     }
 
+    /// Process the capabilities.json file and return as string.
+    std::string getCapabilitiesJson()
+    {
+        std::shared_ptr<StreamSocket> socket = _socket.lock();
+
+        // http://server/hosting/capabilities
+#if defined __linux && defined MOBILEAPP
+        std::string capabilitiesPath = 
Path(Application::instance().commandPath()).parent().parent().toString() + 
"capabilities.json";
+#else
+        std::string capabilitiesPath = 
Path(Application::instance().commandPath()).parent().toString() + 
"capabilities.json";
+#endif
+        if (!File(capabilitiesPath).exists())
+        {
+            capabilitiesPath = LOOLWSD::FileServerRoot + "/capabilities.json";
+        }
+        std::ifstream ifs (capabilitiesPath.c_str(), std::ifstream::in);
+
+        if(!ifs.is_open())
+            return "";
+
+        Poco::JSON::Parser parser;
+        Poco::Dynamic::Var jsonFile = parser.parse(ifs);
+        Poco::JSON::Object::Ptr features = 
jsonFile.extract<Poco::JSON::Object::Ptr>();
+        Poco::JSON::Object::Ptr convert_to = 
features->get("convert-to").extract<Poco::JSON::Object::Ptr>();
+
+        Poco::Dynamic::Var available = allowPostFrom(socket->clientAddress());
+        convert_to->set("available", available);
+
+        std::ostringstream ostrJSON;
+        features->stringify(ostrJSON);
+        return ostrJSON.str();
+    }
+
 private:
     // The socket that owns us (we can't own it).
     std::weak_ptr<StreamSocket> _socket;
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to