[Libreoffice-commits] online.git: Branch 'private/Ashod/nonblocking' - 13 commits - common/Common.hpp common/Session.cpp common/Session.hpp net/Socket.hpp net/WebSocketHandler.hpp test/helpers.hpp test/httpcrashtest.cpp test/httpwserror.cpp test/httpwstest.cpp test/UnitAdmin.cpp test/UnitMinSocketBufferSize.cpp test/UnitStorage.cpp test/UnitTileCache.cpp wsd/DocumentBroker.cpp wsd/LOOLWSD.cpp

Ashod Nakashian ashod.nakashian at collabora.co.uk
Wed Mar 1 07:00:57 UTC 2017


 common/Common.hpp                |    3 
 common/Session.cpp               |    7 
 common/Session.hpp               |    2 
 net/Socket.hpp                   |   33 ++-
 net/WebSocketHandler.hpp         |  165 +++++++++++++----
 test/UnitAdmin.cpp               |    4 
 test/UnitMinSocketBufferSize.cpp |    2 
 test/UnitStorage.cpp             |    2 
 test/UnitTileCache.cpp           |    2 
 test/helpers.hpp                 |    6 
 test/httpcrashtest.cpp           |    5 
 test/httpwserror.cpp             |    2 
 test/httpwstest.cpp              |   73 ++++---
 wsd/DocumentBroker.cpp           |    4 
 wsd/LOOLWSD.cpp                  |  364 +++++++++++++++++++++++++++++++--------
 15 files changed, 515 insertions(+), 159 deletions(-)

New commits:
commit 7d0d0fd4af9088c6a875aabe7c530d97ea613dbc
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Wed Mar 1 01:49:10 2017 -0500

    nb: support inserting image
    
    Change-Id: I6ef11634bbda7e3ecdc467ce10727c8573caef8d

diff --git a/net/Socket.hpp b/net/Socket.hpp
index a1e1f1f..5ba902e 100644
--- a/net/Socket.hpp
+++ b/net/Socket.hpp
@@ -403,6 +403,15 @@ public:
         send(str.data(), str.size(), flush);
     }
 
+    void send(Poco::Net::HTTPResponse& response)
+    {
+        response.set("User-Agent", HTTP_AGENT_STRING);
+        std::ostringstream oss;
+        response.write(oss);
+        LOG_INF(oss.str());
+        send(oss.str());
+    }
+
     /// Reads data by invoking readData() and buffering.
     /// Return false iff the socket is closed.
     virtual bool readIncomingData()
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index b26c669..2985b25 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -3040,7 +3040,9 @@ private:
                     File(dirPath).createDirectories();
                     std::string fileName = dirPath + "/" + form.get("name");
                     File(tmpPath).moveTo(fileName);
-                    return; // FIXME send response
+                    response.setContentLength(0);
+                    socket->send(response);
+                    return;
                 }
             }
         }
commit a6df778c209d1bcc6fedf8d962d700c8527e813d
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Tue Feb 28 17:45:27 2017 -0500

    nb: enable testCloseAfterClose
    
    Change-Id: I4150c547c3859e22c6628cd8d65e470b71d8cad4

diff --git a/test/httpwstest.cpp b/test/httpwstest.cpp
index 3150cf9..cd5e156 100644
--- a/test/httpwstest.cpp
+++ b/test/httpwstest.cpp
@@ -58,7 +58,7 @@ class HTTPWSTest : public CPPUNIT_NS::TestFixture
 
     CPPUNIT_TEST(testBadRequest);
     CPPUNIT_TEST(testHandshake);
-    // CPPUNIT_TEST(testCloseAfterClose); //FIXME: loolnb
+    CPPUNIT_TEST(testCloseAfterClose);
     CPPUNIT_TEST(testConnectNoLoad); // This fails most of the times but occasionally succeeds
     CPPUNIT_TEST(testLoadSimple);
     CPPUNIT_TEST(testLoadTortureODT);
@@ -326,7 +326,9 @@ void HTTPWSTest::testCloseAfterClose()
         {
             bytes = socket->receiveFrame(buffer, sizeof(buffer), flags);
         }
-        while (bytes && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
+        while (bytes > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
+
+        std::cerr << "Received " << bytes << " bytes, flags: "<< std::hex << flags << std::dec << std::endl;
 
         // no more messages is received.
         bytes = socket->receiveFrame(buffer, sizeof(buffer), flags);
commit 8b7aea14b12ce6dee0ae837671c2cddb85e54105
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Wed Mar 1 01:21:49 2017 -0500

    nb: shutdown on reaching connection or document limit
    
    Change-Id: I5eedaf780ed00804d93c362e83ac4fcfd5d057b1

diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index cb36d3a..b26c669 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -204,58 +204,22 @@ static int careerSpanSeconds = 0;
 namespace
 {
 
-inline void shutdownLimitReached(LOOLWebSocket& ws)
+inline void shutdownLimitReached(WebSocketSender& ws)
 {
     const std::string error = Poco::format(PAYLOAD_UNAVAILABLE_LIMIT_REACHED, MAX_DOCUMENTS, MAX_CONNECTIONS);
     LOG_INF("Sending client limit-reached message: " << error);
 
-    /* loleaflet sends loolclient, load and partrectangles message immediately
-       after web socket handshake, so closing web socket fails loading page in
-       some sensible browsers. Ignore handshake messages and gracefully
-       close in order to send error messages.
-    */
     try
     {
-        int flags = 0;
-        int retries = 7;
-        std::vector<char> buffer(READ_BUFFER_SIZE * 100);
+        // Let the client know we are shutting down.
+        ws.sendFrame(error);
 
-        const Poco::Timespan waitTime(POLL_TIMEOUT_MS * 1000 / retries);
-        do
-        {
-            if (ws.poll(Poco::Timespan(0), Poco::Net::Socket::SelectMode::SELECT_ERROR))
-            {
-                // Already disconnected, can't send 'close' frame.
-                ws.close();
-                return;
-            }
-
-            // Let the client know we are shutting down.
-            ws.sendFrame(error.data(), error.size());
-
-            // Ignore incoming messages.
-            if (ws.poll(waitTime, Poco::Net::Socket::SELECT_READ))
-            {
-                ws.receiveFrame(buffer.data(), buffer.capacity(), flags);
-            }
-
-            // Shutdown.
-            ws.shutdown(WebSocket::WS_POLICY_VIOLATION);
-        }
-        while (--retries > 0 && (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE);
+        // Shutdown.
+        ws.shutdown(WebSocketHandler::StatusCodes::POLICY_VIOLATION);
     }
     catch (const std::exception& ex)
     {
         LOG_ERR("Error while shuting down socket on reaching limit: " << ex.what());
-        try
-        {
-            // Persist, in case it was unrelated error.
-            ws.shutdown(WebSocket::WS_POLICY_VIOLATION);
-        }
-        catch (const std::exception&)
-        {
-            // Nothing to do.
-        }
     }
 }
 
@@ -2376,10 +2340,8 @@ bool LOOLWSD::createForKit()
 std::mutex Connection::Mutex;
 #endif
 
-// TODO loolnb FIXME
-static const std::string HARDCODED_PATH("file:///tmp/hello-world.odt");
-
-static std::shared_ptr<DocumentBroker> createDocBroker(const std::string& uri,
+static std::shared_ptr<DocumentBroker> createDocBroker(WebSocketSender& ws,
+                                                       const std::string& uri,
                                                        const std::string& docKey,
                                                        const Poco::URI& uriPublic)
 {
@@ -2389,8 +2351,7 @@ static std::shared_ptr<DocumentBroker> createDocBroker(const std::string& uri,
     if (DocBrokers.size() + 1 > MAX_DOCUMENTS)
     {
         LOG_ERR("Maximum number of open documents reached.");
-        //FIXME: shutdown on limit.
-        // shutdownLimitReached(*ws);
+        shutdownLimitReached(ws);
         return nullptr;
     }
 
@@ -2417,7 +2378,7 @@ static std::shared_ptr<DocumentBroker> createDocBroker(const std::string& uri,
 /// Otherwise, creates and adds a new one to DocBrokers.
 /// May return null if terminating or MaxDocuments limit is reached.
 /// After returning a valid instance DocBrokers must be cleaned up after exceptions.
-static std::shared_ptr<DocumentBroker> findOrCreateDocBroker(const WebSocketSender& ws,
+static std::shared_ptr<DocumentBroker> findOrCreateDocBroker(WebSocketSender& ws,
                                                              const std::string& uri,
                                                              const std::string& docKey,
                                                              const std::string& id,
@@ -2523,7 +2484,7 @@ static std::shared_ptr<DocumentBroker> findOrCreateDocBroker(const WebSocketSend
 
     if (!docBroker)
     {
-        docBroker = createDocBroker(uri, docKey, uriPublic);
+        docBroker = createDocBroker(ws, uri, docKey, uriPublic);
     }
 
     return docBroker;
@@ -3167,8 +3128,7 @@ private:
         if (_connectionNum > MAX_CONNECTIONS)
         {
             LOG_ERR("Limit on maximum number of connections of " << MAX_CONNECTIONS << " reached.");
-            //FIXME: gracefully reject the connection request.
-            // shutdownLimitReached(ws);
+            shutdownLimitReached(ws);
             return;
         }
 
commit 3a2c480b1bc26181dc53fee7ab0c5c174e70efe3
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Wed Mar 1 01:12:12 2017 -0500

    nb: websocket closing handshake
    
    Change-Id: I45a6f2e680349fa2f77c20bb8f783093b9e1f212

diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp
index 8307d62..3b5756d 100644
--- a/net/WebSocketHandler.hpp
+++ b/net/WebSocketHandler.hpp
@@ -19,6 +19,7 @@ class WebSocketHandler : public SocketHandlerInterface
     // The socket that owns us (we can't own it).
     std::weak_ptr<StreamSocket> _socket;
     std::vector<char> _wsPayload;
+    bool _shuttingDown;
 
     enum class WSFrameMask : unsigned char
     {
@@ -27,7 +28,8 @@ class WebSocketHandler : public SocketHandlerInterface
     };
 
 public:
-    WebSocketHandler()
+    WebSocketHandler() :
+        _shuttingDown(false)
     {
     }
 
@@ -86,6 +88,7 @@ public:
 
         auto lock = socket->getWriteLock();
         sendFrame(socket, buf.data(), buf.size(), flags);
+        _shuttingDown = true;
     }
 
     /// Implementation of the SocketHandlerInterface.
@@ -161,7 +164,32 @@ public:
         // FIXME: fin, aggregating payloads into _wsPayload etc.
         LOG_TRC("Incoming WebSocket message code " << code << " fin? " << fin << " payload length " << _wsPayload.size());
 
-        handleMessage(fin, code, _wsPayload);
+        if (code & WSOpCode::Close)
+        {
+            if (!_shuttingDown)
+            {
+                // Peer-initiated shutdown must be echoed.
+                // Otherwise, this is the echo to _out_ shutdown message.
+                const StatusCodes statusCode = static_cast<StatusCodes>((static_cast<unsigned>(_wsPayload[0]) << 8) | _wsPayload[1]);
+                if (_wsPayload.size() > 2)
+                {
+                    const std::string message(&_wsPayload[2], &_wsPayload[2] + _wsPayload.size() - 2);
+                    shutdown(statusCode, message);
+                }
+                else
+                {
+                    shutdown(statusCode);
+                }
+            }
+
+            // TCP Close.
+            socket->shutdown();
+        }
+        else
+        {
+            handleMessage(fin, code, _wsPayload);
+        }
+
         _wsPayload.clear();
     }
 
commit 04dd2299422770c88d7277f88884af618450a624
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Tue Feb 28 23:40:09 2017 -0500

    nb: clear the input buffer only on success
    
    With multipart streams the parsing isn't
    done until all parts are in. We will need
    to reparse the header when more data comes in
    until we can parse all parts. Otherwise we
    end up removing the header and losing it
    when we can't find all parts in the body.
    
    This fixes convert-to requests.
    
    Change-Id: Ic1d5ccbd00fd6763eb91fdda35177f6df847f100

diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index 526906a..cb36d3a 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -2706,10 +2706,6 @@ private:
                 LOG_DBG("Not enough content yet: ContentLength: " << contentLength << ", available: " << available);
                 return;
             }
-
-            // if we succeeded - remove the request from our input buffer
-            // we expect one request per socket
-            in.clear();
         }
         catch (const std::exception& exc)
         {
@@ -2778,6 +2774,10 @@ private:
                     socket->shutdown();
                 }
             }
+
+            // if we succeeded - remove the request from our input buffer
+            // we expect one request per socket
+            in.clear();
         }
         catch (const std::exception& exc)
         {
commit 221038146ae017c20d85c52f9c27681232287402
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Tue Feb 28 22:33:39 2017 -0500

    nb: websocket shutdown support
    
    Change-Id: I2fcab98e9725eca86d097f34236889fdf9289c47

diff --git a/common/Session.cpp b/common/Session.cpp
index 8258a2c..f80d1d3 100644
--- a/common/Session.cpp
+++ b/common/Session.cpp
@@ -147,15 +147,16 @@ bool Session::handleDisconnect()
     return false;
 }
 
-void Session::shutdown(Poco::UInt16 statusCode, const std::string& statusMessage)
+void Session::shutdown(const WebSocketHandler::StatusCodes statusCode, const std::string& statusMessage)
 {
     LOG_TRC("Shutting down WS [" << getName() << "] with statusCode [" <<
-            statusCode << "] and reason [" << statusMessage << "].");
+            static_cast<unsigned>(statusCode) << "] and reason [" << statusMessage << "].");
 
     // See protocol.txt for this application-level close frame.
     const std::string msg = "close: " + statusMessage;
     sendTextFrame(msg.data(), msg.size());
-    // TODO loolnb anything needed here? _ws->shutdown(statusCode, statusMessage);
+
+    WebSocketHandler::shutdown(statusCode, statusMessage);
 }
 
 void Session::handleMessage(bool /*fin*/, WSOpCode /*code*/, std::vector<char> &data)
diff --git a/common/Session.hpp b/common/Session.hpp
index 63bc444..b2a40dd 100644
--- a/common/Session.hpp
+++ b/common/Session.hpp
@@ -53,7 +53,7 @@ public:
     /// Called to handle disconnection command from socket.
     virtual bool handleDisconnect();
 
-    void shutdown(Poco::UInt16 statusCode = Poco::Net::WebSocket::StatusCodes::WS_NORMAL_CLOSE,
+    void shutdown(const WebSocketHandler::StatusCodes statusCode = WebSocketHandler::StatusCodes::NORMAL_CLOSE,
                   const std::string& statusMessage = "");
 
     bool isActive() const { return _isActive; }
diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp
index fbbdda3..8307d62 100644
--- a/net/WebSocketHandler.hpp
+++ b/net/WebSocketHandler.hpp
@@ -79,8 +79,8 @@ public:
 
         const size_t len = statusMessage.size();
         std::vector<char> buf(2 + len);
-        buf[0] = (((int)statusCode >> 8) & 0xff);
-        buf[1] = (((int)statusCode >> 0) & 0xff);
+        buf[0] = ((((int)statusCode) >> 8) & 0xff);
+        buf[1] = ((((int)statusCode) >> 0) & 0xff);
         std::copy(statusMessage.begin(), statusMessage.end(), buf.end());
         const unsigned char flags = static_cast<unsigned char>(WSFrameMask::Fin) | static_cast<char>(WSOpCode::Close);
 
@@ -123,10 +123,10 @@ public:
             if (len < 2 + 8)
                 return;
 
-            payloadLen = ((((uint64_t)(p[9])) <<  0) + (((uint64_t)(p[8])) <<  8) +
-                          (((uint64_t)(p[7])) << 16) + (((uint64_t)(p[6])) << 24) +
-                          (((uint64_t)(p[5])) << 32) + (((uint64_t)(p[4])) << 40) +
-                          (((uint64_t)(p[3])) << 48) + (((uint64_t)(p[2])) << 56));
+            payloadLen = ((((uint64_t)p[9]) <<  0) + (((uint64_t)p[8]) <<  8) +
+                          (((uint64_t)p[7]) << 16) + (((uint64_t)p[6]) << 24) +
+                          (((uint64_t)p[5]) << 32) + (((uint64_t)p[4]) << 40) +
+                          (((uint64_t)p[3]) << 48) + (((uint64_t)p[2]) << 56));
             // FIXME: crop read length to remove top / sign bits.
             headerLen += 8;
         }
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index 0f67e81..5cd7fb3 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -1091,7 +1091,7 @@ void DocumentBroker::childSocketTerminated()
     {
         try
         {
-            pair.second->shutdown(Poco::Net::WebSocket::WS_ENDPOINT_GOING_AWAY, "");
+            pair.second->shutdown(WebSocketHandler::StatusCodes::ENDPOINT_GOING_AWAY, "");
         }
         catch (const std::exception& ex)
         {
@@ -1112,7 +1112,7 @@ void DocumentBroker::terminateChild(std::unique_lock<std::mutex>& lock, const st
     {
         try
         {
-            pair.second->shutdown(Poco::Net::WebSocket::WS_ENDPOINT_GOING_AWAY, closeReason);
+            pair.second->shutdown(WebSocketHandler::StatusCodes::ENDPOINT_GOING_AWAY, closeReason);
         }
         catch (const std::exception& ex)
         {
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index db95a3d..526906a 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -3223,10 +3223,9 @@ private:
                 LOG_ERR("handleGetRequest: Giving up trying to connect client: " << msg);
                 try
                 {
-                    // FIXME: send error and close.
-                    // ws->sendFrame(msg.data(), msg.size());
-                    // // abnormal close frame handshake
-                    // ws->shutdown(WebSocket::WS_ENDPOINT_GOING_AWAY);
+                    ws.sendFrame(msg);
+                    // abnormal close frame handshake
+                    ws.shutdown(WebSocketHandler::StatusCodes::ENDPOINT_GOING_AWAY);
                 }
                 catch (const std::exception& exc2)
                 {
@@ -3244,6 +3243,8 @@ private:
         const auto docBroker = _clientSession->getDocumentBroker();
         LOG_CHECK_RET(docBroker && "Null DocumentBroker instance", );
         const auto docKey = docBroker->getDocKey();
+
+        WebSocketSender ws(_socket);
         try
         {
             // Connection terminated. Destroy session.
@@ -3279,9 +3280,9 @@ private:
         catch (const UnauthorizedRequestException& exc)
         {
             LOG_ERR("Error in client request handler: " << exc.toString());
-            // const std::string status = "error: cmd=internal kind=unauthorized";
-            // LOG_TRC("Sending to Client [" << status << "].");
-            // ws->sendFrame(status.data(), status.size());
+            const std::string status = "error: cmd=internal kind=unauthorized";
+            LOG_TRC("Sending to Client [" << status << "].");
+            ws.sendFrame(status);
         }
         catch (const std::exception& exc)
         {
@@ -3294,21 +3295,21 @@ private:
             {
                 LOG_TRC("Normal close handshake.");
                 // Client initiated close handshake
-                // respond close frame
-                // ws->shutdown();
+                // respond with close frame
+                ws.shutdown();
             }
             else if (!SigUtil::isShuttingDown())
             {
                 // something wrong, with internal exceptions
                 LOG_TRC("Abnormal close handshake.");
                 _clientSession->closeFrame();
-                // ws->shutdown(WebSocket::WS_ENDPOINT_GOING_AWAY);
+                ws.shutdown(WebSocketHandler::StatusCodes::ENDPOINT_GOING_AWAY);
             }
             else
             {
                 std::lock_guard<std::mutex> lock(ClientWebSocketsMutex);
                 LOG_TRC("Capturing Client WS for [" << _id << "]");
-                // ClientWebSockets.push_back(ws);
+                // ClientWebSockets.push_back(ws); //FIXME
             }
         }
         catch (const std::exception& exc)
commit 828cda2cf91600b2eef8a9beacf39c6d2f18900f
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Tue Feb 28 22:31:36 2017 -0500

    nb: unittest improvements
    
    Change-Id: Ife4ed343d54137f254077d4f194157120cf82bad

diff --git a/test/helpers.hpp b/test/helpers.hpp
index fd7115f..16142e7 100644
--- a/test/helpers.hpp
+++ b/test/helpers.hpp
@@ -183,7 +183,7 @@ int getErrorCode(LOOLWebSocket& ws, std::string& message, const std::string& tes
         bytes = ws.receiveFrame(buffer.begin(), READ_BUFFER_SIZE, flags);
         std::cerr << testname << "Got " << LOOLProtocol::getAbbreviatedFrameDump(buffer.begin(), bytes, flags) << std::endl;
     }
-    while (bytes != 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
+    while (bytes > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
 
     if (bytes > 0)
     {
diff --git a/test/httpcrashtest.cpp b/test/httpcrashtest.cpp
index f34c214..9cb7d43 100644
--- a/test/httpcrashtest.cpp
+++ b/test/httpcrashtest.cpp
@@ -130,6 +130,8 @@ void HTTPCrashTest::testCrashKit()
     {
         auto socket = loadDocAndGetSocket("empty.odt", _uri, testname);
 
+        std::cerr << "Killing loolkit instances." << std::endl;
+
         killLoKitProcesses("(loolkit)");
         countLoolKitProcesses(0);
 
@@ -145,8 +147,11 @@ void HTTPCrashTest::testCrashKit()
         CPPUNIT_ASSERT_EQUAL(static_cast<int>(Poco::Net::WebSocket::WS_ENDPOINT_GOING_AWAY), statusCode);
 
         // respond close frame
+        std::cerr << "Shutting down socket." << std::endl;
         socket->shutdown();
 
+        std::cerr << "Reading after shutdown." << std::endl;
+
         // no more messages is received.
         int flags;
         char buffer[READ_BUFFER_SIZE];
commit 93e21519b78d242816937934509cbecc6b6fc4e9
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Tue Feb 28 21:06:29 2017 -0500

    nb: support WS shutdown
    
    Change-Id: I08084abb25e494384235260dd1df83adc5e3a624

diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp
index 51030ee..fbbdda3 100644
--- a/net/WebSocketHandler.hpp
+++ b/net/WebSocketHandler.hpp
@@ -52,6 +52,42 @@ public:
         // ... reserved
     };
 
+    /// Status codes sent to peer on shutdown.
+    enum class StatusCodes : unsigned short
+    {
+        NORMAL_CLOSE            = 1000,
+        ENDPOINT_GOING_AWAY     = 1001,
+        PROTOCOL_ERROR          = 1002,
+        PAYLOAD_NOT_ACCEPTABLE  = 1003,
+        RESERVED                = 1004,
+        RESERVED_NO_STATUS_CODE = 1005,
+        RESERVED_ABNORMAL_CLOSE = 1006,
+        MALFORMED_PAYLOAD       = 1007,
+        POLICY_VIOLATION        = 1008,
+        PAYLOAD_TOO_BIG         = 1009,
+        EXTENSION_REQUIRED      = 1010,
+        UNEXPECTED_CONDITION    = 1011,
+        RESERVED_TLS_FAILURE    = 1015
+    };
+
+    /// Sends WS shutdown message to the peer.
+    void shutdown(const StatusCodes statusCode = StatusCodes::NORMAL_CLOSE, const std::string& statusMessage = "")
+    {
+        auto socket = _socket.lock();
+        if (socket == nullptr)
+            return;
+
+        const size_t len = statusMessage.size();
+        std::vector<char> buf(2 + len);
+        buf[0] = (((int)statusCode >> 8) & 0xff);
+        buf[1] = (((int)statusCode >> 0) & 0xff);
+        std::copy(statusMessage.begin(), statusMessage.end(), buf.end());
+        const unsigned char flags = static_cast<unsigned char>(WSFrameMask::Fin) | static_cast<char>(WSOpCode::Close);
+
+        auto lock = socket->getWriteLock();
+        sendFrame(socket, buf.data(), buf.size(), flags);
+    }
+
     /// Implementation of the SocketHandlerInterface.
     virtual void handleIncomingMessage() override
     {
@@ -227,6 +263,8 @@ public:
         sendMessage(msg.data(), msg.size(), WSOpCode::Text);
     }
 
+    using WebSocketHandler::shutdown;
+
 private:
     void handleMessage(bool, WSOpCode, std::vector<char>&) override
     {
commit 40b19a0f3f063a449fc84b833ab63910ebb80ee5
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Tue Feb 28 21:04:44 2017 -0500

    nb: separate sendMessage and sendFrame
    
    There are other types of frames than application
    data.
    
    Also a message can be composed of multiple frames.
    
    Change-Id: Ia97349553b61ae05fa78854222808eaa43386c0e

diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp
index 055a807..51030ee 100644
--- a/net/WebSocketHandler.hpp
+++ b/net/WebSocketHandler.hpp
@@ -139,48 +139,57 @@ public:
 
         auto socket = _socket.lock();
         if (socket == nullptr)
-            return 0; // no socket == connection closed.
-
-        //TODO: Support fragmented messages.
-        const unsigned char fin = static_cast<unsigned char>(WSFrameMask::Fin);
-        const unsigned char mask = 0; // Server must not mask, only clients.
+            return -1; // no socket == error.
 
         auto lock = socket->getWriteLock();
         std::vector<char>& out = socket->_outBuffer;
 
+        //TODO: Support fragmented messages.
+        const unsigned char fin = static_cast<unsigned char>(WSFrameMask::Fin);
+
         // FIXME: need to support fragmented mesages, but for now send prefix message with size.
         if (len >= LARGE_MESSAGE_SIZE)
         {
             const std::string nextmessage = "nextmessage: size=" + std::to_string(len);
             const unsigned char size = (nextmessage.size() & 0xff);
             out.push_back(fin | WSOpCode::Text);
-            out.push_back(mask | size);
+            out.push_back(size);
             out.insert(out.end(), nextmessage.data(), nextmessage.data() + size);
             socket->writeOutgoingData();
         }
 
-        unsigned char header[2];
-        header[0] = fin | static_cast<unsigned char>(code);
-        header[1] = mask;
-        out.push_back((char)header[0]);
+        return sendFrame(socket, data, len, static_cast<unsigned char>(fin | code), flush);
+    }
+
+protected:
+
+    /// Sends a WebSocket frame given the data, length, and flags.
+    /// Returns the number of bytes written (including frame overhead) on success,
+    /// 0 for closed/invalid socket, and -1 for other errors.
+    static int sendFrame(const std::shared_ptr<StreamSocket>& socket,
+                         const char* data, const size_t len,
+                         const unsigned char flags, const bool flush = true)
+    {
+        if (!socket || data == nullptr || len == 0)
+            return -1;
+
+        std::vector<char>& out = socket->_outBuffer;
+
+        out.push_back(flags);
 
-        // no out-bound masking ...
         if (len < 126)
         {
-            header[1] |= len;
-            out.push_back((char)header[1]);
+            out.push_back((char)len);
         }
         else if (len <= 0xffff)
         {
-            header[1] |= 126;
-            out.push_back((char)header[1]);
+            out.push_back((char)126);
             out.push_back(static_cast<char>((len >> 8) & 0xff));
             out.push_back(static_cast<char>((len >> 0) & 0xff));
         }
         else
         {
-            header[1] |= 127;
-            out.push_back((char)header[1]);
+            out.push_back((char)127);
             out.push_back(static_cast<char>((len >> 56) & 0xff));
             out.push_back(static_cast<char>((len >> 48) & 0xff));
             out.push_back(static_cast<char>((len >> 40) & 0xff));
@@ -191,14 +200,14 @@ public:
             out.push_back(static_cast<char>((len >> 0) & 0xff));
         }
 
-        // FIXME: pick random number and mask in the outbuffer etc.
-        assert (!mask);
-
+        // Copy the data.
         out.insert(out.end(), data, data + len);
+
         if (flush)
             socket->writeOutgoingData();
 
-        return len + sizeof(header);
+        // Data + header.
+        return len + 2;
     }
 
     /// To me overriden to handle the websocket messages the way you need.
commit 2cbd2ff4ba915a044e1ed5255587f2a31d5cf8c9
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Tue Feb 28 19:34:21 2017 -0500

    nb: process POST requests
    
    These are really GET requests that aren't
    WebSocket upgrade. Should rename to something
    less misleading.
    
    Re-enabled testSlideShow which depended on this.
    
    Change-Id: I52b7f67b650fcdcbae7c2bff020b756099263141

diff --git a/common/Common.hpp b/common/Common.hpp
index 5f27f84..ab4e1bd 100644
--- a/common/Common.hpp
+++ b/common/Common.hpp
@@ -40,6 +40,9 @@ constexpr auto CHILD_URI = "/loolws/child?";
 constexpr auto NEW_CHILD_URI = "/loolws/newchild?";
 constexpr auto LO_JAIL_SUBPATH = "lo";
 
+/// The HTTP response User-Agent. TODO: Include version.
+constexpr auto HTTP_AGENT_STRING = "LOOLWSD Agent";
+
 // The client port number, both loolwsd and the kits have this.
 extern int ClientPortNumber;
 extern int MasterPortNumber;
diff --git a/net/Socket.hpp b/net/Socket.hpp
index fcbfe49..a1e1f1f 100644
--- a/net/Socket.hpp
+++ b/net/Socket.hpp
@@ -30,7 +30,9 @@
 #include <Poco/Timespan.h>
 #include <Poco/Timestamp.h>
 #include <Poco/Net/SocketAddress.h>
+#include <Poco/Net/HTTPResponse.h>
 
+#include "Common.hpp"
 #include "Log.hpp"
 #include "Util.hpp"
 
@@ -568,7 +570,8 @@ protected:
 
 namespace HttpHelper
 {
-    inline void sendFile(const std::shared_ptr<StreamSocket>& socket, const std::string& path, const std::string& mediaType)
+    inline void sendFile(const std::shared_ptr<StreamSocket>& socket, const std::string& path,
+                         Poco::Net::HTTPResponse& response)
     {
         struct stat st;
         if (stat(path.c_str(), &st) != 0)
@@ -577,14 +580,11 @@ namespace HttpHelper
             return;
         }
 
+        response.setContentLength(st.st_size);
+        response.set("User-Agent", HTTP_AGENT_STRING);
         std::ostringstream oss;
-        oss << "HTTP/1.1 200 OK\r\n"
-            //<< "Last-Modified: " << FIXME << "\r\n"
-            << "User-Agent: LOOLWSD WOPI Agent\r\n"
-            << "Content-Length: " << st.st_size << "\r\n"
-            << "Content-Type: " << mediaType << "\r\n"
-            << "\r\n";
-
+        response.write(oss);
+        LOG_INF(oss.str());
         socket->send(oss.str());
 
         std::ifstream file(path, std::ios::binary);
@@ -600,6 +600,14 @@ namespace HttpHelper
         }
         while (file);
     }
+
+    inline void sendFile(const std::shared_ptr<StreamSocket>& socket, const std::string& path,
+                         const std::string& mediaType)
+    {
+        Poco::Net::HTTPResponse response;
+        response.setContentType(mediaType);
+        sendFile(socket, path, response);
+    }
 };
 
 #endif
diff --git a/test/httpwstest.cpp b/test/httpwstest.cpp
index 692090e..3150cf9 100644
--- a/test/httpwstest.cpp
+++ b/test/httpwstest.cpp
@@ -82,7 +82,7 @@ class HTTPWSTest : public CPPUNIT_NS::TestFixture
     CPPUNIT_TEST(testPasswordProtectedOOXMLDocument);
     CPPUNIT_TEST(testPasswordProtectedBinaryMSOfficeDocument);
     CPPUNIT_TEST(testInsertDelete);
-    // CPPUNIT_TEST(testSlideShow); //FIXME: loolnb
+    CPPUNIT_TEST(testSlideShow);
     CPPUNIT_TEST(testInactiveClient);
     CPPUNIT_TEST(testMaxColumn);
     CPPUNIT_TEST(testMaxRow);
@@ -1147,6 +1147,7 @@ void HTTPWSTest::testSlideShow()
         session->receiveResponse(responseSVG);
         CPPUNIT_ASSERT_EQUAL(Poco::Net::HTTPResponse::HTTP_OK, responseSVG.getStatus());
         CPPUNIT_ASSERT_EQUAL(std::string("image/svg+xml"), responseSVG.getContentType());
+        CPPUNIT_ASSERT_EQUAL(std::streamsize(451329), responseSVG.getContentLength());
     }
     catch (const Poco::Exception& exc)
     {
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index 4c38c21..db95a3d 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -2890,11 +2890,270 @@ private:
         LOG_INF("Sent discovery.xml successfully.");
     }
 
+    static std::string getContentType(const std::string& fileName)
+    {
+        const std::string nodePath = Poco::format("//[@ext='%s']", Poco::Path(fileName).getExtension());
+        std::string discPath = Path(Application::instance().commandPath()).parent().toString() + "discovery.xml";
+        if (!File(discPath).exists())
+        {
+            discPath = LOOLWSD::FileServerRoot + "/discovery.xml";
+        }
+
+        InputSource input(discPath);
+        DOMParser domParser;
+        AutoPtr<Poco::XML::Document> doc = domParser.parse(&input);
+        // TODO. discovery.xml missing application/pdf
+        Node* node = doc->getNodeByPath(nodePath);
+        if (node && (node = node->parentNode()) && node->hasAttributes())
+        {
+            return dynamic_cast<Element*>(node)->getAttribute("name");
+        }
+
+        return "application/octet-stream";
+    }
+
     void handlePostRequest(const Poco::Net::HTTPRequest& request, Poco::MemoryInputStream& message)
     {
-        LOG_ERR("Post request: " << request.getURI());
-        (void)message;
-        // responded = handlePostRequest(request, response, id);
+        LOG_INF("Post request: [" << request.getURI() << "]");
+
+        Poco::Net::HTTPResponse response;
+        auto socket = _socket.lock();
+
+        StringTokenizer tokens(request.getURI(), "/?");
+        if (tokens.count() >= 3 && tokens[2] == "convert-to")
+        {
+            std::string fromPath;
+            ConvertToPartHandler handler(fromPath);
+            HTMLForm form(request, message, handler);
+            const std::string format = (form.has("format") ? form.get("format") : "");
+
+            bool sent = false;
+            if (!fromPath.empty())
+            {
+                if (!format.empty())
+                {
+                    LOG_INF("Conversion request for URI [" << fromPath << "].");
+
+                    auto uriPublic = DocumentBroker::sanitizeURI(fromPath);
+                    const auto docKey = DocumentBroker::getDocKey(uriPublic);
+
+                    // This lock could become a bottleneck.
+                    // In that case, we can use a pool and index by publicPath.
+                    std::unique_lock<std::mutex> docBrokersLock(DocBrokersMutex);
+
+                    // Request a kit process for this doc.
+                    auto child = getNewChild();
+                    if (!child)
+                    {
+                        // Let the client know we can't serve now.
+                        throw std::runtime_error("Failed to spawn lokit child.");
+                    }
+
+                    LOG_DBG("New DocumentBroker for docKey [" << docKey << "].");
+                    auto docBroker = std::make_shared<DocumentBroker>(fromPath, uriPublic, docKey, LOOLWSD::ChildRoot, child);
+                    child->setDocumentBroker(docBroker);
+
+                    cleanupDocBrokers();
+
+                    // FIXME: What if the same document is already open? Need a fake dockey here?
+                    LOG_DBG("New DocumentBroker for docKey [" << docKey << "].");
+                    DocBrokers.emplace(docKey, docBroker);
+                    LOG_TRC("Have " << DocBrokers.size() << " DocBrokers after inserting [" << docKey << "].");
+
+                    // Load the document.
+                    auto session = std::make_shared<ClientSession>(_id, docBroker, uriPublic);
+
+                    auto lock = docBroker->getLock();
+                    auto sessionsCount = docBroker->addSession(session);
+                    lock.unlock();
+                    LOG_TRC(docKey << ", ws_sessions++: " << sessionsCount);
+
+                    docBrokersLock.unlock();
+
+                    std::string encodedFrom;
+                    URI::encode(docBroker->getPublicUri().getPath(), "", encodedFrom);
+                    const std::string load = "load url=" + encodedFrom;
+                    std::vector<char> loadRequest(load.begin(), load.end());
+                    session->handleMessage(true, WebSocketHandler::WSOpCode::Text, loadRequest);
+
+                    // FIXME: Check for security violations.
+                    Path toPath(docBroker->getPublicUri().getPath());
+                    toPath.setExtension(format);
+                    const std::string toJailURL = "file://" + std::string(JAILED_DOCUMENT_ROOT) + toPath.getFileName();
+                    std::string encodedTo;
+                    URI::encode(toJailURL, "", encodedTo);
+
+                    // Convert it to the requested format.
+                    const auto saveas = "saveas url=" + encodedTo + " format=" + format + " options=";
+                    std::vector<char> saveasRequest(saveas.begin(), saveas.end());
+                    session->handleMessage(true, WebSocketHandler::WSOpCode::Text, saveasRequest);
+
+                    // Send it back to the client.
+                    try
+                    {
+                        Poco::URI resultURL(session->getSaveAsUrl(COMMAND_TIMEOUT_MS));
+                        LOG_TRC("Save-as URL: " << resultURL.toString());
+
+                        if (!resultURL.getPath().empty())
+                        {
+                            const std::string mimeType = "application/octet-stream";
+                            std::string encodedFilePath;
+                            URI::encode(resultURL.getPath(), "", encodedFilePath);
+                            LOG_TRC("Sending file: " << encodedFilePath);
+                            HttpHelper::sendFile(socket, encodedFilePath, mimeType);
+                            sent = true;
+                        }
+                    }
+                    catch (const std::exception& ex)
+                    {
+                        LOG_ERR("Failed to get save-as url: " << ex.what());
+                    }
+
+                    docBrokersLock.lock();
+                    auto docLock = docBroker->getLock();
+                    sessionsCount = docBroker->removeSession(_id);
+                    if (sessionsCount == 0)
+                    {
+                        // At this point we're done.
+                        LOG_DBG("Removing DocumentBroker for docKey [" << docKey << "].");
+                        DocBrokers.erase(docKey);
+                        docBroker->terminateChild(docLock, "");
+                        LOG_TRC("Have " << DocBrokers.size() << " DocBrokers after removing [" << docKey << "].");
+                    }
+                    else
+                    {
+                        LOG_ERR("Multiple sessions during conversion. " << sessionsCount << " sessions remain.");
+                    }
+                }
+
+                // Clean up the temporary directory the HTMLForm ctor created.
+                Path tempDirectory(fromPath);
+                tempDirectory.setFileName("");
+                FileUtil::removeFile(tempDirectory, /*recursive=*/true);
+            }
+
+            if (!sent)
+            {
+                // TODO: We should differentiate between bad request and failed conversion.
+                throw BadRequestException("Failed to convert and send file.");
+            }
+
+            return;
+        }
+        else if (tokens.count() >= 4 && tokens[3] == "insertfile")
+        {
+            LOG_INF("Insert file request.");
+            response.set("Access-Control-Allow-Origin", "*");
+            response.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
+            response.set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
+
+            std::string tmpPath;
+            ConvertToPartHandler handler(tmpPath);
+            HTMLForm form(request, message, handler);
+
+            if (form.has("childid") && form.has("name"))
+            {
+                const std::string formChildid(form.get("childid"));
+                const std::string formName(form.get("name"));
+
+                // Validate the docKey
+                std::unique_lock<std::mutex> docBrokersLock(DocBrokersMutex);
+                std::string decodedUri;
+                URI::decode(tokens[2], decodedUri);
+                const auto docKey = DocumentBroker::getDocKey(DocumentBroker::sanitizeURI(decodedUri));
+                auto docBrokerIt = DocBrokers.find(docKey);
+
+                // Maybe just free the client from sending childid in form ?
+                if (docBrokerIt == DocBrokers.end() || docBrokerIt->second->getJailId() != formChildid)
+                {
+                    throw BadRequestException("DocKey [" + docKey + "] or childid [" + formChildid + "] is invalid.");
+                }
+                docBrokersLock.unlock();
+
+                // protect against attempts to inject something funny here
+                if (formChildid.find('/') == std::string::npos && formName.find('/') == std::string::npos)
+                {
+                    LOG_INF("Perform insertfile: " << formChildid << ", " << formName);
+                    const std::string dirPath = LOOLWSD::ChildRoot + formChildid
+                                              + JAILED_DOCUMENT_ROOT + "insertfile";
+                    File(dirPath).createDirectories();
+                    std::string fileName = dirPath + "/" + form.get("name");
+                    File(tmpPath).moveTo(fileName);
+                    return; // FIXME send response
+                }
+            }
+        }
+        else if (tokens.count() >= 6)
+        {
+            LOG_INF("File download request.");
+            // TODO: Check that the user in question has access to this file!
+
+            // 1. Validate the dockey
+            std::string decodedUri;
+            URI::decode(tokens[2], decodedUri);
+            const auto docKey = DocumentBroker::getDocKey(DocumentBroker::sanitizeURI(decodedUri));
+            std::unique_lock<std::mutex> docBrokersLock(DocBrokersMutex);
+            auto docBrokerIt = DocBrokers.find(docKey);
+            if (docBrokerIt == DocBrokers.end())
+            {
+                throw BadRequestException("DocKey [" + docKey + "] is invalid.");
+            }
+
+            // 2. Cross-check if received child id is correct
+            if (docBrokerIt->second->getJailId() != tokens[3])
+            {
+                throw BadRequestException("ChildId does not correspond to docKey");
+            }
+
+            // 3. Don't let user download the file in main doc directory containing
+            // the document being edited otherwise we will end up deleting main directory
+            // after download finishes
+            if (docBrokerIt->second->getJailId() == tokens[4])
+            {
+                throw BadRequestException("RandomDir cannot be equal to ChildId");
+            }
+            docBrokersLock.unlock();
+
+            std::string fileName;
+            bool responded = false;
+            URI::decode(tokens[5], fileName);
+            const Path filePath(LOOLWSD::ChildRoot + tokens[3]
+                                + JAILED_DOCUMENT_ROOT + tokens[4] + "/" + fileName);
+            LOG_INF("HTTP request for: " << filePath.toString());
+            if (filePath.isAbsolute() && File(filePath).exists())
+            {
+                std::string contentType = getContentType(fileName);
+                response.set("Access-Control-Allow-Origin", "*");
+                if (Poco::Path(fileName).getExtension() == "pdf")
+                {
+                    contentType = "application/pdf";
+                    response.set("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
+                }
+
+                try
+                {
+                    response.setContentType(contentType);
+                    HttpHelper::sendFile(socket, filePath.toString(), response);
+                    responded = true;
+                }
+                catch (const Exception& exc)
+                {
+                    LOG_ERR("Error sending file to client: " << exc.displayText() <<
+                            (exc.nested() ? " (" + exc.nested()->displayText() + ")" : ""));
+                }
+
+                FileUtil::removeFile(File(filePath.parent()).path(), true);
+            }
+            else
+            {
+                LOG_ERR("Download file [" << filePath.toString() << "] not found.");
+            }
+
+            (void)responded;
+            return; // responded;
+        }
+
+        throw BadRequestException("Invalid or unknown request.");
     }
 
     void handleClientWsRequest(const Poco::Net::HTTPRequest& request, const std::string& url)
commit 97fc689f4f90c43e23bfc2e3d3f6fb20a59acbeb
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Tue Feb 28 17:31:27 2017 -0500

    nb: prefix large WS with nextmessage
    
    Change-Id: Ie6583c93ada9cff8aa50137431b5c2c5f7b39d97

diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp
index d4b6073..055a807 100644
--- a/net/WebSocketHandler.hpp
+++ b/net/WebSocketHandler.hpp
@@ -10,6 +10,7 @@
 #ifndef INCLUDED_WEBSOCKETHANDLER_HPP
 #define INCLUDED_WEBSOCKETHANDLER_HPP
 
+#include "Common.hpp"
 #include "Log.hpp"
 #include "Socket.hpp"
 
@@ -19,6 +20,12 @@ class WebSocketHandler : public SocketHandlerInterface
     std::weak_ptr<StreamSocket> _socket;
     std::vector<char> _wsPayload;
 
+    enum class WSFrameMask : unsigned char
+    {
+        Fin = 0x80,
+        Mask = 0x80
+    };
+
 public:
     WebSocketHandler()
     {
@@ -134,47 +141,60 @@ public:
         if (socket == nullptr)
             return 0; // no socket == connection closed.
 
-        bool fin = true;
-        bool mask = false;
+        //TODO: Support fragmented messages.
+        const unsigned char fin = static_cast<unsigned char>(WSFrameMask::Fin);
+        const unsigned char mask = 0; // Server must not mask, only clients.
 
         auto lock = socket->getWriteLock();
+        std::vector<char>& out = socket->_outBuffer;
+
+        // FIXME: need to support fragmented mesages, but for now send prefix message with size.
+        if (len >= LARGE_MESSAGE_SIZE)
+        {
+            const std::string nextmessage = "nextmessage: size=" + std::to_string(len);
+            const unsigned char size = (nextmessage.size() & 0xff);
+            out.push_back(fin | WSOpCode::Text);
+            out.push_back(mask | size);
+            out.insert(out.end(), nextmessage.data(), nextmessage.data() + size);
+            socket->writeOutgoingData();
+        }
 
         unsigned char header[2];
-        header[0] = (fin ? 0x80 : 0) | static_cast<unsigned char>(code);
-        header[1] = mask ? 0x80 : 0;
-        socket->_outBuffer.push_back((char)header[0]);
+        header[0] = fin | static_cast<unsigned char>(code);
+        header[1] = mask;
+        out.push_back((char)header[0]);
 
         // no out-bound masking ...
         if (len < 126)
         {
             header[1] |= len;
-            socket->_outBuffer.push_back((char)header[1]);
+            out.push_back((char)header[1]);
         }
         else if (len <= 0xffff)
         {
             header[1] |= 126;
-            socket->_outBuffer.push_back((char)header[1]);
-            socket->_outBuffer.push_back(static_cast<char>((len >> 8) & 0xff));
-            socket->_outBuffer.push_back(static_cast<char>((len >> 0) & 0xff));
+            out.push_back((char)header[1]);
+            out.push_back(static_cast<char>((len >> 8) & 0xff));
+            out.push_back(static_cast<char>((len >> 0) & 0xff));
         }
         else
         {
             header[1] |= 127;
-            socket->_outBuffer.push_back((char)header[1]);
-            socket->_outBuffer.push_back(static_cast<char>((len >> 56) & 0xff));
-            socket->_outBuffer.push_back(static_cast<char>((len >> 48) & 0xff));
-            socket->_outBuffer.push_back(static_cast<char>((len >> 40) & 0xff));
-            socket->_outBuffer.push_back(static_cast<char>((len >> 32) & 0xff));
-            socket->_outBuffer.push_back(static_cast<char>((len >> 24) & 0xff));
-            socket->_outBuffer.push_back(static_cast<char>((len >> 16) & 0xff));
-            socket->_outBuffer.push_back(static_cast<char>((len >> 8) & 0xff));
-            socket->_outBuffer.push_back(static_cast<char>((len >> 0) & 0xff));
+            out.push_back((char)header[1]);
+            out.push_back(static_cast<char>((len >> 56) & 0xff));
+            out.push_back(static_cast<char>((len >> 48) & 0xff));
+            out.push_back(static_cast<char>((len >> 40) & 0xff));
+            out.push_back(static_cast<char>((len >> 32) & 0xff));
+            out.push_back(static_cast<char>((len >> 24) & 0xff));
+            out.push_back(static_cast<char>((len >> 16) & 0xff));
+            out.push_back(static_cast<char>((len >> 8) & 0xff));
+            out.push_back(static_cast<char>((len >> 0) & 0xff));
         }
 
         // FIXME: pick random number and mask in the outbuffer etc.
         assert (!mask);
 
-        socket->_outBuffer.insert(socket->_outBuffer.end(), data, data + len);
+        out.insert(out.end(), data, data + len);
         if (flush)
             socket->writeOutgoingData();
 
commit 6b3104962c90490ea2d567022ce964d2d523d180
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Tue Feb 28 16:51:19 2017 -0500

    nb: send the WS upgrade response separate from later frames
    
    Apparently if we don't send immediately after the upgrade
    the first frame doesn't get parsed as WebSocket (at least
    in Poco).
    
    Change-Id: Ieb30afae1423d8352d81c79af568947d10fca1e6

diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index 964abf1..4c38c21 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -3082,8 +3082,8 @@ private:
             << "Connection: Upgrade\r\n"
             << "Sec-Websocket-Accept: " << PublicComputeAccept::doComputeAccept(wsKey) << "\r\n"
             << "\r\n";
-        std::string str = oss.str();
-        socket->_outBuffer.insert(socket->_outBuffer.end(), str.begin(), str.end());
+
+        socket->send(oss.str());
         _wsState = WSState::WS;
 
         // Create a WS wrapper to use for sending the client status.
commit 4e9d5bfa720f7df847245a3dbc38b46033f39bf3
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Tue Feb 28 16:49:24 2017 -0500

    nb: log the test name where possible
    
    Helps troubleshoot failed tests faster.
    
    Change-Id: I12172bcdd46403b902bf440b919916263cb1a02b

diff --git a/test/UnitAdmin.cpp b/test/UnitAdmin.cpp
index 799eb12..4f4f877 100644
--- a/test/UnitAdmin.cpp
+++ b/test/UnitAdmin.cpp
@@ -206,7 +206,7 @@ private:
         Poco::Thread::sleep(250);
 
         std::string documentPath1, documentURL1;
-        helpers::getDocumentPathAndURL("hello.odt", documentPath1, documentURL1);
+        helpers::getDocumentPathAndURL("hello.odt", documentPath1, documentURL1, "unitAdmin-hello.odt ");
         HTTPRequest request1(HTTPRequest::HTTP_GET, documentURL1);
         HTTPResponse response1;
         const Poco::URI docUri1(helpers::getTestServerURI());
@@ -270,7 +270,7 @@ private:
 
         // Open another document (different)
         std::string documentPath2, documentURL2;
-        helpers::getDocumentPathAndURL("insert-delete.odp", documentPath2, documentURL2);
+        helpers::getDocumentPathAndURL("insert-delete.odp", documentPath2, documentURL2, "unitAdmin-insert-delete.odp ");
         HTTPRequest request2(HTTPRequest::HTTP_GET, documentURL2);
         HTTPResponse response2;
         const Poco::URI docUri2(helpers::getTestServerURI());
diff --git a/test/UnitMinSocketBufferSize.cpp b/test/UnitMinSocketBufferSize.cpp
index 7134a62..4910683 100644
--- a/test/UnitMinSocketBufferSize.cpp
+++ b/test/UnitMinSocketBufferSize.cpp
@@ -36,7 +36,7 @@ public:
         {
         case Phase::Load:
         {
-            getDocumentPathAndURL("Example.odt", _docPath, _docURL);
+            getDocumentPathAndURL("Example.odt", _docPath, _docURL, "unitMinSocketBufferSize ");
             _ws = std::unique_ptr<UnitWebSocket>(new UnitWebSocket(_docURL));
             assert(_ws.get());
 
diff --git a/test/UnitStorage.cpp b/test/UnitStorage.cpp
index be5e8ee..1a2f717 100644
--- a/test/UnitStorage.cpp
+++ b/test/UnitStorage.cpp
@@ -48,7 +48,7 @@ public:
     {
         std::string docPath;
         std::string docURL;
-        getDocumentPathAndURL("empty.odt", docPath, docURL);
+        getDocumentPathAndURL("empty.odt", docPath, docURL, "unitStorage ");
         _ws = std::unique_ptr<UnitWebSocket>(new UnitWebSocket(docURL));
         assert(_ws.get());
     }
diff --git a/test/UnitTileCache.cpp b/test/UnitTileCache.cpp
index 4aa35aa..65cf37c 100644
--- a/test/UnitTileCache.cpp
+++ b/test/UnitTileCache.cpp
@@ -53,7 +53,7 @@ public:
             _phase = Phase::Tile;
             std::string docPath;
             std::string docURL;
-            getDocumentPathAndURL("empty.odt", docPath, docURL);
+            getDocumentPathAndURL("empty.odt", docPath, docURL, "unitTileCache ");
             _ws = std::unique_ptr<UnitWebSocket>(new UnitWebSocket(docURL));
             assert(_ws.get());
 
diff --git a/test/helpers.hpp b/test/helpers.hpp
index 60b94cb..fd7115f 100644
--- a/test/helpers.hpp
+++ b/test/helpers.hpp
@@ -115,7 +115,7 @@ std::vector<char> readDataFromFile(std::unique_ptr<std::fstream>& file)
 }
 
 inline
-void getDocumentPathAndURL(const std::string& docFilename, std::string& documentPath, std::string& documentURL, std::string prefix="")
+void getDocumentPathAndURL(const std::string& docFilename, std::string& documentPath, std::string& documentURL, std::string prefix)
 {
     std::replace(prefix.begin(), prefix.end(), ' ', '_');
     documentPath = FileUtil::getTempFilePath(TDOC, docFilename, prefix);
@@ -418,7 +418,7 @@ std::shared_ptr<LOOLWebSocket> loadDocAndGetSocket(const std::string& docFilenam
     try
     {
         std::string documentPath, documentURL;
-        getDocumentPathAndURL(docFilename, documentPath, documentURL);
+        getDocumentPathAndURL(docFilename, documentPath, documentURL, name);
         return loadDocAndGetSocket(uri, documentURL, name, isView);
     }
     catch (const Poco::Exception& exc)
diff --git a/test/httpwserror.cpp b/test/httpwserror.cpp
index f805fd7..d658905 100644
--- a/test/httpwserror.cpp
+++ b/test/httpwserror.cpp
@@ -87,7 +87,7 @@ void HTTPWSError::testBadDocLoadFail()
     try
     {
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("corrupted.odt", documentPath, documentURL);
+        getDocumentPathAndURL("corrupted.odt", documentPath, documentURL, testname);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
         auto socket = connectLOKit(_uri, request, _response);
diff --git a/test/httpwstest.cpp b/test/httpwstest.cpp
index f02eac5..692090e 100644
--- a/test/httpwstest.cpp
+++ b/test/httpwstest.cpp
@@ -251,10 +251,11 @@ void HTTPWSTest::testBadRequest()
 
 void HTTPWSTest::testHandshake()
 {
+    const auto testname = "handshake ";
     try
     {
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("hello.odt", documentPath, documentURL);
+        getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
 
         // NOTE: Do not replace with wrappers. This has to be explicit.
         Poco::Net::HTTPResponse response;
@@ -364,7 +365,7 @@ void HTTPWSTest::testConnectNoLoad()
     const auto testname3 = "connectNoLoad-3 ";
 
     std::string documentPath, documentURL;
-    getDocumentPathAndURL("hello.odt", documentPath, documentURL);
+    getDocumentPathAndURL("hello.odt", documentPath, documentURL, "connectNoLoad ");
 
     Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
     std::cerr << testname1 << "Connecting." << std::endl;
@@ -393,8 +394,10 @@ void HTTPWSTest::testConnectNoLoad()
 
 void HTTPWSTest::testLoadSimple()
 {
+    const auto testname = "loadSimple ";
+
     std::string documentPath, documentURL;
-    getDocumentPathAndURL("hello.odt", documentPath, documentURL);
+    getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
     loadDoc(documentURL, "load ");
 }
 
@@ -405,7 +408,7 @@ int HTTPWSTest::loadTorture(const std::string& testname,
 {
     // Load same document from many threads together.
     std::string documentPath, documentURL;
-    getDocumentPathAndURL(docName, documentPath, documentURL);
+    getDocumentPathAndURL(docName, documentPath, documentURL, testname);
 
     std::atomic<int> sum_view_ids;
     sum_view_ids = 0;
@@ -556,11 +559,12 @@ void HTTPWSTest::testLoadTorture()
 
 void HTTPWSTest::testBadLoad()
 {
+    const auto testname = "badLoad ";
     try
     {
         // Load a document and get its status.
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("hello.odt", documentPath, documentURL);
+        getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
         auto socket = connectLOKit(_uri, request, _response);
@@ -582,7 +586,7 @@ void HTTPWSTest::testReload()
     auto const testname = "reload ";
 
     std::string documentPath, documentURL;
-    getDocumentPathAndURL("hello.odt", documentPath, documentURL);
+    getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
     for (auto i = 0; i < 3; ++i)
     {
         std::cerr << testname << "loading #" << (i+1) << std::endl;
@@ -596,7 +600,7 @@ void HTTPWSTest::testGetTextSelection()
     try
     {
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("hello.odt", documentPath, documentURL);
+        getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
 
         auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
         auto socket2 = loadDocAndGetSocket(_uri, documentURL, testname);
@@ -620,7 +624,7 @@ void HTTPWSTest::testSaveOnDisconnect()
     std::cerr << "Test string: [" << text << "]." << std::endl;
 
     std::string documentPath, documentURL;
-    getDocumentPathAndURL("hello.odt", documentPath, documentURL);
+    getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
 
     int kitcount = -1;
     try
@@ -686,7 +690,7 @@ void HTTPWSTest::testReloadWhileDisconnecting()
     try
     {
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("hello.odt", documentPath, documentURL);
+        getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
 
         auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
 
@@ -835,11 +839,12 @@ void HTTPWSTest::testLargePaste()
 
 void HTTPWSTest::testRenderingOptions()
 {
+    const auto testname = "renderingOptions ";
     try
     {
         // Load a document and get its size.
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("hide-whitespace.odt", documentPath, documentURL);
+        getDocumentPathAndURL("hide-whitespace.odt", documentPath, documentURL, testname);
 
         const std::string options = "{\"rendering\":{\".uno:HideWhitespace\":{\"type\":\"boolean\",\"value\":\"true\"}}}";
 
@@ -873,7 +878,7 @@ void HTTPWSTest::testPasswordProtectedDocumentWithoutPassword()
     try
     {
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("password-protected.ods", documentPath, documentURL);
+        getDocumentPathAndURL("password-protected.ods", documentPath, documentURL, testname);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
         auto socket = connectLOKit(_uri, request, _response);
@@ -904,7 +909,7 @@ void HTTPWSTest::testPasswordProtectedDocumentWithWrongPassword()
     try
     {
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("password-protected.ods", documentPath, documentURL);
+        getDocumentPathAndURL("password-protected.ods", documentPath, documentURL, testname);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
         auto socket = connectLOKit(_uri, request, _response);
@@ -931,10 +936,11 @@ void HTTPWSTest::testPasswordProtectedDocumentWithWrongPassword()
 
 void HTTPWSTest::testPasswordProtectedDocumentWithCorrectPassword()
 {
+    const auto testname = "passwordProtectedDocumentWithCorrectPassword ";
     try
     {
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("password-protected.ods", documentPath, documentURL);
+        getDocumentPathAndURL("password-protected.ods", documentPath, documentURL, testname);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
         auto socket = connectLOKit(_uri, request, _response);
@@ -957,10 +963,11 @@ void HTTPWSTest::testPasswordProtectedDocumentWithCorrectPasswordAgain()
 
 void HTTPWSTest::testPasswordProtectedOOXMLDocument()
 {
+    const auto testname = "passwordProtectedOOXMLDocument ";
     try
     {
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("password-protected.docx", documentPath, documentURL);
+        getDocumentPathAndURL("password-protected.docx", documentPath, documentURL, testname);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
         auto socket = connectLOKit(_uri, request, _response);
@@ -978,10 +985,11 @@ void HTTPWSTest::testPasswordProtectedOOXMLDocument()
 
 void HTTPWSTest::testPasswordProtectedBinaryMSOfficeDocument()
 {
+    const auto testname = "passwordProtectedBinaryMSOfficeDocument ";
     try
     {
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("password-protected.doc", documentPath, documentURL);
+        getDocumentPathAndURL("password-protected.doc", documentPath, documentURL, testname);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
         auto socket = connectLOKit(_uri, request, _response);
@@ -999,6 +1007,7 @@ void HTTPWSTest::testPasswordProtectedBinaryMSOfficeDocument()
 
 void HTTPWSTest::testInsertDelete()
 {
+    const auto testname = "insertDelete ";
     try
     {
         std::vector<std::string> parts;
@@ -1006,7 +1015,7 @@ void HTTPWSTest::testInsertDelete()
 
         // Load a document
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("insert-delete.odp", documentPath, documentURL);
+        getDocumentPathAndURL("insert-delete.odp", documentPath, documentURL, testname);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
         auto socket = connectLOKit(_uri, request, _response);
@@ -1101,7 +1110,7 @@ void HTTPWSTest::testSlideShow()
         // Load a document
         std::string documentPath, documentURL;
         std::string response;
-        getDocumentPathAndURL("setclientpart.odp", documentPath, documentURL);
+        getDocumentPathAndURL("setclientpart.odp", documentPath, documentURL, testname);
 
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
         auto socket = connectLOKit(_uri, request, _response);
@@ -1147,10 +1156,11 @@ void HTTPWSTest::testSlideShow()
 
 void HTTPWSTest::testInactiveClient()
 {
+    const auto testname = "inactiveClient ";
     try
     {
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("hello.odt", documentPath, documentURL);
+        getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
 
         auto socket1 = loadDocAndGetSocket(_uri, documentURL, "inactiveClient-1 ");
 
@@ -1401,7 +1411,7 @@ void HTTPWSTest::testInsertAnnotationWriter()
     const auto testname = "insertAnnotationWriter ";
 
     std::string documentPath, documentURL;
-    getDocumentPathAndURL("hello.odt", documentPath, documentURL);
+    getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
     Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
 
     auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
@@ -1490,7 +1500,7 @@ void HTTPWSTest::testEditAnnotationWriter()
     const auto testname = "editAnnotationWriter ";
 
     std::string documentPath, documentURL;
-    getDocumentPathAndURL("with_comment.odt", documentPath, documentURL);
+    getDocumentPathAndURL("with_comment.odt", documentPath, documentURL, testname);
 
     auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
 
@@ -1940,7 +1950,7 @@ void HTTPWSTest::testColumnRowResize()
         std::string documentPath, documentURL;
         double oldHeight, oldWidth;
 
-        getDocumentPathAndURL("setclientpart.ods", documentPath, documentURL);
+        getDocumentPathAndURL("setclientpart.ods", documentPath, documentURL, testname);
         auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
 
         const std::string commandValues = "commandvalues command=.uno:ViewRowColumnHeaders";
@@ -2038,7 +2048,7 @@ void HTTPWSTest::testOptimalResize()
         objModifier.set("value", 0);
 
         std::string documentPath, documentURL;
-        getDocumentPathAndURL("empty.ods", documentPath, documentURL);
+        getDocumentPathAndURL("empty.ods", documentPath, documentURL, testname);
         auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
 
         const std::string commandValues = "commandvalues command=.uno:ViewRowColumnHeaders";
@@ -2133,7 +2143,7 @@ void HTTPWSTest::testEachView(const std::string& doc, const std::string& type,
     {
         // Load a document
         std::string documentPath, documentURL;
-        getDocumentPathAndURL(doc, documentPath, documentURL);
+        getDocumentPathAndURL(doc, documentPath, documentURL, testname);
 
         int itView = 0;
         auto socket = loadDocAndGetSocket(_uri, documentURL, Poco::format(view, itView));
@@ -2251,7 +2261,7 @@ void HTTPWSTest::testCursorPosition()
         std::string docURL;
         std::string response;
 
-        getDocumentPathAndURL("Example.odt", docPath, docURL);
+        getDocumentPathAndURL("Example.odt", docPath, docURL, testname);
         auto socket0 = loadDocAndGetSocket(_uri, docURL, testname);
 
         // receive cursor position
@@ -2296,8 +2306,8 @@ void HTTPWSTest::testAlertAllUsers()
         std::string docPath[2];
         std::string docURL[2];
 
-        getDocumentPathAndURL("Example.odt", docPath[0], docURL[0]);
-        getDocumentPathAndURL("hello.odt", docPath[1], docURL[1]);
+        getDocumentPathAndURL("Example.odt", docPath[0], docURL[0], testname);
+        getDocumentPathAndURL("hello.odt", docPath[1], docURL[1], testname);
 
         Poco::Net::HTTPRequest* request[2];
 
@@ -2341,7 +2351,7 @@ void HTTPWSTest::testViewInfoMsg()
     const std::string testname = "testViewInfoMsg-";
     std::string docPath;
     std::string docURL;
-    getDocumentPathAndURL("hello.odt", docPath, docURL);
+    getDocumentPathAndURL("hello.odt", docPath, docURL, testname);
 
     Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, docURL);
     auto socket0 = connectLOKit(_uri, request, _response);


More information about the Libreoffice-commits mailing list