loolwsd/AdminModel.cpp | 361 ++++++++++++++++++++++++++++++++++++++++++ loolwsd/AdminModel.hpp | 385 +++------------------------------------------ loolwsd/Auth.cpp | 211 ++++++++++++++++++++++++ loolwsd/Auth.hpp | 187 --------------------- loolwsd/DocumentBroker.cpp | 2 loolwsd/Makefile.am | 97 ++++++++--- loolwsd/Storage.cpp | 275 ++++++++++++++++++++++++++++++++ loolwsd/Storage.hpp | 245 +--------------------------- 8 files changed, 972 insertions(+), 791 deletions(-)
New commits: commit 4ae077200cb1090b109125d4534dfbf5282a9740 Author: Pranav Kant <pran...@collabora.com> Date: Thu Mar 31 12:55:35 2016 +0530 loolwsd: Separate AdminModel header and implementation Change-Id: Iddf107aa7985988deba800030e75243a831a7532 diff --git a/loolwsd/AdminModel.cpp b/loolwsd/AdminModel.cpp new file mode 100644 index 0000000..644fa9c --- /dev/null +++ b/loolwsd/AdminModel.cpp @@ -0,0 +1,361 @@ +/* -*- 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 <memory> +#include <set> +#include <sstream> +#include <string> + +#include <Poco/Net/WebSocket.h> +#include <Poco/Process.h> +#include <Poco/StringTokenizer.h> + +#include "AdminModel.hpp" +#include "Util.hpp" + +using Poco::StringTokenizer; + +///////////////// +// Document Impl +//////////////// +void Document::addView(int nSessionId) +{ + const auto ret = _views.emplace(nSessionId, View(nSessionId)); + if (!ret.second) + { + Log::warn() << "View with SessionID [" + std::to_string(nSessionId) + "] already exists." << Log::end; + } + else + { + _nActiveViews++; + } +} + +void Document::removeView(int nSessionId) +{ + auto it = _views.find(nSessionId); + if (it != _views.end()) + { + it->second.expire(); + _nActiveViews--; + } +} + +/////////////////// +// Subscriber Impl +////////////////// +bool Subscriber::notify(const std::string& message) +{ + StringTokenizer tokens(message, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); + + if (_subscriptions.find(tokens[0]) == _subscriptions.end()) + return true; + + auto webSocket = _ws.lock(); + if (webSocket) + { + webSocket->sendFrame(message.data(), message.length()); + return true; + } + else + { + return false; + } +} + +bool Subscriber::subscribe(const std::string& command) +{ + auto ret = _subscriptions.insert(command); + return ret.second; +} + +void Subscriber::unsubscribe(const std::string& command) +{ + _subscriptions.erase(command); +} + +/////////////////// +// AdminModel Impl +////////////////// + +void AdminModel::update(const std::string& data) +{ + StringTokenizer tokens(data, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); + + Log::info("AdminModel Recv: " + data); + + if (tokens[0] == "document") + { + addDocument(std::stoi(tokens[1]), tokens[2]); + unsigned mem = Util::getMemoryUsage(std::stoi(tokens[1])); + std::string response = data + std::to_string(mem); + notify(response); + return; + } + else if (tokens[0] == "addview") + { + auto it = _documents.find(std::stoi(tokens[1])); + if (it != _documents.end()) + { + const unsigned nSessionId = Util::decodeId(tokens[2]); + it->second.addView(nSessionId); + } + } + else if (tokens[0] == "rmview") + { + auto it = _documents.find(std::stoi(tokens[1])); + if (it != _documents.end()) + { + const unsigned nSessionId = Util::decodeId(tokens[2]); + it->second.removeView(nSessionId); + } + } + else if (tokens[0] == "rmdoc") + { + removeDocument(std::stoi(tokens[1])); + } + + notify(data); +} + +std::string AdminModel::query(const std::string command) +{ + StringTokenizer tokens(command, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); + + if (tokens[0] == "documents") + { + return getDocuments(); + } + else if (tokens[0] == "active_users_count") + { + return std::to_string(getTotalActiveViews()); + } + else if (tokens[0] == "active_docs_count") + { + return std::to_string(_documents.size()); + } + else if (tokens[0] == "mem_stats") + { + return getMemStats(); + } + else if (tokens[0] == "mem_stats_size") + { + return std::to_string(_memStatsSize); + } + else if (tokens[0] == "cpu_stats") + { + return getCpuStats(); + } + else if (tokens[0] == "cpu_stats_size") + { + return std::to_string(_cpuStatsSize); + } + + return std::string(""); +} + +/// Returns memory consumed by all active loolkit processes +unsigned AdminModel::getTotalMemoryUsage() +{ + unsigned totalMem = 0; + for (auto& it: _documents) + { + if (it.second.isExpired()) + continue; + + totalMem += Util::getMemoryUsage(it.second.getPid()); + } + + return totalMem; +} + +void AdminModel::subscribe(int nSessionId, std::shared_ptr<Poco::Net::WebSocket>& ws) +{ + const auto ret = _subscribers.emplace(nSessionId, Subscriber(nSessionId, ws)); + if (!ret.second) + { + Log::warn() << "Subscriber already exists" << Log::end; + } +} + +void AdminModel::subscribe(int nSessionId, const std::string& command) +{ + auto subscriber = _subscribers.find(nSessionId); + if (subscriber == _subscribers.end() ) + return; + + subscriber->second.subscribe(command); +} + +void AdminModel::unsubscribe(int nSessionId, const std::string& command) +{ + auto subscriber = _subscribers.find(nSessionId); + if (subscriber == _subscribers.end()) + return; + + subscriber->second.unsubscribe(command); +} + +void AdminModel::addMemStats(unsigned memUsage) +{ + _memStats.push_back(memUsage); + if (_memStats.size() > _memStatsSize) + { + _memStats.pop_front(); + } + + std::ostringstream oss; + oss << "mem_stats " + << std::to_string(memUsage); + notify(oss.str()); +} + +void AdminModel::addCpuStats(unsigned cpuUsage) +{ + _cpuStats.push_back(cpuUsage); + if (_cpuStats.size() > _cpuStatsSize) + { + _cpuStats.pop_front(); + } + + std::ostringstream oss; + oss << "cpu_stats " + << std::to_string(cpuUsage); + notify(oss.str()); +} + +void AdminModel::setCpuStatsSize(unsigned size) +{ + int wasteValuesLen = _cpuStats.size() - size; + while (wasteValuesLen-- > 0) + { + _cpuStats.pop_front(); + } + _cpuStatsSize = size; + + std::ostringstream oss; + oss << "settings " + << "cpu_stats_size=" + << std::to_string(_cpuStatsSize); + notify(oss.str()); +} + +void AdminModel::setMemStatsSize(unsigned size) +{ + int wasteValuesLen = _memStats.size() - size; + while (wasteValuesLen-- > 0) + { + _memStats.pop_front(); + } + _memStatsSize = size; + + std::ostringstream oss; + oss << "settings " + << "mem_stats_size=" + << std::to_string(_memStatsSize); + notify(oss.str()); +} + +void AdminModel::notify(const std::string& message) +{ + auto it = std::begin(_subscribers); + while (it != std::end(_subscribers)) + { + if (!it->second.notify(message)) + { + it = _subscribers.erase(it); + } + else + { + it++; + } + } +} + +void AdminModel::addDocument(Poco::Process::PID pid, std::string url) +{ + const auto ret = _documents.emplace(pid, Document(pid, url)); + if (!ret.second) + { + Log::warn() << "Document with PID [" + std::to_string(pid) + "] already exists." << Log::end; + } +} + +void AdminModel::removeDocument(Poco::Process::PID pid) +{ + auto it = _documents.find(pid); + if (it != _documents.end() && !it->second.isExpired()) + { + // TODO: The idea is to only expire the document and keep the history + // of documents open and close, to be able to give a detailed summary + // to the admin console with views. For now, just remove the document. + it->second.expire(); + _documents.erase(it); + } +} + +std::string AdminModel::getMemStats() +{ + std::string response; + for (auto& i: _memStats) + { + response += std::to_string(i) + ","; + } + + return response; +} + +std::string AdminModel::getCpuStats() +{ + std::string response; + for (auto& i: _cpuStats) + { + response += std::to_string(i) + ","; + } + + return response; +} + +unsigned AdminModel::getTotalActiveViews() +{ + unsigned nTotalViews = 0; + for (auto& it: _documents) + { + if (it.second.isExpired()) + continue; + + nTotalViews += it.second.getActiveViews(); + } + + return nTotalViews; +} + +std::string AdminModel::getDocuments() +{ + std::ostringstream oss; + for (auto& it: _documents) + { + if (it.second.isExpired()) + continue; + + std::string sPid = std::to_string(it.second.getPid()); + std::string sUrl = it.second.getUrl(); + std::string sViews = std::to_string(it.second.getActiveViews()); + std::string sMem = std::to_string(Util::getMemoryUsage(it.second.getPid())); + + oss << sPid << " " + << sUrl << " " + << sViews << " " + << sMem << " \n "; + } + + return oss.str(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/loolwsd/AdminModel.hpp b/loolwsd/AdminModel.hpp index af38c6d..444689b 100644 --- a/loolwsd/AdminModel.hpp +++ b/loolwsd/AdminModel.hpp @@ -12,13 +12,10 @@ #include <memory> #include <set> -#include <sstream> #include <string> -#include <queue> #include <Poco/Net/WebSocket.h> #include <Poco/Process.h> -#include <Poco/StringTokenizer.h> #include "Util.hpp" @@ -30,15 +27,9 @@ public: _start(std::time(nullptr)) { } - void expire() - { - _end = std::time(nullptr); - } + void expire() { _end = std::time(nullptr); } - bool isExpired() - { - return _end != 0 && std::time(nullptr) >= _end; - } + bool isExpired() { return _end != 0 && std::time(nullptr) >= _end; } private: int _nSessionId; @@ -63,53 +54,19 @@ public: Log::info("Document " + std::to_string(_nPid) + " dtor."); } - Poco::Process::PID getPid() const - { - return _nPid; - } + Poco::Process::PID getPid() const { return _nPid; } - std::string getUrl() const - { - return _sUrl; - } + std::string getUrl() const { return _sUrl; } - void expire() - { - _end = std::time(nullptr); - } + void expire() { _end = std::time(nullptr); } - bool isExpired() const - { - return _end != 0 && std::time(nullptr) >= _end; - } + bool isExpired() const { return _end != 0 && std::time(nullptr) >= _end; } - void addView(int nSessionId) - { - const auto ret = _views.emplace(nSessionId, View(nSessionId)); - if (!ret.second) - { - Log::warn() << "View with SessionID [" + std::to_string(nSessionId) + "] already exists." << Log::end; - } - else - { - _nActiveViews++; - } - } + void addView(int nSessionId); - void removeView(int nSessionId) - { - auto it = _views.find(nSessionId); - if (it != _views.end()) - { - it->second.expire(); - _nActiveViews--; - } - } + void removeView(int nSessionId); - unsigned getActiveViews() const - { - return _nActiveViews; - } + unsigned getActiveViews() const { return _nActiveViews; } private: Poco::Process::PID _nPid; @@ -140,45 +97,15 @@ public: Log::info("Subscriber dtor."); } - bool notify(const std::string& message) - { - Poco::StringTokenizer tokens(message, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM); - - if (_subscriptions.find(tokens[0]) == _subscriptions.end()) - return true; - - auto webSocket = _ws.lock(); - if (webSocket) - { - webSocket->sendFrame(message.data(), message.length()); - return true; - } - else - { - return false; - } - } + bool notify(const std::string& message); - bool subscribe(const std::string& command) - { - auto ret = _subscriptions.insert(command); - return ret.second; - } + bool subscribe(const std::string& command); - void unsubscribe(const std::string& command) - { - _subscriptions.erase(command); - } + void unsubscribe(const std::string& command); - void expire() - { - _end = std::time(nullptr); - } + void expire() { _end = std::time(nullptr); } - bool isExpired() const - { - return _end != 0 && std::time(nullptr) >= _end; - } + bool isExpired() const { return _end != 0 && std::time(nullptr) >= _end; } private: /// Admin session Id @@ -210,290 +137,44 @@ public: Log::info("AdminModel dtor."); } - void update(const std::string& data) - { - Poco::StringTokenizer tokens(data, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM); - - Log::info("AdminModel Recv: " + data); - - if (tokens[0] == "document") - { - addDocument(std::stoi(tokens[1]), tokens[2]); - unsigned mem = Util::getMemoryUsage(std::stoi(tokens[1])); - std::string response = data + std::to_string(mem); - notify(response); - return; - } - else if (tokens[0] == "addview") - { - auto it = _documents.find(std::stoi(tokens[1])); - if (it != _documents.end()) - { - const unsigned nSessionId = Util::decodeId(tokens[2]); - it->second.addView(nSessionId); - } - } - else if (tokens[0] == "rmview") - { - auto it = _documents.find(std::stoi(tokens[1])); - if (it != _documents.end()) - { - const unsigned nSessionId = Util::decodeId(tokens[2]); - it->second.removeView(nSessionId); - } - } - else if (tokens[0] == "rmdoc") - { - removeDocument(std::stoi(tokens[1])); - } - - notify(data); - } + void update(const std::string& data); - std::string query(const std::string command) - { - Poco::StringTokenizer tokens(command, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM); - - if (tokens[0] == "documents") - { - return getDocuments(); - } - else if (tokens[0] == "active_users_count") - { - return std::to_string(getTotalActiveViews()); - } - else if (tokens[0] == "active_docs_count") - { - return std::to_string(_documents.size()); - } - else if (tokens[0] == "mem_stats") - { - return getMemStats(); - } - else if (tokens[0] == "mem_stats_size") - { - return std::to_string(_memStatsSize); - } - else if (tokens[0] == "cpu_stats") - { - return getCpuStats(); - } - else if (tokens[0] == "cpu_stats_size") - { - return std::to_string(_cpuStatsSize); - } - - return std::string(""); - } + std::string query(const std::string command); /// Returns memory consumed by all active loolkit processes - unsigned getTotalMemoryUsage() - { - unsigned totalMem = 0; - for (auto& it: _documents) - { - if (it.second.isExpired()) - continue; + unsigned getTotalMemoryUsage(); - totalMem += Util::getMemoryUsage(it.second.getPid()); - } + void subscribe(int nSessionId, std::shared_ptr<Poco::Net::WebSocket>& ws); + void subscribe(int nSessionId, const std::string& command); - return totalMem; - } + void unsubscribe(int nSessionId, const std::string& command); - void subscribe(int nSessionId, std::shared_ptr<Poco::Net::WebSocket>& ws) - { - const auto ret = _subscribers.emplace(nSessionId, Subscriber(nSessionId, ws)); - if (!ret.second) - { - Log::warn() << "Subscriber already exists" << Log::end; - } - } + void clearMemStats() { _memStats.clear(); } - void subscribe(int nSessionId, const std::string& command) - { - auto subscriber = _subscribers.find(nSessionId); - if (subscriber == _subscribers.end() ) - return; + void clearCpuStats() { _cpuStats.clear(); } - subscriber->second.subscribe(command); - } + void addMemStats(unsigned memUsage); - void unsubscribe(int nSessionId, const std::string& command) - { - auto subscriber = _subscribers.find(nSessionId); - if (subscriber == _subscribers.end()) - return; - - subscriber->second.unsubscribe(command); - } + void addCpuStats(unsigned cpuUsage); - void clearMemStats() - { - _memStats.clear(); - } + void setCpuStatsSize(unsigned size); - void clearCpuStats() - { - _cpuStats.clear(); - } + void setMemStatsSize(unsigned size); - void addMemStats(unsigned memUsage) - { - _memStats.push_back(memUsage); - if (_memStats.size() > _memStatsSize) - { - _memStats.pop_front(); - } - - std::ostringstream oss; - oss << "mem_stats " - << std::to_string(memUsage); - notify(oss.str()); - } - - void addCpuStats(unsigned cpuUsage) - { - _cpuStats.push_back(cpuUsage); - if (_cpuStats.size() > _cpuStatsSize) - { - _cpuStats.pop_front(); - } - - std::ostringstream oss; - oss << "cpu_stats " - << std::to_string(cpuUsage); - notify(oss.str()); - } - - void setCpuStatsSize(unsigned size) - { - int wasteValuesLen = _cpuStats.size() - size; - while (wasteValuesLen-- > 0) - { - _cpuStats.pop_front(); - } - _cpuStatsSize = size; - - std::ostringstream oss; - oss << "settings " - << "cpu_stats_size=" - << std::to_string(_cpuStatsSize); - notify(oss.str()); - } - - void setMemStatsSize(unsigned size) - { - int wasteValuesLen = _memStats.size() - size; - while (wasteValuesLen-- > 0) - { - _memStats.pop_front(); - } - _memStatsSize = size; - - std::ostringstream oss; - oss << "settings " - << "mem_stats_size=" - << std::to_string(_memStatsSize); - notify(oss.str()); - } - - void notify(const std::string& message) - { - auto it = std::begin(_subscribers); - while (it != std::end(_subscribers)) - { - if (!it->second.notify(message)) - { - it = _subscribers.erase(it); - } - else - { - it++; - } - } - } + void notify(const std::string& message); private: - void addDocument(Poco::Process::PID pid, std::string url) - { - const auto ret = _documents.emplace(pid, Document(pid, url)); - if (!ret.second) - { - Log::warn() << "Document with PID [" + std::to_string(pid) + "] already exists." << Log::end; - } - } - - void removeDocument(Poco::Process::PID pid) - { - auto it = _documents.find(pid); - if (it != _documents.end() && !it->second.isExpired()) - { - // TODO: The idea is to only expire the document and keep the history - // of documents open and close, to be able to give a detailed summary - // to the admin console with views. For now, just remove the document. - it->second.expire(); - _documents.erase(it); - } - } - - std::string getMemStats() - { - std::string response; - for (auto& i: _memStats) - { - response += std::to_string(i) + ","; - } + void addDocument(Poco::Process::PID pid, std::string url); - return response; - } + void removeDocument(Poco::Process::PID pid); - std::string getCpuStats() - { - std::string response; - for (auto& i: _cpuStats) - { - response += std::to_string(i) + ","; - } + std::string getMemStats(); - return response; - } - - unsigned getTotalActiveViews() - { - unsigned nTotalViews = 0; - for (auto& it: _documents) - { - if (it.second.isExpired()) - continue; + std::string getCpuStats(); - nTotalViews += it.second.getActiveViews(); - } + unsigned getTotalActiveViews(); - return nTotalViews; - } - - std::string getDocuments() - { - std::ostringstream oss; - for (auto& it: _documents) - { - if (it.second.isExpired()) - continue; - - std::string sPid = std::to_string(it.second.getPid()); - std::string sUrl = it.second.getUrl(); - std::string sViews = std::to_string(it.second.getActiveViews()); - std::string sMem = std::to_string(Util::getMemoryUsage(it.second.getPid())); - - oss << sPid << " " - << sUrl << " " - << sViews << " " - << sMem << " \n "; - } - - return oss.str(); - } + std::string getDocuments(); private: std::map<int, Subscriber> _subscribers; diff --git a/loolwsd/Makefile.am b/loolwsd/Makefile.am index 53e952a..f3f5662 100644 --- a/loolwsd/Makefile.am +++ b/loolwsd/Makefile.am @@ -17,6 +17,7 @@ shared_sources = IoUtil.cpp \ Util.cpp loolwsd_SOURCES = Admin.cpp \ + AdminModel.cpp \ Auth.cpp \ ChildProcessSession.cpp \ DocumentBroker.cpp \ commit 939388c6e6362d9977a460139e55587de0be47d4 Author: Pranav Kant <pran...@collabora.com> Date: Thu Mar 31 12:31:05 2016 +0530 loolwsd: Arrange sources/header in makefile alphabetically Change-Id: I0bc70b34a590d84ac9c15f0d9d0000d02cadba74 diff --git a/loolwsd/Makefile.am b/loolwsd/Makefile.am index 7428229..53e952a 100644 --- a/loolwsd/Makefile.am +++ b/loolwsd/Makefile.am @@ -10,34 +10,80 @@ AM_LDFLAGS = -pthread AM_ETAGSFLAGS = --c++-kinds=+p --fields=+iaS --extra=+q -R --totals=yes * AM_CTAGSFLAGS = $(AM_ETAGSFLAGS) -shared_sources = LOOLProtocol.cpp LOOLSession.cpp MessageQueue.cpp IoUtil.cpp Util.cpp - -loolwsd_SOURCES = LOOLWSD.cpp ChildProcessSession.cpp MasterProcessSession.cpp TileCache.cpp Admin.cpp DocumentBroker.cpp Auth.cpp Storage.cpp $(shared_sources) - -noinst_PROGRAMS = loadtest connect lokitclient - -loadtest_SOURCES = LoadTest.cpp Util.cpp LOOLProtocol.cpp - -connect_SOURCES = Connect.cpp Util.cpp LOOLProtocol.cpp - -lokitclient_SOURCES = LOKitClient.cpp LOOLProtocol.cpp IoUtil.cpp Util.cpp - -broker_shared_sources = ChildProcessSession.cpp $(shared_sources) - -loolkit_SOURCES = LOOLKit.cpp $(broker_shared_sources) - -loolbroker_SOURCES = LOOLBroker.cpp $(broker_shared_sources) +shared_sources = IoUtil.cpp \ + LOOLProtocol.cpp \ + LOOLSession.cpp \ + MessageQueue.cpp \ + Util.cpp + +loolwsd_SOURCES = Admin.cpp \ + Auth.cpp \ + ChildProcessSession.cpp \ + DocumentBroker.cpp \ + LOOLWSD.cpp \ + MasterProcessSession.cpp \ + Storage.cpp \ + TileCache.cpp \ + $(shared_sources) + +noinst_PROGRAMS = connect \ + loadtest \ + lokitclient + +loadtest_SOURCES = LoadTest.cpp \ + LOOLProtocol.cpp \ + Util.cpp + +connect_SOURCES = Connect.cpp \ + LOOLProtocol.cpp \ + Util.cpp + +lokitclient_SOURCES = IoUtil.cpp \ + LOKitClient.cpp \ + LOOLProtocol.cpp \ + Util.cpp + +broker_shared_sources = ChildProcessSession.cpp \ + $(shared_sources) + +loolkit_SOURCES = LOOLKit.cpp \ + $(broker_shared_sources) + +loolbroker_SOURCES = LOOLBroker.cpp \ + $(broker_shared_sources) loolmap_SOURCES = loolmap.c -noinst_HEADERS = LOKitHelper.hpp LOOLProtocol.hpp LOOLSession.hpp MasterProcessSession.hpp ChildProcessSession.hpp \ - LOOLWSD.hpp LoadTest.hpp MessageQueue.hpp TileCache.hpp Util.hpp Png.hpp Common.hpp Capabilities.hpp \ - Rectangle.hpp QueueHandler.hpp Admin.hpp Auth.hpp Storage.hpp AdminModel.hpp DocumentBroker.hpp \ - FileServer.hpp IoUtil.hpp \ - bundled/include/LibreOfficeKit/LibreOfficeKit.h bundled/include/LibreOfficeKit/LibreOfficeKitEnums.h \ - bundled/include/LibreOfficeKit/LibreOfficeKitInit.h bundled/include/LibreOfficeKit/LibreOfficeKitTypes.h - -EXTRA_DIST = loolwsd.service sysconfig.loolwsd discovery.xml +noinst_HEADERS = Admin.hpp \ + AdminModel.hpp \ + Auth.hpp \ + Capabilities.hpp \ + ChildProcessSession.hpp \ + Common.hpp \ + DocumentBroker.hpp \ + FileServer.hpp \ + IoUtil.hpp \ + LoadTest.hpp \ + LOKitHelper.hpp \ + LOOLProtocol.hpp \ + LOOLSession.hpp \ + LOOLWSD.hpp \ + MasterProcessSession.hpp \ + MessageQueue.hpp \ + Png.hpp \ + QueueHandler.hpp \ + Rectangle.hpp \ + Storage.hpp \ + TileCache.hpp \ + Util.hpp \ + bundled/include/LibreOfficeKit/LibreOfficeKit.h \ + bundled/include/LibreOfficeKit/LibreOfficeKitEnums.h \ + bundled/include/LibreOfficeKit/LibreOfficeKitInit.h \ + bundled/include/LibreOfficeKit/LibreOfficeKitTypes.h + +EXTRA_DIST = discovery.xml \ + loolwsd.service \ + sysconfig.loolwsd clean-cache: # Intentionally don't use "*" below... Avoid risk of accidentally running rm -rf /* commit f1ede0c4baa0dc77050e11f4283e3ddb4e256fb9 Author: Pranav Kant <pran...@collabora.com> Date: Thu Mar 31 12:18:34 2016 +0530 loolwsd: Split Storage, Auth classes into header/impl files Change-Id: I5d8990db8f8f4aa7a88bbb0cc84b97149cf4f8c0 diff --git a/loolwsd/Auth.cpp b/loolwsd/Auth.cpp new file mode 100644 index 0000000..5d079d4 --- /dev/null +++ b/loolwsd/Auth.cpp @@ -0,0 +1,211 @@ +/* -*- 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 <cstdlib> +#include <string> + +#include <Poco/Base64Encoder.h> +#include <Poco/Base64Decoder.h> +#include <Poco/Crypto/RSADigestEngine.h> +#include <Poco/Crypto/RSAKey.h> +#include <Poco/JSON/Object.h> +#include <Poco/LineEndingConverter.h> +#include <Poco/Net/HTTPClientSession.h> +#include <Poco/Net/HTTPRequest.h> +#include <Poco/Net/HTTPResponse.h> +#include <Poco/StringTokenizer.h> +#include <Poco/Timestamp.h> +#include <Poco/URI.h> + +#include "Auth.hpp" +#include "Util.hpp" + + +////////////// +// OAuth Impl +////////////// +const std::string JWTAuth::getAccessToken() +{ + std::string encodedHeader = createHeader(); + std::string encodedPayload = createPayload(); + + // trim '=' from end of encoded header + encodedHeader.erase(std::find_if(encodedHeader.rbegin(), encodedHeader.rend(), + [](char& ch)->bool {return ch != '='; }).base(), encodedHeader.end()); + // trim '=' from end of encoded payload + encodedPayload.erase(std::find_if(encodedPayload.rbegin(), encodedPayload.rend(), + [](char& ch)->bool { return ch != '='; }).base(), encodedPayload.end()); + Log::info("Encoded JWT header: " + encodedHeader); + Log::info("Encoded JWT payload: " + encodedPayload); + + // Convert to a URL and filename safe variant: + // Replace '+' with '-' && '/' with '_' + std::replace(encodedHeader.begin(), encodedHeader.end(), '+','-'); + std::replace(encodedHeader.begin(), encodedHeader.end(), '/','_'); + + std::replace(encodedPayload.begin(), encodedPayload.end(), '+','-'); + std::replace(encodedPayload.begin(), encodedPayload.end(), '/','_'); + + std::string encodedBody = encodedHeader + "." + encodedPayload; + + // sign the encoded body + _digestEngine.update(encodedBody.c_str(), static_cast<unsigned>(encodedBody.length())); + Poco::Crypto::DigestEngine::Digest digest = _digestEngine.signature(); + + // The signature generated contains CRLF line endings. + // Use a line ending converter to remove these CRLF + std::ostringstream ostr; + Poco::OutputLineEndingConverter lineEndingConv(ostr, ""); + Poco::Base64Encoder encoder(lineEndingConv); + encoder << std::string(digest.begin(), digest.end()); + encoder.close(); + std::string encodedSig = ostr.str(); + + // trim '=' from end of encoded signature + encodedSig.erase(std::find_if(encodedSig.rbegin(), encodedSig.rend(), + [](char& ch)->bool { return ch != '='; }).base(), encodedSig.end()); + + // Be URL and filename safe + std::replace(encodedSig.begin(), encodedSig.end(), '+','-'); + std::replace(encodedSig.begin(), encodedSig.end(), '/','_'); + + Log::info("Sig generated is : " + encodedSig); + + const std::string jwtToken = encodedBody + "." + encodedSig; + Log::info("JWT token generated: " + jwtToken); + + return jwtToken; +} + +bool JWTAuth::verify(const std::string& accessToken) +{ + Poco::StringTokenizer tokens(accessToken, ".", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM); + + std::string encodedBody = tokens[0] + "." + tokens[1]; + _digestEngine.update(encodedBody.c_str(), static_cast<unsigned>(encodedBody.length())); + Poco::Crypto::DigestEngine::Digest digest = _digestEngine.signature(); + + std::ostringstream ostr; + Poco::OutputLineEndingConverter lineEndingConv(ostr, ""); + Poco::Base64Encoder encoder(lineEndingConv); + + encoder << std::string(digest.begin(), digest.end()); + encoder.close(); + std::string encodedSig = ostr.str(); + + // trim '=' from end of encoded signature. + encodedSig.erase(std::find_if(encodedSig.rbegin(), encodedSig.rend(), + [](char& ch)->bool { return ch != '='; }).base(), encodedSig.end()); + + // Make the encoded sig URL and filename safe + std::replace(encodedSig.begin(), encodedSig.end(), '+', '-'); + std::replace(encodedSig.begin(), encodedSig.end(), '/', '_'); + + if (encodedSig != tokens[2]) + { + Log::info("JWTAuth::Token verification failed; Expected: " + encodedSig + ", Received: " + tokens[2]); + return false; + } + + // TODO: Check for expiry etc. + + return true; +} + +const std::string JWTAuth::createHeader() +{ + // TODO: Some sane code to represent JSON objects + std::string header = "{\"alg\":\""+_alg+"\",\"typ\":\""+_typ+"\"}"; + + Log::info("JWT Header: " + header); + std::ostringstream ostr; + Poco::OutputLineEndingConverter lineEndingConv(ostr, ""); + Poco::Base64Encoder encoder(lineEndingConv); + encoder << header; + encoder.close(); + + return ostr.str(); +} + +const std::string JWTAuth::createPayload() +{ + std::time_t curtime = Poco::Timestamp().epochTime(); + std::string exptime = std::to_string(curtime + 3600); + + // TODO: Some sane code to represent JSON objects + std::string payload = "{\"iss\":\""+_iss+"\",\"sub\":\""+_sub+"\",\"aud\":\""+_aud+"\",\"nme\":\""+_name+"\",\"exp\":\""+exptime+"\"}"; + + Log::info("JWT Payload: " + payload); + std::ostringstream ostr; + Poco::OutputLineEndingConverter lineEndingConv(ostr, ""); + Poco::Base64Encoder encoder(lineEndingConv); + encoder << payload; + encoder.close(); + + return ostr.str(); +} + + +////////////// +// OAuth Impl +////////////// + +//TODO: This MUST be done over TLS to protect the token. +const std::string OAuth::getAccessToken() +{ + std::string url = _tokenEndPoint + + "?client_id=" + _clientId + + "&client_secret=" + _clientSecret + + "&grant_type=authorization_code" + + "&code=" + _authorizationCode; + // + "&redirect_uri=" + + Poco::URI uri(url); + Poco::Net::HTTPClientSession session(uri.getHost(), uri.getPort()); + Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, url, Poco::Net::HTTPMessage::HTTP_1_1); + Poco::Net::HTTPResponse response; + session.sendRequest(request); + std::istream& rs = session.receiveResponse(response); + Log::info() << "Status: " << response.getStatus() << " " << response.getReason() << Log::end; + std::string reply(std::istreambuf_iterator<char>(rs), {}); + Log::info("Response: " + reply); + //TODO: Parse the token. + + return std::string(); +} + +bool OAuth::verify(const std::string& token) +{ + const std::string url = _authVerifyUrl + token; + Log::debug("Verifying authorization token from: " + url); + Poco::URI uri(url); + Poco::Net::HTTPClientSession session(uri.getHost(), uri.getPort()); + Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, url, Poco::Net::HTTPMessage::HTTP_1_1); + Poco::Net::HTTPResponse response; + session.sendRequest(request); + std::istream& rs = session.receiveResponse(response); + Log::info() << "Status: " << response.getStatus() << " " << response.getReason() << Log::end; + std::string reply(std::istreambuf_iterator<char>(rs), {}); + Log::info("Response: " + reply); + + //TODO: Parse the response. + /* + // This is used for the demo site. + const auto lastLogTime = std::strtoul(reply.c_str(), nullptr, 0); + if (lastLogTime < 1) + { + //TODO: Redirect to login page. + return; + } + */ + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/loolwsd/Auth.hpp b/loolwsd/Auth.hpp index fdc2fc1..f4e10a1 100644 --- a/loolwsd/Auth.hpp +++ b/loolwsd/Auth.hpp @@ -11,24 +11,10 @@ #ifndef INCLUDED_AUTH_HPP #define INCLUDED_AUTH_HPP -#include <cstdlib> #include <string> -#include <Poco/Base64Encoder.h> -#include <Poco/Base64Decoder.h> #include <Poco/Crypto/RSADigestEngine.h> #include <Poco/Crypto/RSAKey.h> -#include <Poco/DigestEngine.h> -#include <Poco/JSON/Object.h> -#include <Poco/LineEndingConverter.h> -#include <Poco/Net/HTTPClientSession.h> -#include <Poco/Net/HTTPRequest.h> -#include <Poco/Net/HTTPResponse.h> -#include <Poco/StringTokenizer.h> -#include <Poco/Timestamp.h> -#include <Poco/URI.h> - -#include "Util.hpp" /// Base class of all Authentication/Authorization implementations. class AuthBase @@ -53,127 +39,14 @@ public: _digestEngine(_key, "SHA256") { } - const std::string getAccessToken() - { - std::string encodedHeader = createHeader(); - std::string encodedPayload = createPayload(); - - // trim '=' from end of encoded header - encodedHeader.erase(std::find_if(encodedHeader.rbegin(), encodedHeader.rend(), - [](char& ch)->bool {return ch != '='; }).base(), encodedHeader.end()); - // trim '=' from end of encoded payload - encodedPayload.erase(std::find_if(encodedPayload.rbegin(), encodedPayload.rend(), - [](char& ch)->bool { return ch != '='; }).base(), encodedPayload.end()); - Log::info("Encoded JWT header: " + encodedHeader); - Log::info("Encoded JWT payload: " + encodedPayload); - - // Convert to a URL and filename safe variant: - // Replace '+' with '-' && '/' with '_' - std::replace(encodedHeader.begin(), encodedHeader.end(), '+','-'); - std::replace(encodedHeader.begin(), encodedHeader.end(), '/','_'); - - std::replace(encodedPayload.begin(), encodedPayload.end(), '+','-'); - std::replace(encodedPayload.begin(), encodedPayload.end(), '/','_'); - - std::string encodedBody = encodedHeader + "." + encodedPayload; - - // sign the encoded body - _digestEngine.update(encodedBody.c_str(), static_cast<unsigned>(encodedBody.length())); - Poco::Crypto::DigestEngine::Digest digest = _digestEngine.signature(); - - // The signature generated contains CRLF line endings. - // Use a line ending converter to remove these CRLF - std::ostringstream ostr; - Poco::OutputLineEndingConverter lineEndingConv(ostr, ""); - Poco::Base64Encoder encoder(lineEndingConv); - encoder << std::string(digest.begin(), digest.end()); - encoder.close(); - std::string encodedSig = ostr.str(); - - // trim '=' from end of encoded signature - encodedSig.erase(std::find_if(encodedSig.rbegin(), encodedSig.rend(), - [](char& ch)->bool { return ch != '='; }).base(), encodedSig.end()); - - // Be URL and filename safe - std::replace(encodedSig.begin(), encodedSig.end(), '+','-'); - std::replace(encodedSig.begin(), encodedSig.end(), '/','_'); - - Log::info("Sig generated is : " + encodedSig); - - const std::string jwtToken = encodedBody + "." + encodedSig; - Log::info("JWT token generated: " + jwtToken); - - return jwtToken; - } - - bool verify(const std::string& accessToken) - { - Poco::StringTokenizer tokens(accessToken, ".", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM); - - std::string encodedBody = tokens[0] + "." + tokens[1]; - _digestEngine.update(encodedBody.c_str(), static_cast<unsigned>(encodedBody.length())); - Poco::Crypto::DigestEngine::Digest digest = _digestEngine.signature(); - - std::ostringstream ostr; - Poco::OutputLineEndingConverter lineEndingConv(ostr, ""); - Poco::Base64Encoder encoder(lineEndingConv); - - encoder << std::string(digest.begin(), digest.end()); - encoder.close(); - std::string encodedSig = ostr.str(); - - // trim '=' from end of encoded signature. - encodedSig.erase(std::find_if(encodedSig.rbegin(), encodedSig.rend(), - [](char& ch)->bool { return ch != '='; }).base(), encodedSig.end()); - - // Make the encoded sig URL and filename safe - std::replace(encodedSig.begin(), encodedSig.end(), '+', '-'); - std::replace(encodedSig.begin(), encodedSig.end(), '/', '_'); + const std::string getAccessToken() override; - if (encodedSig != tokens[2]) - { - Log::info("JWTAuth::Token verification failed; Expected: " + encodedSig + ", Received: " + tokens[2]); - return false; - } - - // TODO: Check for expiry etc. - - return true; - } + bool verify(const std::string& accessToken) override; private: - const std::string createHeader() - { - // TODO: Some sane code to represent JSON objects - std::string header = "{\"alg\":\""+_alg+"\",\"typ\":\""+_typ+"\"}"; - - Log::info("JWT Header: " + header); - std::ostringstream ostr; - Poco::OutputLineEndingConverter lineEndingConv(ostr, ""); - Poco::Base64Encoder encoder(lineEndingConv); - encoder << header; - encoder.close(); - - return ostr.str(); - } - - const std::string createPayload() - { - std::time_t curtime = Poco::Timestamp().epochTime(); - std::string exptime = std::to_string(curtime + 3600); - - // TODO: Some sane code to represent JSON objects - std::string payload = "{\"iss\":\""+_iss+"\",\"sub\":\""+_sub+"\",\"aud\":\""+_aud+"\",\"nme\":\""+_name+"\",\"exp\":\""+exptime+"\"}"; + const std::string createHeader(); - Log::info("JWT Payload: " + payload); - std::ostringstream ostr; - Poco::OutputLineEndingConverter lineEndingConv(ostr, ""); - Poco::Base64Encoder encoder(lineEndingConv); - encoder << payload; - encoder.close(); - - return ostr.str(); - } + const std::string createPayload(); private: const std::string _alg = "RS256"; @@ -204,57 +77,9 @@ public: { } - //TODO: This MUST be done over TLS to protect the token. - const std::string getAccessToken() override - { - std::string url = _tokenEndPoint - + "?client_id=" + _clientId - + "&client_secret=" + _clientSecret - + "&grant_type=authorization_code" - + "&code=" + _authorizationCode; - // + "&redirect_uri=" - - Poco::URI uri(url); - Poco::Net::HTTPClientSession session(uri.getHost(), uri.getPort()); - Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, url, Poco::Net::HTTPMessage::HTTP_1_1); - Poco::Net::HTTPResponse response; - session.sendRequest(request); - std::istream& rs = session.receiveResponse(response); - Log::info() << "Status: " << response.getStatus() << " " << response.getReason() << Log::end; - std::string reply(std::istreambuf_iterator<char>(rs), {}); - Log::info("Response: " + reply); - //TODO: Parse the token. - - return std::string(); - } - - bool verify(const std::string& token) override - { - const std::string url = _authVerifyUrl + token; - Log::debug("Verifying authorization token from: " + url); - Poco::URI uri(url); - Poco::Net::HTTPClientSession session(uri.getHost(), uri.getPort()); - Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, url, Poco::Net::HTTPMessage::HTTP_1_1); - Poco::Net::HTTPResponse response; - session.sendRequest(request); - std::istream& rs = session.receiveResponse(response); - Log::info() << "Status: " << response.getStatus() << " " << response.getReason() << Log::end; - std::string reply(std::istreambuf_iterator<char>(rs), {}); - Log::info("Response: " + reply); + const std::string getAccessToken() override; - //TODO: Parse the response. - /* - // This is used for the demo site. - const auto lastLogTime = std::strtoul(reply.c_str(), nullptr, 0); - if (lastLogTime < 1) - { - //TODO: Redirect to login page. - return; - } - */ - - return true; - } + bool verify(const std::string& token) override; private: const std::string _clientId; diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp index 5390f67..5e35a86 100644 --- a/loolwsd/DocumentBroker.cpp +++ b/loolwsd/DocumentBroker.cpp @@ -7,6 +7,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <cassert> + #include <Poco/Path.h> #include <Poco/SHA1Engine.h> diff --git a/loolwsd/Makefile.am b/loolwsd/Makefile.am index 458ea85..7428229 100644 --- a/loolwsd/Makefile.am +++ b/loolwsd/Makefile.am @@ -12,7 +12,7 @@ AM_CTAGSFLAGS = $(AM_ETAGSFLAGS) shared_sources = LOOLProtocol.cpp LOOLSession.cpp MessageQueue.cpp IoUtil.cpp Util.cpp -loolwsd_SOURCES = LOOLWSD.cpp ChildProcessSession.cpp MasterProcessSession.cpp TileCache.cpp Admin.cpp DocumentBroker.cpp $(shared_sources) +loolwsd_SOURCES = LOOLWSD.cpp ChildProcessSession.cpp MasterProcessSession.cpp TileCache.cpp Admin.cpp DocumentBroker.cpp Auth.cpp Storage.cpp $(shared_sources) noinst_PROGRAMS = loadtest connect lokitclient diff --git a/loolwsd/Storage.cpp b/loolwsd/Storage.cpp new file mode 100644 index 0000000..f649a79 --- /dev/null +++ b/loolwsd/Storage.cpp @@ -0,0 +1,275 @@ +/* -*- 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 <cassert> +#include <string> +#include <fstream> + +#include <Poco/Net/HTTPResponse.h> +#include <Poco/Net/HTTPRequest.h> +#include <Poco/Net/HTTPSClientSession.h> +#include <Poco/Net/SSLManager.h> +#include <Poco/StreamCopier.h> +#include <Poco/JSON/Object.h> +#include <Poco/JSON/Parser.h> + +#include "Common.hpp" +#include "Auth.hpp" +#include "Storage.hpp" +#include "Util.hpp" + +/////////////////// +// StorageBase Impl +/////////////////// +std::string StorageBase::getLocalRootPath() const +{ + auto localPath = _jailPath; + if (localPath[0] == '/') + { + // Remove the leading / + localPath.erase(0, 1); + } + + // /chroot/jailId/user/doc/childId + const auto rootPath = Poco::Path(_localStorePath, localPath); + Poco::File(rootPath).createDirectories(); + + return rootPath.toString(); +} + +size_t StorageBase::getFileSize(const std::string& filename) +{ + return std::ifstream(filename, std::ifstream::ate | std::ifstream::binary).tellg(); +} + +//////////////////// +// LocalStorage Impl +///////////////////// +StorageBase::FileInfo LocalStorage::getFileInfo(const Poco::URI& uri) +{ + const auto path = uri.getPath(); + Log::debug("Getting info for local uri [" + uri.toString() + "], path [" + path + "]."); + const auto filename = Poco::Path(path).getFileName(); + const auto lastModified = Poco::File(path).getLastModified(); + const auto size = Poco::File(path).getSize(); + return FileInfo({filename, lastModified, size}); +} + +std::string LocalStorage::loadStorageFileToLocal() +{ + const auto rootPath = getLocalRootPath(); + + // /chroot/jailId/user/doc/childId/file.ext + const auto filename = Poco::Path(_uri).getFileName(); + _jailedFilePath = Poco::Path(rootPath, filename).toString(); + + Log::info("Public URI [" + _uri + + "] jailed to [" + _jailedFilePath + "]."); + + const auto publicFilePath = _uri; + Log::info("Linking " + publicFilePath + " to " + _jailedFilePath); + if (!Poco::File(_jailedFilePath).exists() && link(publicFilePath.c_str(), _jailedFilePath.c_str()) == -1) + { + // Failed + Log::warn("link(\"" + publicFilePath + "\", \"" + _jailedFilePath + "\") failed. Will copy."); + } + + try + { + // Fallback to copying. + if (!Poco::File(_jailedFilePath).exists()) + { + Log::info("Copying " + publicFilePath + " to " + _jailedFilePath); + Poco::File(publicFilePath).copyTo(_jailedFilePath); + _isCopy = true; + } + } + catch (const Poco::Exception& exc) + { + Log::error("copyTo(\"" + publicFilePath + "\", \"" + _jailedFilePath + "\") failed: " + exc.displayText()); + throw; + } + + // Now return the jailed path. + return Poco::Path(_jailPath, filename).toString(); +} + +bool LocalStorage::saveLocalFileToStorage() +{ + try + { + // Copy the file back. + if (_isCopy && Poco::File(_jailedFilePath).exists()) + { + Log::info("Copying " + _jailedFilePath + " to " + _uri); + Poco::File(_jailedFilePath).copyTo(_uri); + } + } + catch (const Poco::Exception& exc) + { + Log::error("copyTo(\"" + _jailedFilePath + "\", \"" + _uri + "\") failed: " + exc.displayText()); + throw; + } + + return true; +} + +/////////////////// +// WopiStorage Impl +/////////////////// +StorageBase::FileInfo WopiStorage::getFileInfo(const Poco::URI& uri) +{ + Log::debug("Getting info for wopi uri [" + uri.toString() + "]."); + + Poco::URI uriObject(uri); + Poco::Net::HTTPSClientSession session(uriObject.getHost(), uriObject.getPort(), Poco::Net::SSLManager::instance().defaultClientContext()); + Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, uriObject.getPathAndQuery(), Poco::Net::HTTPMessage::HTTP_1_1); + request.set("User-Agent", "LOOLWSD WOPI Agent"); + session.sendRequest(request); + + Poco::Net::HTTPResponse response; + std::istream& rs = session.receiveResponse(response); + + auto logger = Log::trace(); + logger << "WOPI::CheckFileInfo header for URI [" << uri.toString() << "]:\n"; + for (auto& pair : response) + { + logger << '\t' + pair.first + ": " + pair.second << " / "; + } + + logger << Log::end; + + // Parse the response. + std::string filename; + size_t size = 0; + std::string resMsg; + Poco::StreamCopier::copyToString(rs, resMsg); + Log::debug("WOPI::CheckFileInfo returned: " + resMsg); + const auto index = resMsg.find_first_of("{"); + if (index != std::string::npos) + { + const std::string stringJSON = resMsg.substr(index); + Poco::JSON::Parser parser; + const auto result = parser.parse(stringJSON); + const auto object = result.extract<Poco::JSON::Object::Ptr>(); + filename = object->get("BaseFileName").toString(); + size = std::stoul (object->get("Size").toString(), nullptr, 0); + } + + // WOPI doesn't support file last modified time. + return FileInfo({filename, Poco::Timestamp(), size}); +} + +/// uri format: http://server/<...>/wopi*/files/<id>/content +std::string WopiStorage::loadStorageFileToLocal() +{ + Log::info("Downloading URI [" + _uri + "]."); + + _fileInfo = getFileInfo(Poco::URI(_uri)); + if (_fileInfo.Size == 0 && _fileInfo.Filename.empty()) + { + //TODO: Should throw a more appropriate exception. + throw std::runtime_error("Failed to load file from storage."); + } + + // WOPI URI to download files ends in '/contents'. + // Add it here to get the payload instead of file info. + Poco::URI uriObject(_uri); + const auto url = uriObject.getPath() + "/contents?" + uriObject.getQuery(); + Log::debug("Wopi requesting: " + url); + + Poco::Net::HTTPSClientSession session(uriObject.getHost(), uriObject.getPort(), Poco::Net::SSLManager::instance().defaultClientContext()); + Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, url, Poco::Net::HTTPMessage::HTTP_1_1); + request.set("User-Agent", "LOOLWSD WOPI Agent"); + session.sendRequest(request); + + Poco::Net::HTTPResponse response; + std::istream& rs = session.receiveResponse(response); + + auto logger = Log::trace(); + logger << "WOPI::GetFile header for URI [" << _uri << "]:\n"; + for (auto& pair : response) + { + logger << '\t' + pair.first + ": " + pair.second << " / "; + } + + logger << Log::end; + + _jailedFilePath = Poco::Path(getLocalRootPath(), _fileInfo.Filename).toString(); + std::ofstream ofs(_jailedFilePath); + std::copy(std::istreambuf_iterator<char>(rs), + std::istreambuf_iterator<char>(), + std::ostreambuf_iterator<char>(ofs)); + const auto size = getFileSize(_jailedFilePath); + + Log::info() << "WOPI::GetFile downloaded " << size << " bytes from [" << _uri + << "] -> " << _jailedFilePath << ": " + << response.getStatus() << " " << response.getReason() << Log::end; + + // Now return the jailed path. + return Poco::Path(_jailPath, _fileInfo.Filename).toString(); +} + +bool WopiStorage::saveLocalFileToStorage() +{ + Log::info("Uploading URI [" + _uri + "] from [" + _jailedFilePath + "]."); + const auto size = getFileSize(_jailedFilePath); + + Poco::URI uriObject(_uri); + const auto url = uriObject.getPath() + "/contents?" + uriObject.getQuery(); + Log::debug("Wopi posting: " + url); + + Poco::Net::HTTPSClientSession session(uriObject.getHost(), uriObject.getPort(), Poco::Net::SSLManager::instance().defaultClientContext()); + Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, url, Poco::Net::HTTPMessage::HTTP_1_1); + request.set("X-WOPIOverride", "PUT"); + request.setContentType("application/octet-stream"); + request.setContentLength(size); + + std::ostream& os = session.sendRequest(request); + std::ifstream ifs(_jailedFilePath); + Poco::StreamCopier::copyStream(ifs, os); + + Poco::Net::HTTPResponse response; + std::istream& rs = session.receiveResponse(response); + std::ostringstream oss; + Poco::StreamCopier::copyStream(rs, oss); + + Log::info("WOPI::PutFile response: " + oss.str()); + const auto success = (response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK); + Log::info() << "WOPI::PutFile uploaded " << size << " bytes from [" << _jailedFilePath << "]:" + << "] -> [" << _uri << "]: " + << response.getStatus() << " " << response.getReason() << Log::end; + + return success; +} + +////////////////////// +// WebDAVStorage Impl +/////////////////////// +StorageBase::FileInfo WebDAVStorage::getFileInfo(const Poco::URI& uri) +{ + Log::debug("Getting info for webdav uri [" + uri.toString() + "]."); + (void)uri; + assert(!"Not Implemented!"); + return FileInfo({"bazinga", Poco::Timestamp(), 0}); +} + +std::string WebDAVStorage::loadStorageFileToLocal() +{ + // TODO: implement webdav GET. + return _uri; +} + +bool WebDAVStorage::saveLocalFileToStorage() +{ + // TODO: implement webdav PUT. + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/loolwsd/Storage.hpp b/loolwsd/Storage.hpp index 08bec04..9e46f3d 100644 --- a/loolwsd/Storage.hpp +++ b/loolwsd/Storage.hpp @@ -11,18 +11,10 @@ #ifndef INCLUDED_STORAGE_HPP #define INCLUDED_STORAGE_HPP -#include <cassert> #include <string> -#include <fstream> -#include <Poco/Net/HTTPResponse.h> -#include <Poco/Net/HTTPSClientSession.h> -#include <Poco/Net/SSLManager.h> -#include <Poco/StreamCopier.h> -#include <Poco/JSON/Object.h> -#include <Poco/JSON/Parser.h> +#include <Poco/URI.h> -#include "Common.hpp" #include "Auth.hpp" #include "Util.hpp" @@ -51,21 +43,7 @@ public: Log::debug("Storage ctor: " + uri); } - std::string getLocalRootPath() const - { - auto localPath = _jailPath; - if (localPath[0] == '/') - { - // Remove the leading / - localPath.erase(0, 1); - } - - // /chroot/jailId/user/doc/childId - const auto rootPath = Poco::Path(_localStorePath, localPath); - Poco::File(rootPath).createDirectories(); - - return rootPath.toString(); - } + std::string getLocalRootPath() const; const std::string& getUri() const { return _uri; } @@ -83,10 +61,7 @@ public: virtual bool saveLocalFileToStorage() = 0; static - size_t getFileSize(const std::string& filename) - { - return std::ifstream(filename, std::ifstream::ate | std::ifstream::binary).tellg(); - } + size_t getFileSize(const std::string& filename); protected: const std::string _localStorePath; @@ -108,74 +83,11 @@ public: { } - FileInfo getFileInfo(const Poco::URI& uri) override - { - const auto path = uri.getPath(); - Log::debug("Getting info for local uri [" + uri.toString() + "], path [" + path + "]."); - const auto filename = Poco::Path(path).getFileName(); - const auto lastModified = Poco::File(path).getLastModified(); - const auto size = Poco::File(path).getSize(); - return FileInfo({filename, lastModified, size}); - } - - std::string loadStorageFileToLocal() override - { - const auto rootPath = getLocalRootPath(); - - // /chroot/jailId/user/doc/childId/file.ext - const auto filename = Poco::Path(_uri).getFileName(); - _jailedFilePath = Poco::Path(rootPath, filename).toString(); - - Log::info("Public URI [" + _uri + - "] jailed to [" + _jailedFilePath + "]."); - - const auto publicFilePath = _uri; - Log::info("Linking " + publicFilePath + " to " + _jailedFilePath); - if (!Poco::File(_jailedFilePath).exists() && link(publicFilePath.c_str(), _jailedFilePath.c_str()) == -1) - { - // Failed - Log::warn("link(\"" + publicFilePath + "\", \"" + _jailedFilePath + "\") failed. Will copy."); - } + FileInfo getFileInfo(const Poco::URI& uri) override; - try - { - // Fallback to copying. - if (!Poco::File(_jailedFilePath).exists()) - { - Log::info("Copying " + publicFilePath + " to " + _jailedFilePath); - Poco::File(publicFilePath).copyTo(_jailedFilePath); - _isCopy = true; - } - } - catch (const Poco::Exception& exc) - { - Log::error("copyTo(\"" + publicFilePath + "\", \"" + _jailedFilePath + "\") failed: " + exc.displayText()); - throw; - } + std::string loadStorageFileToLocal() override; - // Now return the jailed path. - return Poco::Path(_jailPath, filename).toString(); - } - - bool saveLocalFileToStorage() override - { - try - { - // Copy the file back. - if (_isCopy && Poco::File(_jailedFilePath).exists()) - { - Log::info("Copying " + _jailedFilePath + " to " + _uri); - Poco::File(_jailedFilePath).copyTo(_uri); - } - } - catch (const Poco::Exception& exc) - { - Log::error("copyTo(\"" + _jailedFilePath + "\", \"" + _uri + "\") failed: " + exc.displayText()); - throw; - } - - return true; - } + bool saveLocalFileToStorage() override; private: /// True if the jailed file is not linked but copied. @@ -192,131 +104,12 @@ public: { } - FileInfo getFileInfo(const Poco::URI& uri) override - { - Log::debug("Getting info for wopi uri [" + uri.toString() + "]."); - - Poco::URI uriObject(uri); - Poco::Net::HTTPSClientSession session(uriObject.getHost(), uriObject.getPort(), Poco::Net::SSLManager::instance().defaultClientContext()); - Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, uriObject.getPathAndQuery(), Poco::Net::HTTPMessage::HTTP_1_1); - request.set("User-Agent", "LOOLWSD WOPI Agent"); - session.sendRequest(request); - - Poco::Net::HTTPResponse response; - std::istream& rs = session.receiveResponse(response); - - auto logger = Log::trace(); - logger << "WOPI::CheckFileInfo header for URI [" << uri.toString() << "]:\n"; - for (auto& pair : response) - { - logger << '\t' + pair.first + ": " + pair.second << " / "; - } - - logger << Log::end; - - // Parse the response. - std::string filename; - size_t size = 0; - std::string resMsg; - Poco::StreamCopier::copyToString(rs, resMsg); - Log::debug("WOPI::CheckFileInfo returned: " + resMsg); - const auto index = resMsg.find_first_of("{"); - if (index != std::string::npos) - { - const std::string stringJSON = resMsg.substr(index); - Poco::JSON::Parser parser; - const auto result = parser.parse(stringJSON); - const auto object = result.extract<Poco::JSON::Object::Ptr>(); - filename = object->get("BaseFileName").toString(); - size = std::stoul (object->get("Size").toString(), nullptr, 0); - } - - // WOPI doesn't support file last modified time. - return FileInfo({filename, Poco::Timestamp(), size}); - } + FileInfo getFileInfo(const Poco::URI& uri) override; /// uri format: http://server/<...>/wopi*/files/<id>/content - std::string loadStorageFileToLocal() override - { - Log::info("Downloading URI [" + _uri + "]."); - - _fileInfo = getFileInfo(Poco::URI(_uri)); - if (_fileInfo.Size == 0 && _fileInfo.Filename.empty()) - { - //TODO: Should throw a more appropriate exception. - throw std::runtime_error("Failed to load file from storage."); - } - - // WOPI URI to download files ends in '/contents'. - // Add it here to get the payload instead of file info. - Poco::URI uriObject(_uri); - const auto url = uriObject.getPath() + "/contents?" + uriObject.getQuery(); - Log::debug("Wopi requesting: " + url); - - Poco::Net::HTTPSClientSession session(uriObject.getHost(), uriObject.getPort(), Poco::Net::SSLManager::instance().defaultClientContext()); - Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, url, Poco::Net::HTTPMessage::HTTP_1_1); - request.set("User-Agent", "LOOLWSD WOPI Agent"); - session.sendRequest(request); - - Poco::Net::HTTPResponse response; - std::istream& rs = session.receiveResponse(response); - - auto logger = Log::trace(); - logger << "WOPI::GetFile header for URI [" << _uri << "]:\n"; - for (auto& pair : response) - { - logger << '\t' + pair.first + ": " + pair.second << " / "; - } - - logger << Log::end; - - _jailedFilePath = Poco::Path(getLocalRootPath(), _fileInfo.Filename).toString(); - std::ofstream ofs(_jailedFilePath); - std::copy(std::istreambuf_iterator<char>(rs), - std::istreambuf_iterator<char>(), - std::ostreambuf_iterator<char>(ofs)); - const auto size = getFileSize(_jailedFilePath); - - Log::info() << "WOPI::GetFile downloaded " << size << " bytes from [" << _uri - << "] -> " << _jailedFilePath << ": " - << response.getStatus() << " " << response.getReason() << Log::end; - - // Now return the jailed path. - return Poco::Path(_jailPath, _fileInfo.Filename).toString(); - } - - bool saveLocalFileToStorage() override - { - Log::info("Uploading URI [" + _uri + "] from [" + _jailedFilePath + "]."); - const auto size = getFileSize(_jailedFilePath); + std::string loadStorageFileToLocal() override; - Poco::URI uriObject(_uri); - const auto url = uriObject.getPath() + "/contents?" + uriObject.getQuery(); - Log::debug("Wopi posting: " + url); - - Poco::Net::HTTPSClientSession session(uriObject.getHost(), uriObject.getPort(), Poco::Net::SSLManager::instance().defaultClientContext()); - Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, url, Poco::Net::HTTPMessage::HTTP_1_1); - request.set("X-WOPIOverride", "PUT"); - request.setContentType("application/octet-stream"); - request.setContentLength(size); - - std::ostream& os = session.sendRequest(request); - std::ifstream ifs(_jailedFilePath); - Poco::StreamCopier::copyStream(ifs, os); - - Poco::Net::HTTPResponse response; - std::istream& rs = session.receiveResponse(response); - std::ostringstream oss; - Poco::StreamCopier::copyStream(rs, oss); - - Log::info("WOPI::PutFile response: " + oss.str()); - const auto success = (response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK); - Log::info() << "WOPI::PutFile uploaded " << size << " bytes from [" << _jailedFilePath << "]:" - << "] -> [" << _uri << "]: " - << response.getStatus() << " " << response.getReason() << Log::end; - - return success; - } + bool saveLocalFileToStorage() override; }; class WebDAVStorage : public StorageBase @@ -331,25 +124,11 @@ public: { } - FileInfo getFileInfo(const Poco::URI& uri) override - { - Log::debug("Getting info for webdav uri [" + uri.toString() + "]."); - (void)uri; - assert(!"Not Implemented!"); - return FileInfo({"bazinga", Poco::Timestamp(), 0}); - } + FileInfo getFileInfo(const Poco::URI& uri) override; - std::string loadStorageFileToLocal() override - { - // TODO: implement webdav GET. - return _uri; - } + std::string loadStorageFileToLocal() override; - bool saveLocalFileToStorage() override - { - // TODO: implement webdav PUT. - return false; - } + bool saveLocalFileToStorage() override; private: std::unique_ptr<AuthBase> _authAgent; _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits