d02dd19f33
This allows us to use multiple hosts using same coolwsd instance. added aliases configuration to coolwsd.xml to avoid possibility of opening the same file as two if the WOPI host is accessed using different aliases Signed-off-by: Rash419 <rashesh.padia@collabora.com> Change-Id: I32913015c15fd396cecc702b76e0dcaa8bcafad3
300 lines
9 KiB
C++
300 lines
9 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
/*
|
|
* 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 "COOLWSD.hpp"
|
|
#include "RequestDetails.hpp"
|
|
#include "common/Log.hpp"
|
|
#include "Storage.hpp"
|
|
|
|
#include <Poco/URI.h>
|
|
#include "Exceptions.hpp"
|
|
|
|
namespace
|
|
{
|
|
|
|
std::map<std::string, std::string> getParams(const std::string& uri)
|
|
{
|
|
std::map<std::string, std::string> result;
|
|
for (const auto& param : Poco::URI(uri).getQueryParameters())
|
|
{
|
|
std::string key;
|
|
Poco::URI::decode(param.first, key);
|
|
std::string value;
|
|
Poco::URI::decode(param.second, value);
|
|
LOG_TRC("Decoding param [" << param.first << "] = [" << param.second << "] -> [" << key
|
|
<< "] = [" << value << "].");
|
|
|
|
result.emplace(key, value);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Returns true iff the two containers are equal.
|
|
template <typename T> bool equal(const T& lhs, const T& rhs)
|
|
{
|
|
if (lhs.size() != rhs.size())
|
|
{
|
|
LOG_ERR("!!! Size mismatch: [" << lhs.size() << "] != [" << rhs.size() << "].");
|
|
return false;
|
|
}
|
|
|
|
const auto endLeft = std::end(lhs);
|
|
|
|
auto itRight = std::begin(rhs);
|
|
|
|
for (auto itLeft = std::begin(lhs); itLeft != endLeft; ++itLeft, ++itRight)
|
|
{
|
|
const auto subLeft = lhs.getParam(*itLeft);
|
|
const auto subRight = rhs.getParam(*itRight);
|
|
|
|
if (subLeft != subRight)
|
|
{
|
|
LOG_ERR("!!! Data mismatch: [" << subLeft << "] != [" << subRight << ']');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
RequestDetails::RequestDetails(Poco::Net::HTTPRequest &request, const std::string& serviceRoot)
|
|
: _isMobile(false)
|
|
{
|
|
// Check and remove the ServiceRoot from the request.getURI()
|
|
if (!Util::startsWith(request.getURI(), serviceRoot))
|
|
throw BadRequestException("The request does not start with prefix: " + serviceRoot);
|
|
|
|
// re-writes ServiceRoot out of request
|
|
_uriString = request.getURI().substr(serviceRoot.length());
|
|
dehexify();
|
|
request.setURI(_uriString);
|
|
const std::string &method = request.getMethod();
|
|
_isGet = method == "GET";
|
|
_isHead = method == "HEAD";
|
|
auto it = request.find("ProxyPrefix");
|
|
_isProxy = it != request.end();
|
|
if (_isProxy)
|
|
_proxyPrefix = it->second;
|
|
it = request.find("Upgrade");
|
|
_isWebSocket = it != request.end() && Util::iequal(it->second, "websocket");
|
|
#if MOBILEAPP
|
|
// request.getHost fires an exception on mobile.
|
|
#else
|
|
_hostUntrusted = request.getHost();
|
|
#endif
|
|
|
|
processURI();
|
|
}
|
|
|
|
RequestDetails::RequestDetails(const std::string &mobileURI)
|
|
: _isGet(true)
|
|
, _isHead(false)
|
|
, _isProxy(false)
|
|
, _isWebSocket(false)
|
|
{
|
|
_isMobile = true;
|
|
_uriString = mobileURI;
|
|
dehexify();
|
|
processURI();
|
|
}
|
|
|
|
void RequestDetails::dehexify()
|
|
{
|
|
// For now, we only hexify cool/ URLs.
|
|
constexpr auto Prefix = "cool/0x";
|
|
constexpr auto PrefixLen = sizeof(Prefix) - 1;
|
|
|
|
const auto hexPos = _uriString.find(Prefix);
|
|
if (hexPos != std::string::npos)
|
|
{
|
|
// The start of the hex token.
|
|
const auto start = hexPos + PrefixLen;
|
|
// Find the next '/' after the hex token.
|
|
const auto end = _uriString.find_first_of('/', start);
|
|
|
|
std::string res = _uriString.substr(0, start - 2); // The prefix, without '0x'.
|
|
|
|
const std::string encoded =
|
|
_uriString.substr(start, (end == std::string::npos) ? end : end - start);
|
|
std::string decoded;
|
|
Util::dataFromHexString(encoded, decoded);
|
|
res += decoded;
|
|
|
|
res += _uriString.substr(end); // Concatinate the remainder.
|
|
|
|
_uriString = res; // Replace the original uri with the decoded one.
|
|
}
|
|
}
|
|
|
|
void RequestDetails::processURI()
|
|
{
|
|
// Poco::SyntaxException is thrown when the syntax is invalid.
|
|
_params = getParams(_uriString);
|
|
|
|
// First tokenize by '/' then by '?'.
|
|
std::vector<StringToken> tokens;
|
|
const auto len = _uriString.size();
|
|
if (len > 0)
|
|
{
|
|
std::size_t i, start;
|
|
for (i = start = 0; i < len; ++i)
|
|
{
|
|
if (_uriString[i] == '/' || _uriString[i] == '?')
|
|
{
|
|
if (i - start > 0) // ignore empty
|
|
tokens.emplace_back(start, i - start);
|
|
start = i + 1;
|
|
}
|
|
}
|
|
if (i - start > 0) // ignore empty
|
|
tokens.emplace_back(start, i - start);
|
|
_pathSegs = StringVector(_uriString, std::move(tokens));
|
|
}
|
|
|
|
|
|
std::size_t off = 0;
|
|
std::size_t posDocUri = _uriString.find_first_of('/');
|
|
if (posDocUri == 0)
|
|
{
|
|
off = 1;
|
|
posDocUri = _uriString.find_first_of('/', 1);
|
|
}
|
|
|
|
_fields[Field::Type] = _uriString.substr(off, posDocUri - off); // The first is always the type.
|
|
std::string uriRes = _uriString.substr(posDocUri + 1);
|
|
|
|
const auto posLastWS = uriRes.rfind("/ws");
|
|
// DocumentURI is the second segment in cool URIs.
|
|
if (_pathSegs.equals(0, "cool"))
|
|
{
|
|
//FIXME: For historic reasons the DocumentURI includes the WOPISrc.
|
|
// This is problematic because decoding a URI that embedds not one, but
|
|
// *two* encoded URIs within it is bound to produce an invalid URI.
|
|
// Potentially three '?' might exist in the result (after decoding).
|
|
std::size_t end = uriRes.rfind("/ws?");
|
|
if (end != std::string::npos)
|
|
{
|
|
// Until the end of the WOPISrc.
|
|
// e.g. <encoded-document-URI+options>/ws?WOPISrc=<encoded-document-URI>&compat=
|
|
end = uriRes.find_first_of("/?", end + 4, 2); // Start searching after '/ws?'.
|
|
}
|
|
else
|
|
{
|
|
end = (posLastWS != std::string::npos ? posLastWS : uriRes.find('/'));
|
|
if (end == std::string::npos)
|
|
end = uriRes.find('?'); // e.g. /cool/clipboard?WOPISrc=file%3A%2F%2F%2Ftmp%2Fcopypasteef324307_empty.ods...
|
|
}
|
|
|
|
const std::string docUri = uriRes.substr(0, end);
|
|
|
|
std::string decoded;
|
|
Poco::URI::decode(docUri, decoded);
|
|
_fields[Field::LegacyDocumentURI] = decoded;
|
|
|
|
// Find the DocumentURI proper.
|
|
end = uriRes.find_first_of("/?", 0, 2);
|
|
decoded.clear();
|
|
Poco::URI::decode(uriRes.substr(0, end), decoded);
|
|
_fields[Field::DocumentURI] = decoded;
|
|
}
|
|
else // Otherwise, it's the full URI.
|
|
{
|
|
_fields[Field::LegacyDocumentURI] = _uriString;
|
|
_fields[Field::DocumentURI] = _uriString;
|
|
}
|
|
|
|
_docUriParams = getParams(_fields[Field::DocumentURI]);
|
|
|
|
_fields[Field::WOPISrc] = getParam("WOPISrc");
|
|
|
|
// &compat=
|
|
const std::string compat = getParam("compat");
|
|
if (!compat.empty())
|
|
_fields[Field::Compat] = compat;
|
|
|
|
// /ws[/<sessionId>/<command>/<serial>]
|
|
if (posLastWS != std::string::npos)
|
|
{
|
|
std::string lastWS = uriRes.substr(posLastWS);
|
|
const auto proxyTokens = Util::tokenize(lastWS, '/');
|
|
if (proxyTokens.size() > 1)
|
|
{
|
|
_fields[Field::SessionId] = proxyTokens[1];
|
|
if (proxyTokens.size() > 2)
|
|
{
|
|
_fields[Field::Command] = proxyTokens[2];
|
|
if (proxyTokens.size() > 3)
|
|
{
|
|
_fields[Field::Serial] = proxyTokens[3];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Poco::URI RequestDetails::sanitizeURI(const std::string& uri)
|
|
{
|
|
// The URI of the document should be url-encoded.
|
|
#if !MOBILEAPP
|
|
std::string decodedUri;
|
|
Poco::URI::decode(uri, decodedUri);
|
|
Poco::URI uriPublic(decodedUri);
|
|
#else
|
|
Poco::URI uriPublic(uri);
|
|
#endif
|
|
|
|
if (uriPublic.isRelative() || uriPublic.getScheme() == "file")
|
|
{
|
|
// TODO: Validate and limit access to local paths!
|
|
uriPublic.normalize();
|
|
}
|
|
|
|
if (uriPublic.getPath().empty())
|
|
{
|
|
throw std::runtime_error("Invalid URI.");
|
|
}
|
|
|
|
// We decoded access token before embedding it in cool.html
|
|
// So, we need to decode it now to get its actual value
|
|
Poco::URI::QueryParameters queryParams = uriPublic.getQueryParameters();
|
|
for (auto& param : queryParams)
|
|
{
|
|
// look for encoded query params (access token as of now)
|
|
if (param.first == "access_token")
|
|
{
|
|
std::string decodedToken;
|
|
Poco::URI::decode(param.second, decodedToken);
|
|
param.second = decodedToken;
|
|
}
|
|
}
|
|
|
|
uriPublic.setQueryParameters(queryParams);
|
|
|
|
LOG_DBG("Sanitized URI [" << uri << "] to [" << uriPublic.toString() << ']');
|
|
return uriPublic;
|
|
}
|
|
|
|
std::string RequestDetails::getDocKey(const Poco::URI& uri)
|
|
{
|
|
std::string docKey;
|
|
std::string newUri = uri.getPath();
|
|
|
|
// resolve aliases
|
|
#if !MOBILEAPP
|
|
newUri = StorageBase::getNewUri(uri);
|
|
#endif
|
|
|
|
Poco::URI::encode(newUri, "", docKey);
|
|
LOG_INF("DocKey from URI [" << uri.toString() << "] => [" << docKey << ']');
|
|
return docKey;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|