libreoffice-online/wsd/FileServer.cpp
Ashod Nakashian 1bb29282f1 nb: serve files using non-blocking sockets
Change-Id: I254288980f72f197d29b7b57ec9c88a01a5a1d03
2017-03-10 10:47:40 +01:00

287 lines
10 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 "config.h"
#include <string>
#include <vector>
#include <Poco/DateTimeFormat.h>
#include <Poco/DateTimeFormatter.h>
#include <Poco/Exception.h>
#include <Poco/FileStream.h>
#include <Poco/Net/HTTPCookie.h>
#include <Poco/Net/HTTPBasicCredentials.h>
#include <Poco/Net/HTMLForm.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPRequestHandler.h>
#include <Poco/Net/HTTPServer.h>
#include <Poco/Net/HTTPServerParams.h>
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Net/HTTPServerResponse.h>
#include <Poco/Net/NameValueCollection.h>
#include <Poco/Net/NetException.h>
#include <Poco/Net/SecureServerSocket.h>
#include <Poco/Net/WebSocket.h>
#include <Poco/RegularExpression.h>
#include <Poco/Runnable.h>
#include <Poco/StreamCopier.h>
#include <Poco/StringTokenizer.h>
#include <Poco/URI.h>
#include <Poco/Util/ServerApplication.h>
#include <Poco/Util/Timer.h>
#include "Auth.hpp"
#include "Common.hpp"
#include "FileServer.hpp"
#include "LOOLWSD.hpp"
#include "Log.hpp"
using Poco::FileInputStream;
using Poco::Net::HTMLForm;
using Poco::Net::HTTPRequest;
using Poco::Net::HTTPResponse;
using Poco::Net::HTTPServerRequest;
using Poco::Net::HTTPServerResponse;
using Poco::Net::NameValueCollection;
using Poco::Net::HTTPBasicCredentials;
using Poco::StreamCopier;
using Poco::Util::Application;
bool FileServerRequestHandler::isAdminLoggedIn(HTTPServerRequest& request, HTTPServerResponse& response)
{
const auto& config = Application::instance().config();
const auto sslKeyPath = config.getString("ssl.key_file_path", "");
NameValueCollection cookies;
request.getCookies(cookies);
try
{
const std::string jwtToken = cookies.get("jwt");
LOG_INF("Verifying JWT token: " << jwtToken);
JWTAuth authAgent(sslKeyPath, "admin", "admin", "admin");
if (authAgent.verify(jwtToken))
{
LOG_TRC("JWT token is valid");
return true;
}
LOG_INF("Invalid JWT token, let the administrator re-login");
}
catch (const Poco::Exception& exc)
{
LOG_INF("No existing JWT cookie found");
}
// If no cookie found, or is invalid, let admin re-login
const auto user = config.getString("admin_console.username", "");
const auto pass = config.getString("admin_console.password", "");
if (user.empty() || pass.empty())
{
LOG_ERR("Admin Console credentials missing. Denying access until set.");
return false;
}
HTTPBasicCredentials credentials(request);
if (credentials.getUsername() == user &&
credentials.getPassword() == pass)
{
const std::string htmlMimeType = "text/html";
// generate and set the cookie
JWTAuth authAgent(sslKeyPath, "admin", "admin", "admin");
const std::string jwtToken = authAgent.getAccessToken();
Poco::Net::HTTPCookie cookie("jwt", jwtToken);
cookie.setPath("/loleaflet/dist/admin/");
cookie.setSecure(true);
response.addCookie(cookie);
return true;
}
LOG_INF("Wrong admin credentials.");
return false;
}
void FileServerRequestHandler::handleRequest(const HTTPRequest& request, const std::shared_ptr<StreamSocket>& socket)
{
try
{
Poco::URI requestUri(request.getURI());
LOG_TRC("Fileserver request: " << requestUri.toString());
requestUri.normalize(); // avoid .'s and ..'s
std::vector<std::string> requestSegments;
requestUri.getPathSegments(requestSegments);
if (requestSegments.size() < 1)
{
throw Poco::FileNotFoundException("Invalid URI request: [" + requestUri.toString() + "].");
}
const auto& config = Application::instance().config();
const std::string loleafletHtml = config.getString("loleaflet_html", "loleaflet.html");
const std::string endPoint = requestSegments[requestSegments.size() - 1];
if (endPoint == loleafletHtml)
{
preprocessFile(request, socket);
return;
}
if (request.getMethod() == HTTPRequest::HTTP_GET)
{
if (endPoint == "admin.html" ||
endPoint == "adminSettings.html" ||
endPoint == "adminAnalytics.html")
{
// FIXME: support admin console.
//if (!FileServerRequestHandler::isAdminLoggedIn(request, response))
throw Poco::Net::NotAuthenticatedException("Invalid admin login");
}
const auto path = Poco::Path(LOOLWSD::FileServerRoot, getRequestPathname(request));
const auto filepath = path.absolute().toString();
if (filepath.find(LOOLWSD::FileServerRoot) != 0)
{
// Accessing unauthorized path.
throw Poco::FileAccessDeniedException("Invalid or forbidden file path: [" + filepath + "].");
}
const std::size_t extPoint = endPoint.find_last_of('.');
if (extPoint == std::string::npos)
{
throw Poco::FileNotFoundException("Invalid file.");
}
const std::string fileType = endPoint.substr(extPoint + 1);
std::string mimeType;
if (fileType == "js")
mimeType = "application/javascript";
else if (fileType == "css")
mimeType = "text/css";
else if (fileType == "html")
mimeType = "text/html";
else if (fileType == "svg")
mimeType = "image/svg+xml";
else
mimeType = "text/plain";
HttpHelper::sendFile(socket, filepath, mimeType);
}
}
catch (const Poco::Net::NotAuthenticatedException& exc)
{
LOG_ERR("FileServerRequestHandler::NotAuthenticated: " << exc.displayText());
// response.set("WWW-Authenticate", "Basic realm=\"online\"");
// response.setStatusAndReason(HTTPResponse::HTTP_UNAUTHORIZED);
// response.setContentLength(0);
// response.send();
}
catch (const Poco::FileAccessDeniedException& exc)
{
LOG_ERR("FileServerRequestHandler: " << exc.displayText());
// response.setStatusAndReason(HTTPResponse::HTTP_FORBIDDEN);
// response.setContentLength(0); // TODO return some 403 page?
// response.send();
}
catch (const Poco::FileNotFoundException& exc)
{
LOG_ERR("FileServerRequestHandler: " << exc.displayText());
// response.setStatusAndReason(HTTPResponse::HTTP_NOT_FOUND);
// response.setContentLength(0); // TODO return some 404 page?
// response.send();
}
}
std::string FileServerRequestHandler::getRequestPathname(const HTTPRequest& request)
{
Poco::URI requestUri(request.getURI());
// avoid .'s and ..'s
requestUri.normalize();
std::string path(requestUri.getPath());
// Convert version back to a real file name. Remove first foreslash as the root ends in one.
Poco::replaceInPlace(path, std::string("/loleaflet/" LOOLWSD_VERSION_HASH "/"), std::string("loleaflet/dist/"));
return path;
}
void FileServerRequestHandler::preprocessFile(const HTTPRequest& request, const std::shared_ptr<StreamSocket>& socket)
{
const auto host = ((LOOLWSD::isSSLEnabled() || LOOLWSD::isSSLTermination()) ? "wss://" : "ws://") + (LOOLWSD::ServerName.empty() ? request.getHost() : LOOLWSD::ServerName);
const auto path = Poco::Path(LOOLWSD::FileServerRoot, getRequestPathname(request));
LOG_DBG("Preprocessing file: " << path.toString());
if (!Poco::File(path).exists())
{
LOG_ERR("File [" << path.toString() << "] does not exist.");
// response.setStatusAndReason(HTTPResponse::HTTP_NOT_FOUND);
// response.setContentLength(0); // TODO return some 404 page?
// response.send();
return;
}
std::string preprocess;
FileInputStream file(path.toString());
StreamCopier::copyToString(file, preprocess);
file.close();
const std::string& accessToken = request.get("access_token", "");
const std::string& accessTokenTtl = request.get("access_token_ttl", "");
// Escape bad characters in access token.
// This is placed directly in javascript in loleaflet.html, we need to make sure
// that no one can do anything nasty with their clever inputs.
std::string escapedAccessToken;
Poco::URI::encode(accessToken, "'", escapedAccessToken);
unsigned long tokenTtl = 0;
if (accessToken != "")
{
if (accessTokenTtl != "")
{
try
{
tokenTtl = std::stoul(accessTokenTtl);
}
catch(const std::exception& exc)
{
LOG_ERR("access_token_ttl must be represented as the number of milliseconds since January 1, 1970 UTC, when the token will expire");
}
}
else
{
LOG_WRN("WOPI host did not pass optional access_token_ttl");
}
}
Poco::replaceInPlace(preprocess, std::string("%ACCESS_TOKEN%"), escapedAccessToken);
Poco::replaceInPlace(preprocess, std::string("%ACCESS_TOKEN_TTL%"), std::to_string(tokenTtl));
Poco::replaceInPlace(preprocess, std::string("%HOST%"), host);
Poco::replaceInPlace(preprocess, std::string("%VERSION%"), std::string(LOOLWSD_VERSION_HASH));
const auto& config = Application::instance().config();
const auto loleafletLogging = config.getString("loleaflet_logging", "false");
Poco::replaceInPlace(preprocess, std::string("%LOLEAFLET_LOGGING%"), loleafletLogging);
const std::string mimeType = "text/html";
std::ostringstream oss;
oss << "HTTP/1.1 200 OK\r\n"
<< "Last-Modified: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n"
<< "User-Agent: LOOLWSD WOPI Agent\r\n"
<< "Content-Length: " << preprocess.size() << "\r\n"
<< "Content-Type: " << mimeType << "\r\n"
<< "\r\n"
<< preprocess;
socket->send(oss.str());
LOG_DBG("Sent file: " << path.toString());
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */