mirror of
https://git.tesses.org/tesses50/tessesframework.git
synced 2026-09-30 08:44:13 +00:00
Fix license compliance and fix things up, add reverse proxy, fix some security flaws with HttpUtils
This commit is contained in:
@@ -45,13 +45,24 @@ class ClientTLSPrivateData {
|
||||
public:
|
||||
bool eos;
|
||||
bool success;
|
||||
bool mTLS;
|
||||
std::shared_ptr<Stream> strm;
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
mbedtls_ssl_context ssl;
|
||||
mbedtls_ssl_config conf;
|
||||
mbedtls_x509_crt cachain;
|
||||
mbedtls_x509_crt client_cert;
|
||||
mbedtls_pk_context client_key;
|
||||
|
||||
~ClientTLSPrivateData() {
|
||||
strm->SetSendTimeout((uint64_t)7);
|
||||
mbedtls_ssl_close_notify(&ssl);
|
||||
if (mTLS) {
|
||||
mbedtls_x509_crt_free(&client_cert);
|
||||
mbedtls_pk_free(&client_key);
|
||||
}
|
||||
|
||||
mbedtls_x509_crt_free(&cachain);
|
||||
mbedtls_ctr_drbg_free(&ctr_drbg);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
@@ -101,6 +112,7 @@ ClientTLSStream::ClientTLSStream(
|
||||
data->eos = false;
|
||||
data->success = false;
|
||||
data->strm = innerStream;
|
||||
data->mTLS = false;
|
||||
|
||||
mbedtls_ssl_init(&data->ssl);
|
||||
mbedtls_ssl_config_init(&data->conf);
|
||||
@@ -119,15 +131,10 @@ ClientTLSStream::ClientTLSStream(
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
printf("FAILED mbedtls_x509_crt_parse cert %i\n", ret);
|
||||
return;
|
||||
}
|
||||
ret = mbedtls_x509_crt_parse(
|
||||
&data->cachain, (const unsigned char *)cert.c_str(), cert.size() + 1);
|
||||
|
||||
if (ret != 0) {
|
||||
printf("FAILED mbedtls_x509_crt_parse chain %i\n", ret);
|
||||
printf("FAILED mbedtls_x509_crt_parse cert %i\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,6 +192,132 @@ ClientTLSStream::ClientTLSStream(
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
ClientTLSStream::ClientTLSStream(
|
||||
std::shared_ptr<Tesses::Framework::Streams::Stream> innerStream,
|
||||
bool verify, std::string domain, std::string cert,
|
||||
CertificateKeyStore keyStore) {
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
|
||||
if (cert.empty()) {
|
||||
cert = GetCertChain();
|
||||
}
|
||||
|
||||
ClientTLSPrivateData *data = new ClientTLSPrivateData();
|
||||
this->privateData = static_cast<void *>(data);
|
||||
data->eos = false;
|
||||
data->success = false;
|
||||
data->strm = innerStream;
|
||||
data->mTLS = true;
|
||||
|
||||
mbedtls_ssl_init(&data->ssl);
|
||||
mbedtls_ssl_config_init(&data->conf);
|
||||
mbedtls_x509_crt_init(&data->cachain);
|
||||
mbedtls_ctr_drbg_init(&data->ctr_drbg);
|
||||
mbedtls_entropy_init(&data->entropy);
|
||||
mbedtls_x509_crt_init(&data->client_cert);
|
||||
mbedtls_pk_init(&data->client_key);
|
||||
|
||||
const char *pers = "TessesFramework";
|
||||
|
||||
int ret = 0;
|
||||
|
||||
if ((ret = mbedtls_ctr_drbg_seed(
|
||||
&data->ctr_drbg, mbedtls_entropy_func, &data->entropy,
|
||||
(const unsigned char *)pers, strlen(pers))) != 0) {
|
||||
printf("FAILED mbedtls_ctr_drbg_seed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = mbedtls_x509_crt_parse(
|
||||
&data->cachain, (const unsigned char *)cert.c_str(), cert.size() + 1);
|
||||
|
||||
if (ret != 0) {
|
||||
printf("FAILED mbedtls_x509_crt_parse chain %i\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = mbedtls_x509_crt_parse(
|
||||
&data->client_cert, (const unsigned char *)keyStore.certificate.c_str(),
|
||||
keyStore.certificate.size() + 1);
|
||||
|
||||
if (ret != 0) {
|
||||
printf("FAILED mbedtls_x509_crt_parse client_certificate %i\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = mbedtls_pk_parse_key(
|
||||
&data->client_key, (const unsigned char *)keyStore.certificate.c_str(),
|
||||
keyStore.certificate.size() + 1,
|
||||
keyStore.password.empty()
|
||||
? NULL
|
||||
: (const unsigned char *)keyStore.password.c_str(),
|
||||
keyStore.password.size(), mbedtls_entropy_func, &data->entropy);
|
||||
|
||||
if (ret != 0) {
|
||||
printf("FAILED mbedtls_x509_crt_parse client_certificate %i\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = mbedtls_ssl_config_defaults(&data->conf, MBEDTLS_SSL_IS_CLIENT,
|
||||
MBEDTLS_SSL_TRANSPORT_STREAM,
|
||||
MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
|
||||
char buffer[100];
|
||||
mbedtls_strerror(ret, buffer, sizeof(buffer));
|
||||
printf("FAILED mbedtls_ssl_conf_defaults %s\n", buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
mbedtls_ssl_conf_rng(&data->conf, mbedtls_ctr_drbg_random, &data->ctr_drbg);
|
||||
|
||||
/* #if defined(MBEDTLS_SSL_CACHE_C)
|
||||
mbedtls_ssl_conf_session_cache(&conf, &cache,
|
||||
mbedtls_ssl_cache_get,
|
||||
mbedtls_ssl_cache_set);
|
||||
#endif*/
|
||||
mbedtls_ssl_conf_authmode(&data->conf, verify ? MBEDTLS_SSL_VERIFY_REQUIRED
|
||||
: MBEDTLS_SSL_VERIFY_NONE);
|
||||
mbedtls_ssl_conf_ca_chain(&data->conf, &data->cachain, NULL);
|
||||
|
||||
mbedtls_ssl_conf_own_cert(&data->conf, &data->client_cert,
|
||||
&data->client_key);
|
||||
|
||||
mbedtls_ssl_set_bio(&data->ssl, static_cast<void *>(data), strm_send,
|
||||
strm_recv, NULL);
|
||||
if ((ret = mbedtls_ssl_setup(&data->ssl, &data->conf) != 0)) {
|
||||
printf("FAILED mbedtls_ssl_setup %i\n", ret);
|
||||
return;
|
||||
}
|
||||
if ((ret = mbedtls_ssl_set_hostname(&data->ssl, domain.c_str()) != 0)) {
|
||||
printf("FAILED mbedtls_ssl_set_hostname %i\n", ret);
|
||||
return;
|
||||
}
|
||||
if ((ret = mbedtls_ssl_handshake(&data->ssl)) != 0) {
|
||||
char buffer[100];
|
||||
mbedtls_strerror(ret, buffer, sizeof(buffer));
|
||||
printf("FAILED mbedtls_ssl_handshake %s\n", buffer);
|
||||
return;
|
||||
}
|
||||
uint32_t flags;
|
||||
if ((flags = mbedtls_ssl_get_verify_result(&data->ssl)) != 0) {
|
||||
#if !defined(MBEDTLS_X509_REMOVE_INFO)
|
||||
char vrfy_buf[512];
|
||||
#endif
|
||||
|
||||
#if !defined(MBEDTLS_X509_REMOVE_INFO)
|
||||
mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), " ! ", flags);
|
||||
|
||||
#endif
|
||||
if (verify)
|
||||
return;
|
||||
}
|
||||
|
||||
data->success = true;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void ClientTLSStream::Shutdown(StreamShutdownMode mode) {}
|
||||
|
||||
size_t ClientTLSStream::Read(uint8_t *buffer, size_t len) {
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
|
||||
auto priv = static_cast<ClientTLSPrivateData *>(this->privateData);
|
||||
@@ -245,4 +378,18 @@ ClientTLSStream::~ClientTLSStream() {
|
||||
delete static_cast<ClientTLSPrivateData *>(this->privateData);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ClientTLSStream::SetSendTimeout(uint64_t seconds) {
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
|
||||
static_cast<ClientTLSPrivateData *>(this->privateData)
|
||||
->strm->SetSendTimeout(seconds);
|
||||
#endif
|
||||
}
|
||||
void ClientTLSStream::SetRecvTimeout(uint64_t seconds) {
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
|
||||
static_cast<ClientTLSPrivateData *>(this->privateData)
|
||||
->strm->SetRecvTimeout(seconds);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Tesses::Framework::Crypto
|
||||
+34
-10
@@ -392,7 +392,10 @@ const char *weekday_long[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
|
||||
const char *months_long[] = {"January", "Febuary", "March", "April",
|
||||
"May", "June", "July", "August",
|
||||
"September", "October", "November", "December"};
|
||||
bool DateTime::TryParseHttpDate(std::string txt, DateTime &dt) {
|
||||
bool DateTime::TryParseHttpDate(std::string_view txt) {
|
||||
return TryParseHttpDate(txt, *this);
|
||||
}
|
||||
bool DateTime::TryParseHttpDate(std::string_view txt, DateTime &dt) {
|
||||
// Mon, 24 Jul 2018 11:00:00 GMT
|
||||
auto split = Http::HttpUtils::SplitString(txt, ", ", 2);
|
||||
if (split.size() != 2)
|
||||
@@ -579,9 +582,8 @@ std::string DateTime::ToString(std::string fmt) const {
|
||||
|
||||
break;
|
||||
case 'u': {
|
||||
int dow = weekday + 6;
|
||||
dow %= 7;
|
||||
text.append(std::to_string(dow + 1));
|
||||
|
||||
text.append(std::to_string(weekday + 1));
|
||||
} break;
|
||||
case 'w': {
|
||||
|
||||
@@ -590,10 +592,10 @@ std::string DateTime::ToString(std::string fmt) const {
|
||||
case 'c': {
|
||||
text.append(weekday_short[weekday]);
|
||||
text.push_back(' ');
|
||||
text.append(months_short[month]);
|
||||
text.append(months_short[month - 1]);
|
||||
text.push_back(' ');
|
||||
text.append(
|
||||
Http::HttpUtils::LeftPad(std::to_string(day), 2, '0'));
|
||||
Http::HttpUtils::LeftPad(std::to_string(day), 2, ' '));
|
||||
text.push_back(' ');
|
||||
text.append(
|
||||
Http::HttpUtils::LeftPad(std::to_string(hour), 2, '0'));
|
||||
@@ -709,7 +711,29 @@ void TimeSpan::SetTotalMinutes(int64_t totalMinutes) {
|
||||
void TimeSpan::SetTotalHours(int64_t totalHours) {
|
||||
this->totalSeconds = totalHours * 3600;
|
||||
}
|
||||
|
||||
void TimeSpan::SetTotalDays(int64_t totalDays) {
|
||||
this->totalSeconds = totalDays * 86400;
|
||||
}
|
||||
TimeSpan TimeSpan::FromSeconds(int64_t sec) {
|
||||
TimeSpan span;
|
||||
span.SetTotalSeconds(sec);
|
||||
return span;
|
||||
}
|
||||
TimeSpan TimeSpan::FromMinutes(int64_t minutes) {
|
||||
TimeSpan span;
|
||||
span.SetTotalMinutes(minutes);
|
||||
return span;
|
||||
}
|
||||
TimeSpan TimeSpan::FromHours(int64_t hours) {
|
||||
TimeSpan span;
|
||||
span.SetTotalMinutes(hours);
|
||||
return span;
|
||||
}
|
||||
TimeSpan TimeSpan::FromDays(int64_t days) {
|
||||
TimeSpan span;
|
||||
span.SetTotalMinutes(days);
|
||||
return span;
|
||||
}
|
||||
std::string TimeSpan::ToString(bool slim) const {
|
||||
std::string str = {};
|
||||
if (this->totalSeconds < 0)
|
||||
@@ -750,8 +774,8 @@ std::string TimeSpan::ToString(bool slim) const {
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
bool TimeSpan::TryParse(std::string text, TimeSpan &span) {
|
||||
bool TimeSpan::TryParse(std::string_view text) { return TryParse(text, *this); }
|
||||
bool TimeSpan::TryParse(std::string_view text, TimeSpan &span) {
|
||||
if (text.empty())
|
||||
return false;
|
||||
bool negative = text[0] == '-';
|
||||
@@ -759,7 +783,7 @@ bool TimeSpan::TryParse(std::string text, TimeSpan &span) {
|
||||
|
||||
try {
|
||||
|
||||
std::string colonPart = text.substr(negative ? 1 : 0);
|
||||
std::string_view colonPart = text.substr(negative ? 1 : 0);
|
||||
auto res = Http::HttpUtils::SplitString(colonPart, ":");
|
||||
|
||||
if (res.size() < 2 || res.size() > 3)
|
||||
|
||||
@@ -296,6 +296,7 @@ VFSPath VFSPath::CollapseRelativeParents() const {
|
||||
newpath.path = parts;
|
||||
return newpath;
|
||||
}
|
||||
|
||||
VFSPath VFSPath::CurrentDirectoryAsRelative() {
|
||||
VFSPath path;
|
||||
path.relative = true;
|
||||
|
||||
@@ -27,9 +27,22 @@ ChangeableServer::ChangeableServer(std::shared_ptr<IHttpServer> original) {
|
||||
this->server = original;
|
||||
}
|
||||
|
||||
std::shared_ptr<IHttpServer> ChangeableServer::GetServer() {
|
||||
mtx.Lock();
|
||||
auto server = this->server;
|
||||
mtx.Unlock();
|
||||
return server;
|
||||
}
|
||||
void ChangeableServer::SetServer(std::shared_ptr<IHttpServer> server) {
|
||||
mtx.Lock();
|
||||
this->server = server;
|
||||
mtx.Unlock();
|
||||
}
|
||||
bool ChangeableServer::Handle(ServerContext &ctx) {
|
||||
if (this->server)
|
||||
this->server->Handle(ctx);
|
||||
auto server = GetServer();
|
||||
|
||||
if (server)
|
||||
return server->Handle(ctx);
|
||||
return false;
|
||||
}
|
||||
ChangeableServer::~ChangeableServer() {}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "TessesFramework/Http/DomainServer.hpp"
|
||||
|
||||
namespace Tesses::Framework::Http {
|
||||
DomainServer::DomainServer() {}
|
||||
DomainServer::DomainServer(std::shared_ptr<IHttpServer> root) : root(root) {}
|
||||
void DomainServer::Set(std::string domain,
|
||||
std::shared_ptr<IHttpServer> server) {
|
||||
mtx.Lock();
|
||||
this->servers[domain] = server;
|
||||
mtx.Unlock();
|
||||
}
|
||||
void DomainServer::Unset(std::string domain) {
|
||||
mtx.Lock();
|
||||
this->servers.erase(domain);
|
||||
mtx.Unlock();
|
||||
}
|
||||
void DomainServer::Clear() {
|
||||
mtx.Lock();
|
||||
this->servers.clear();
|
||||
mtx.Unlock();
|
||||
}
|
||||
bool DomainServer::Handle(ServerContext &ctx) {
|
||||
std::string host;
|
||||
|
||||
std::shared_ptr<IHttpServer> server = nullptr;
|
||||
if (ctx.requestHeaders.TryGetFirst("Host", host)) {
|
||||
|
||||
mtx.Lock();
|
||||
if (this->servers.count(host) > 0)
|
||||
server = this->servers[host];
|
||||
mtx.Unlock();
|
||||
}
|
||||
|
||||
if (server)
|
||||
return server->Handle(ctx);
|
||||
if (this->root)
|
||||
return this->root->Handle(ctx);
|
||||
return false;
|
||||
}
|
||||
} // namespace Tesses::Framework::Http
|
||||
@@ -63,7 +63,7 @@ bool FileServer::SendFile(ServerContext &ctx, VFSPath path) {
|
||||
Date::DateTime lw, la;
|
||||
this->vfs->GetDate(path, lw, la);
|
||||
ctx.WithLastModified(lw)
|
||||
.WithMimeType(HttpUtils::MimeType(path.GetFileName()))
|
||||
.WithMimeType(HttpUtils::GetMimeTypePath(path))
|
||||
.SendStream(strm);
|
||||
retVal = true;
|
||||
}
|
||||
|
||||
+34
-22
@@ -65,10 +65,12 @@ StreamHttpRequestBody::StreamHttpRequestBody(std::shared_ptr<Stream> strm,
|
||||
this->mimeType = mimeType;
|
||||
}
|
||||
void StreamHttpRequestBody::HandleHeaders(HttpDictionary &dict) {
|
||||
dict.AddValue("Content-Type", this->mimeType);
|
||||
dict.SetValue("Content-Type", this->mimeType);
|
||||
auto len = this->strm->GetLength();
|
||||
if (len > -1)
|
||||
dict.AddValue("Content-Length", std::to_string(len));
|
||||
dict.SetValue("Content-Length", std::to_string(len));
|
||||
else
|
||||
dict.SetValue("Transfer-Encoding", "chunked");
|
||||
}
|
||||
void StreamHttpRequestBody::Write(
|
||||
std::shared_ptr<Tesses::Framework::Streams::Stream> strm) {
|
||||
@@ -96,6 +98,7 @@ void HttpRequest::SendRequest(
|
||||
|
||||
std::string request = method + " " + uri.GetPathAndQuery() +
|
||||
" HTTP/1.1\r\nHost: " + uri.HostPort() + "\r\n";
|
||||
|
||||
for (auto headers : requestHeaders.kvp) {
|
||||
for (auto item : headers.second) {
|
||||
request.append(headers.first);
|
||||
@@ -106,7 +109,6 @@ void HttpRequest::SendRequest(
|
||||
}
|
||||
|
||||
request.append("\r\n");
|
||||
|
||||
StreamWriter writer(strm);
|
||||
writer.Write(request);
|
||||
|
||||
@@ -114,9 +116,9 @@ void HttpRequest::SendRequest(
|
||||
body->Write(strm);
|
||||
}
|
||||
}
|
||||
std::shared_ptr<Stream>
|
||||
HttpRequest::EstablishConnection(Uri uri, bool ignoreSSLErrors,
|
||||
std::string trusted_root_cert_bundle) {
|
||||
std::shared_ptr<Stream> HttpRequest::EstablishConnection(
|
||||
Uri uri, bool ignoreSSLErrors, std::string trusted_root_cert_bundle,
|
||||
std::optional<Crypto::CertificateKeyStore> mTLS_keyStore) {
|
||||
if (uri.scheme == "http:" || uri.scheme == "ws:") {
|
||||
return std::make_shared<NetworkStream>(uri.host, uri.GetPort(), false,
|
||||
false, false);
|
||||
@@ -125,24 +127,34 @@ HttpRequest::EstablishConnection(Uri uri, bool ignoreSSLErrors,
|
||||
false, false, false);
|
||||
if (netStrm == nullptr)
|
||||
return nullptr;
|
||||
return std::make_shared<ClientTLSStream>(
|
||||
netStrm, !ignoreSSLErrors, uri.host, trusted_root_cert_bundle);
|
||||
return mTLS_keyStore
|
||||
? std::make_shared<ClientTLSStream>(
|
||||
netStrm, !ignoreSSLErrors, uri.host,
|
||||
trusted_root_cert_bundle, mTLS_keyStore.value())
|
||||
: std::make_shared<ClientTLSStream>(
|
||||
netStrm, !ignoreSSLErrors, uri.host,
|
||||
trusted_root_cert_bundle);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
std::shared_ptr<Stream>
|
||||
HttpRequest::EstablishUnixPathConnection(std::string unixPath, Uri uri,
|
||||
bool ignoreSSLErrors,
|
||||
std::string trusted_root_cert_bundle) {
|
||||
std::shared_ptr<Stream> HttpRequest::EstablishUnixPathConnection(
|
||||
std::string unixPath, Uri uri, bool ignoreSSLErrors,
|
||||
std::string trusted_root_cert_bundle,
|
||||
std::optional<Crypto::CertificateKeyStore> mTLS_keyStore) {
|
||||
if (uri.scheme == "http:" || uri.scheme == "ws:") {
|
||||
return std::make_shared<NetworkStream>(unixPath, false);
|
||||
} else if (uri.scheme == "https:" || uri.scheme == "wss:") {
|
||||
auto netStrm = std::make_shared<NetworkStream>(unixPath, false);
|
||||
if (netStrm == nullptr)
|
||||
return nullptr;
|
||||
return std::make_shared<ClientTLSStream>(
|
||||
netStrm, !ignoreSSLErrors, uri.host, trusted_root_cert_bundle);
|
||||
return mTLS_keyStore
|
||||
? std::make_shared<ClientTLSStream>(
|
||||
netStrm, !ignoreSSLErrors, uri.host,
|
||||
trusted_root_cert_bundle, mTLS_keyStore.value())
|
||||
: std::make_shared<ClientTLSStream>(
|
||||
netStrm, !ignoreSSLErrors, uri.host,
|
||||
trusted_root_cert_bundle);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@@ -186,13 +198,13 @@ HttpResponse::HttpResponse(HttpRequest &req) : responseHeaders(false) {
|
||||
std::string url = req.url;
|
||||
Uri uri;
|
||||
while (Uri::TryParse(url, uri)) {
|
||||
auto strm =
|
||||
req.unixSocket.empty()
|
||||
? HttpRequest::EstablishConnection(uri, req.ignoreSSLErrors,
|
||||
req.trusted_root_cert_bundle)
|
||||
: HttpRequest::EstablishUnixPathConnection(
|
||||
req.unixSocket, uri, req.ignoreSSLErrors,
|
||||
req.trusted_root_cert_bundle);
|
||||
auto strm = req.unixSocket.empty()
|
||||
? HttpRequest::EstablishConnection(
|
||||
uri, req.ignoreSSLErrors,
|
||||
req.trusted_root_cert_bundle, req.mTLS_keyStore)
|
||||
: HttpRequest::EstablishUnixPathConnection(
|
||||
req.unixSocket, uri, req.ignoreSSLErrors,
|
||||
req.trusted_root_cert_bundle, req.mTLS_keyStore);
|
||||
if (strm == nullptr)
|
||||
return;
|
||||
auto reqHeaders = req.requestHeaders;
|
||||
@@ -628,7 +640,7 @@ void WebSocketUnixSocketClient(std::string unixSocket, std::string url,
|
||||
if (resp.statusCode != 101 ||
|
||||
!resp.responseHeaders.TryGetFirst("Sec-WebSocket-Accept", accept) ||
|
||||
!resp.responseHeaders.AnyEquals("Connection", "Upgrade") ||
|
||||
!resp.responseHeaders.AnyEquals("Upgrade", "websocket")) {
|
||||
!resp.responseHeaders.AnyEqualsCSV("Upgrade", "websocket")) {
|
||||
cb(resp.responseHeaders, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,525 @@
|
||||
/*
|
||||
TessesFramework a library to make C++ easier for me, used in CrossLang:
|
||||
https://git.tesses.org/tesses50/crosslang
|
||||
|
||||
Copyright (C) 2026 Mike Nolan
|
||||
SPDX-License-Identifier: GPL-3.0-or-later WITH TessesFramework-Exception-1.0
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "TessesFramework/Http/HttpReverseProxy.hpp"
|
||||
namespace Tesses::Framework::Http {
|
||||
|
||||
static std::string CONNECTION = "CONNECTION";
|
||||
static std::string UPGRADE = "UPGRADE";
|
||||
|
||||
static std::initializer_list<std::string> STRIP_EQ = {
|
||||
"XREALIP", "FORWARDED", "KEEPALIVE", "TE",
|
||||
"TRAILER", "TRANSFERENCODING", "EXPECT", "CONTENTLENGTH",
|
||||
"CONTENTTYPE", "ACCEPTENCODING", "HOST"}; // Content-Type is OK but we get
|
||||
// it elsewhere, Content-Length
|
||||
// is the same way
|
||||
|
||||
static std::initializer_list<std::string> STRIP_PREFIX = {
|
||||
"XFORWARDED", "PROXY", "XORIGINAL", "CF", "SSL", "XSSL"};
|
||||
|
||||
static void sanitise_uppercase_only(const std::string &src, std::string &dest) {
|
||||
dest.reserve(src.size());
|
||||
for (size_t i = 0; i < src.size(); i++) {
|
||||
char c = (src[i] & 0b11011111);
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
dest.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
static void get_upgrades(const std::string &src,
|
||||
std::unordered_set<std::string> &upgrades) {
|
||||
auto parts = HttpUtils::SplitString(src, ",");
|
||||
for (auto &item : parts) {
|
||||
size_t start = item.find_first_not_of(" \t");
|
||||
if (start == std::string::npos)
|
||||
continue; // Empty
|
||||
size_t end = item.find_last_not_of(" \t");
|
||||
upgrades.insert(
|
||||
HttpUtils::ToLower(item.substr(start, end - start + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
static bool uppercase_eq(const std::string &src, const std::string &match,
|
||||
bool startsWith) {
|
||||
std::string cmp;
|
||||
sanitise_uppercase_only(src, cmp);
|
||||
|
||||
if (startsWith) {
|
||||
if (cmp.size() < match.size())
|
||||
return false;
|
||||
for (size_t i = 0; i < match.size(); i++) {
|
||||
if (cmp[i] != match[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return cmp == match;
|
||||
}
|
||||
}
|
||||
|
||||
ReverseProxyConnectionBuilder::ReverseProxyConnectionBuilder(
|
||||
ServerContext &ctx, bool essentalheaders)
|
||||
: m_reqheaders(false), m_ctx(ctx) {
|
||||
|
||||
if (essentalheaders) {
|
||||
std::string str;
|
||||
|
||||
if (ctx.requestHeaders.TryGetFirst("Host", str))
|
||||
m_reqheaders.SetValue("X-Forwarded-Host", str);
|
||||
|
||||
m_reqheaders.SetValue("X-Forwarded-For", ctx.ip);
|
||||
|
||||
m_reqheaders.SetValue("X-Forwarded-Proto",
|
||||
ctx.encrypted ? "https" : "http");
|
||||
}
|
||||
}
|
||||
ReverseProxyConnectionBuilder &
|
||||
ReverseProxyConnectionBuilder::WithHeadersFromRequest() {
|
||||
std::unordered_set<std::string> wanted_upgrade_protocols;
|
||||
bool doUpgradeIfApprovedUpgrade = false;
|
||||
|
||||
for (auto item : this->m_ctx.requestHeaders.kvp) {
|
||||
|
||||
if (!item.first.empty()) {
|
||||
|
||||
if (uppercase_eq(item.first, UPGRADE, false)) {
|
||||
for (auto &item2 : item.second)
|
||||
get_upgrades(item2, wanted_upgrade_protocols);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (uppercase_eq(item.first, CONNECTION, false)) {
|
||||
bool anyNotUpgrade = item.second.size() != 1;
|
||||
|
||||
if (!anyNotUpgrade &&
|
||||
!uppercase_eq(item.second.front(), UPGRADE, false)) {
|
||||
anyNotUpgrade = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (anyNotUpgrade)
|
||||
continue;
|
||||
|
||||
doUpgradeIfApprovedUpgrade = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
bool doCont = false;
|
||||
for (auto &strip : STRIP_EQ)
|
||||
if (uppercase_eq(item.first, strip, false)) {
|
||||
doCont = true;
|
||||
break;
|
||||
}
|
||||
if (doCont)
|
||||
continue;
|
||||
for (auto &strip : STRIP_PREFIX)
|
||||
if (uppercase_eq(item.first, strip, true)) {
|
||||
doCont = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (doCont)
|
||||
continue;
|
||||
|
||||
this->m_reqheaders.SetValue(item.first, item.second);
|
||||
}
|
||||
}
|
||||
|
||||
if (doUpgradeIfApprovedUpgrade && !wanted_upgrade_protocols.empty()) {
|
||||
std::vector<std::string> valid_upgrades;
|
||||
for (auto &item : this->m_whitelistedupgrades) {
|
||||
if (wanted_upgrade_protocols.find(item) !=
|
||||
wanted_upgrade_protocols.end()) {
|
||||
valid_upgrades.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid_upgrades.empty()) {
|
||||
this->m_reqheaders.SetValue("Connection", "upgrade");
|
||||
|
||||
std::string upgrade_text = "";
|
||||
for (auto &item : valid_upgrades) {
|
||||
if (upgrade_text.empty())
|
||||
upgrade_text = item;
|
||||
else
|
||||
upgrade_text += ", " + item;
|
||||
}
|
||||
|
||||
this->m_reqheaders.SetValue("Upgrade", upgrade_text);
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
ReverseProxyConnectionBuilder &
|
||||
ReverseProxyConnectionBuilder::WithHeader(std::string key, std::string value) {
|
||||
this->m_reqheaders.AddValue(key, value);
|
||||
return *this;
|
||||
}
|
||||
ReverseProxyConnectionBuilder &
|
||||
ReverseProxyConnectionBuilder::SetHeader(std::string key, std::string value) {
|
||||
this->m_reqheaders.SetValue(key, value);
|
||||
return *this;
|
||||
}
|
||||
ReverseProxyConnectionBuilder &
|
||||
ReverseProxyConnectionBuilder::WithoutHeader(std::string key) {
|
||||
this->m_reqheaders.Clear(key, false);
|
||||
return *this;
|
||||
}
|
||||
ReverseProxyConnectionBuilder &
|
||||
ReverseProxyConnectionBuilder::WithUrl(std::string url) {
|
||||
this->m_url = url;
|
||||
return *this;
|
||||
}
|
||||
ReverseProxyConnectionBuilder &
|
||||
ReverseProxyConnectionBuilder::WithResponseCallback(
|
||||
std::function<ReverseProxyAction(ServerContext &, HttpResponse &)> rc) {
|
||||
this->m_rc = rc;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void
|
||||
StreamExchange(std::shared_ptr<Tesses::Framework::Streams::Stream> server,
|
||||
std::shared_ptr<Tesses::Framework::Streams::Stream> client) {
|
||||
Tesses::Framework::Threading::Thread server2client([server,
|
||||
client]() -> void {
|
||||
try {
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(1024);
|
||||
|
||||
size_t read = 0;
|
||||
|
||||
do {
|
||||
read = server->Read(buffer.data(), buffer.size());
|
||||
if (read == 0)
|
||||
return;
|
||||
read = client->Write(buffer.data(),
|
||||
std::min<size_t>(read, buffer.size()));
|
||||
} while (read != 0);
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
server->Shutdown(Tesses::Framework::Streams::StreamShutdownMode::Read);
|
||||
|
||||
client->Shutdown(Tesses::Framework::Streams::StreamShutdownMode::Write);
|
||||
});
|
||||
|
||||
Tesses::Framework::Threading::Thread client2server([client,
|
||||
server]() -> void {
|
||||
try {
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(1024);
|
||||
|
||||
size_t read = 0;
|
||||
|
||||
do {
|
||||
read = client->Read(buffer.data(), buffer.size());
|
||||
if (read == 0)
|
||||
return;
|
||||
read = server->Write(buffer.data(),
|
||||
std::min<size_t>(read, buffer.size()));
|
||||
} while (read != 0);
|
||||
} catch (...) {
|
||||
}
|
||||
client->Shutdown(Tesses::Framework::Streams::StreamShutdownMode::Read);
|
||||
|
||||
server->Shutdown(Tesses::Framework::Streams::StreamShutdownMode::Write);
|
||||
});
|
||||
|
||||
server2client.Join();
|
||||
client2server.Join();
|
||||
}
|
||||
|
||||
bool ReverseProxyConnectionBuilder::Handle() {
|
||||
if (m_fail) {
|
||||
m_ctx.statusCode = StatusCode::BadRequest;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_url) {
|
||||
m_ctx.statusCode = StatusCode::InternalServerError;
|
||||
m_ctx.SendText("<!DOCTYPE html>"
|
||||
"<html>"
|
||||
"<head><meta name=\"color-scheme\" "
|
||||
"content=\"dark light\"><title>500 Internal Server "
|
||||
"Error</title></head>"
|
||||
"<body>"
|
||||
"<h1>500 Internal Server Error</h1><p>" +
|
||||
HttpUtils::HtmlEncode(
|
||||
"Please chain the reverse proxy url with "
|
||||
".WithUrl(\"http://example.com/path/on/backend\")") +
|
||||
"</p>"
|
||||
"</body>"
|
||||
"</html>"
|
||||
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HttpRequest req;
|
||||
|
||||
req.url = m_url.value();
|
||||
req.method = m_ctx.method;
|
||||
req.requestHeaders = m_reqheaders;
|
||||
req.followRedirects = false;
|
||||
std::string type;
|
||||
|
||||
bool isNotUpgradeOrGet =
|
||||
!(m_ctx.method == "GET" || m_ctx.method == "HEAD" ||
|
||||
m_reqheaders.AnyEquals("Connection", "upgrade"));
|
||||
|
||||
if (isNotUpgradeOrGet &&
|
||||
m_ctx.requestHeaders.TryGetFirst("Content-Type", type)) {
|
||||
if (type == "application/x-www-form-urlencoded") {
|
||||
std::string body = HttpUtils::QueryParamsEncode(m_ctx.bodyParams);
|
||||
req.body = std::make_shared<TextHttpRequestBody>(
|
||||
body, "application/x-www-form-urlencoded");
|
||||
} else {
|
||||
req.body = std::make_shared<StreamHttpRequestBody>(
|
||||
m_ctx.OpenRequestStream(), type);
|
||||
}
|
||||
} else if (isNotUpgradeOrGet) {
|
||||
req.body = std::make_shared<StreamHttpRequestBody>(
|
||||
m_ctx.OpenRequestStream(), "application/octet-stream");
|
||||
}
|
||||
std::unique_ptr<HttpResponse> resp;
|
||||
|
||||
try {
|
||||
resp = std::make_unique<HttpResponse>(req);
|
||||
} catch (...) {
|
||||
m_ctx.statusCode = StatusCode::BadGateway;
|
||||
m_ctx.SendErrorPage(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string val;
|
||||
|
||||
if (resp->responseHeaders.TryGetOnlyOne("Content-Type", val))
|
||||
this->m_ctx.responseHeaders.SetValue("Content-Type", val);
|
||||
if (resp->responseHeaders.TryGetOnlyOne("Set-Cookie", val))
|
||||
this->m_ctx.responseHeaders.SetValue("Set-Cookie", val);
|
||||
if (resp->responseHeaders.TryGetOnlyOne("Cache-Control", val))
|
||||
this->m_ctx.responseHeaders.SetValue("Cache-Control", val);
|
||||
if (resp->responseHeaders.TryGetOnlyOne("ETag", val))
|
||||
this->m_ctx.responseHeaders.SetValue("ETag", val);
|
||||
if (resp->responseHeaders.TryGetOnlyOne("WWW-Authenticate", val))
|
||||
this->m_ctx.responseHeaders.SetValue("WWW-Authenticate", val);
|
||||
if (resp->responseHeaders.TryGetOnlyOne("Last-Modified", val))
|
||||
this->m_ctx.responseHeaders.SetValue("Last-Modified", val);
|
||||
if (resp->responseHeaders.TryGetOnlyOne("Date", val))
|
||||
this->m_ctx.responseHeaders.SetValue("Date", val);
|
||||
|
||||
m_ctx.statusCode = resp->statusCode;
|
||||
|
||||
std::unordered_set<std::string> wanted_upgrade_protocols;
|
||||
bool doUpgradeIfApprovedUpgrade = false;
|
||||
|
||||
for (auto &item : resp->responseHeaders.kvp) {
|
||||
if (!item.first.empty()) {
|
||||
if (uppercase_eq(item.first, UPGRADE, false)) {
|
||||
for (auto &item2 : item.second)
|
||||
get_upgrades(item2, wanted_upgrade_protocols);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (uppercase_eq(item.first, CONNECTION, false)) {
|
||||
bool anyNotUpgrade = item.second.size() != 1;
|
||||
|
||||
if (!anyNotUpgrade &&
|
||||
!uppercase_eq(item.second.front(), UPGRADE, false)) {
|
||||
anyNotUpgrade = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (anyNotUpgrade)
|
||||
continue;
|
||||
|
||||
doUpgradeIfApprovedUpgrade = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resp->responseHeaders.TryGetOnlyOne("Location", val)) {
|
||||
Uri uri_src;
|
||||
if (Uri::TryParse(m_url.value(), uri_src)) {
|
||||
Uri uri_dest;
|
||||
if (uri_src.Relative(val, uri_dest)) {
|
||||
|
||||
if (uri_dest.host == uri_src.host &&
|
||||
uri_dest.port == uri_src.port) {
|
||||
Tesses::Framework::Filesystem::VFSPath dest = uri_dest.path;
|
||||
Tesses::Framework::Filesystem::VFSPath src = uri_src.path;
|
||||
|
||||
auto path = dest.MakeRelative(src);
|
||||
|
||||
auto path2 = path.MakeAbsolute(m_ctx.path);
|
||||
|
||||
std::vector<std::string> parts;
|
||||
|
||||
for (auto item : path2.path) {
|
||||
if (item == "..") {
|
||||
if (!parts.empty()) {
|
||||
parts.erase(parts.end() - 1);
|
||||
} else {
|
||||
m_ctx.statusCode =
|
||||
StatusCode::InternalServerError;
|
||||
m_ctx.SendText(
|
||||
"<!DOCTYPE html>"
|
||||
"<html>"
|
||||
"<head><meta name=\"color-scheme\" "
|
||||
"content=\"dark light\"><title>500 "
|
||||
"Internal Server "
|
||||
"Error</title></head>"
|
||||
"<body>"
|
||||
"<h1>500 Internal Server Error</h1><p>"
|
||||
"A redirect went beyond the reverse "
|
||||
"proxy "
|
||||
"root"
|
||||
"</p>"
|
||||
"</body>"
|
||||
"</html>"
|
||||
|
||||
);
|
||||
return true;
|
||||
}
|
||||
} else if (item == ".") {
|
||||
// do nothing but don't emit this
|
||||
} else {
|
||||
parts.push_back(item);
|
||||
}
|
||||
}
|
||||
Tesses::Framework::Filesystem::VFSPath newpath =
|
||||
m_ctx.GetServerRoot();
|
||||
|
||||
newpath.path.insert(newpath.path.cend(), parts.cbegin(),
|
||||
parts.cend());
|
||||
|
||||
std::string location =
|
||||
newpath.ToString() + uri_dest.GetQuery();
|
||||
if (!uri_dest.hash.empty())
|
||||
location += "#" + uri_dest.hash;
|
||||
this->m_ctx.responseHeaders.SetValue("Location", location);
|
||||
} else {
|
||||
this->m_ctx.responseHeaders.SetValue("Location",
|
||||
uri_dest.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this->m_rc)
|
||||
switch (this->m_rc(m_ctx, *resp)) {
|
||||
case ReverseProxyAction::Continue:
|
||||
break;
|
||||
case ReverseProxyAction::Handled:
|
||||
return true;
|
||||
case ReverseProxyAction::Unhandled:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resp->statusCode == StatusCode::SwitchingProtocols) {
|
||||
if (doUpgradeIfApprovedUpgrade && !wanted_upgrade_protocols.empty()) {
|
||||
std::vector<std::string> valid_upgrades;
|
||||
for (auto &item : this->m_whitelistedupgrades) {
|
||||
if (wanted_upgrade_protocols.find(item) !=
|
||||
wanted_upgrade_protocols.end()) {
|
||||
valid_upgrades.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid_upgrades.empty()) {
|
||||
m_ctx.responseHeaders.SetValue("Connection", "upgrade");
|
||||
|
||||
std::string upgrade_text = "";
|
||||
for (auto &item : valid_upgrades) {
|
||||
if (upgrade_text.empty())
|
||||
upgrade_text = item;
|
||||
else
|
||||
upgrade_text += ", " + item;
|
||||
}
|
||||
|
||||
m_ctx.responseHeaders.SetValue("Upgrade", upgrade_text);
|
||||
|
||||
m_ctx.WriteHeaders();
|
||||
|
||||
StreamExchange(m_ctx.GetStream(), resp->GetInternalStream());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
m_ctx.statusCode = StatusCode::BadRequest;
|
||||
m_ctx.SendErrorPage(true);
|
||||
return true;
|
||||
} else {
|
||||
m_ctx.SendStream(resp->ReadAsStream());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ReverseProxyServer::ReverseProxyServer(
|
||||
std::string url,
|
||||
std::function<ReverseProxyAction(ServerContext &,
|
||||
ReverseProxyConnectionBuilder &, Uri &)>
|
||||
request_callback,
|
||||
std::function<ReverseProxyAction(ServerContext &, HttpResponse &)>
|
||||
response_callback,
|
||||
bool essentialheaders)
|
||||
: url(url), request_callback(request_callback),
|
||||
response_callback(response_callback), essentialheaders(essentialheaders) {
|
||||
}
|
||||
bool ReverseProxyServer::Handle(ServerContext &ctx) {
|
||||
Uri uri;
|
||||
|
||||
if (Uri::TryParse(url, uri)) {
|
||||
uri.query = ctx.queryParams;
|
||||
Tesses::Framework::Filesystem::VFSPath srcUrl = uri.path;
|
||||
Tesses::Framework::Filesystem::VFSPath destUrl = ctx.path;
|
||||
|
||||
srcUrl.path.insert(srcUrl.path.cend(), destUrl.path.cbegin(),
|
||||
destUrl.path.cend());
|
||||
uri.path = srcUrl.ToString();
|
||||
|
||||
ReverseProxyConnectionBuilder builder(ctx, essentialheaders);
|
||||
|
||||
if (request_callback) {
|
||||
switch (request_callback(ctx, builder, uri)) {
|
||||
case ReverseProxyAction::Continue:
|
||||
return builder.WithResponseCallback(response_callback).Handle();
|
||||
break;
|
||||
case ReverseProxyAction::Unhandled:
|
||||
return false;
|
||||
case ReverseProxyAction::Handled:
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return builder.WithUrl(uri.ToString())
|
||||
.WithHeadersFromRequest()
|
||||
.WithResponseCallback(response_callback)
|
||||
.Handle();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Tesses::Framework::Http
|
||||
+49
-6
@@ -51,6 +51,11 @@ using namespace Tesses::Framework::TextStreams;
|
||||
|
||||
namespace Tesses::Framework::Http {
|
||||
|
||||
static const std::initializer_list<std::string> ONLY_ONE_OF_THESE = {
|
||||
"Host", "Content-Length", "Transfer-Encoding",
|
||||
"Connection", "Date", "Expect",
|
||||
"Max-Forwards", "Range", "Referer"};
|
||||
|
||||
void ServerSentEvents::SendEventRaw(const std::string &evt) {
|
||||
this->mtx.Lock();
|
||||
for (auto &item : this->strms) {
|
||||
@@ -283,7 +288,7 @@ class WSServer {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx->requestHeaders.AnyEquals("Upgrade", "websocket")) {
|
||||
if (!ctx->requestHeaders.AnyEqualsCSV("Upgrade", "websocket")) {
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -647,7 +652,7 @@ void HttpServer::StartAccepting() {
|
||||
|
||||
if (sock == nullptr) {
|
||||
std::cout << "STREAM ERROR" << std::endl;
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
TF_LOG("Before entering socket thread");
|
||||
|
||||
@@ -904,10 +909,6 @@ ServerContext &ServerContext::WithContentDisposition(std::string filename,
|
||||
ContentDisposition cd;
|
||||
cd.type = isInline ? "inline" : "attachment";
|
||||
cd.filename = filename;
|
||||
|
||||
// std::string cd;
|
||||
// cd = (isInline ? "inline; filename*=UTF-8''" : "attachment;
|
||||
// filename*=UTF-8''") + HttpUtils::UrlPathEncode(filename);
|
||||
this->responseHeaders.SetValue("Content-Disposition", cd.ToString());
|
||||
return *this;
|
||||
}
|
||||
@@ -985,6 +986,9 @@ ServerContext &ServerContext::WriteHeaders() {
|
||||
return *this;
|
||||
this->sent = true;
|
||||
|
||||
if (this->responseHeaders.kvp.count("Date") == 0)
|
||||
this->responseHeaders.SetValue("Date", Date::DateTime::NowUTC());
|
||||
|
||||
StreamWriter writer(this->strm);
|
||||
writer.newline = "\r\n";
|
||||
writer.WriteLine("HTTP/1.1 " + std::to_string((int)statusCode) + " " +
|
||||
@@ -999,6 +1003,21 @@ ServerContext &ServerContext::WriteHeaders() {
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void sanitise_path(std::string &path) {
|
||||
bool endsWithSlash = !path.empty() ? (path.back() == '/') : false;
|
||||
Tesses::Framework::Filesystem::VFSPath path2 = path;
|
||||
for (auto ittr = path2.path.begin(); ittr != path2.path.end(); ittr++) {
|
||||
if (*ittr == ".." || *ittr == ".") {
|
||||
path2.path.erase(ittr);
|
||||
ittr--;
|
||||
}
|
||||
}
|
||||
path = path2.ToString();
|
||||
if (endsWithSlash)
|
||||
path += '/';
|
||||
}
|
||||
|
||||
void HttpServer::Process(std::shared_ptr<Stream> strm,
|
||||
std::shared_ptr<IHttpServer> server, std::string ip,
|
||||
uint16_t port, uint16_t serverPort, bool encrypted,
|
||||
@@ -1042,6 +1061,7 @@ void HttpServer::Process(std::shared_ptr<Stream> strm,
|
||||
pp.resize(2);
|
||||
|
||||
ctx.originalPath = pp[0];
|
||||
sanitise_path(ctx.originalPath);
|
||||
ctx.path = ctx.originalPath;
|
||||
|
||||
TF_LOG(ctx.method + " with path " + ctx.path);
|
||||
@@ -1074,6 +1094,29 @@ void HttpServer::Process(std::shared_ptr<Stream> strm,
|
||||
std::string type;
|
||||
int64_t length;
|
||||
|
||||
for (auto &item : ONLY_ONE_OF_THESE) {
|
||||
if (ctx.requestHeaders.kvp.count(item) > 1) {
|
||||
ctx.statusCode = StatusCode::BadRequest;
|
||||
ctx.SendText(
|
||||
"<!DOCTYPE html>"
|
||||
"<html>"
|
||||
"<head><meta name=\"color-scheme\" "
|
||||
"content=\"dark light\"><title>400 Bad "
|
||||
"Request</title></head>"
|
||||
"<body>"
|
||||
"<h1>400 Bad Request</h1><p>" +
|
||||
HttpUtils::HtmlEncode(
|
||||
"There are multiple instances of the header \"" + item +
|
||||
"\".") +
|
||||
"</p>"
|
||||
"</body>"
|
||||
"</html>"
|
||||
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(ctx.method == "GET" || ctx.method == "HEAD") &&
|
||||
ctx.requestHeaders.TryGetFirst("Content-Type", type) &&
|
||||
type == "application/x-www-form-urlencoded" &&
|
||||
|
||||
+60
-18
@@ -57,6 +57,8 @@ bool HttpStream::CanWrite() {
|
||||
bool HttpStream::EndOfStream() {
|
||||
if (this->done)
|
||||
return true;
|
||||
if (this->recv && this->length > 0)
|
||||
return this->position >= this->length;
|
||||
if (this->offset < this->read)
|
||||
return false;
|
||||
return this->strm->EndOfStream();
|
||||
@@ -64,6 +66,8 @@ bool HttpStream::EndOfStream() {
|
||||
int64_t HttpStream::GetLength() { return this->length; }
|
||||
int64_t HttpStream::GetPosition() { return this->position; }
|
||||
size_t HttpStream::Read(uint8_t *buff, size_t len) {
|
||||
if (len == 0)
|
||||
return 0;
|
||||
if (this->done)
|
||||
return 0;
|
||||
if (!this->recv)
|
||||
@@ -71,11 +75,21 @@ size_t HttpStream::Read(uint8_t *buff, size_t len) {
|
||||
if (this->length == 0)
|
||||
return 0;
|
||||
if (this->length > 0) {
|
||||
|
||||
if (this->position >= this->length)
|
||||
return 0;
|
||||
len = std::min((size_t)(this->length - this->position), len);
|
||||
|
||||
if (len > 0)
|
||||
len = this->strm->Read(buff, len);
|
||||
|
||||
if (len == 0 && this->position < this->length) {
|
||||
throw std::runtime_error(
|
||||
"Incomplete Read: " + std::to_string(this->position) +
|
||||
" bytes read, " +
|
||||
std::to_string(this->length - this->position) +
|
||||
" more expected");
|
||||
}
|
||||
|
||||
this->position += len;
|
||||
return len;
|
||||
} else {
|
||||
@@ -85,6 +99,14 @@ size_t HttpStream::Read(uint8_t *buff, size_t len) {
|
||||
len = std::min((size_t)(this->read - this->offset), len);
|
||||
if (len > 0)
|
||||
len = this->strm->Read(buff, len);
|
||||
|
||||
if (len == 0 && this->offset < this->read) {
|
||||
throw std::runtime_error(
|
||||
"Incomplete Read (chunked): " +
|
||||
std::to_string(this->offset) + " of " +
|
||||
std::to_string(this->read) + " bytes in current chunk");
|
||||
}
|
||||
|
||||
this->offset += len;
|
||||
this->position += len;
|
||||
if (this->offset >= this->read) {
|
||||
@@ -95,30 +117,48 @@ size_t HttpStream::Read(uint8_t *buff, size_t len) {
|
||||
} else {
|
||||
StreamReader reader(this->strm);
|
||||
std::string line = reader.ReadLine();
|
||||
if (!line.empty()) {
|
||||
this->read = std::stoull(line, NULL, 16);
|
||||
if (line.empty()) {
|
||||
|
||||
if (this->read == 0) {
|
||||
reader.ReadLine();
|
||||
this->done = true;
|
||||
return 0;
|
||||
} else {
|
||||
this->offset = 0;
|
||||
|
||||
len =
|
||||
std::min((size_t)(this->read - this->offset), len);
|
||||
if (len > 0)
|
||||
len = this->strm->Read(buff, len);
|
||||
this->offset += len;
|
||||
this->position += len;
|
||||
return len;
|
||||
if (this->position > 0) {
|
||||
throw std::runtime_error(
|
||||
"Incomplete Read (chunked): connection closed "
|
||||
"before final 0 chunk");
|
||||
}
|
||||
this->done = true;
|
||||
return 0;
|
||||
}
|
||||
this->read = std::stoull(line, NULL, 16);
|
||||
|
||||
if (this->read == 0) {
|
||||
reader.ReadLine();
|
||||
this->done = true;
|
||||
return 0;
|
||||
} else {
|
||||
this->offset = 0;
|
||||
|
||||
len = std::min((size_t)(this->read - this->offset), len);
|
||||
if (len > 0)
|
||||
len = this->strm->Read(buff, len);
|
||||
if (len == 0 && this->read > 0) {
|
||||
throw std::runtime_error(
|
||||
"Incomplete Read (chunked): connection closed "
|
||||
"after chunk size, 0 of " +
|
||||
std::to_string(this->read) + " bytes");
|
||||
}
|
||||
this->offset += len;
|
||||
this->position += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
return this->strm->Read(buff, len);
|
||||
len = this->strm->Read(buff, len);
|
||||
if (len == 0)
|
||||
this->done = true;
|
||||
this->position += len;
|
||||
return len;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,8 +191,10 @@ size_t HttpStream::Write(const uint8_t *buff, size_t len) {
|
||||
this->strm->WriteBlock(buff, len);
|
||||
|
||||
writer.WriteLine();
|
||||
this->position += len;
|
||||
return len;
|
||||
} else {
|
||||
this->position += len;
|
||||
return this->strm->Write(buff, len);
|
||||
}
|
||||
}
|
||||
|
||||
+293
-203
@@ -162,17 +162,19 @@ std::string Uri::ToString() {
|
||||
uri.append(this->GetPathAndQuery());
|
||||
return uri;
|
||||
}
|
||||
std::string HttpUtils::Replace(std::string text, std::string find,
|
||||
std::string replace) {
|
||||
std::string HttpUtils::Replace(std::string_view text, std::string_view find,
|
||||
std::string_view replace) {
|
||||
if (find.empty())
|
||||
return std::string(text);
|
||||
std::string dest;
|
||||
while (text.length() > 0) {
|
||||
std::size_t index = text.find(find);
|
||||
|
||||
if (index == std::string::npos) {
|
||||
if (index == std::string_view::npos) {
|
||||
dest.append(text);
|
||||
break;
|
||||
} else {
|
||||
std::string left = text.substr(0, index);
|
||||
std::string_view left = text.substr(0, index);
|
||||
|
||||
text = text.substr(index + find.size());
|
||||
dest.append(left);
|
||||
@@ -182,12 +184,12 @@ std::string HttpUtils::Replace(std::string text, std::string find,
|
||||
return dest;
|
||||
}
|
||||
|
||||
std::string HttpUtils::LeftPad(std::string text, int count, char c) {
|
||||
if (text.size() >= (size_t)count)
|
||||
return text;
|
||||
|
||||
text.insert(text.begin(), (size_t)count - text.size(), c);
|
||||
return text;
|
||||
std::string HttpUtils::LeftPad(std::string_view text, int count, char c) {
|
||||
if (text.size() >= count)
|
||||
return std::string(text);
|
||||
std::string newStr(count - text.size(), c);
|
||||
newStr.append(text);
|
||||
return newStr;
|
||||
}
|
||||
char HttpUtils::NibbleToHex(uint8_t b, bool isUppercase) {
|
||||
if (isUppercase) {
|
||||
@@ -221,74 +223,105 @@ uint8_t HttpUtils::HexToNibble(char c) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string HttpUtils::MimeType(std::filesystem::path p) {
|
||||
std::string ext = p.extension().string();
|
||||
if (ext == ".html" || ext == ".htm") {
|
||||
return "text/html";
|
||||
}
|
||||
if (ext == ".txt" || ext == ".log" || ext == ".twss") {
|
||||
return "text/plain";
|
||||
}
|
||||
if (ext == ".woff") {
|
||||
return "application/x-font-woff";
|
||||
}
|
||||
if (ext == ".vtt") {
|
||||
return "text/vtt";
|
||||
}
|
||||
if (ext == ".svg") {
|
||||
return "image/svg+xml";
|
||||
}
|
||||
if (ext == ".webp") {
|
||||
return "image/webp";
|
||||
}
|
||||
if (ext == ".vcf") {
|
||||
return "text/v-card";
|
||||
}
|
||||
if (ext == ".rss" || ext == ".xml" || ext == ".atom" || ext == ".rdf") {
|
||||
return "application/xml";
|
||||
}
|
||||
if (ext == ".js") {
|
||||
return "text/javascript";
|
||||
}
|
||||
if (ext == ".json") {
|
||||
return "application/json";
|
||||
}
|
||||
if (ext == ".wasm") {
|
||||
return "application/wasm";
|
||||
}
|
||||
if (ext == ".png") {
|
||||
return "image/png";
|
||||
}
|
||||
if (ext == ".jpg" || ext == ".jpeg") {
|
||||
return "image/jpeg";
|
||||
}
|
||||
if (ext == ".css") {
|
||||
return "text/css";
|
||||
}
|
||||
if (ext == ".gif") {
|
||||
return "image/gif";
|
||||
}
|
||||
if (ext == ".mp4") {
|
||||
return "video/mp4";
|
||||
}
|
||||
if (ext == ".mov") {
|
||||
return "video/quicktime";
|
||||
}
|
||||
if (ext == ".m4a") {
|
||||
return "audio/mp4";
|
||||
}
|
||||
if (ext == ".webm") {
|
||||
return "video/webm";
|
||||
}
|
||||
if (ext == ".webmanifest") {
|
||||
return "application/manifest+json";
|
||||
}
|
||||
if (ext == ".ico") {
|
||||
return "image/x-icon";
|
||||
}
|
||||
struct mimetype_reg {
|
||||
Threading::Mutex mtx;
|
||||
std::unordered_map<std::string, std::string> map = {
|
||||
{".html", "text/html"},
|
||||
{".htm", "text/html"},
|
||||
{".txt", "text/plain"},
|
||||
{".log", "text/plain"},
|
||||
{".twss", "text/twss"},
|
||||
{".css", "text/css"},
|
||||
{".js", "text/javascript"},
|
||||
{".mjs", "text/javascript"},
|
||||
{".json", "application/json"},
|
||||
{".map", "application/json"},
|
||||
{".pdf", "application/pdf"},
|
||||
{".png", "image/png"},
|
||||
{".jpg", "image/jpeg"},
|
||||
{".jpeg", "image/jpeg"},
|
||||
{".gif", "image/gif"},
|
||||
{".webp", "image/webp"},
|
||||
{".svg", "image/svg+xml"},
|
||||
{".ico", "image/x-icon"},
|
||||
{".bmp", "image/bmp"},
|
||||
{".avif", "image/avif"},
|
||||
{".heic", "image/heic"},
|
||||
{".tif", "image/tiff"},
|
||||
{".tiff", "image/tiff"},
|
||||
{".mp4", "video/mp4"},
|
||||
{".webm", "video/webm"},
|
||||
{".mov", "video/quicktime"},
|
||||
{".mkv", "video/x-matroska"},
|
||||
{".avi", "video/x-msvideo"},
|
||||
{".ts", "video/mp2t"},
|
||||
{".mp3", "audio/mpeg"},
|
||||
{".m4a", "audio/mp4"},
|
||||
{".ogg", "audio/ogg"},
|
||||
{".oga", "audio/ogg"},
|
||||
{".opus", "audio/ogg"},
|
||||
{".wav", "audio/wav"},
|
||||
{".flac", "audio/flac"},
|
||||
{".aac", "audio/aac"},
|
||||
{".weba", "audio/webm"},
|
||||
{".woff", "font/woff"},
|
||||
{".woff2", "font/woff2"},
|
||||
{".ttf", "font/ttf"},
|
||||
{".zip", "application/zip"},
|
||||
{".gz", "application/gzip"},
|
||||
{".7z", "application/x-7z-compressed"},
|
||||
{".rar", "application/vnd.rar"},
|
||||
{".csv", "text/csv"},
|
||||
{".vtt", "text/vtt"},
|
||||
{".vcf", "text/v-card"},
|
||||
{".rss", "application/xml"},
|
||||
{".xml", "application/xml"},
|
||||
{".atom", "application/xml"},
|
||||
{".rdf", "application/xml"},
|
||||
{".wasm", "application/wasm"},
|
||||
{".webmanifest", "application/manifest+json"},
|
||||
{".m3u8", "application/vnd.apple.mpegurl"},
|
||||
{".mpd", "application/dash+xml"},
|
||||
{".md", "text/markdown"},
|
||||
{".epub", "application/epub+zip"},
|
||||
{".ics", "text/calendar"},
|
||||
{".apk", "application/vnd.android.package-archive"},
|
||||
{".crvm", "application/crvm"},
|
||||
{".tcross", "text/tcross"},
|
||||
};
|
||||
std::string Get(const std::string &ext) {
|
||||
Threading::LockGuard lg(mtx);
|
||||
std::string mime;
|
||||
|
||||
return "application/octet-stream";
|
||||
if (map.count(ext) != 0)
|
||||
mime = map[ext];
|
||||
|
||||
if (mime.empty())
|
||||
return "application/octet-stream";
|
||||
return mime;
|
||||
}
|
||||
void Add(const std::string &ext, const std::string &mime) {
|
||||
Threading::LockGuard lg(mtx);
|
||||
map[ext] = mime;
|
||||
}
|
||||
};
|
||||
|
||||
static mimetype_reg mime_reg;
|
||||
|
||||
std::string HttpUtils::GetMimeType(const std::string &ext) {
|
||||
return mime_reg.Get(ext);
|
||||
}
|
||||
std::string HttpUtils::GetMimeTypePath(const Filesystem::VFSPath &p) {
|
||||
return GetMimeType(p.GetExtension());
|
||||
}
|
||||
void HttpUtils::AddMimeType(const std::string &ext, const std::string &mime) {
|
||||
mime_reg.Add(ext, mime);
|
||||
}
|
||||
void HttpUtils::AddMimeTypePath(const Filesystem::VFSPath &p,
|
||||
const std::string &mime) {
|
||||
AddMimeType(p.GetExtension(), mime);
|
||||
}
|
||||
|
||||
bool HttpUtils::Invalid(char c) {
|
||||
// just do windows because it is the strictist when it comes to windows, mac
|
||||
// and linux
|
||||
@@ -316,7 +349,7 @@ bool HttpUtils::Invalid(char c) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
std::string HttpUtils::Sanitise(std::string text) {
|
||||
std::string HttpUtils::Sanitise(std::string_view text) {
|
||||
std::string myStr = {};
|
||||
for (auto item : text) {
|
||||
if (Invalid(item))
|
||||
@@ -326,7 +359,8 @@ std::string HttpUtils::Sanitise(std::string text) {
|
||||
return myStr;
|
||||
}
|
||||
|
||||
void HttpUtils::QueryParamsDecode(HttpDictionary &dict, std::string query) {
|
||||
void HttpUtils::QueryParamsDecode(HttpDictionary &dict,
|
||||
std::string_view query) {
|
||||
for (auto item : SplitString(query, "&")) {
|
||||
std::vector<std::string> ss = SplitString(item, "=", 2);
|
||||
if (ss.size() >= 1) {
|
||||
@@ -338,7 +372,7 @@ void HttpUtils::QueryParamsDecode(HttpDictionary &dict, std::string query) {
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string HttpUtils::Join(std::string joinStr,
|
||||
std::string HttpUtils::Join(std::string_view joinStr,
|
||||
std::vector<std::string> ents) {
|
||||
std::string str = {};
|
||||
bool first = true;
|
||||
@@ -368,41 +402,43 @@ std::string HttpUtils::QueryParamsEncode(HttpDictionary &dict) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string HttpUtils::UrlDecode(std::string v) {
|
||||
std::string HttpUtils::UrlDecode(std::string_view v) {
|
||||
std::string s = {};
|
||||
|
||||
for (size_t i = 0; i < v.size(); i++) {
|
||||
if (v[i] == '+')
|
||||
s.push_back(' ');
|
||||
else if (v[i] == '%') {
|
||||
i++;
|
||||
uint8_t n = HexToNibble(v[i]) << 4;
|
||||
i++;
|
||||
n |= HexToNibble(v[i]);
|
||||
s.push_back((char)n);
|
||||
if (i + 2 >= v.size())
|
||||
break;
|
||||
|
||||
uint8_t n = (HexToNibble(v[i + 1]) << 4) | (HexToNibble(v[i + 2]));
|
||||
i += 2;
|
||||
s.push_back(static_cast<char>(n));
|
||||
} else
|
||||
s.push_back(v[i]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
std::string HttpUtils::UrlPathEncode(std::string v, bool ignoreSpace) {
|
||||
std::string HttpUtils::UrlPathEncode(std::string_view v, bool ignoreSpace) {
|
||||
std::string s = {};
|
||||
|
||||
for (auto item : v) {
|
||||
if (item >= 'A' && item <= 'Z')
|
||||
uint8_t byte = static_cast<uint8_t>(item);
|
||||
if (byte >= 'A' && byte <= 'Z')
|
||||
s.push_back(item);
|
||||
else if (item >= 'a' && item <= 'z')
|
||||
else if (byte >= 'a' && byte <= 'z')
|
||||
s.push_back(item);
|
||||
else if (item >= '0' && item <= '9')
|
||||
else if (byte >= '0' && byte <= '9')
|
||||
s.push_back(item);
|
||||
else if (item == '-' || item == '_' || item == '.' || item == '~' ||
|
||||
item == '/')
|
||||
else if (byte == '-' || byte == '_' || byte == '.' || byte == '~' ||
|
||||
byte == '/')
|
||||
s.push_back(item);
|
||||
else {
|
||||
if (item != ' ' || !ignoreSpace) {
|
||||
if (byte != ' ' || !ignoreSpace) {
|
||||
s.push_back('%');
|
||||
s.push_back(NibbleToHex((item >> 4) & 0xF));
|
||||
s.push_back(NibbleToHex((item) & 0xF));
|
||||
s.push_back(NibbleToHex((byte >> 4) & 0xF));
|
||||
s.push_back(NibbleToHex((byte) & 0xF));
|
||||
} else {
|
||||
s.push_back(' ');
|
||||
}
|
||||
@@ -410,134 +446,87 @@ std::string HttpUtils::UrlPathEncode(std::string v, bool ignoreSpace) {
|
||||
}
|
||||
return s;
|
||||
}
|
||||
std::string HttpUtils::UrlPathDecode(std::string v) {
|
||||
std::string HttpUtils::UrlPathDecode(std::string_view v) {
|
||||
std::string s = {};
|
||||
|
||||
for (size_t i = 0; i < v.size(); i++) {
|
||||
if (v[i] == '%') {
|
||||
i++;
|
||||
uint8_t n = HexToNibble(v[i]) << 4;
|
||||
i++;
|
||||
n |= HexToNibble(v[i]);
|
||||
s.push_back((char)n);
|
||||
if (i + 2 >= v.size())
|
||||
break;
|
||||
|
||||
uint8_t n = (HexToNibble(v[i + 1]) << 4) | (HexToNibble(v[i + 2]));
|
||||
i += 2;
|
||||
s.push_back(static_cast<char>(n));
|
||||
} else
|
||||
s.push_back(v[i]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string HttpUtils::UrlEncode(std::string v) {
|
||||
std::string HttpUtils::UrlEncode(std::string_view v) {
|
||||
std::string s = {};
|
||||
|
||||
for (auto item : v) {
|
||||
if (item == ' ')
|
||||
uint8_t byte = static_cast<uint8_t>(item);
|
||||
if (byte == ' ')
|
||||
s.push_back('+');
|
||||
else if (item >= 'A' && item <= 'Z')
|
||||
else if (byte >= 'A' && byte <= 'Z')
|
||||
s.push_back(item);
|
||||
else if (item >= 'a' && item <= 'z')
|
||||
else if (byte >= 'a' && byte <= 'z')
|
||||
s.push_back(item);
|
||||
else if (item >= '0' && item <= '9')
|
||||
else if (byte >= '0' && byte <= '9')
|
||||
s.push_back(item);
|
||||
else if (item == '-' || item == '_' || item == '.' || item == '~')
|
||||
else if (byte == '-' || byte == '_' || byte == '.' || byte == '~')
|
||||
s.push_back(item);
|
||||
else {
|
||||
s.push_back('%');
|
||||
s.push_back(NibbleToHex((item >> 4) & 0xF));
|
||||
s.push_back(NibbleToHex((item) & 0xF));
|
||||
s.push_back(NibbleToHex((byte >> 4) & 0xF));
|
||||
s.push_back(NibbleToHex((byte) & 0xF));
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
std::vector<std::string> HttpUtils::SplitString(std::string text,
|
||||
std::string delimiter,
|
||||
std::size_t maxCnt) {
|
||||
std::vector<std::string> strs;
|
||||
void HttpUtils::SplitString(std::vector<std::string> &strs,
|
||||
std::string_view text, std::string_view delimiter,
|
||||
std::size_t maxCnt) {
|
||||
if (maxCnt == 0)
|
||||
return;
|
||||
if (delimiter.empty()) {
|
||||
strs.push_back(std::string(text));
|
||||
return;
|
||||
}
|
||||
std::size_t i = 1;
|
||||
while (text.length() > 0) {
|
||||
while (true) {
|
||||
if (i == maxCnt) {
|
||||
strs.push_back(text);
|
||||
strs.push_back(std::string(text));
|
||||
break;
|
||||
}
|
||||
std::size_t index = text.find(delimiter);
|
||||
|
||||
if (index == std::string::npos) {
|
||||
strs.push_back(text);
|
||||
if (index == std::string_view::npos) {
|
||||
strs.push_back(std::string(text));
|
||||
break;
|
||||
} else {
|
||||
std::string left = text.substr(0, index);
|
||||
|
||||
text = text.substr(index + delimiter.size());
|
||||
|
||||
strs.push_back(left);
|
||||
}
|
||||
std::string_view left = text.substr(0, index);
|
||||
|
||||
text = text.substr(index + delimiter.size());
|
||||
|
||||
strs.push_back(std::string(left));
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> HttpUtils::SplitString(std::string_view text,
|
||||
std::string_view delimiter,
|
||||
std::size_t maxCnt) {
|
||||
std::vector<std::string> strs;
|
||||
SplitString(strs, text, delimiter, maxCnt);
|
||||
|
||||
return strs;
|
||||
}
|
||||
std::string HttpUtils::HtmlDecodeOnlyEntityNumber(std::string v) {
|
||||
std::string buff = {};
|
||||
int state = 0;
|
||||
uint64_t n = 0;
|
||||
for (auto item : v) {
|
||||
switch (state) {
|
||||
case 0:
|
||||
if (item == '&')
|
||||
state = 1;
|
||||
else
|
||||
buff.push_back(item);
|
||||
break;
|
||||
case 1:
|
||||
if (item == '#') {
|
||||
state = 2;
|
||||
n = 0;
|
||||
} else {
|
||||
state = 0;
|
||||
buff.push_back('&');
|
||||
buff.push_back(item);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (item == ';') {
|
||||
state = 0;
|
||||
if (n <= 0x7F) {
|
||||
buff.push_back((char)n);
|
||||
} else if (n >= 0x80 && n <= 0x7FF) {
|
||||
uint8_t high =
|
||||
0b11000000 | ((uint8_t)(n >> 6) & 0b00011111);
|
||||
uint8_t low = 0b10000000 | ((uint8_t)(n) & 0b00111111);
|
||||
buff.push_back((char)high);
|
||||
buff.push_back((char)low);
|
||||
} else if (n >= 0x800 && n <= 0xFFFF) {
|
||||
uint8_t high =
|
||||
0b11100000 | ((uint8_t)(n >> 12) & 0b00001111);
|
||||
uint8_t low = 0b10000000 | ((uint8_t)(n >> 6) & 0b00111111);
|
||||
uint8_t lowest = 0b10000000 | ((uint8_t)(n) & 0b00111111);
|
||||
buff.push_back((char)high);
|
||||
buff.push_back((char)low);
|
||||
buff.push_back((char)lowest);
|
||||
} else if (n >= 0x010000 && n <= 0x10FFFF) {
|
||||
uint8_t highest =
|
||||
0b11110000 | ((uint8_t)(n >> 18) & 0b00000111);
|
||||
uint8_t high =
|
||||
0b10000000 | ((uint8_t)(n >> 12) & 0b00111111);
|
||||
uint8_t low = 0b10000000 | ((uint8_t)(n >> 6) & 0b00111111);
|
||||
uint8_t lowest = 0b10000000 | ((uint8_t)(n) & 0b00111111);
|
||||
buff.push_back((char)highest);
|
||||
|
||||
buff.push_back((char)high);
|
||||
buff.push_back((char)low);
|
||||
buff.push_back((char)lowest);
|
||||
}
|
||||
} else if (item >= '0' && item <= '9') {
|
||||
n *= 10;
|
||||
n += item - '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
return buff;
|
||||
}
|
||||
std::string HttpUtils::HtmlP(std::string text) {
|
||||
std::string HttpUtils::HtmlP(std::string_view text) {
|
||||
std::string newText = "";
|
||||
std::string builder = "";
|
||||
|
||||
@@ -589,7 +578,7 @@ std::string HttpUtils::HtmlP(std::string text) {
|
||||
|
||||
return newText;
|
||||
}
|
||||
std::string HttpUtils::HtmlEncode(std::string html) {
|
||||
std::string HttpUtils::HtmlEncode(std::string_view html) {
|
||||
std::string myHtml = {};
|
||||
for (auto item : html) {
|
||||
if (item == '\"') {
|
||||
@@ -736,18 +725,12 @@ std::string HttpUtils::StatusCodeString(StatusCode code) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
CaseInsensitiveLess::CaseInsensitiveLess(const CaseInsensitiveLess &str) {
|
||||
this->caseSensitive = str.caseSensitive;
|
||||
this->offset = this;
|
||||
}
|
||||
CaseInsensitiveLess::CaseInsensitiveLess() {
|
||||
this->caseSensitive = false;
|
||||
this->offset = this;
|
||||
}
|
||||
HttpDictionary::HttpDictionary(bool isCaseSensitive) {
|
||||
|
||||
this->kvp.key_comp().offset->caseSensitive = isCaseSensitive;
|
||||
CaseInsensitiveLess::CaseInsensitiveLess(bool caseSensitive) {
|
||||
this->caseSensitive = caseSensitive;
|
||||
}
|
||||
HttpDictionary::HttpDictionary(bool isCaseSensitive)
|
||||
: kvp(CaseInsensitiveLess(isCaseSensitive)) {}
|
||||
bool HttpDictionary::AnyEquals(std::string key, std::string value) {
|
||||
if (this->kvp.count(key) > 0)
|
||||
for (auto v : this->kvp[key])
|
||||
@@ -756,6 +739,45 @@ bool HttpDictionary::AnyEquals(std::string key, std::string value) {
|
||||
// for(auto v : item.second) if(v == value) return true;
|
||||
return false;
|
||||
}
|
||||
bool HttpUtils::CaseInsensitiveCompare(std::string_view left,
|
||||
std::string_view right) {
|
||||
if (left.size() != right.size())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < left.size(); i++) {
|
||||
unsigned char senleft = static_cast<unsigned char>(left[i]);
|
||||
unsigned char senright = static_cast<unsigned char>(right[i]);
|
||||
unsigned char insenleft = (senleft & 0b11011111);
|
||||
unsigned char insenright = (senright & 0b11011111);
|
||||
|
||||
if (insenleft >= 'A' && insenleft <= 'Z' && insenright >= 'A' &&
|
||||
insenright <= 'Z') {
|
||||
if (insenleft != insenright)
|
||||
return false;
|
||||
} else if (senleft != senright)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool HttpDictionary::AnyEqualsCSV(std::string key, std::string value) {
|
||||
|
||||
if (this->kvp.count(key)) {
|
||||
for (std::string v : this->kvp[key]) {
|
||||
auto items = HttpUtils::SplitString(v, ",");
|
||||
for (auto item : items) {
|
||||
std::string_view itm = item;
|
||||
size_t start = itm.find_first_not_of(" \t");
|
||||
if (start == std::string::npos)
|
||||
continue; // Empty
|
||||
size_t end = itm.find_last_not_of(" \t");
|
||||
std::string_view val = itm.substr(start, end - start + 1);
|
||||
if (HttpUtils::CaseInsensitiveCompare(value, val))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HttpDictionary::Clear() { kvp.clear(); }
|
||||
void HttpDictionary::Clear(std::string key, bool kvpExistsAfter) {
|
||||
@@ -848,20 +870,89 @@ bool HttpDictionary::TryGetFirstDouble(std::string key, double &value) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HttpDictionary::TryGetOnlyOne(std::string key, std::string &value) {
|
||||
if (kvp.count(key) == 0)
|
||||
return false;
|
||||
auto &ls = kvp[key];
|
||||
if (ls.size() != 1)
|
||||
return false;
|
||||
value = ls.front();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HttpDictionary::TryGetOnlyOneInt(std::string key, int64_t &value) {
|
||||
std::string val;
|
||||
if (!TryGetOnlyOne(key, val))
|
||||
return false;
|
||||
try {
|
||||
size_t off = 0;
|
||||
auto v = std::stoll(val, &off);
|
||||
if (off != val.size())
|
||||
return false;
|
||||
value = v;
|
||||
} catch (std::exception &ex) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HttpDictionary::TryGetOnlyOneDouble(std::string key, double &value) {
|
||||
std::string val;
|
||||
if (!TryGetOnlyOne(key, val))
|
||||
return false;
|
||||
try {
|
||||
size_t off = 0;
|
||||
auto v = std::stod(val, &off);
|
||||
if (off != val.size())
|
||||
return false;
|
||||
value = v;
|
||||
} catch (std::exception &ex) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool HttpDictionary::TryGetOnlyOneDate(std::string key, Date::DateTime &value) {
|
||||
std::string val;
|
||||
if (!TryGetOnlyOne(key, val))
|
||||
return false;
|
||||
return Date::DateTime::TryParseHttpDate(val, value);
|
||||
}
|
||||
|
||||
bool HttpDictionary::TryGetOnlyOneBoolean(std::string key, bool &val) {
|
||||
val = false;
|
||||
if (kvp.count(key) == 0)
|
||||
return true;
|
||||
|
||||
auto &ls = kvp[key];
|
||||
if (ls.size() != 1)
|
||||
return false;
|
||||
|
||||
auto front = ls.front();
|
||||
val = front == "on" || front == "true";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CaseInsensitiveLess::operator()(const std::string &s1,
|
||||
const std::string &s2) const {
|
||||
if (this->caseSensitive)
|
||||
return s1 == s2;
|
||||
return HttpUtils::ToLower(s1) < HttpUtils::ToLower(s2);
|
||||
return s1 < s2;
|
||||
return std::lexicographical_compare(
|
||||
s1.begin(), s1.end(), s2.begin(), s2.end(), [](char a, char b) {
|
||||
return std::tolower(static_cast<unsigned char>(a)) <
|
||||
std::tolower(static_cast<unsigned char>(b));
|
||||
});
|
||||
}
|
||||
|
||||
std::string HttpUtils::ToLower(std::string str) {
|
||||
std::string HttpUtils::ToLower(std::string_view str) {
|
||||
std::string str1(str.length(), ' ');
|
||||
std::transform(str.begin(), str.end(), str1.begin(), tolower);
|
||||
return str1;
|
||||
}
|
||||
|
||||
std::string HttpUtils::ToUpper(std::string str) {
|
||||
std::string HttpUtils::ToUpper(std::string_view str) {
|
||||
std::string str1(str.length(), ' ');
|
||||
std::transform(str.begin(), str.end(), str1.begin(), toupper);
|
||||
return str1;
|
||||
@@ -909,13 +1000,12 @@ void HttpUtils::BytesToHex(std::string &text, const std::vector<uint8_t> &data,
|
||||
text[i * 2 + 1] += NibbleToHex(data[i], isUpper);
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> HttpUtils::HexToBytes(const std::string &text) {
|
||||
std::vector<uint8_t> HttpUtils::HexToBytes(std::string_view text) {
|
||||
std::vector<uint8_t> data;
|
||||
HexToBytes(data, text);
|
||||
return data;
|
||||
}
|
||||
void HttpUtils::HexToBytes(std::vector<uint8_t> &data,
|
||||
const std::string &text) {
|
||||
void HttpUtils::HexToBytes(std::vector<uint8_t> &data, std::string_view text) {
|
||||
if (text.empty()) {
|
||||
data.clear();
|
||||
return;
|
||||
|
||||
@@ -51,36 +51,46 @@ MountableServer::MountableServer(std::shared_ptr<IHttpServer> root) {
|
||||
|
||||
void MountableServer::Mount(std::string path,
|
||||
std::shared_ptr<IHttpServer> server) {
|
||||
mtx.Lock();
|
||||
this->servers.insert(
|
||||
this->servers.begin(),
|
||||
std::pair<std::string, std::shared_ptr<IHttpServer>>(path, server));
|
||||
mtx.Unlock();
|
||||
}
|
||||
void MountableServer::Unmount(std::string path) {
|
||||
mtx.Lock();
|
||||
for (auto i = this->servers.begin(); i != this->servers.end(); i++) {
|
||||
auto &item = *i;
|
||||
if (item.first == path) {
|
||||
this->servers.erase(i);
|
||||
mtx.Unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
mtx.Unlock();
|
||||
}
|
||||
bool MountableServer::Handle(ServerContext &ctx) {
|
||||
std::string oldPath = ctx.path;
|
||||
mtx.Lock();
|
||||
bool needUnlock = true;
|
||||
for (auto item : this->servers) {
|
||||
if (StartsWith(oldPath, item.first)) {
|
||||
ctx.path = Subpath(oldPath, item.first);
|
||||
mtx.Unlock();
|
||||
if (item.second->Handle(ctx)) {
|
||||
ctx.path = oldPath;
|
||||
return true;
|
||||
}
|
||||
ctx.path = oldPath;
|
||||
needUnlock = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (needUnlock)
|
||||
mtx.Unlock();
|
||||
ctx.path = oldPath;
|
||||
if (this->root && this->root->Handle(ctx))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
MountableServer::~MountableServer() {}
|
||||
} // namespace Tesses::Framework::Http
|
||||
|
||||
@@ -58,22 +58,36 @@ RouteServer::RouteServer(std::shared_ptr<IHttpServer> root) : root(root) {}
|
||||
|
||||
void RouteServer::Add(std::string method, std::string pattern,
|
||||
ServerRequestHandler handler) {
|
||||
mtx.Lock();
|
||||
this->routes.emplace_back(pattern, method, handler);
|
||||
mtx.Unlock();
|
||||
}
|
||||
|
||||
bool RouteServer::Handle(ServerContext &ctx) {
|
||||
auto pathArgs = ctx.pathArguments;
|
||||
auto path = Tesses::Framework::Filesystem::VFSPath::ParseUriPath(ctx.path);
|
||||
bool mustUnlock = true;
|
||||
mtx.Lock();
|
||||
for (auto &svr : this->routes) {
|
||||
if (svr.method != ctx.method &&
|
||||
!((svr.method == "GET" && ctx.method == "HEAD") ||
|
||||
(svr.method == "HEAD" && ctx.method == "GET")))
|
||||
continue;
|
||||
ctx.pathArguments = pathArgs;
|
||||
if (svr.Equals(path, ctx.pathArguments) && svr.handler &&
|
||||
svr.handler(ctx))
|
||||
return true;
|
||||
if (svr.Equals(path, ctx.pathArguments) && svr.handler) {
|
||||
auto hndl = svr.handler;
|
||||
mtx.Unlock();
|
||||
|
||||
if (hndl(ctx)) {
|
||||
return true;
|
||||
}
|
||||
mustUnlock = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mustUnlock)
|
||||
mtx.Unlock();
|
||||
ctx.pathArguments = pathArgs;
|
||||
|
||||
if (this->root)
|
||||
@@ -104,4 +118,9 @@ void RouteServer::Query(std::string pattern, ServerRequestHandler handler) {
|
||||
void RouteServer::Options(std::string pattern, ServerRequestHandler handler) {
|
||||
Add("OPTIONS", pattern, handler);
|
||||
}
|
||||
void RouteServer::Clear() {
|
||||
mtx.Lock();
|
||||
this->routes.clear();
|
||||
mtx.Unlock();
|
||||
}
|
||||
} // namespace Tesses::Framework::Http
|
||||
@@ -699,12 +699,56 @@ void ShellFileOrUrl(std::string fileOrUrl) {
|
||||
#elif !defined(TESSESFRAMEWORK_ENABLE_PROCESS)
|
||||
throw std::runtime_error("Process not enabled");
|
||||
#elif defined(_WIN32)
|
||||
auto exec = Tesses::Framework::Platform::Environment::GetRealExecutablePath(
|
||||
(std::string) "cmd");
|
||||
Process p(exec.ToString(), {"cmd", "/c", "start", fileOrUrl});
|
||||
if (p.Start())
|
||||
if (p.WaitForExit() != 0)
|
||||
throw std::runtime_error("Exit code did not indicate success");
|
||||
std::u16string wname;
|
||||
UTF16::FromUTF8(wname, fileOrUrl);
|
||||
|
||||
auto result = ShellExecuteW(NULL, L"open", (LPCWSTR)wname.c_str(), NULL,
|
||||
NULL, SW_SHOWNORMAL);
|
||||
if ((INT_PTR)result <= 32) {
|
||||
switch ((int)result) {
|
||||
case 0:
|
||||
throw std::runtime_error(
|
||||
"The operating system is out of memory or resources.");
|
||||
case 2:
|
||||
throw std::runtime_error(
|
||||
"The specified file was not found (SE_ERR_FNF).");
|
||||
case 3:
|
||||
throw std::runtime_error(
|
||||
"The specified path was not found (SE_ERR_PNF).");
|
||||
case 5:
|
||||
throw std::runtime_error("Access denied (SE_ERR_ACCESSDENIED).");
|
||||
case 8:
|
||||
throw std::runtime_error("Out of memory (SE_ERR_OOM).");
|
||||
case 11:
|
||||
throw std::runtime_error(
|
||||
"Bad executable format (ERROR_BAD_FORMAT).");
|
||||
case 26:
|
||||
throw std::runtime_error(
|
||||
"Sharing violation occurred (SE_ERR_SHARE).");
|
||||
case 27:
|
||||
throw std::runtime_error("File association incomplete or invalid "
|
||||
"(SE_ERR_ASSOCINCOMPLETE).");
|
||||
case 28:
|
||||
throw std::runtime_error(
|
||||
"DDE transaction timed out (SE_ERR_DDETIMEOUT).");
|
||||
case 29:
|
||||
throw std::runtime_error(
|
||||
"DDE transaction failed (SE_ERR_DDEFAIL).");
|
||||
case 30:
|
||||
throw std::runtime_error("DDE transaction busy (SE_ERR_DDEBUSY).");
|
||||
case 31:
|
||||
throw std::runtime_error(
|
||||
"No application associated with file extension "
|
||||
"(SE_ERR_NOASSOC).");
|
||||
case 32:
|
||||
throw std::runtime_error("Dynamic-link library (DLL) not found "
|
||||
"(SE_ERR_DLLNOTFOUND).");
|
||||
default:
|
||||
throw std::runtime_error("Unknown error: " +
|
||||
std::to_string((int)result));
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
auto exec = Tesses::Framework::Platform::Environment::GetRealExecutablePath(
|
||||
(std::string) "open");
|
||||
|
||||
@@ -43,12 +43,15 @@ using HttpUtils = Tesses::Framework::Http::HttpUtils;
|
||||
#else
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <iphlpapi.h>
|
||||
// clang-format off
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <iphlpapi.h>
|
||||
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
#include <windows.h>
|
||||
// clang-format on
|
||||
#if defined __has_include
|
||||
#if __has_include(<afunix.h>)
|
||||
#include <afunix.h>
|
||||
@@ -72,8 +75,8 @@ extern "C" {
|
||||
!defined(__PS2__)
|
||||
#include <sys/un.h>
|
||||
#endif
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/time.h>
|
||||
}
|
||||
#endif
|
||||
#if defined(GEKKO)
|
||||
@@ -98,6 +101,7 @@ extern "C" uint32_t if_config(char *local_ip, char *netmask, char *gateway,
|
||||
#define NETWORK_GETADDRINFO getaddrinfo
|
||||
#define NETWORK_FREEADDRINFO freeaddrinfo
|
||||
#define NETWORK_GETSOCKNAME getsockname
|
||||
#define NETWORK_SHUTDOWN shutdown
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define NETWORK_CLOSE closesocket
|
||||
@@ -841,6 +845,33 @@ size_t NetworkStream::WriteTo(const uint8_t *buff, size_t sz, std::string ip,
|
||||
return 0;
|
||||
return (size_t)sz2;
|
||||
}
|
||||
void NetworkStream::Shutdown(StreamShutdownMode mode) {
|
||||
|
||||
switch (mode) {
|
||||
case StreamShutdownMode::Read:
|
||||
#if defined(_WIN32)
|
||||
|
||||
NETWORK_SHUTDOWN(this->sock, SD_RECEIVE);
|
||||
#else
|
||||
NETWORK_SHUTDOWN(this->sock, SHUT_RD);
|
||||
#endif
|
||||
break;
|
||||
case StreamShutdownMode::Write:
|
||||
#if defined(_WIN32)
|
||||
NETWORK_SHUTDOWN(this->sock, SD_SEND);
|
||||
#else
|
||||
NETWORK_SHUTDOWN(this->sock, SHUT_WR);
|
||||
#endif
|
||||
break;
|
||||
case StreamShutdownMode::ReadWrite:
|
||||
#if defined(_WIN32)
|
||||
NETWORK_SHUTDOWN(this->sock, SD_BOTH);
|
||||
#else
|
||||
NETWORK_SHUTDOWN(this->sock, SHUT_RDWR);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
void NetworkStream::Close() {
|
||||
if (this->owns && this->success)
|
||||
NETWORK_CLOSE(this->sock);
|
||||
@@ -858,6 +889,22 @@ void NetworkStream::SetNoDelay(bool noDelay) {
|
||||
(const char *)&noDelay2, (socklen_t)sizeof(noDelay2));
|
||||
}
|
||||
|
||||
void NetworkStream::SetSendTimeout(uint64_t seconds) {
|
||||
timeval timeout;
|
||||
timeout.tv_sec = (time_t)seconds;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
NETWORK_SETSOCKOPT(this->sock, SOL_SOCKET, SO_SNDTIMEO,
|
||||
(const char *)&timeout, (socklen_t)sizeof(timeout));
|
||||
}
|
||||
void NetworkStream::SetRecvTimeout(uint64_t seconds) {
|
||||
timeval timeout;
|
||||
timeout.tv_sec = (time_t)seconds;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
NETWORK_SETSOCKOPT(this->sock, SOL_SOCKET, SO_RCVTIMEO,
|
||||
(const char *)&timeout, (socklen_t)sizeof(timeout));
|
||||
}
|
||||
} // namespace Tesses::Framework::Streams
|
||||
#else
|
||||
namespace Tesses::Framework::Streams {
|
||||
@@ -911,6 +958,7 @@ NetworkStream::GetIPs(bool ipV6) {
|
||||
}
|
||||
NetworkStream::~NetworkStream() {}
|
||||
void NetworkStream::SetNoDelay(bool noDelay) {}
|
||||
void NetworkStream::Shutdown(StreamShutdownMode mode) {}
|
||||
void NetworkStream::Close() {}
|
||||
uint16_t NetworkStream::GetPort() { return 0; }
|
||||
uint16_t TcpServer::GetPort() { return 0; }
|
||||
|
||||
@@ -117,4 +117,22 @@ void Stream::CopyTo(std::shared_ptr<Stream> strm, size_t buffSize) {
|
||||
strm->Flush();
|
||||
}
|
||||
Stream::~Stream() {}
|
||||
|
||||
void Stream::Shutdown(StreamShutdownMode mode) {}
|
||||
void Stream::SetSendTimeout(Tesses::Framework::Date::TimeSpan ts) {
|
||||
int64_t sec = ts.TotalSeconds();
|
||||
if (sec < 0)
|
||||
SetSendTimeout(0);
|
||||
else
|
||||
SetSendTimeout(sec);
|
||||
}
|
||||
void Stream::SetRecvTimeout(Tesses::Framework::Date::TimeSpan ts) {
|
||||
int64_t sec = ts.TotalSeconds();
|
||||
if (sec < 0)
|
||||
SetRecvTimeout(0);
|
||||
else
|
||||
SetRecvTimeout(sec);
|
||||
}
|
||||
void Stream::SetSendTimeout(uint64_t seconds) {}
|
||||
void Stream::SetRecvTimeout(uint64_t seconds) {}
|
||||
} // namespace Tesses::Framework::Streams
|
||||
|
||||
+6
-10
@@ -521,7 +521,7 @@ std::string TF_GetExecutableName() {
|
||||
if (sysctl(mib, 4, path.data(), &len, NULL, 0) < 0) {
|
||||
return "";
|
||||
}
|
||||
path.resize(strlen(path.c_str()));
|
||||
path.resize(len - 1);
|
||||
return path;
|
||||
#elif defined(__NetBSD__)
|
||||
auto path = Filesystem::LocalFS->ReadLink(
|
||||
@@ -534,18 +534,14 @@ std::string TF_GetExecutableName() {
|
||||
return path.ToString();
|
||||
#elif TARGET_OS_MAC && !TARGET_OS_IPHONE
|
||||
std::string path;
|
||||
path.resize(1025);
|
||||
uint32_t bufsize = (uint32_t)path.size();
|
||||
uint32_t bufsize = 0;
|
||||
_NSGetExecutablePath(NULL, &bufsize);
|
||||
path.resize(bufsize);
|
||||
if (_NSGetExecutablePath(path.data(), &bufsize) == 0) {
|
||||
path.resize(strlen(path.c_str()));
|
||||
path.resize(bufsize - 1);
|
||||
return path;
|
||||
} else {
|
||||
path.resize(bufsize);
|
||||
if (_NSGetExecutablePath(path.data(), &bufsize) == 0) {
|
||||
path.resize(strlen(path.c_str()));
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return "";
|
||||
|
||||
+102
-7
@@ -34,26 +34,45 @@ namespace Tesses::Framework::Threading {
|
||||
class MutexHiddenFieldData : public HiddenFieldData {
|
||||
public:
|
||||
#if defined(_WIN32)
|
||||
HANDLE mtx;
|
||||
CRITICAL_SECTION mtx;
|
||||
#else
|
||||
pthread_mutex_t mtx;
|
||||
pthread_mutexattr_t attr;
|
||||
#endif
|
||||
~MutexHiddenFieldData() {
|
||||
#if defined(_WIN32)
|
||||
CloseHandle(mtx);
|
||||
DeleteCriticalSection(&mtx);
|
||||
#else
|
||||
pthread_mutex_destroy(&mtx);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
class MutexCondHiddenFieldData : public HiddenFieldData {
|
||||
public:
|
||||
#if defined(_WIN32)
|
||||
CONDITION_VARIABLE cond;
|
||||
#else
|
||||
|
||||
pthread_cond_t cond;
|
||||
#endif
|
||||
|
||||
~MutexCondHiddenFieldData() {
|
||||
#if defined(_WIN32)
|
||||
|
||||
#else
|
||||
pthread_cond_destroy(&cond);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
Mutex::Mutex() {
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_THREADING)
|
||||
auto md = this->data.AllocField<MutexHiddenFieldData>();
|
||||
#if defined(_WIN32)
|
||||
md->mtx = CreateMutex(NULL, false, NULL);
|
||||
InitializeCriticalSection(&(md->mtx));
|
||||
#else
|
||||
pthread_mutexattr_init(&md->attr);
|
||||
pthread_mutexattr_settype(&md->attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
@@ -66,7 +85,7 @@ void Mutex::Lock() {
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_THREADING)
|
||||
auto md = this->data.GetField<MutexHiddenFieldData *>();
|
||||
#if defined(_WIN32)
|
||||
WaitForSingleObject(md->mtx, INFINITE);
|
||||
EnterCriticalSection(&(md->mtx));
|
||||
|
||||
#else
|
||||
pthread_mutex_lock(&md->mtx);
|
||||
@@ -77,7 +96,7 @@ void Mutex::Unlock() {
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_THREADING)
|
||||
auto md = this->data.GetField<MutexHiddenFieldData *>();
|
||||
#if defined(_WIN32)
|
||||
ReleaseMutex(md->mtx);
|
||||
LeaveCriticalSection(&(md->mtx));
|
||||
#else
|
||||
pthread_mutex_unlock(&md->mtx);
|
||||
#endif
|
||||
@@ -87,12 +106,88 @@ bool Mutex::TryLock() {
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_THREADING)
|
||||
auto md = this->data.GetField<MutexHiddenFieldData *>();
|
||||
#if defined(_WIN32)
|
||||
return WaitForSingleObject(md->mtx, 100) == WAIT_OBJECT_0;
|
||||
|
||||
return TryEnterCriticalSection(&md->mtx) != FALSE;
|
||||
#else
|
||||
return pthread_mutex_trylock(&md->mtx) == 0;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
Mutex::~Mutex() {}
|
||||
|
||||
Cond::Cond() {
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_THREADING)
|
||||
auto md = this->data.AllocField<MutexCondHiddenFieldData>();
|
||||
#if defined(_WIN32)
|
||||
InitializeConditionVariable(&md->cond);
|
||||
#else
|
||||
pthread_cond_init(&md->cond, NULL);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void Cond::Wait(Mutex *mtx) {
|
||||
if (mtx == nullptr)
|
||||
return;
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_THREADING)
|
||||
auto mcd = this->data.GetField<MutexCondHiddenFieldData *>();
|
||||
auto md = mtx->data.GetField<MutexHiddenFieldData *>();
|
||||
#if defined(_WIN32)
|
||||
SleepConditionVariableCS(&mcd->cond, &md->mtx, INFINITE);
|
||||
#else
|
||||
pthread_cond_wait(&mcd->cond, &md->mtx);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
bool Cond::Wait(Mutex *mtx, uint32_t milliseconds) {
|
||||
if (mtx == nullptr)
|
||||
return false;
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_THREADING)
|
||||
auto mcd = this->data.GetField<MutexCondHiddenFieldData *>();
|
||||
auto md = mtx->data.GetField<MutexHiddenFieldData *>();
|
||||
#if defined(_WIN32)
|
||||
return SleepConditionVariableCS(&mcd->cond, &md->mtx, milliseconds) !=
|
||||
FALSE;
|
||||
#else
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += milliseconds / 1000;
|
||||
ts.tv_nsec += (milliseconds % 1000) * 1000000L;
|
||||
if (ts.tv_nsec >= 1000000000L) {
|
||||
ts.tv_sec++;
|
||||
ts.tv_nsec -= 1000000000L;
|
||||
}
|
||||
return pthread_cond_timedwait(&mcd->cond, &md->mtx, &ts) == 0;
|
||||
#endif
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
bool Cond::Wait(Mutex *mtx, Date::TimeSpan ts) {
|
||||
return Wait(mtx, (uint32_t)ts.TotalSeconds() * 1000);
|
||||
}
|
||||
void Cond::Signal() {
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_THREADING)
|
||||
auto mcd = this->data.GetField<MutexCondHiddenFieldData *>();
|
||||
|
||||
#if defined(_WIN32)
|
||||
WakeConditionVariable(&mcd->cond);
|
||||
#else
|
||||
pthread_cond_signal(&mcd->cond);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
void Cond::Broadcast() {
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_THREADING)
|
||||
auto mcd = this->data.GetField<MutexCondHiddenFieldData *>();
|
||||
|
||||
#if defined(_WIN32)
|
||||
WakeConditionVariable(&mcd->cond);
|
||||
#else
|
||||
pthread_cond_broadcast(&mcd->cond);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
Cond::~Cond() {}
|
||||
|
||||
}; // namespace Tesses::Framework::Threading
|
||||
|
||||
Reference in New Issue
Block a user