libreoffice-online/loolwsd/test/httpwstest.cpp
Tor Lillqvist 4b5e799a33 Split testPasswordProtectedDocument() into three
It does three separate things, and the first two intentionally result
in errors, and the server probably disconnects after errors. Not
sure. This is horrible, horrible.
2016-02-25 11:02:35 +02:00

517 lines
19 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <Poco/Net/HTTPClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/Net/NetException.h>
#include <Poco/Net/WebSocket.h>
#include <Poco/Net/Socket.h>
#include <Poco/Path.h>
#include <Poco/StringTokenizer.h>
#include <Poco/URI.h>
#include <cppunit/extensions/HelperMacros.h>
#include <Poco/JSON/JSON.h>
#include <Poco/JSON/Parser.h>
#include <Poco/Dynamic/Var.h>
#include <LOOLProtocol.hpp>
#include <Common.hpp>
#include <ChildProcessSession.hpp>
/// Tests the HTTP WebSocket API of loolwsd. The server has to be started manually before running this test.
class HTTPWSTest : public CPPUNIT_NS::TestFixture
{
const Poco::URI _uri;
Poco::Net::HTTPClientSession _session;
Poco::Net::HTTPRequest _request;
Poco::Net::HTTPResponse _response;
CPPUNIT_TEST_SUITE(HTTPWSTest);
CPPUNIT_TEST(testPaste);
CPPUNIT_TEST(testLargePaste);
CPPUNIT_TEST(testRenderingOptions);
CPPUNIT_TEST(testPasswordProtectedDocument);
CPPUNIT_TEST(testPasswordProtectedDocument2);
CPPUNIT_TEST(testPasswordProtectedDocument3);
CPPUNIT_TEST(testImpressPartCountChanged);
CPPUNIT_TEST_SUITE_END();
void testPaste();
void testLargePaste();
void testRenderingOptions();
void testPasswordProtectedDocument();
void testPasswordProtectedDocument2();
void testPasswordProtectedDocument3();
void testImpressPartCountChanged();
static
void sendTextFrame(Poco::Net::WebSocket& socket, const std::string& string);
static
bool isDocumentLoaded(Poco::Net::WebSocket& socket);
static
void getResponseMessage(Poco::Net::WebSocket& socket,
const std::string& prefix,
std::string& response,
const bool isLine);
public:
HTTPWSTest()
: _uri("http://127.0.0.1:" + std::to_string(ClientPortNumber)),
_session(_uri.getHost(), _uri.getPort()),
_request(Poco::Net::HTTPRequest::HTTP_GET, "/ws")
{
}
void setUp()
{
}
void tearDown()
{
}
};
void HTTPWSTest::testPaste()
{
try
{
Poco::Net::WebSocket socket(_session, _request, _response);
// Load a document and make it empty.
const std::string documentPath = TDOC "/hello.odt";
const std::string documentURL = "file://" + Poco::Path(documentPath).makeAbsolute().toString();
sendTextFrame(socket, "load url=" + documentURL);
sendTextFrame(socket, "status");
CPPUNIT_ASSERT_MESSAGE("cannot load the document " + documentURL, isDocumentLoaded(socket));
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "uno .uno:Delete");
// Paste some text into it.
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\naaa bbb ccc");
// Check if the document contains the pasted text.
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
std::string selection;
int flags;
int n;
do
{
char buffer[READ_BUFFER_SIZE];
n = socket.receiveFrame(buffer, sizeof(buffer), flags);
if (n > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE)
{
std::cout << "Received message length " << n << ": " << LOOLProtocol::getAbbreviatedMessage(buffer, n) << '\n';
const std::string line = LOOLProtocol::getFirstLine(buffer, n);
const std::string prefix = "textselectioncontent: ";
if (line.find(prefix) == 0)
{
selection = line.substr(prefix.length());
break;
}
}
}
while (n > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
socket.shutdown();
CPPUNIT_ASSERT_EQUAL(std::string("aaa bbb ccc"), selection);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_ASSERT_MESSAGE(exc.displayText(), false);
}
}
void HTTPWSTest::testLargePaste()
{
try
{
Poco::Net::WebSocket socket(_session, _request, _response);
// Load a document and make it empty.
std::string documentPath = TDOC "/hello.odt";
std::string documentURL = "file://" + Poco::Path(documentPath).makeAbsolute().toString();
sendTextFrame(socket, "load url=" + documentURL);
sendTextFrame(socket, "status");
CPPUNIT_ASSERT_MESSAGE("cannot load the document " + documentURL, isDocumentLoaded(socket));
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "uno .uno:Delete");
// Paste some text into it.
std::ifstream documentStream(documentPath);
std::string documentContents((std::istreambuf_iterator<char>(documentStream)), std::istreambuf_iterator<char>());
sendTextFrame(socket, "paste mimetype=text/html\n" + documentContents);
// Check if the server is still alive.
// This resulted first in a hang, as respose for the message never arrived, then a bit later in a Poco::TimeoutException.
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
std::string selection;
int flags;
int n;
do
{
char buffer[READ_BUFFER_SIZE];
n = socket.receiveFrame(buffer, sizeof(buffer), flags);
if (n > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE)
{
std::cout << "Received message length " << n << ": " << LOOLProtocol::getAbbreviatedMessage(buffer, n) << '\n';
std::string line = LOOLProtocol::getFirstLine(buffer, n);
std::string prefix = "textselectioncontent: ";
if (line.find(prefix) == 0)
break;
}
}
while (n > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
socket.shutdown();
}
catch (const Poco::Exception& exc)
{
CPPUNIT_ASSERT_MESSAGE(exc.displayText(), false);
}
}
void HTTPWSTest::testRenderingOptions()
{
try
{
Poco::Net::WebSocket socket(_session, _request, _response);
// Load a document and get its size.
const std::string documentPath = TDOC "/hide-whitespace.odt";
const std::string documentURL = "file://" + Poco::Path(documentPath).makeAbsolute().toString();
const std::string options = "{\"rendering\":{\".uno:HideWhitespace\":{\"type\":\"boolean\",\"value\":\"true\"}}}";
sendTextFrame(socket, "load url=" + documentURL + " options=" + options);
sendTextFrame(socket, "status");
std::string status;
int flags;
int n;
do
{
char buffer[READ_BUFFER_SIZE];
n = socket.receiveFrame(buffer, sizeof(buffer), flags);
if (n > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE)
{
std::cout << "Received message length " << n << ": " << LOOLProtocol::getAbbreviatedMessage(buffer, n) << '\n';
std::string line = LOOLProtocol::getFirstLine(buffer, n);
std::string prefix = "status: ";
if (line.find(prefix) == 0)
{
status = line.substr(prefix.length());
break;
}
}
}
while (n > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
socket.shutdown();
// Expected format is something like 'type=text parts=2 current=0 width=12808 height=1142'.
Poco::StringTokenizer tokens(status, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), tokens.count());
const std::string token = tokens[4];
const std::string prefix = "height=";
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), token.find(prefix));
const int height = std::stoi(token.substr(prefix.size()));
// HideWhitespace was ignored, this was 32532, should be around 16706.
CPPUNIT_ASSERT(height < 20000);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_ASSERT_MESSAGE(exc.displayText(), false);
}
}
void HTTPWSTest::testPasswordProtectedDocument()
{
try {
Poco::Net::WebSocket socket(_session, _request, _response);
const std::string documentPath = TDOC "/password-protected.ods";
const std::string documentURL = "file://" + Poco::Path(documentPath).makeAbsolute().toString();
// Send a load request without password first
sendTextFrame(socket, "load url=" + documentURL);
std::string response;
getResponseMessage(socket, "error:", response, true);
CPPUNIT_ASSERT_MESSAGE("failed command load: ", !response.empty());
{
Poco::StringTokenizer tokens(response, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), tokens.count());
std::string errorCommand;
std::string errorKind;
LOOLProtocol::getTokenString(tokens[0], "cmd", errorCommand);
LOOLProtocol::getTokenString(tokens[1], "kind", errorKind);
CPPUNIT_ASSERT_EQUAL(std::string("load"), errorCommand);
CPPUNIT_ASSERT_EQUAL(std::string("passwordrequired:to-view"), errorKind);
}
socket.shutdown();
}
catch (const Poco::Exception& exc)
{
CPPUNIT_ASSERT_MESSAGE(exc.displayText(), false);
}
}
void HTTPWSTest::testPasswordProtectedDocument2()
{
try {
Poco::Net::WebSocket socket(_session, _request, _response);
const std::string documentPath = TDOC "/password-protected.ods";
const std::string documentURL = "file://" + Poco::Path(documentPath).makeAbsolute().toString();
std::string response;
// Send a load request with incorrect password
sendTextFrame(socket, "load url=" + documentURL + " password=2");
getResponseMessage(socket, "error:", response, true);
CPPUNIT_ASSERT_MESSAGE("failed command load: ", !response.empty());
{
Poco::StringTokenizer tokens(response, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), tokens.count());
std::string errorCommand;
std::string errorKind;
LOOLProtocol::getTokenString(tokens[0], "cmd", errorCommand);
LOOLProtocol::getTokenString(tokens[1], "kind", errorKind);
CPPUNIT_ASSERT_EQUAL(std::string("load"), errorCommand);
CPPUNIT_ASSERT_EQUAL(std::string("wrongpassword"), errorKind);
}
socket.shutdown();
}
catch (const Poco::Exception& exc)
{
CPPUNIT_ASSERT_MESSAGE(exc.displayText(), false);
}
}
void HTTPWSTest::testPasswordProtectedDocument3()
{
try {
Poco::Net::WebSocket socket(_session, _request, _response);
const std::string documentPath = TDOC "/password-protected.ods";
const std::string documentURL = "file://" + Poco::Path(documentPath).makeAbsolute().toString();
std::string response;
// Send a load request with correct password
sendTextFrame(socket, "load url=" + documentURL + " password=1");
CPPUNIT_ASSERT_MESSAGE("cannot load the document with correct password " + documentURL, isDocumentLoaded(socket));
socket.shutdown();
}
catch (const Poco::Exception& exc)
{
CPPUNIT_ASSERT_MESSAGE(exc.displayText(), false);
}
}
void HTTPWSTest::testImpressPartCountChanged()
{
try
{
Poco::Net::WebSocket socket(_session, _request, _response);
// Load a document
const std::string documentPath = TDOC "/insert-delete.odp";
const std::string documentURL = "file://" + Poco::Path(documentPath).makeAbsolute().toString();
sendTextFrame(socket, "load url=" + documentURL);
sendTextFrame(socket, "status");
CPPUNIT_ASSERT_MESSAGE("cannot load the document " + documentURL, isDocumentLoaded(socket));
std::string response;
// check total slides 1
sendTextFrame(socket, "status");
getResponseMessage(socket, "status:", response, true);
CPPUNIT_ASSERT_MESSAGE("failed command status: ", !response.empty());
{
Poco::StringTokenizer tokens(response, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), tokens.count());
// Expected format is something like 'type= parts= current= width= height='.
const std::string prefix = "parts=";
const int totalParts = std::stoi(tokens[1].substr(prefix.size()));
CPPUNIT_ASSERT_EQUAL(totalParts, 1);
}
// insert 10 slides
for(unsigned it = 1; it <= 10; it++)
{
sendTextFrame(socket, "uno .uno:InsertPage");
getResponseMessage(socket, "partscountchanged:", response, false);
CPPUNIT_ASSERT_MESSAGE("failed command partscountchanged: ", !response.empty());
{
Poco::JSON::Parser parser;
Poco::Dynamic::Var result = parser.parse(response);
Poco::DynamicStruct values = *result.extract<Poco::JSON::Object::Ptr>();
CPPUNIT_ASSERT(values["action"] == "PartInserted");
}
}
// delete 10 slides
for(unsigned it = 1; it <= 10; it++)
{
sendTextFrame(socket, "uno .uno:DeletePage");
getResponseMessage(socket, "partscountchanged:", response, false);
CPPUNIT_ASSERT_MESSAGE("failed command partscountchanged: ", !response.empty());
{
Poco::JSON::Parser parser;
Poco::Dynamic::Var result = parser.parse(response);
Poco::DynamicStruct values = *result.extract<Poco::JSON::Object::Ptr>();
CPPUNIT_ASSERT(values["action"] == "PartDeleted");
}
}
// undo delete slides
for(unsigned it = 1; it <= 10; it++)
{
sendTextFrame(socket, "uno .uno:Undo");
getResponseMessage(socket, "partscountchanged:", response, false);
CPPUNIT_ASSERT_MESSAGE("failed command partscountchanged: ", !response.empty());
{
Poco::JSON::Parser parser;
Poco::Dynamic::Var result = parser.parse(response);
Poco::DynamicStruct values = *result.extract<Poco::JSON::Object::Ptr>();
CPPUNIT_ASSERT(values["action"] == "PartInserted");
}
}
// redo inserted slides
for(unsigned it = 1; it <= 10; it++)
{
sendTextFrame(socket, "uno .uno:Redo");
getResponseMessage(socket, "partscountchanged:", response, false);
CPPUNIT_ASSERT_MESSAGE("failed command partscountchanged: ", !response.empty());
{
Poco::JSON::Parser parser;
Poco::Dynamic::Var result = parser.parse(response);
Poco::DynamicStruct values = *result.extract<Poco::JSON::Object::Ptr>();
CPPUNIT_ASSERT(values["action"] == "PartDeleted");
}
}
socket.shutdown();
}
catch (const Poco::Exception& exc)
{
CPPUNIT_ASSERT_MESSAGE(exc.displayText(), false);
}
}
void HTTPWSTest::sendTextFrame(Poco::Net::WebSocket& socket, const std::string& string)
{
socket.sendFrame(string.data(), string.size());
}
bool HTTPWSTest::isDocumentLoaded(Poco::Net::WebSocket& ws)
{
bool isLoaded = false;
try
{
int flags;
int bytes;
int retries = 10;
const Poco::Timespan waitTime(1000000);
ws.setReceiveTimeout(0);
do
{
char buffer[READ_BUFFER_SIZE];
if (ws.poll(waitTime, Poco::Net::Socket::SELECT_READ))
{
bytes = ws.receiveFrame(buffer, sizeof(buffer), flags);
if (bytes > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE)
{
std::cout << "Received message length " << bytes << ": " << LOOLProtocol::getAbbreviatedMessage(buffer, bytes) << '\n';
const std::string line = LOOLProtocol::getFirstLine(buffer, bytes);
const std::string prefixIndicator = "statusindicatorfinish:";
const std::string prefixStatus = "status:";
if (line.find(prefixIndicator) == 0 || line.find(prefixStatus) == 0)
{
isLoaded = true;
break;
}
}
retries = 10;
}
else
{
--retries;
}
}
while (retries > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
}
catch (const Poco::Net::WebSocketException& exc)
{
std::cout << exc.message();
}
return isLoaded;
}
void HTTPWSTest::getResponseMessage(Poco::Net::WebSocket& ws, const std::string& prefix, std::string& response, const bool isLine)
{
try
{
int flags;
int bytes;
int retries = 10;
const Poco::Timespan waitTime(1000000);
response.clear();
ws.setReceiveTimeout(0);
do
{
char buffer[READ_BUFFER_SIZE];
if (ws.poll(waitTime, Poco::Net::Socket::SELECT_READ))
{
bytes = ws.receiveFrame(buffer, sizeof(buffer), flags);
if (bytes > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE)
{
std::cout << "Received message length " << bytes << ": " << LOOLProtocol::getAbbreviatedMessage(buffer, bytes) << '\n';
const std::string message = isLine ?
LOOLProtocol::getFirstLine(buffer, bytes) :
std::string(buffer, bytes);
if (message.find(prefix) == 0)
{
response = message.substr(prefix.length());
break;
}
}
retries = 10;
}
else
{
--retries;
}
}
while (retries > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
}
catch (const Poco::Net::WebSocketException& exc)
{
std::cout << exc.message();
}
}
CPPUNIT_TEST_SUITE_REGISTRATION(HTTPWSTest);
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */