2020-05-12 10:19:41 -05:00
|
|
|
/* -*- 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>
|
2020-05-24 08:21:18 -05:00
|
|
|
|
2021-09-02 07:08:37 -05:00
|
|
|
#include "LOOLWSD.hpp"
|
2020-05-24 08:21:18 -05:00
|
|
|
#include "RequestDetails.hpp"
|
2020-05-25 09:52:37 -05:00
|
|
|
#include "common/Log.hpp"
|
2020-05-24 08:21:18 -05:00
|
|
|
|
2020-05-12 10:19:41 -05:00
|
|
|
#include <Poco/URI.h>
|
|
|
|
#include "Exceptions.hpp"
|
|
|
|
|
2020-06-20 14:12:13 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-05-25 09:52:37 -05:00
|
|
|
/// 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)
|
|
|
|
{
|
2020-12-06 22:01:18 -06:00
|
|
|
LOG_ERR("!!! Data mismatch: [" << subLeft << "] != [" << subRight << ']');
|
2020-05-25 09:52:37 -05:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2020-06-20 14:12:13 -05:00
|
|
|
}
|
2020-05-25 09:52:37 -05:00
|
|
|
|
2020-05-24 08:21:18 -05:00
|
|
|
RequestDetails::RequestDetails(Poco::Net::HTTPRequest &request, const std::string& serviceRoot)
|
2020-05-12 10:19:41 -05:00
|
|
|
: _isMobile(false)
|
|
|
|
{
|
|
|
|
// Check and remove the ServiceRoot from the request.getURI()
|
2020-05-24 08:21:18 -05:00
|
|
|
if (!Util::startsWith(request.getURI(), serviceRoot))
|
|
|
|
throw BadRequestException("The request does not start with prefix: " + serviceRoot);
|
2020-05-12 10:19:41 -05:00
|
|
|
|
|
|
|
// re-writes ServiceRoot out of request
|
2020-05-24 08:21:18 -05:00
|
|
|
_uriString = request.getURI().substr(serviceRoot.length());
|
2021-09-02 07:08:37 -05:00
|
|
|
dehexify();
|
2020-05-12 10:19:41 -05:00
|
|
|
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");
|
2021-03-22 15:58:35 -05:00
|
|
|
_isWebSocket = it != request.end() && Util::iequal(it->second, "websocket");
|
2020-05-12 10:19:41 -05:00
|
|
|
#if MOBILEAPP
|
|
|
|
// request.getHost fires an exception on mobile.
|
|
|
|
#else
|
|
|
|
_hostUntrusted = request.getHost();
|
|
|
|
#endif
|
|
|
|
|
2020-06-05 10:19:58 -05:00
|
|
|
processURI();
|
|
|
|
}
|
|
|
|
|
|
|
|
RequestDetails::RequestDetails(const std::string &mobileURI)
|
|
|
|
: _isGet(true)
|
|
|
|
, _isHead(false)
|
|
|
|
, _isProxy(false)
|
|
|
|
, _isWebSocket(false)
|
|
|
|
{
|
|
|
|
_isMobile = true;
|
|
|
|
_uriString = mobileURI;
|
2021-09-02 07:08:37 -05:00
|
|
|
dehexify();
|
2020-06-05 10:19:58 -05:00
|
|
|
processURI();
|
|
|
|
}
|
|
|
|
|
2021-09-02 07:08:37 -05:00
|
|
|
void RequestDetails::dehexify()
|
|
|
|
{
|
2021-11-15 09:26:57 -06:00
|
|
|
// For now, we only hexify cool/ URLs.
|
2021-09-02 07:08:37 -05:00
|
|
|
constexpr auto Prefix = "lool/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.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-05 10:19:58 -05:00
|
|
|
void RequestDetails::processURI()
|
|
|
|
{
|
2020-05-25 09:52:37 -05:00
|
|
|
// Poco::SyntaxException is thrown when the syntax is invalid.
|
2020-06-20 14:12:13 -05:00
|
|
|
_params = getParams(_uriString);
|
2020-05-25 09:52:37 -05:00
|
|
|
|
|
|
|
// First tokenize by '/' then by '?'.
|
2020-05-12 10:19:41 -05:00
|
|
|
std::vector<StringToken> tokens;
|
2020-05-18 16:42:20 -05:00
|
|
|
const auto len = _uriString.size();
|
|
|
|
if (len > 0)
|
2020-05-12 10:19:41 -05:00
|
|
|
{
|
2020-05-18 16:42:20 -05:00
|
|
|
std::size_t i, start;
|
|
|
|
for (i = start = 0; i < len; ++i)
|
2020-05-12 10:19:41 -05:00
|
|
|
{
|
|
|
|
if (_uriString[i] == '/' || _uriString[i] == '?')
|
|
|
|
{
|
2020-05-25 06:51:04 -05:00
|
|
|
if (i - start > 0) // ignore empty
|
2020-05-12 10:19:41 -05:00
|
|
|
tokens.emplace_back(start, i - start);
|
|
|
|
start = i + 1;
|
|
|
|
}
|
|
|
|
}
|
2020-05-25 06:51:04 -05:00
|
|
|
if (i - start > 0) // ignore empty
|
2020-05-12 10:19:41 -05:00
|
|
|
tokens.emplace_back(start, i - start);
|
2020-06-01 21:46:49 -05:00
|
|
|
_pathSegs = StringVector(_uriString, std::move(tokens));
|
2020-05-12 10:19:41 -05:00
|
|
|
}
|
2020-05-25 09:52:37 -05:00
|
|
|
|
|
|
|
|
|
|
|
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 lool URIs.
|
|
|
|
if (_pathSegs.equals(0, "lool"))
|
|
|
|
{
|
|
|
|
//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)
|
2021-11-15 09:26:57 -06:00
|
|
|
end = uriRes.find('?'); // e.g. /cool/clipboard?WOPISrc=file%3A%2F%2F%2Ftmp%2Fcopypasteef324307_empty.ods...
|
2020-05-25 09:52:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-06-20 14:12:13 -05:00
|
|
|
_docUriParams = getParams(_fields[Field::DocumentURI]);
|
|
|
|
|
2020-05-25 09:52:37 -05:00
|
|
|
_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];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-12 10:19:41 -05:00
|
|
|
}
|
|
|
|
|
2021-09-18 18:52:33 -05:00
|
|
|
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.");
|
|
|
|
}
|
|
|
|
|
2021-10-26 08:23:28 -05:00
|
|
|
// We decoded access token before embedding it in cool.html
|
2021-09-18 18:52:33 -05:00
|
|
|
// 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);
|
2021-10-21 22:27:28 -05:00
|
|
|
|
|
|
|
LOG_DBG("Sanitized URI [" << uri << "] to [" << uriPublic.toString() << ']');
|
2021-09-18 18:52:33 -05:00
|
|
|
return uriPublic;
|
|
|
|
}
|
|
|
|
|
2021-10-25 22:00:20 -05:00
|
|
|
std::string RequestDetails::getDocKey(const Poco::URI& uri)
|
|
|
|
{
|
|
|
|
// If multiple host-names are used to access us, then
|
|
|
|
// they must be aliases. Permission to access aliased hosts
|
|
|
|
// is checked at the point of accepting incoming connections.
|
|
|
|
// At this point storing the hostname artificially discriminates
|
|
|
|
// between aliases and forces same document (when opened from
|
|
|
|
// alias hosts) to load as separate documents and sharing doesn't
|
|
|
|
// work. Worse, saving overwrites one another.
|
|
|
|
std::string docKey;
|
|
|
|
Poco::URI::encode(uri.getPath(), "", docKey);
|
|
|
|
LOG_INF("DocKey from URI [" << uri.toString() << "] => [" << docKey << ']');
|
|
|
|
return docKey;
|
|
|
|
}
|
|
|
|
|
2020-05-12 10:19:41 -05:00
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|