5 Commits
54 changed files with 3234 additions and 467 deletions
+47 -39
View File
@@ -11,11 +11,12 @@ env:
jobs: jobs:
build-ppc: build-ppc:
name: "🔨 Build for PowerPC" name: "🔨 Build for PowerPC"
runs-on: ubuntu-latest runs-on: asrock
container: container:
image: git.tesses.org/tesses50/linux-ppc:latest image: git.tesses.org/tesses50/linux-ppc:latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
url: https://git.tesses.org/tesses50/tessesframework.git
- run: bash build-for-ppc.sh - run: bash build-for-ppc.sh
- uses: akkuman/gitea-release-action@v1 - uses: akkuman/gitea-release-action@v1
env: env:
@@ -24,48 +25,13 @@ jobs:
prerelease: true prerelease: true
files: |- files: |-
artifacts/** artifacts/**
build-packages:
name: "🔨 Build for everything else"
runs-on: fw13-ubuntu-latest
strategy:
fail-fast: true
matrix:
include:
- container: "git.tesses.org/tesses50/linux-x64:latest"
nfpmName: "amd64"
debianName: "x86_64-linux-gnu"
- container: "git.tesses.org/tesses50/linux-x86:latest"
nfpmName: "386"
debianName: "i386-linux-gnu"
- container: "git.tesses.org/tesses50/linux-arm:latest"
nfpmName: "arm7"
debianName: "arm-linux-gnueabihf"
- container: "git.tesses.org/tesses50/linux-arm64:latest"
nfpmName: "arm64"
debianName: "aarch64-linux-gnu"
- container: "git.tesses.org/tesses50/linux-riscv64:latest"
nfpmName: "riscv64"
debianName: "riscv64-linux-gnu"
container:
image: ${{ matrix.container }}
steps:
- uses: actions/checkout@v4
- run: bash build-for-nfpm.sh ${{ matrix.nfpmName }} ${{ matrix.debianName }}
- uses: akkuman/gitea-release-action@v1
env:
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
with:
prerelease: true
files: |-
artifacts/**
update-tap-and-build-win32: update-tap-and-build-win32:
name: "🔨 Build win32 and update the tap 🍺" name: "🔨 Build win32 and update the tap 🍺"
runs-on: global-container-mingw runs-on: msi-walmart
container: git.tesses.org/tesses50/global-containers-mingw:latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
url: https://git.tesses.org/tesses50/tessesframework.git
- run: | - run: |
mkdir artifacts mkdir artifacts
env -C Packaging/WinToolsIntel bash build.sh env -C Packaging/WinToolsIntel bash build.sh
@@ -92,3 +58,45 @@ jobs:
git add . git add .
git commit -m "Push tessesframework=${{ env.VERSION }}" git commit -m "Push tessesframework=${{ env.VERSION }}"
git push git push
build-packages:
name: "🔨 Build for everything else"
strategy:
fail-fast: true
matrix:
include:
- container: "git.tesses.org/tesses50/linux-x64:latest"
puter: "msi-walmart"
nfpmName: "amd64"
debianName: "x86_64-linux-gnu"
- container: "git.tesses.org/tesses50/linux-x86:latest"
puter: "asrock"
nfpmName: "386"
debianName: "i386-linux-gnu"
- container: "git.tesses.org/tesses50/linux-arm:latest"
puter: "fw13-ubuntu-latest"
nfpmName: "arm7"
debianName: "arm-linux-gnueabihf"
- container: "git.tesses.org/tesses50/linux-arm64:latest"
puter: "msi-walmart"
nfpmName: "arm64"
debianName: "aarch64-linux-gnu"
- container: "git.tesses.org/tesses50/linux-riscv64:latest"
nfpmName: "riscv64"
debianName: "riscv64-linux-gnu"
puter: "asrock"
runs-on: ${{ matrix.puter }}
container:
image: ${{ matrix.container }}
steps:
- uses: actions/checkout@v4
url: https://git.tesses.org/tesses50/tessesframework.git
- run: bash build-for-nfpm.sh ${{ matrix.nfpmName }} ${{ matrix.debianName }}
- uses: akkuman/gitea-release-action@v1
env:
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
with:
prerelease: true
files: |-
artifacts/**
-1
View File
@@ -7,7 +7,6 @@ set(CMAKE_CXX_STANDARD 17)
include(GNUInstallDirs) include(GNUInstallDirs)
include(CheckLibraryExists) include(CheckLibraryExists)
include(FetchContent) include(FetchContent)
include(cmake/sources.cmake)
include(cmake/options.cmake) include(cmake/options.cmake)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
-2
View File
@@ -1,8 +1,6 @@
GENERATE_HTML = YES GENERATE_HTML = YES
GENERATE_LATEX = YES
HTML_OUTPUT = doxy_html HTML_OUTPUT = doxy_html
LATEX_OUTPUT = doxy_latex
# optional but common # optional but common
RECURSIVE = YES RECURSIVE = YES
+1
View File
@@ -22,6 +22,7 @@ Tesses Framework
- EventLoop - EventLoop
- Basic crytography using mbedtls - Basic crytography using mbedtls
- a LCG Random number generator (do not use for crypto, use it for non security things like number guessing games and stuff, where rand would be ok) - a LCG Random number generator (do not use for crypto, use it for non security things like number guessing games and stuff, where rand would be ok)
- Simple HTML parser
## Supported Platforms ## Supported Platforms
- Linux - Linux
+84
View File
@@ -0,0 +1,84 @@
#include "TessesFramework/TessesFramework.hpp"
using namespace Tesses::Framework::Streams;
using namespace Tesses::Framework::Serialization;
using namespace Tesses::Framework::TextStreams;
Json::JToken Html2JToken(std::shared_ptr<Html::HtmlNode> node) {
if (node->isText) {
return node->text_or_tag;
} else {
Json::JObject obj;
obj.SetValue("Tag", node->text_or_tag);
Json::JArray attrs;
for (auto &item : node->attributes) {
if (item.second)
attrs.Add(Json::JObject(
{{"Key", item.first}, {"Value", item.second.value()}}));
else
attrs.Add(
Json::JObject({{"Key", item.first}, {"Value", nullptr}}));
}
obj.SetValue("Attributes", attrs);
Json::JArray childern;
for (auto &item : node->children)
childern.Add(Html2JToken(item));
obj.SetValue("Children", childern);
return obj;
}
}
std::shared_ptr<FileStream> OpenWrite(std::string dest) {
if (dest == "-") {
return std::make_shared<FileStream>(stdout, false, "w");
} else {
auto strm = std::make_shared<FileStream>(dest, "w");
if (!strm->CanWrite()) {
return nullptr;
}
return strm;
}
}
std::shared_ptr<FileStream> OpenRead(std::string src) {
if (src == "-") {
return std::make_shared<FileStream>(stdin, false, "r");
} else {
auto strm = std::make_shared<FileStream>(src, "r");
if (!strm->CanRead()) {
return nullptr;
}
return strm;
}
}
int main(int argc, char **argv) {
if (argc < 3) {
std::cout << "USAGE: " << argv[0] << " SRC DEST" << std::endl;
std::cout << "SRC: html file or - for stdin to convert to json doc"
<< std::endl;
std::cout << "DEST: json file or - for stdout" << std::endl;
return 0;
}
auto src = OpenRead(argv[1]);
auto dest = OpenWrite(argv[2]);
if (src == nullptr) {
std::cerr << "ERROR: Input could not be read" << std::endl;
return 1;
}
if (dest == nullptr) {
std::cerr << "ERROR: Output could not be read" << std::endl;
return 1;
}
StreamWriter writer(dest);
auto html = Html::Parse(std::make_shared<StreamReader>(src));
writer.WriteLine(Json::Json::Encode(Html2JToken(html)));
return 0;
}
+77
View File
@@ -0,0 +1,77 @@
#include "TessesFramework/TessesFramework.hpp"
using namespace Tesses::Framework::Streams;
using namespace Tesses::Framework::Serialization;
using namespace Tesses::Framework::TextStreams;
std::shared_ptr<Stream> OpenRead(std::string src) {
if (src == "-") {
return std::make_shared<FileStream>(stdin, false, "r");
} else if (src.find("http://") == 0 || src.find("https://") == 0) {
Tesses::Framework::Http::HttpRequest req;
req.url = src;
req.method = "GET";
Tesses::Framework::Http::HttpResponse resp(req);
if ((int)resp.statusCode < 200 || (int)resp.statusCode > 299) {
throw std::runtime_error(
"Response does not indicate success: " +
std::to_string((int)resp.statusCode) + " " +
Tesses::Framework::Http::HttpUtils::StatusCodeString(
resp.statusCode) +
".");
}
return resp.ReadAsStream();
} else {
auto strm = std::make_shared<FileStream>(src, "r");
if (!strm->CanRead()) {
return nullptr;
}
return strm;
}
}
int main(int argc, char **argv) {
if (argc < 2) {
std::cout << "USAGE: " << argv[0] << " SRC" << std::endl;
std::cout << "SRC: html file or url or - for stdin" << std::endl;
return 0;
}
auto src = OpenRead(argv[1]);
if (src == nullptr) {
std::cerr << "ERROR: Input could not be read" << std::endl;
return 1;
}
auto html = Html::Parse(std::make_shared<StreamReader>(src));
std::vector<std::shared_ptr<const Html::HtmlNode>> titleTags;
html->FindNodes("title", titleTags);
for (auto &title : titleTags) {
Tesses::Framework::Console::WriteView("Title: ");
Tesses::Framework::Console::WriteLine(title->InnerText());
break;
}
std::vector<std::shared_ptr<const Html::HtmlNode>> metaTags;
html->FindNodes("meta", metaTags);
for (auto &meta : metaTags) {
std::optional<std::string> prop, content;
for (auto &[k, v] : meta->attributes) {
if (k == "property" && v)
prop = *v;
if (k == "content" && v)
content = *v;
}
if (prop && content && prop->find("og:") == 0) {
Tesses::Framework::Console::WriteView(prop.value());
Tesses::Framework::Console::WriteView(": ");
Tesses::Framework::Console::WriteLineView(content.value());
}
}
return 0;
}
+33
View File
@@ -0,0 +1,33 @@
#include "TessesFramework/TessesFramework.hpp"
#include <iostream>
using namespace Tesses::Framework;
using namespace Tesses::Framework::Http;
using namespace Tesses::Framework::Streams;
using namespace Tesses::Framework::TextStreams;
using namespace Tesses::Framework::Threading;
int main(int argc, char **argv) {
TF_InitWithConsole();
uint16_t port = 4269;
Args args(argc, argv);
for (auto &item : args.options) {
if (item.first == "port")
port = (uint16_t)std::stoul(item.second);
}
if (args.positional.empty()) {
Console::WriteLine(args.filename + " [--port=] <url_root>");
return 1;
}
Tesses::Framework::Http::HttpServer server(
port, std::make_shared<ReverseProxyServer>(args.positional[0]));
server.StartAccepting();
TF_RunEventLoop();
std::cout << "Closing server" << std::endl;
TF_Quit();
return 0;
}
+8 -1
View File
@@ -1,7 +1,14 @@
#include <TessesFramework/TessesFramework.hpp> #include <TessesFramework/TessesFramework.hpp>
int main(int argc, char **argv) { int main(int argc, char **argv) {
using namespace Tesses::Framework;
using namespace Tesses::Framework::TextStreams;
Tesses::Framework::TF_InitWithConsole(); Tesses::Framework::TF_InitWithConsole();
std::cout << Tesses::Framework::Date::GetTimeZone() << std::endl; StdOut() << "TimeZone Standard Offset: "
<< (int64_t)(Date::GetTimeZone() / 3600) << NewLine();
StdOut() << (Date::TimeZoneSupportDST() ? "TimeZone Supports DST"
: "TimeZone Doesn't Support DST")
<< NewLine();
return 0; return 0;
} }
+4 -3
View File
@@ -39,7 +39,7 @@ int main(int argc, char **argv) {
Args args(argc, argv); Args args(argc, argv);
if (args.positional.size() < 2) if (args.positional.size() < 2)
usage(args); usage(args);
int64_t num;
for (auto &opt : args.options) { for (auto &opt : args.options) {
if (opt.first == "tracker") if (opt.first == "tracker")
trackers.push_back(opt.second); trackers.push_back(opt.second);
@@ -49,8 +49,9 @@ int main(int argc, char **argv) {
comment = opt.second; comment = opt.second;
else if (opt.first == "created_by") else if (opt.first == "created_by")
created_by = opt.second; created_by = opt.second;
else if (opt.first == "piece_length") else if (opt.first == "piece_length" &&
pieceLength = std::stoll(opt.second); Serialization::BitConverter::TryParseSigned(opt.second, num))
pieceLength = num;
} }
for (auto &flag : args.flags) { for (auto &flag : args.flags) {
+4 -1
View File
@@ -1,7 +1,10 @@
# Changelog # Changelog
## 0.0.8
Fixed a stoll bug that was there since day one, html parser, fix pkgconfig
## 0.0.7 ## 0.0.7
Fix license compliance and fix things up Fix license compliance and fix things up, add reverse proxy, fix some security flaws with HttpUtils
## 0.0.6 ## 0.0.6
Use my new toolchain, fix a bug on mac/freebsd Use my new toolchain, fix a bug on mac/freebsd
+14 -1
View File
@@ -66,7 +66,20 @@ target_link_libraries(tuuid PUBLIC tessesframework)
install(TARGETS tuuid DESTINATION "${CMAKE_INSTALL_BINDIR}") install(TARGETS tuuid DESTINATION "${CMAKE_INSTALL_BINDIR}")
add_executable(ttimezone apps/ttimezone.cpp) add_executable(ttimezone apps/ttimezone.cpp)
target_link_libraries(ttimezone PUBLIC tessesframework) target_link_libraries(ttimezone PUBLIC tessesframework)
install(TARGETS ttimezone DESTINATION "${CMAKE_INSTALL_BINDIR}") install(TARGETS ttimezone DESTINATION "${CMAKE_INSTALL_BINDIR}")
add_executable(tsimplereverseproxy apps/tsimplereverseproxy.cpp)
target_link_libraries(tsimplereverseproxy PUBLIC tessesframework)
install(TARGETS tsimplereverseproxy DESTINATION "${CMAKE_INSTALL_BINDIR}")
add_executable(thtml2json apps/thtml2json.cpp)
target_link_libraries(thtml2json PUBLIC tessesframework)
install(TARGETS thtml2json DESTINATION "${CMAKE_INSTALL_BINDIR}")
add_executable(topengraph apps/topengraph.cpp)
target_link_libraries(topengraph PUBLIC tessesframework)
install(TARGETS topengraph DESTINATION "${CMAKE_INSTALL_BINDIR}")
endif() endif()
+6 -8
View File
@@ -18,21 +18,19 @@ if(NOT mbedtls_POPULATED)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build static" FORCE) set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build static" FORCE)
if(NOT TESSESFRAMEWORK_STATIC) if(NOT TESSESFRAMEWORK_STATIC)
set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "Build PIC" FORCE) set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "Build PIC" FORCE)
set(CMAKE_SKIP_INSTALL_RULES ON)
set(DISABLE_PACKAGE_CONFIG_AND_INSTALL ON CACHE BOOL "" FORCE)
endif() endif()
FetchContent_Populate(mbedtls) FetchContent_Populate(mbedtls)
if(NOT TESSESFRAMEWORK_STATIC)
install(CODE "set(CMAKE_INSTALL_LOCAL_ONLY TRUE)" ALL_COMPONENTS)
endif()
if(NOT TESSESFRAMEWORK_STATIC)
add_subdirectory(${mbedtls_SOURCE_DIR} ${mbedtls_BINARY_DIR} EXCLUDE_FROM_ALL)
else()
add_subdirectory(${mbedtls_SOURCE_DIR} ${mbedtls_BINARY_DIR}) add_subdirectory(${mbedtls_SOURCE_DIR} ${mbedtls_BINARY_DIR})
if(NOT TESSESFRAMEWORK_STATIC)
set(CMAKE_SKIP_INSTALL_RULES OFF)
endif() endif()
endif() endif()
else() else()
if(TESSESFRAMEWORK_MBEDTLS_PORTLIB STREQUAL "") if(TESSESFRAMEWORK_MBEDTLS_PORTLIB STREQUAL "")
+3
View File
@@ -5,12 +5,14 @@ src/Date/Date.cpp
src/Http/FileServer.cpp src/Http/FileServer.cpp
src/Http/MountableServer.cpp src/Http/MountableServer.cpp
src/Http/RouteServer.cpp src/Http/RouteServer.cpp
src/Http/DomainServer.cpp
src/Http/CallbackServer.cpp src/Http/CallbackServer.cpp
src/Http/HttpServer.cpp src/Http/HttpServer.cpp
src/Http/HttpUtils.cpp src/Http/HttpUtils.cpp
src/Http/HttpClient.cpp src/Http/HttpClient.cpp
src/Http/HttpStream.cpp src/Http/HttpStream.cpp
src/Http/ContentDisposition.cpp src/Http/ContentDisposition.cpp
src/Http/HttpReverseProxy.cpp
src/Http/WebSocket.cpp src/Http/WebSocket.cpp
src/Http/ChangeableServer.cpp src/Http/ChangeableServer.cpp
src/Http/BasicAuthServer.cpp src/Http/BasicAuthServer.cpp
@@ -20,6 +22,7 @@ src/Serialization/Json.cpp
src/Serialization/SQLite.cpp src/Serialization/SQLite.cpp
src/Serialization/BitConverter.cpp src/Serialization/BitConverter.cpp
src/Serialization/Bencode.cpp src/Serialization/Bencode.cpp
src/Serialization/Html.cpp
src/Platform/Environment.cpp src/Platform/Environment.cpp
src/Platform/Process.cpp src/Platform/Process.cpp
src/Streams/FileStream.cpp src/Streams/FileStream.cpp
+1 -1
View File
@@ -1,3 +1,3 @@
set(TESSESFRAMEWORK_MAJOR_VERSION 0) set(TESSESFRAMEWORK_MAJOR_VERSION 0)
set(TESSESFRAMEWORK_MINOR_VERSION 0) set(TESSESFRAMEWORK_MINOR_VERSION 0)
set(TESSESFRAMEWORK_PATCH_VERSION 7) set(TESSESFRAMEWORK_PATCH_VERSION 8)
@@ -21,6 +21,7 @@
#pragma once #pragma once
#include "../Streams/Stream.hpp" #include "../Streams/Stream.hpp"
#include "Crypto.hpp"
namespace Tesses::Framework::Crypto { namespace Tesses::Framework::Crypto {
/** /**
@@ -59,6 +60,32 @@ class ClientTLSStream : public Tesses::Framework::Streams::Stream {
ClientTLSStream( ClientTLSStream(
std::shared_ptr<Tesses::Framework::Streams::Stream> innerStream, std::shared_ptr<Tesses::Framework::Streams::Stream> innerStream,
bool verify, std::string domain, std::string cert); bool verify, std::string domain, std::string cert);
/**
* @brief Construct a new Client TLS Stream object, with mTLS
*
* @param innerStream the underlying encrypted in transit stream
* @param verify do we verify the certificate
* @param domain the domain name
* @param keyStore the keystore for mTLS
*/
ClientTLSStream(
std::shared_ptr<Tesses::Framework::Streams::Stream> innerStream,
bool verify, std::string domain, CertificateKeyStore keyStore);
/**
* @brief Construct a new Client TLS Stream object with an alternative
* certificate chain (for server with self signed certificates) for mTLS
*
* @param innerStream the underlying encrypted in transit stream
* @param verify do we verify the certificate
* @param domain the domain name
* @param cert the actual certificate
* @param keyStore the keystore for mTLS
*/
ClientTLSStream(
std::shared_ptr<Tesses::Framework::Streams::Stream> innerStream,
bool verify, std::string domain, std::string cert,
CertificateKeyStore keyStore);
/** /**
* @brief Read from the stream * @brief Read from the stream
* *
@@ -96,6 +123,10 @@ class ClientTLSStream : public Tesses::Framework::Streams::Stream {
* @return false no * @return false no
*/ */
bool EndOfStream(); bool EndOfStream();
void Shutdown(Tesses::Framework::Streams::StreamShutdownMode sdm);
void SetSendTimeout(uint64_t seconds);
void SetRecvTimeout(uint64_t seconds);
~ClientTLSStream(); ~ClientTLSStream();
}; };
+24 -1
View File
@@ -316,6 +316,29 @@ typedef enum {
*/ */
bool PBKDF2(std::vector<uint8_t> &output, std::string pass, bool PBKDF2(std::vector<uint8_t> &output, std::string pass,
std::vector<uint8_t> &salt, long itterations, ShaVersion version); std::vector<uint8_t> &salt, long itterations, ShaVersion version);
/**
* @brief Get secure random bytes
*
* @param output The buffer to write random bytes to
* @param personal_str Some string to ensure the rng is unique (for mbedtls at
* least)
* @return true successfully generated the bytes
* @return false we failed to generate the bytes
*/
bool RandomBytes(std::vector<uint8_t> &output, std::string personal_str); bool RandomBytes(std::vector<uint8_t> &output, std::string personal_str);
struct CertificateKeyStore {
CertificateKeyStore() = default;
CertificateKeyStore(std::string certificate, std::string key,
std::optional<std::string> chain = std::nullopt,
std::string password = "")
: certificate(certificate), key(key), chain(chain), password(password) {
}
std::string certificate;
std::string key;
std::optional<std::string> chain;
std::string password;
};
} // namespace Tesses::Framework::Crypto } // namespace Tesses::Framework::Crypto
+358 -14
View File
@@ -26,9 +26,23 @@
namespace Tesses::Framework::Date { namespace Tesses::Framework::Date {
/**
* @brief Get the time zone in seconds from UTC
*
* @return int the utc distance in seconds, west
*/
int GetTimeZone(); int GetTimeZone();
/**
* @brief Does the timezone support daylight savings
*
* @return true yes
* @return false no
*/
bool TimeZoneSupportDST(); bool TimeZoneSupportDST();
/**
* @brief Stores a DateTime
*
*/
class DateTime { class DateTime {
int year = 1970; int year = 1970;
int month = 1; int month = 1;
@@ -41,83 +55,413 @@ class DateTime {
void FromEpochNoConvert(int64_t gmt); void FromEpochNoConvert(int64_t gmt);
public: public:
/**
* @brief Construct a new Date Time object with Jan 1, 1970 at 12:00AM UTC
*
*/
DateTime(); DateTime();
/**
* @brief Construct a new Date Time object
*
* @param year the year
* @param month the month (1-12)
* @param day the day (1-31)
* @param hour the hour (0-23)
* @param minute the minute (0-59)
* @param seconds the second (0-59)
* @param isLocal true: your timezone, false: utc
*/
DateTime(int year, int month, int day, int hour, int minute, int seconds, DateTime(int year, int month, int day, int hour, int minute, int seconds,
bool isLocal = true); bool isLocal = true);
/**
* @brief Construct a new DateTime object
*
* @param epoch seconds since Jan 1, 1970 at 12:00AM UTC
*/
DateTime(int64_t epoch); DateTime(int64_t epoch);
/**
* @brief Get the year
*
* @return int ex 1992 means the year is 1992
*/
int Year() const; int Year() const;
/**
* @brief Get the month (1-12)
*
* @return int ex 8 means august
*/
int Month() const; int Month() const;
/**
* @brief Get the day (1-31)
*
* @return int ex 20 means the day is the 20th
*/
int Day() const; int Day() const;
/**
* @brief Get the hour (0-23)
*
* @return int ex 12 means noon, 0 means midnight, 18 means 6 PM
*/
int Hour() const; int Hour() const;
/**
* @brief Get the minute (0-59)
*
* @return int ex 15 means quarter after the hour
*/
int Minute() const; int Minute() const;
/**
* @brief Get the second (0-59)
*
* @return int the seconds value
*/
int Second() const; int Second() const;
/**
* @brief Get the day of week (0-6)
*
* @retval 0 Sunday
* @retval 1 Monday
* @retval 2 Tuesday
* @retval 3 Wednesday
* @retval 4 Thursday
* @retval 5 Friday
* @retval 6 Saturday
*/
int DayOfWeek() const; int DayOfWeek() const;
/**
* @brief Is local timezone
*
* @return true yes
* @return false no
*/
bool IsLocal() const; bool IsLocal() const;
/**
* @brief Get the time as epoch
*
* @return int64_t seconds since Jan 1, 1970 at 12:00AM UTC
*/
int64_t ToEpoch() const; int64_t ToEpoch() const;
/**
* @brief Convert this time to local time
*
* @return DateTime this time in local time
*/
DateTime ToLocal() const; DateTime ToLocal() const;
/**
* @brief Convert this time to utc time
*
* @return DateTime this time in UTC time
*/
DateTime ToUTC() const; DateTime ToUTC() const;
/**
* @brief Set this time to local
*
*/
void SetToLocal(); void SetToLocal();
/**
* @brief Set this time to UTC
*
*/
void SetToUTC(); void SetToUTC();
/**
* @brief Set the current year
*
* @param y year
*/
void SetYear(int y); void SetYear(int y);
/**
* @brief Set the current month (1-12)
*
* @param m month
*/
void SetMonth(int m); void SetMonth(int m);
/**
* @brief Set the current day (1-31)
*
* @param d day
*/
void SetDay(int d); void SetDay(int d);
/**
* @brief Set the current hour (0-23)
*
* @param h hour
*/
void SetHour(int h); void SetHour(int h);
/**
* @brief Set the current minute (0-59)
*
* @param m minute
*/
void SetMinute(int m); void SetMinute(int m);
/**
* @brief Set the current second (0-59)
*
* @param s second
*/
void SetSecond(int s); void SetSecond(int s);
/**
* @brief Set whether this time is local or utc
*
* @param local true set this time to localtime or false set this time to
* utc
*/
void SetLocal(bool local); void SetLocal(bool local);
/**
* @brief Set the seconds since Jan 1, 1970 at 12:00AM UTC
*
* @param epoch seconds since Jan 1, 1970 at 12:00AM UTC
*/
void Set(int64_t epoch); void Set(int64_t epoch);
/**
* @brief Set the time of this DateTime object
*
* @param year the year
* @param month the month (1-12)
* @param day the day (1-31)
* @param hour the hour (0-23)
* @param minute the minute (0-59)
* @param seconds the second (0-59)
* @param isLocal true: your timezone, false: utc
*/
void Set(int year, int month, int day, int hour, int minute, int seconds, void Set(int year, int month, int day, int hour, int minute, int seconds,
bool isLocal = true); bool isLocal = true);
/**
* @brief Set this DateTime to right now in your timezone
*
*/
void SetToNow(); void SetToNow();
/**
* @brief Set this DateTime to right now in utc
*
*/
void SetToNowUTC(); void SetToNowUTC();
/**
* @brief Construct a datetime from right now in your timezone
*
* @return DateTime the current time object
*/
static DateTime Now(); static DateTime Now();
/**
* @brief Construct a datetime from right now in your timezone
*
* @return DateTime the current time object
*/
static DateTime NowUTC(); static DateTime NowUTC();
/**
* @brief Convert date to string same as ToString("%Y/%m/%d %H:%M:%S")
*
* @return std::string the date as string
*/
std::string ToString() const; std::string ToString() const;
/**
* @brief Convert date to string with your own fmt
*
* @param fmt the format, see:
* https://git.tesses.org/tesses50/tessesframework/wiki/DateTime_ToString_Formating
* for more details
* @return std::string the date as a string based on fmt
*/
std::string ToString(std::string fmt) const; std::string ToString(std::string fmt) const;
/**
* @brief Format as IMF-fixdate (RFC 9110 §5.6.7)
*
* Example: "Tue, 01 Sep 2026 18:07:05 GMT"
*/
std::string ToHttpDate() const; std::string ToHttpDate() const;
static bool TryParseHttpDate(std::string txt, DateTime &dt); /**
* @brief Tries to parse an IMF-fixdate (RFC 9110 §5.6.7) date into a
* DateTime
*
* @param txt example "Tue, 01 Sep 2026 18:07:05 GMT"
* @param dt a reference to a datetime
* @return true we did parse the date correctly
* @return false we didn't parse the date correctly
*/
static bool TryParseHttpDate(std::string_view txt, DateTime &dt);
/**
* @brief Tries to parse an IMF-fixdate (RFC 9110 §5.6.7) date into the
* DateTime
*
* @param txt example "Tue, 01 Sep 2026 18:07:05 GMT"
* @return true we did parse the date correctly
* @return false we didn't parse the date correctly
*/
bool TryParseHttpDate(std::string_view txt);
}; };
/**
* @brief Stores a time offset in seconds
*
*/
class TimeSpan { class TimeSpan {
int64_t totalSeconds; int64_t totalSeconds;
public: public:
/**
* @brief Construct a new TimeSpan with 0 seconds
*
*/
TimeSpan(); TimeSpan();
/**
* @brief Construct a new TimeSpan object with totalSeconds
*
*/
TimeSpan(int64_t totalSeconds); TimeSpan(int64_t totalSeconds);
/**
* @brief Construct a new TimeSpan object with hours, minutes and seconds
*
*/
TimeSpan(int hours, int minutes, int seconds); TimeSpan(int hours, int minutes, int seconds);
/**
* @brief Construct a new TimeSpan object with days, hours, minutes and
* seconds
*
*/
TimeSpan(int days, int hours, int minutes, int seconds); TimeSpan(int days, int hours, int minutes, int seconds);
/**
* @brief Set the timespan with new days, hours, minutes and seconds
*
*/
void Set(int days, int hours, int minutes, int seconds); void Set(int days, int hours, int minutes, int seconds);
/**
* @brief Set the timespan with new hours, minutes and seconds
*
*/
void Set(int hours, int minutes, int seconds); void Set(int hours, int minutes, int seconds);
/**
* @brief Set the days component (hours, minutes, seconds unchanged)
*/
void SetDays(int d); void SetDays(int d);
/**
* @brief Set the hours component (days, minutes, seconds unchanged)
*/
void SetHours(int h); void SetHours(int h);
/**
* @brief Set the minutes component (days, hours, seconds unchanged)
*/
void SetMinutes(int m); void SetMinutes(int m);
/**
* @brief Set the seconds component (days, hours, minutes unchanged)
*/
void SetSeconds(int s); void SetSeconds(int s);
/**
* @brief Get the days of the timespan
*
*/
int Days() const; int Days() const;
/**
* @brief Get the hours of the timespan
*
*/
int Hours() const; int Hours() const;
/**
* @brief Get the minutes of the timespan
*
*/
int Minutes() const; int Minutes() const;
/**
* @brief Get the seconds of the timespan
*
*/
int Seconds() const; int Seconds() const;
/**
* @brief Get the total seconds of the timespan
*
*/
int64_t TotalSeconds() const; int64_t TotalSeconds() const;
/**
* @brief Get the total minutes of the timespan
*
*/
int64_t TotalMinutes() const; int64_t TotalMinutes() const;
/**
* @brief Get the total hours of the timespan
*
*/
int64_t TotalHours() const; int64_t TotalHours() const;
/**
* @brief Set the total seconds of the timespan, removing any old value
*
*/
void SetTotalSeconds(int64_t totalSeconds); void SetTotalSeconds(int64_t totalSeconds);
/**
* @brief Set the total minutes of the timespan, removing any old value
*
*/
void SetTotalMinutes(int64_t totalMinutes); void SetTotalMinutes(int64_t totalMinutes);
/**
* @brief Set the total hours of the timespan, removing any old value
*
*/
void SetTotalHours(int64_t totalHours); void SetTotalHours(int64_t totalHours);
/**
* @brief Set the total days of the timespan, removing any old value
*
*/
void SetTotalDays(int64_t totalHours);
/**
* @brief Add seconds to the timespan
*
*/
void AddSeconds(int64_t seconds); void AddSeconds(int64_t seconds);
/**
* @brief Add minutes to the timespan
*
*/
void AddMinutes(int64_t minutes); void AddMinutes(int64_t minutes);
/**
* @brief Add hours to the timespan
*
*/
void AddHours(int64_t hours); void AddHours(int64_t hours);
/**
* @brief Add days to the timespan
*
*/
void AddDays(int64_t days); void AddDays(int64_t days);
/**
* @brief Converts the timespan to string
*
* @param slim If true, omits leading zeros (e.g. "1:00", "10:00",
* "1:00:00"). If false, always zero-pads (e.g. "00:00:00") unless there are
* days.
*/
std::string ToString(bool slim = true) const; std::string ToString(bool slim = true) const;
/**
static bool TryParse(std::string text, TimeSpan &span); * @brief Try to parse a TimeSpan from a string
*
* @param text the string to parse
* @param span receives the parsed TimeSpan on success
* @return true we parsed successfully
* @return false we failed to parse
*/
static bool TryParse(std::string_view text, TimeSpan &span);
/**
* @brief Try to parse this timespan from string
*
* @param text the string to parse
* @return true we parsed successfully
* @return false we failed to parse
*/
bool TryParse(std::string_view text);
/**
* @brief Create a timespan with seconds, same as ctor
*
*/
static TimeSpan FromSeconds(int64_t seconds);
/**
* @brief Create a timespan with minutes
*
*/
static TimeSpan FromMinutes(int64_t minutes);
/**
* @brief Create a timespan with hours
*
*/
static TimeSpan FromHours(int64_t hours);
/**
* @brief Create a timespan with days
*
*/
static TimeSpan FromDays(int64_t days);
}; };
inline DateTime operator+(const DateTime &dt, const TimeSpan &ts) { inline DateTime operator+(const DateTime &dt, const TimeSpan &ts) {
@@ -24,13 +24,47 @@
#include "VFSFix.hpp" #include "VFSFix.hpp"
namespace Tesses::Framework::Filesystem::Helpers { namespace Tesses::Framework::Filesystem::Helpers {
/** @brief Read all of the text from a file
* @param vfs the VFS you want to use
* @param path the path to the file in the VFS
* @param text the file's contents
*/
void ReadAllText(std::shared_ptr<VFS> vfs, VFSPath path, std::string &text); void ReadAllText(std::shared_ptr<VFS> vfs, VFSPath path, std::string &text);
/**
* @brief Read all of the lines from a file
*
* @param vfs the VFS you want to use
* @param path the path to the file in the VFS
* @param lines the file's lines
*/
void ReadAllLines(std::shared_ptr<VFS> vfs, VFSPath path, void ReadAllLines(std::shared_ptr<VFS> vfs, VFSPath path,
std::vector<std::string> &lines); std::vector<std::string> &lines);
/**
* @brief Read all of the bytes from a file
*
* @param vfs the VFS you want to use
* @param path the path to the file in the VFS
* @param array the file's contents
*/
void ReadAllBytes(std::shared_ptr<VFS> vfs, VFSPath path, void ReadAllBytes(std::shared_ptr<VFS> vfs, VFSPath path,
std::vector<uint8_t> &array); std::vector<uint8_t> &array);
/**
* @brief Read all of the text from a file
*
* @param vfs the VFS you want to use
* @param path the path to the file in the VFS
* @return std::string the file's contents
*/
std::string ReadAllText(std::shared_ptr<VFS> vfs, VFSPath path); std::string ReadAllText(std::shared_ptr<VFS> vfs, VFSPath path);
/**
* @brief Read all of the lines from a file
*
* @param vfs the VFS you want to use
* @param path the path to the file in the VFS
* @return std::vector<std::string> the file's lines
*/
std::vector<std::string> ReadAllLines(std::shared_ptr<VFS> vfs, VFSPath path); std::vector<std::string> ReadAllLines(std::shared_ptr<VFS> vfs, VFSPath path);
std::vector<uint8_t> ReadAllBytes(std::shared_ptr<VFS> vfs, VFSPath path); std::vector<uint8_t> ReadAllBytes(std::shared_ptr<VFS> vfs, VFSPath path);
void WriteAllText(std::shared_ptr<VFS> vfs, VFSPath path, void WriteAllText(std::shared_ptr<VFS> vfs, VFSPath path,
const std::string &text); const std::string &text);
+2 -2
View File
@@ -38,8 +38,8 @@ class HiddenField {
HiddenField(HiddenFieldData *data); HiddenField(HiddenFieldData *data);
void SetField(HiddenFieldData *data); void SetField(HiddenFieldData *data);
template <typename T> T GetField() { return dynamic_cast<T>(ptr); } template <typename T> T GetField() { return dynamic_cast<T>(ptr); }
template <typename T> T *AllocField() { template <typename T, typename... TArgs> T *AllocField(TArgs &&...args) {
auto v = new T(); auto v = new T(std::forward<TArgs>(args)...);
SetField(v); SetField(v);
return v; return v;
} }
@@ -24,11 +24,15 @@
namespace Tesses::Framework::Http { namespace Tesses::Framework::Http {
class ChangeableServer { class ChangeableServer {
std::shared_ptr<IHttpServer> server;
Tesses::Framework::Threading::Mutex mtx;
public: public:
ChangeableServer(); ChangeableServer();
ChangeableServer(std::shared_ptr<IHttpServer> original); ChangeableServer(std::shared_ptr<IHttpServer> original);
std::shared_ptr<IHttpServer> server;
bool Handle(ServerContext &ctx); bool Handle(ServerContext &ctx);
void SetServer(std::shared_ptr<IHttpServer> server);
std::shared_ptr<IHttpServer> GetServer();
~ChangeableServer(); ~ChangeableServer();
}; };
} // namespace Tesses::Framework::Http } // namespace Tesses::Framework::Http
@@ -0,0 +1,41 @@
/*
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/>.
*/
#pragma once
#include "../Filesystem/VFS.hpp"
#include "../Filesystem/VFSFix.hpp"
#include "HttpServer.hpp"
namespace Tesses::Framework::Http {
class DomainServer : public IHttpServer {
std::shared_ptr<IHttpServer> root;
std::map<std::string, std::shared_ptr<IHttpServer>> servers;
Tesses::Framework::Threading::Mutex mtx;
public:
DomainServer();
DomainServer(std::shared_ptr<IHttpServer> root);
void Set(std::string domain, std::shared_ptr<IHttpServer> server);
void Unset(std::string domain);
void Clear();
bool Handle(ServerContext &ctx);
};
} // namespace Tesses::Framework::Http
+10 -6
View File
@@ -20,6 +20,7 @@
*/ */
#pragma once #pragma once
#include "../Crypto/Crypto.hpp"
#include "../Streams/Stream.hpp" #include "../Streams/Stream.hpp"
#include "HttpUtils.hpp" #include "HttpUtils.hpp"
// clang-format off // clang-format off
@@ -66,6 +67,7 @@ class HttpRequest {
public: public:
HttpRequest(); HttpRequest();
std::string trusted_root_cert_bundle; std::string trusted_root_cert_bundle;
std::optional<Crypto::CertificateKeyStore> mTLS_keyStore;
bool ignoreSSLErrors; bool ignoreSSLErrors;
bool followRedirects; bool followRedirects;
@@ -73,15 +75,17 @@ class HttpRequest {
std::string url; std::string url;
std::string unixSocket; std::string unixSocket;
HttpDictionary requestHeaders; HttpDictionary requestHeaders;
HttpRequestBody *body; std::shared_ptr<HttpRequestBody> body;
static std::shared_ptr<Tesses::Framework::Streams::Stream> static std::shared_ptr<Tesses::Framework::Streams::Stream>
EstablishConnection(Uri uri, bool ignoreSSLErrors, EstablishConnection(
std::string trusted_root_cert_bundle); Uri uri, bool ignoreSSLErrors, std::string trusted_root_cert_bundle,
std::optional<Crypto::CertificateKeyStore> mTLS_keyStore);
static std::shared_ptr<Tesses::Framework::Streams::Stream> static std::shared_ptr<Tesses::Framework::Streams::Stream>
EstablishUnixPathConnection(std::string unixPath, Uri uri, EstablishUnixPathConnection(
bool ignoreSSLErrors, std::string unixPath, Uri uri, bool ignoreSSLErrors,
std::string trusted_root_cert_bundle); std::string trusted_root_cert_bundle,
std::optional<Crypto::CertificateKeyStore> mTLS_keyStore);
void SendRequest(std::shared_ptr<Tesses::Framework::Streams::Stream> strm); void SendRequest(std::shared_ptr<Tesses::Framework::Streams::Stream> strm);
}; };
@@ -0,0 +1,84 @@
/*
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/>.
*/
#pragma once
#include "HttpClient.hpp"
#include "HttpServer.hpp"
#include <unordered_set>
namespace Tesses::Framework::Http {
enum class ReverseProxyAction { Continue, Handled, Unhandled };
class ReverseProxyConnectionBuilder {
public:
ReverseProxyConnectionBuilder(const ReverseProxyConnectionBuilder &b) =
delete;
ReverseProxyConnectionBuilder &
operator=(const ReverseProxyConnectionBuilder &b) = delete;
ReverseProxyConnectionBuilder(ReverseProxyConnectionBuilder &&b) = delete;
ReverseProxyConnectionBuilder &
operator=(ReverseProxyConnectionBuilder &&b) = delete;
ReverseProxyConnectionBuilder(ServerContext &ctx,
bool essentalheaders = true);
ReverseProxyConnectionBuilder &WithHeadersFromRequest();
ReverseProxyConnectionBuilder &WithHeader(std::string key,
std::string value);
ReverseProxyConnectionBuilder &SetHeader(std::string key,
std::string value);
ReverseProxyConnectionBuilder &WithoutHeader(std::string key);
ReverseProxyConnectionBuilder &WithUrl(std::string url);
ReverseProxyConnectionBuilder &WithResponseCallback(
std::function<ReverseProxyAction(ServerContext &, HttpResponse &)> rc);
ReverseProxyConnectionBuilder &
WithWhitelistedUpgrade(std::string protocol = "websocket");
bool Handle();
private:
ServerContext &m_ctx;
std::optional<std::string> m_url;
HttpDictionary m_reqheaders;
std::unordered_set<std::string> m_whitelistedupgrades;
std::function<ReverseProxyAction(ServerContext &, HttpResponse &)> m_rc;
bool m_fail = false;
};
class ReverseProxyServer : public IHttpServer {
private:
std::string url;
std::function<ReverseProxyAction(ServerContext &,
ReverseProxyConnectionBuilder &, Uri &)>
request_callback;
std::function<ReverseProxyAction(ServerContext &, HttpResponse &)>
response_callback;
bool essentialheaders;
public:
ReverseProxyServer(
std::string url,
std::function<ReverseProxyAction(
ServerContext &, ReverseProxyConnectionBuilder &, Uri &)>
request_callback = nullptr,
std::function<ReverseProxyAction(ServerContext &, HttpResponse &)>
response_callback = nullptr,
bool essentialheaders = true);
bool Handle(ServerContext &ctx);
};
} // namespace Tesses::Framework::Http
+1 -1
View File
@@ -98,7 +98,7 @@ class ServerContext {
void SendErrorPage(bool showPath); void SendErrorPage(bool showPath);
void SendNotFound(); void SendNotFound();
void SendBadRequest(); void SendBadRequest();
void SendException(std::exception &ex); void SendException(const std::exception &ex);
void SendServerSentEvents(std::shared_ptr<ServerSentEvents> sse); void SendServerSentEvents(std::shared_ptr<ServerSentEvents> sse);
std::shared_ptr<Tesses::Framework::Streams::Stream> OpenResponseStream(); std::shared_ptr<Tesses::Framework::Streams::Stream> OpenResponseStream();
std::shared_ptr<Tesses::Framework::Streams::Stream> OpenRequestStream(); std::shared_ptr<Tesses::Framework::Streams::Stream> OpenRequestStream();
+48 -25
View File
@@ -22,6 +22,9 @@
#pragma once #pragma once
#include "../Common.hpp" #include "../Common.hpp"
#include "../Date/Date.hpp" #include "../Date/Date.hpp"
#include "../Filesystem/VFS.hpp"
#include "../Filesystem/VFSFix.hpp"
#include <algorithm> #include <algorithm>
namespace Tesses::Framework::Http { namespace Tesses::Framework::Http {
@@ -91,9 +94,7 @@ typedef enum StatusCode {
} StatusCode; } StatusCode;
struct CaseInsensitiveLess { struct CaseInsensitiveLess {
CaseInsensitiveLess(const CaseInsensitiveLess &str); explicit CaseInsensitiveLess(bool caseSensitive);
CaseInsensitiveLess();
CaseInsensitiveLess *offset;
bool caseSensitive; bool caseSensitive;
bool operator()(const std::string &s1, const std::string &s2) const; bool operator()(const std::string &s1, const std::string &s2) const;
}; };
@@ -136,7 +137,17 @@ class HttpDictionary {
bool GetFirstBoolean(std::string key); bool GetFirstBoolean(std::string key);
bool TryGetOnlyOne(std::string key, std::string &value);
bool TryGetOnlyOneInt(std::string key, int64_t &value);
bool TryGetOnlyOneDouble(std::string key, double &value);
bool TryGetOnlyOneDate(std::string key, Date::DateTime &value);
bool TryGetOnlyOneBoolean(std::string key, bool &value);
bool AnyEquals(std::string key, std::string value); bool AnyEquals(std::string key, std::string value);
bool AnyEqualsCSV(std::string key, std::string value);
}; };
class Uri { class Uri {
@@ -146,9 +157,9 @@ class Uri {
std::string GetPathAndQuery(); std::string GetPathAndQuery();
uint16_t GetPort(); uint16_t GetPort();
std::string HostPort(); std::string HostPort();
bool Relative(std::string url, Uri &uri); bool Relative(std::string_view url, Uri &uri);
std::string ToString(); std::string ToString();
static bool TryParse(std::string url, Uri &uri); static bool TryParse(std::string_view url, Uri &uri);
std::string scheme; std::string scheme;
std::string host; std::string host;
uint16_t port; uint16_t port;
@@ -169,30 +180,42 @@ class HttpUtils {
bool isUppercase); bool isUppercase);
static void BytesToHex(std::string &text, const std::vector<uint8_t> &data, static void BytesToHex(std::string &text, const std::vector<uint8_t> &data,
bool isUppercase); bool isUppercase);
static std::vector<uint8_t> HexToBytes(const std::string &text); static std::vector<uint8_t> HexToBytes(std::string_view text);
static void HexToBytes(std::vector<uint8_t> &data, const std::string &text); static void HexToBytes(std::vector<uint8_t> &data, std::string_view text);
static std::string MimeType(std::filesystem::path p); static std::string GetMimeType(const std::string &ext);
static std::string GetMimeTypePath(const Filesystem::VFSPath &pathWithExt);
static void AddMimeType(const std::string &ext, const std::string &mime);
static void AddMimeTypePath(const Filesystem::VFSPath &pathWithExt,
const std::string &mime);
static bool Invalid(char c); static bool Invalid(char c);
static std::string Sanitise(std::string text); static std::string Sanitise(std::string_view text);
static void QueryParamsDecode(HttpDictionary &dict, std::string query); static void QueryParamsDecode(HttpDictionary &dict, std::string_view query);
static std::string Join(std::string joinStr, std::vector<std::string> ents); static std::string Join(std::string_view joinStr,
std::vector<std::string> ents);
static std::string QueryParamsEncode(HttpDictionary &dict); static std::string QueryParamsEncode(HttpDictionary &dict);
static std::string UrlDecode(std::string v); static std::string UrlDecode(std::string_view v);
static std::string UrlEncode(std::string v); static std::string UrlEncode(std::string_view v);
static std::string UrlPathDecode(std::string v); static std::string UrlPathDecode(std::string_view v);
static std::string UrlPathEncode(std::string v, bool ignoreSpace = false); static std::string UrlPathEncode(std::string_view v,
static std::string HtmlEncode(std::string v); bool ignoreSpace = false);
static std::string HtmlP(std::string text); static std::string HtmlEncode(std::string_view v);
static std::string HtmlDecodeOnlyEntityNumber(std::string v); // not all escapes are supported
static std::vector<std::string> static std::string HtmlDecode(std::string_view v);
SplitString(std::string text, std::string delimiter, static std::string HtmlP(std::string_view text);
static void SplitString(std::vector<std::string> &out,
std::string_view text, std::string_view delimiter,
std::size_t maxCnt = std::string::npos); std::size_t maxCnt = std::string::npos);
static std::string Replace(std::string str, std::string find, static std::vector<std::string>
std::string replace); SplitString(std::string_view text, std::string_view delimiter,
std::size_t maxCnt = std::string::npos);
static std::string Replace(std::string_view str, std::string_view find,
std::string_view replace);
static std::string StatusCodeString(StatusCode code); static std::string StatusCodeString(StatusCode code);
static std::string ToLower(std::string str); static std::string ToLower(std::string_view str);
static std::string ToUpper(std::string str); static std::string ToUpper(std::string_view str);
static std::string LeftPad(std::string text, int count, char c); static std::string LeftPad(std::string_view text, int count, char c);
static bool CaseInsensitiveCompare(std::string_view left,
std::string_view right);
}; };
} // namespace Tesses::Framework::Http } // namespace Tesses::Framework::Http
@@ -32,6 +32,7 @@ class MountableServer : public IHttpServer {
Filesystem::VFSPath offsetPath); Filesystem::VFSPath offsetPath);
bool StartsWith(Filesystem::VFSPath fullPath, bool StartsWith(Filesystem::VFSPath fullPath,
Filesystem::VFSPath offsetPath); Filesystem::VFSPath offsetPath);
Tesses::Framework::Threading::Mutex mtx;
public: public:
MountableServer(); MountableServer();
@@ -39,6 +40,5 @@ class MountableServer : public IHttpServer {
void Mount(std::string path, std::shared_ptr<IHttpServer> server); void Mount(std::string path, std::shared_ptr<IHttpServer> server);
void Unmount(std::string path); void Unmount(std::string path);
bool Handle(ServerContext &ctx); bool Handle(ServerContext &ctx);
~MountableServer();
}; };
} // namespace Tesses::Framework::Http } // namespace Tesses::Framework::Http
@@ -42,6 +42,7 @@ class RouteServer : public IHttpServer {
}; };
std::vector<RouteServerRoute> routes; std::vector<RouteServerRoute> routes;
std::shared_ptr<IHttpServer> root; std::shared_ptr<IHttpServer> root;
Tesses::Framework::Threading::Mutex mtx;
public: public:
RouteServer() = default; RouteServer() = default;
@@ -58,5 +59,6 @@ class RouteServer : public IHttpServer {
void Add(std::string method, std::string pattern, void Add(std::string method, std::string pattern,
ServerRequestHandler handler); ServerRequestHandler handler);
bool Handle(ServerContext &ctx); bool Handle(ServerContext &ctx);
void Clear();
}; };
} // namespace Tesses::Framework::Http } // namespace Tesses::Framework::Http
@@ -83,6 +83,12 @@ class BitConverter {
static void FromUuid(uint8_t &b, const Uuid &uuid); static void FromUuid(uint8_t &b, const Uuid &uuid);
static bool TryParseSigned(const std::string &str, int64_t &val,
int base = 10);
static bool TryParseUnsigned(const std::string &str, uint64_t &val,
int base = 10);
static bool TryParseDouble(const std::string &str, double &val);
static inline bool IsLittleEndian() { static inline bool IsLittleEndian() {
uint8_t a[2]; uint8_t a[2];
a[0] = 0x01; a[0] = 0x01;
@@ -0,0 +1,71 @@
/*
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/>.
*/
#pragma once
#include "../TextStreams/TextReader.hpp"
namespace Tesses::Framework::Serialization::Html {
struct HtmlNode;
struct HtmlNode : public std::enable_shared_from_this<HtmlNode> {
std::string text_or_tag;
std::vector<std::pair<std::string, std::optional<std::string>>> attributes;
std::vector<std::shared_ptr<HtmlNode>> children;
bool isText = false;
void FindNodes(std::string_view tag,
std::vector<std::shared_ptr<const HtmlNode>> &nodes) const;
std::vector<std::shared_ptr<const HtmlNode>>
FindNodes(std::string_view tag) const;
void ToString(std::string &str) const;
std::string ToString() const;
void InnerText(std::string &str) const;
std::string InnerText() const;
static std::shared_ptr<HtmlNode> FromText(std::string_view text);
static std::shared_ptr<HtmlNode>
FromNode(std::string_view name,
std::initializer_list<
std::pair<std::string, std::optional<std::string>>>
attrs,
std::initializer_list<std::shared_ptr<HtmlNode>> nodes);
static std::shared_ptr<HtmlNode> FromNode(
std::string_view name,
std::vector<std::pair<std::string, std::optional<std::string>>> attrs,
std::vector<std::shared_ptr<HtmlNode>> nodes);
template <typename IttrAttrs, typename IttrNodes>
static std::shared_ptr<HtmlNode>
FromNode(std::string_view name, IttrAttrs attrBegin, IttrAttrs attrEnd,
IttrNodes nodesBegin, IttrNodes nodesEnd) {
auto node = std::make_shared<HtmlNode>();
node->isText = false;
node->text_or_tag = std::string{name};
node->attributes.insert(node->attributes.end(), attrBegin, attrEnd);
node->children.insert(node->children.end(), nodesBegin, nodesEnd);
return node;
}
};
std::shared_ptr<HtmlNode>
Parse(std::shared_ptr<TextStreams::TextReader> reader);
} // namespace Tesses::Framework::Serialization::Html
@@ -53,7 +53,7 @@ class NetworkStream : public Stream {
int32_t sock; int32_t sock;
bool owns; bool owns;
bool success; bool success;
bool endOfStream; std::atomic<bool> endOfStream;
public: public:
bool DataAvailable(int timeout = 0); bool DataAvailable(int timeout = 0);
@@ -85,6 +85,9 @@ class NetworkStream : public Stream {
~NetworkStream(); ~NetworkStream();
void SetNoDelay(bool noDelay); void SetNoDelay(bool noDelay);
void Shutdown(StreamShutdownMode mode);
void SetSendTimeout(uint64_t seconds);
void SetRecvTimeout(uint64_t seconds);
void Close(); void Close();
}; };
} // namespace Tesses::Framework::Streams } // namespace Tesses::Framework::Streams
@@ -21,8 +21,11 @@
#pragma once #pragma once
#include "../Common.hpp" #include "../Common.hpp"
#include "../Date/Date.hpp"
namespace Tesses::Framework::Streams { namespace Tesses::Framework::Streams {
enum class SeekOrigin : uint8_t { Begin = 0, Current = 1, End = 2 }; enum class SeekOrigin : uint8_t { Begin = 0, Current = 1, End = 2 };
enum class StreamShutdownMode { Read = 0, Write = 1, ReadWrite = 2 };
class Stream { class Stream {
public: public:
int32_t ReadByte(); int32_t ReadByte();
@@ -42,6 +45,12 @@ class Stream {
void CopyTo(std::shared_ptr<Stream> strm, size_t buffSize = 1024); void CopyTo(std::shared_ptr<Stream> strm, size_t buffSize = 1024);
void CopyToLimit(std::shared_ptr<Stream> strm, uint64_t len, void CopyToLimit(std::shared_ptr<Stream> strm, uint64_t len,
size_t buffSize = 1024); size_t buffSize = 1024);
virtual void Shutdown(StreamShutdownMode mode);
void SetSendTimeout(Tesses::Framework::Date::TimeSpan ts);
void SetRecvTimeout(Tesses::Framework::Date::TimeSpan ts);
virtual void SetSendTimeout(uint64_t seconds);
virtual void SetRecvTimeout(uint64_t seconds);
virtual void Close(); virtual void Close();
virtual ~Stream(); virtual ~Stream();
}; };
+3 -2
View File
@@ -38,9 +38,9 @@
#include "Http/CallbackServer.hpp" #include "Http/CallbackServer.hpp"
#include "Http/ChangeableServer.hpp" #include "Http/ChangeableServer.hpp"
#include "Http/ContentDisposition.hpp" #include "Http/ContentDisposition.hpp"
#include "Http/DomainServer.hpp"
#include "Http/FileServer.hpp" #include "Http/FileServer.hpp"
#include "Http/HttpClient.hpp" #include "Http/HttpReverseProxy.hpp"
#include "Http/HttpServer.hpp"
#include "Http/MountableServer.hpp" #include "Http/MountableServer.hpp"
#include "Http/RouteServer.hpp" #include "Http/RouteServer.hpp"
#include "Lazy.hpp" #include "Lazy.hpp"
@@ -50,6 +50,7 @@
#include "Random.hpp" #include "Random.hpp"
#include "Serialization/Bencode.hpp" #include "Serialization/Bencode.hpp"
#include "Serialization/BitConverter.hpp" #include "Serialization/BitConverter.hpp"
#include "Serialization/Html.hpp"
#include "Serialization/Json.hpp" #include "Serialization/Json.hpp"
#include "Serialization/SQLite.hpp" #include "Serialization/SQLite.hpp"
#include "Streams/BufferedStream.hpp" #include "Streams/BufferedStream.hpp"
@@ -21,6 +21,7 @@
#pragma once #pragma once
#include "../Date/Date.hpp"
#include "../HiddenField.hpp" #include "../HiddenField.hpp"
namespace Tesses::Framework::Threading { namespace Tesses::Framework::Threading {
class Mutex { class Mutex {
@@ -28,9 +29,39 @@ class Mutex {
public: public:
Mutex(); Mutex();
void Lock(); void Lock();
void Unlock(); void Unlock();
bool TryLock(); bool TryLock();
void lock() { Lock(); }
void unlock() { Unlock(); }
~Mutex(); ~Mutex();
friend class Cond;
}; };
class LockGuard {
Mutex &mtx;
public:
explicit LockGuard(Mutex &m) : mtx(m) { mtx.Lock(); }
~LockGuard() { mtx.Unlock(); }
LockGuard(const LockGuard &) = delete;
LockGuard &operator=(const LockGuard &) = delete;
};
class Cond {
HiddenField data;
public:
Cond();
void Wait(Mutex *mtx);
bool Wait(Mutex *mtx, uint32_t milliseconds);
bool Wait(Mutex *mtx, Date::TimeSpan ts);
void Signal();
void Broadcast();
~Cond();
};
} // namespace Tesses::Framework::Threading } // namespace Tesses::Framework::Threading
+2
View File
@@ -11,6 +11,8 @@ set(PKGCONFIG_DEPS "")
endif() endif()
configure_file(tessesframework.pc.in tessesframework.pc @ONLY) configure_file(tessesframework.pc.in tessesframework.pc @ONLY)
message(NOTICE "SrcFile ${CMAKE_CURRENT_BINARY_DIR}/tessesframework.pc")
message(NOTICE "Outdir ${CMAKE_INSTALL_LIBDIR}/pkgconfig")
install(FILES install(FILES
${CMAKE_CURRENT_BINARY_DIR}/tessesframework.pc ${CMAKE_CURRENT_BINARY_DIR}/tessesframework.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+153 -6
View File
@@ -45,13 +45,24 @@ class ClientTLSPrivateData {
public: public:
bool eos; bool eos;
bool success; bool success;
bool mTLS;
std::shared_ptr<Stream> strm; std::shared_ptr<Stream> strm;
mbedtls_entropy_context entropy; mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg; mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_context ssl; mbedtls_ssl_context ssl;
mbedtls_ssl_config conf; mbedtls_ssl_config conf;
mbedtls_x509_crt cachain; mbedtls_x509_crt cachain;
mbedtls_x509_crt client_cert;
mbedtls_pk_context client_key;
~ClientTLSPrivateData() { ~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_x509_crt_free(&cachain);
mbedtls_ctr_drbg_free(&ctr_drbg); mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy); mbedtls_entropy_free(&entropy);
@@ -101,6 +112,7 @@ ClientTLSStream::ClientTLSStream(
data->eos = false; data->eos = false;
data->success = false; data->success = false;
data->strm = innerStream; data->strm = innerStream;
data->mTLS = false;
mbedtls_ssl_init(&data->ssl); mbedtls_ssl_init(&data->ssl);
mbedtls_ssl_config_init(&data->conf); mbedtls_ssl_config_init(&data->conf);
@@ -119,15 +131,10 @@ ClientTLSStream::ClientTLSStream(
return; return;
} }
if (ret != 0) {
printf("FAILED mbedtls_x509_crt_parse cert %i\n", ret);
return;
}
ret = mbedtls_x509_crt_parse( ret = mbedtls_x509_crt_parse(
&data->cachain, (const unsigned char *)cert.c_str(), cert.size() + 1); &data->cachain, (const unsigned char *)cert.c_str(), cert.size() + 1);
if (ret != 0) { if (ret != 0) {
printf("FAILED mbedtls_x509_crt_parse chain %i\n", ret); printf("FAILED mbedtls_x509_crt_parse cert %i\n", ret);
return; return;
} }
@@ -185,6 +192,132 @@ ClientTLSStream::ClientTLSStream(
#endif #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) { size_t ClientTLSStream::Read(uint8_t *buffer, size_t len) {
#if defined(TESSESFRAMEWORK_ENABLE_MBED) #if defined(TESSESFRAMEWORK_ENABLE_MBED)
auto priv = static_cast<ClientTLSPrivateData *>(this->privateData); auto priv = static_cast<ClientTLSPrivateData *>(this->privateData);
@@ -245,4 +378,18 @@ ClientTLSStream::~ClientTLSStream() {
delete static_cast<ClientTLSPrivateData *>(this->privateData); delete static_cast<ClientTLSPrivateData *>(this->privateData);
#endif #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 } // namespace Tesses::Framework::Crypto
+34 -10
View File
@@ -392,7 +392,10 @@ const char *weekday_long[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
const char *months_long[] = {"January", "Febuary", "March", "April", const char *months_long[] = {"January", "Febuary", "March", "April",
"May", "June", "July", "August", "May", "June", "July", "August",
"September", "October", "November", "December"}; "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 // Mon, 24 Jul 2018 11:00:00 GMT
auto split = Http::HttpUtils::SplitString(txt, ", ", 2); auto split = Http::HttpUtils::SplitString(txt, ", ", 2);
if (split.size() != 2) if (split.size() != 2)
@@ -579,9 +582,8 @@ std::string DateTime::ToString(std::string fmt) const {
break; break;
case 'u': { case 'u': {
int dow = weekday + 6;
dow %= 7; text.append(std::to_string(weekday + 1));
text.append(std::to_string(dow + 1));
} break; } break;
case 'w': { case 'w': {
@@ -590,10 +592,10 @@ std::string DateTime::ToString(std::string fmt) const {
case 'c': { case 'c': {
text.append(weekday_short[weekday]); text.append(weekday_short[weekday]);
text.push_back(' '); text.push_back(' ');
text.append(months_short[month]); text.append(months_short[month - 1]);
text.push_back(' '); text.push_back(' ');
text.append( text.append(
Http::HttpUtils::LeftPad(std::to_string(day), 2, '0')); Http::HttpUtils::LeftPad(std::to_string(day), 2, ' '));
text.push_back(' '); text.push_back(' ');
text.append( text.append(
Http::HttpUtils::LeftPad(std::to_string(hour), 2, '0')); 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) { void TimeSpan::SetTotalHours(int64_t totalHours) {
this->totalSeconds = totalHours * 3600; 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 TimeSpan::ToString(bool slim) const {
std::string str = {}; std::string str = {};
if (this->totalSeconds < 0) if (this->totalSeconds < 0)
@@ -750,8 +774,8 @@ std::string TimeSpan::ToString(bool slim) const {
} }
return str; return str;
} }
bool TimeSpan::TryParse(std::string_view text) { return TryParse(text, *this); }
bool TimeSpan::TryParse(std::string text, TimeSpan &span) { bool TimeSpan::TryParse(std::string_view text, TimeSpan &span) {
if (text.empty()) if (text.empty())
return false; return false;
bool negative = text[0] == '-'; bool negative = text[0] == '-';
@@ -759,7 +783,7 @@ bool TimeSpan::TryParse(std::string text, TimeSpan &span) {
try { try {
std::string colonPart = text.substr(negative ? 1 : 0); std::string_view colonPart = text.substr(negative ? 1 : 0);
auto res = Http::HttpUtils::SplitString(colonPart, ":"); auto res = Http::HttpUtils::SplitString(colonPart, ":");
if (res.size() < 2 || res.size() > 3) if (res.size() < 2 || res.size() > 3)
+1
View File
@@ -296,6 +296,7 @@ VFSPath VFSPath::CollapseRelativeParents() const {
newpath.path = parts; newpath.path = parts;
return newpath; return newpath;
} }
VFSPath VFSPath::CurrentDirectoryAsRelative() { VFSPath VFSPath::CurrentDirectoryAsRelative() {
VFSPath path; VFSPath path;
path.relative = true; path.relative = true;
+15 -2
View File
@@ -27,9 +27,22 @@ ChangeableServer::ChangeableServer(std::shared_ptr<IHttpServer> original) {
this->server = 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) { bool ChangeableServer::Handle(ServerContext &ctx) {
if (this->server) auto server = GetServer();
this->server->Handle(ctx);
if (server)
return server->Handle(ctx);
return false; return false;
} }
ChangeableServer::~ChangeableServer() {} ChangeableServer::~ChangeableServer() {}
+40
View File
@@ -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
+1 -1
View File
@@ -63,7 +63,7 @@ bool FileServer::SendFile(ServerContext &ctx, VFSPath path) {
Date::DateTime lw, la; Date::DateTime lw, la;
this->vfs->GetDate(path, lw, la); this->vfs->GetDate(path, lw, la);
ctx.WithLastModified(lw) ctx.WithLastModified(lw)
.WithMimeType(HttpUtils::MimeType(path.GetFileName())) .WithMimeType(HttpUtils::GetMimeTypePath(path))
.SendStream(strm); .SendStream(strm);
retVal = true; retVal = true;
} }
+32 -20
View File
@@ -65,10 +65,12 @@ StreamHttpRequestBody::StreamHttpRequestBody(std::shared_ptr<Stream> strm,
this->mimeType = mimeType; this->mimeType = mimeType;
} }
void StreamHttpRequestBody::HandleHeaders(HttpDictionary &dict) { void StreamHttpRequestBody::HandleHeaders(HttpDictionary &dict) {
dict.AddValue("Content-Type", this->mimeType); dict.SetValue("Content-Type", this->mimeType);
auto len = this->strm->GetLength(); auto len = this->strm->GetLength();
if (len > -1) 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( void StreamHttpRequestBody::Write(
std::shared_ptr<Tesses::Framework::Streams::Stream> strm) { std::shared_ptr<Tesses::Framework::Streams::Stream> strm) {
@@ -96,6 +98,7 @@ void HttpRequest::SendRequest(
std::string request = method + " " + uri.GetPathAndQuery() + std::string request = method + " " + uri.GetPathAndQuery() +
" HTTP/1.1\r\nHost: " + uri.HostPort() + "\r\n"; " HTTP/1.1\r\nHost: " + uri.HostPort() + "\r\n";
for (auto headers : requestHeaders.kvp) { for (auto headers : requestHeaders.kvp) {
for (auto item : headers.second) { for (auto item : headers.second) {
request.append(headers.first); request.append(headers.first);
@@ -106,7 +109,6 @@ void HttpRequest::SendRequest(
} }
request.append("\r\n"); request.append("\r\n");
StreamWriter writer(strm); StreamWriter writer(strm);
writer.Write(request); writer.Write(request);
@@ -114,9 +116,9 @@ void HttpRequest::SendRequest(
body->Write(strm); body->Write(strm);
} }
} }
std::shared_ptr<Stream> std::shared_ptr<Stream> HttpRequest::EstablishConnection(
HttpRequest::EstablishConnection(Uri uri, bool ignoreSSLErrors, Uri uri, bool ignoreSSLErrors, std::string trusted_root_cert_bundle,
std::string trusted_root_cert_bundle) { std::optional<Crypto::CertificateKeyStore> mTLS_keyStore) {
if (uri.scheme == "http:" || uri.scheme == "ws:") { if (uri.scheme == "http:" || uri.scheme == "ws:") {
return std::make_shared<NetworkStream>(uri.host, uri.GetPort(), false, return std::make_shared<NetworkStream>(uri.host, uri.GetPort(), false,
false, false); false, false);
@@ -125,24 +127,34 @@ HttpRequest::EstablishConnection(Uri uri, bool ignoreSSLErrors,
false, false, false); false, false, false);
if (netStrm == nullptr) if (netStrm == nullptr)
return nullptr; return nullptr;
return std::make_shared<ClientTLSStream>( return mTLS_keyStore
netStrm, !ignoreSSLErrors, uri.host, trusted_root_cert_bundle); ? 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; return nullptr;
} }
std::shared_ptr<Stream> std::shared_ptr<Stream> HttpRequest::EstablishUnixPathConnection(
HttpRequest::EstablishUnixPathConnection(std::string unixPath, Uri uri, std::string unixPath, Uri uri, bool ignoreSSLErrors,
bool ignoreSSLErrors, std::string trusted_root_cert_bundle,
std::string trusted_root_cert_bundle) { std::optional<Crypto::CertificateKeyStore> mTLS_keyStore) {
if (uri.scheme == "http:" || uri.scheme == "ws:") { if (uri.scheme == "http:" || uri.scheme == "ws:") {
return std::make_shared<NetworkStream>(unixPath, false); return std::make_shared<NetworkStream>(unixPath, false);
} else if (uri.scheme == "https:" || uri.scheme == "wss:") { } else if (uri.scheme == "https:" || uri.scheme == "wss:") {
auto netStrm = std::make_shared<NetworkStream>(unixPath, false); auto netStrm = std::make_shared<NetworkStream>(unixPath, false);
if (netStrm == nullptr) if (netStrm == nullptr)
return nullptr; return nullptr;
return std::make_shared<ClientTLSStream>( return mTLS_keyStore
netStrm, !ignoreSSLErrors, uri.host, trusted_root_cert_bundle); ? 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; return nullptr;
@@ -186,13 +198,13 @@ HttpResponse::HttpResponse(HttpRequest &req) : responseHeaders(false) {
std::string url = req.url; std::string url = req.url;
Uri uri; Uri uri;
while (Uri::TryParse(url, uri)) { while (Uri::TryParse(url, uri)) {
auto strm = auto strm = req.unixSocket.empty()
req.unixSocket.empty() ? HttpRequest::EstablishConnection(
? HttpRequest::EstablishConnection(uri, req.ignoreSSLErrors, uri, req.ignoreSSLErrors,
req.trusted_root_cert_bundle) req.trusted_root_cert_bundle, req.mTLS_keyStore)
: HttpRequest::EstablishUnixPathConnection( : HttpRequest::EstablishUnixPathConnection(
req.unixSocket, uri, req.ignoreSSLErrors, req.unixSocket, uri, req.ignoreSSLErrors,
req.trusted_root_cert_bundle); req.trusted_root_cert_bundle, req.mTLS_keyStore);
if (strm == nullptr) if (strm == nullptr)
return; return;
auto reqHeaders = req.requestHeaders; auto reqHeaders = req.requestHeaders;
@@ -628,7 +640,7 @@ void WebSocketUnixSocketClient(std::string unixSocket, std::string url,
if (resp.statusCode != 101 || if (resp.statusCode != 101 ||
!resp.responseHeaders.TryGetFirst("Sec-WebSocket-Accept", accept) || !resp.responseHeaders.TryGetFirst("Sec-WebSocket-Accept", accept) ||
!resp.responseHeaders.AnyEquals("Connection", "Upgrade") || !resp.responseHeaders.AnyEquals("Connection", "Upgrade") ||
!resp.responseHeaders.AnyEquals("Upgrade", "websocket")) { !resp.responseHeaders.AnyEqualsCSV("Upgrade", "websocket")) {
cb(resp.responseHeaders, false); cb(resp.responseHeaders, false);
return; return;
} }
+525
View File
@@ -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
+116 -28
View File
@@ -39,6 +39,8 @@
#include "TessesFramework/Filesystem/VFS.hpp" #include "TessesFramework/Filesystem/VFS.hpp"
#include "TessesFramework/Filesystem/VFSFix.hpp" #include "TessesFramework/Filesystem/VFSFix.hpp"
// clang-format on // clang-format on
#include "TessesFramework/Serialization/BitConverter.hpp"
using FileStream = Tesses::Framework::Streams::FileStream; using FileStream = Tesses::Framework::Streams::FileStream;
using Stream = Tesses::Framework::Streams::Stream; using Stream = Tesses::Framework::Streams::Stream;
using SeekOrigin = Tesses::Framework::Streams::SeekOrigin; using SeekOrigin = Tesses::Framework::Streams::SeekOrigin;
@@ -51,6 +53,11 @@ using namespace Tesses::Framework::TextStreams;
namespace Tesses::Framework::Http { 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) { void ServerSentEvents::SendEventRaw(const std::string &evt) {
this->mtx.Lock(); this->mtx.Lock();
for (auto &item : this->strms) { for (auto &item : this->strms) {
@@ -283,7 +290,7 @@ class WSServer {
return; return;
} }
if (!ctx->requestHeaders.AnyEquals("Upgrade", "websocket")) { if (!ctx->requestHeaders.AnyEqualsCSV("Upgrade", "websocket")) {
return; return;
} }
@@ -602,12 +609,34 @@ uint16_t HttpServer::GetPort() {
return server->GetPort(); return server->GetPort();
return 0; return 0;
} }
std::shared_ptr<Stream> ServerContext::OpenResponseStream() { std::shared_ptr<Stream> ServerContext::OpenResponseStream() {
if (sent) if (sent)
return nullptr; return nullptr;
int64_t length = -1; int64_t length = -1;
try {
if (!this->responseHeaders.TryGetFirstInt("Content-Length", length)) if (!this->responseHeaders.TryGetFirstInt("Content-Length", length))
length = -1; length = -1;
} catch (std::invalid_argument &invalid) {
std::string error =
"<!DOCTYPE html><html lang=\"en\"><head><meta "
"charset=\"UTF-8\"> <meta name=\"viewport\" "
"content=\"width=device-width, "
"initial-scale=1.0\"><title>Internal Server Error at " +
HttpUtils::HtmlEncode(this->originalPath) +
"</title><meta name=\"color-scheme\" content=\"dark "
"light\"></head><body><h1>Internal Server Error at " +
HttpUtils::HtmlEncode(this->originalPath) +
"</h1><p>what(): " + HttpUtils::HtmlEncode(invalid.what()) +
"</p></body></html>";
this->responseHeaders.SetValue("Content-Length",
static_cast<int64_t>(error.size()));
this->statusCode = StatusCode::InternalServerError;
this->WriteHeaders();
this->strm->WriteBlock(reinterpret_cast<const uint8_t *>(error.data()),
error.size());
return nullptr;
}
if (this->version == "HTTP/1.1" && length == -1) if (this->version == "HTTP/1.1" && length == -1)
this->responseHeaders.SetValue("Transfer-Encoding", "chunked"); this->responseHeaders.SetValue("Transfer-Encoding", "chunked");
@@ -643,11 +672,15 @@ void HttpServer::StartAccepting() {
uint16_t port; uint16_t port;
auto sock = svr->GetStream(ip, port); auto sock = svr->GetStream(ip, port);
if (!TF_IsRunning()) // we need to die, that continue is not what we
// want if we must die from being done
break;
TF_LOG("New Host IP: " + ip + ":" + std::to_string(port)); TF_LOG("New Host IP: " + ip + ":" + std::to_string(port));
if (sock == nullptr) { if (sock == nullptr) {
std::cout << "STREAM ERROR" << std::endl; std::cout << "STREAM ERROR" << std::endl;
return; continue;
} }
TF_LOG("Before entering socket thread"); TF_LOG("Before entering socket thread");
@@ -703,9 +736,14 @@ ServerContext::ServerContext(std::shared_ptr<Stream> strm, bool debug)
} }
std::shared_ptr<Stream> ServerContext::GetStream() { return this->strm; } std::shared_ptr<Stream> ServerContext::GetStream() { return this->strm; }
void ServerContext::SendBytes(std::vector<uint8_t> buff) { void ServerContext::SendBytes(std::vector<uint8_t> buff) {
std::shared_ptr<MemoryStream> strm = std::make_shared<MemoryStream>(false);
strm->GetBuffer() = buff; if (sent)
SendStream(strm); return;
this->responseHeaders.SetValue("Content-Length",
static_cast<int64_t>(buff.size()));
auto resp = OpenResponseStream();
if (resp)
resp->WriteBlock(buff.data(), buff.size());
} }
ServerContext &ServerContext::WithLastModified(Date::DateTime dt) { ServerContext &ServerContext::WithLastModified(Date::DateTime dt) {
this->responseHeaders.SetValue("Last-Modified", dt); this->responseHeaders.SetValue("Last-Modified", dt);
@@ -713,11 +751,13 @@ ServerContext &ServerContext::WithLastModified(Date::DateTime dt) {
} }
void ServerContext::SendText(std::string text) { void ServerContext::SendText(std::string text) {
std::shared_ptr<MemoryStream> strm = std::make_shared<MemoryStream>(false); if (sent)
return;
auto &buff = strm->GetBuffer(); this->responseHeaders.SetValue("Content-Length",
buff.insert(buff.end(), text.begin(), text.end()); static_cast<int64_t>(text.size()));
SendStream(strm); auto resp = OpenResponseStream();
if (resp)
resp->WriteBlock(reinterpret_cast<uint8_t *>(text.data()), text.size());
} }
void ServerContext::SendErrorPage(bool showPath) { void ServerContext::SendErrorPage(bool showPath) {
if (sent) if (sent)
@@ -787,22 +827,34 @@ void ServerContext::SendStream(std::shared_ptr<Stream> strm) {
int64_t begin = 0; int64_t begin = 0;
int64_t end = -1; int64_t end = -1;
if (dash.size() == 1 && if (dash.size() == 2) {
res[0].find_first_of('-') != std::string::npos) {
// NUMBER-
begin = std::stoll(dash[0]);
} else if (dash.size() == 2) {
// NUMBER-NUMBER // NUMBER-NUMBER
// or // or
//-NUMBER //-NUMBER
if (dash[0].empty()) {
if (dash[0].size() > 0) {
// NUMBER-NUMBER
begin = std::stoll(dash[0]);
end = std::stoll(dash[1]);
} else {
//-NUMBER //-NUMBER
end = std::stoll(dash[1]);
if (!Serialization::BitConverter::TryParseSigned(
dash[1], end))
throw std::runtime_error(
"Failed to parse the end of the Range header");
} else if (dash[1].empty()) {
if (!Serialization::BitConverter::TryParseSigned(
dash[0], begin))
throw std::runtime_error(
"Failed to parse the end of the Range header");
} else {
// NUMBER-NUMBER
if (!Serialization::BitConverter::TryParseSigned(
dash[0], begin))
throw std::runtime_error(
"Failed to parse the beginning of the Range "
"header");
if (!Serialization::BitConverter::TryParseSigned(
dash[1], end))
throw std::runtime_error(
"Failed to parse the end of the Range header");
} }
} else { } else {
this->statusCode = BadRequest; this->statusCode = BadRequest;
@@ -881,6 +933,8 @@ void ServerContext::SendStream(std::shared_ptr<Stream> strm) {
} else { } else {
auto chunkedStream = this->OpenResponseStream(); auto chunkedStream = this->OpenResponseStream();
if (!chunkedStream)
throw std::runtime_error("Can't open response stream");
if (method != "HEAD") if (method != "HEAD")
strm->CopyTo(chunkedStream); strm->CopyTo(chunkedStream);
@@ -904,10 +958,6 @@ ServerContext &ServerContext::WithContentDisposition(std::string filename,
ContentDisposition cd; ContentDisposition cd;
cd.type = isInline ? "inline" : "attachment"; cd.type = isInline ? "inline" : "attachment";
cd.filename = filename; 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()); this->responseHeaders.SetValue("Content-Disposition", cd.ToString());
return *this; return *this;
} }
@@ -944,12 +994,14 @@ ServerContext &ServerContext::WithStatusCode(StatusCode code) {
this->statusCode = code; this->statusCode = code;
return *this; return *this;
} }
void ServerContext::SendException(std::exception &ex) { void ServerContext::SendException(const std::exception &ex) {
try {
if (this->debug) { if (this->debug) {
this->WithMimeType("text/html") this->WithMimeType("text/html")
.WithStatusCode(StatusCode::InternalServerError) .WithStatusCode(StatusCode::InternalServerError)
.SendText("<!DOCTYPE html><html lang=\"en\"><head><meta " .SendText(
"<!DOCTYPE html><html lang=\"en\"><head><meta "
"charset=\"UTF-8\"> <meta name=\"viewport\" " "charset=\"UTF-8\"> <meta name=\"viewport\" "
"content=\"width=device-width, " "content=\"width=device-width, "
"initial-scale=1.0\"><title>Internal Server Error at " + "initial-scale=1.0\"><title>Internal Server Error at " +
@@ -963,6 +1015,8 @@ void ServerContext::SendException(std::exception &ex) {
this->WithStatusCode(StatusCode::InternalServerError) this->WithStatusCode(StatusCode::InternalServerError)
.SendErrorPage(true); .SendErrorPage(true);
} }
} catch (...) {
}
} }
ServerContext & ServerContext &
@@ -985,6 +1039,9 @@ ServerContext &ServerContext::WriteHeaders() {
return *this; return *this;
this->sent = true; this->sent = true;
if (this->responseHeaders.kvp.count("Date") == 0)
this->responseHeaders.SetValue("Date", Date::DateTime::NowUTC());
StreamWriter writer(this->strm); StreamWriter writer(this->strm);
writer.newline = "\r\n"; writer.newline = "\r\n";
writer.WriteLine("HTTP/1.1 " + std::to_string((int)statusCode) + " " + writer.WriteLine("HTTP/1.1 " + std::to_string((int)statusCode) + " " +
@@ -999,6 +1056,7 @@ ServerContext &ServerContext::WriteHeaders() {
return *this; return *this;
} }
void HttpServer::Process(std::shared_ptr<Stream> strm, void HttpServer::Process(std::shared_ptr<Stream> strm,
std::shared_ptr<IHttpServer> server, std::string ip, std::shared_ptr<IHttpServer> server, std::string ip,
uint16_t port, uint16_t serverPort, bool encrypted, uint16_t port, uint16_t serverPort, bool encrypted,
@@ -1074,10 +1132,40 @@ void HttpServer::Process(std::shared_ptr<Stream> strm,
std::string type; std::string type;
int64_t length; 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") && if (!(ctx.method == "GET" || ctx.method == "HEAD") &&
ctx.requestHeaders.TryGetFirst("Content-Type", type) && ctx.requestHeaders.TryGetFirst("Content-Type", type) &&
type == "application/x-www-form-urlencoded" && type == "application/x-www-form-urlencoded" &&
ctx.requestHeaders.TryGetFirstInt("Content-Length", length)) { ctx.requestHeaders.TryGetFirstInt("Content-Length", length)) {
if (length <= 0) {
ctx.statusCode = StatusCode::BadRequest;
ctx.SendErrorPage(true);
return;
}
size_t len = (size_t)length; size_t len = (size_t)length;
std::vector<uint8_t> buffer(len); std::vector<uint8_t> buffer(len);
len = bStrm->ReadBlock(buffer.data(), len); len = bStrm->ReadBlock(buffer.data(), len);
+48 -6
View File
@@ -57,6 +57,8 @@ bool HttpStream::CanWrite() {
bool HttpStream::EndOfStream() { bool HttpStream::EndOfStream() {
if (this->done) if (this->done)
return true; return true;
if (this->recv && this->length > 0)
return this->position >= this->length;
if (this->offset < this->read) if (this->offset < this->read)
return false; return false;
return this->strm->EndOfStream(); return this->strm->EndOfStream();
@@ -64,6 +66,8 @@ bool HttpStream::EndOfStream() {
int64_t HttpStream::GetLength() { return this->length; } int64_t HttpStream::GetLength() { return this->length; }
int64_t HttpStream::GetPosition() { return this->position; } int64_t HttpStream::GetPosition() { return this->position; }
size_t HttpStream::Read(uint8_t *buff, size_t len) { size_t HttpStream::Read(uint8_t *buff, size_t len) {
if (len == 0)
return 0;
if (this->done) if (this->done)
return 0; return 0;
if (!this->recv) if (!this->recv)
@@ -71,11 +75,21 @@ size_t HttpStream::Read(uint8_t *buff, size_t len) {
if (this->length == 0) if (this->length == 0)
return 0; return 0;
if (this->length > 0) { if (this->length > 0) {
if (this->position >= this->length)
return 0;
len = std::min((size_t)(this->length - this->position), len); len = std::min((size_t)(this->length - this->position), len);
if (len > 0) if (len > 0)
len = this->strm->Read(buff, len); 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; this->position += len;
return len; return len;
} else { } 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); len = std::min((size_t)(this->read - this->offset), len);
if (len > 0) if (len > 0)
len = this->strm->Read(buff, len); 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->offset += len;
this->position += len; this->position += len;
if (this->offset >= this->read) { if (this->offset >= this->read) {
@@ -95,7 +117,16 @@ size_t HttpStream::Read(uint8_t *buff, size_t len) {
} else { } else {
StreamReader reader(this->strm); StreamReader reader(this->strm);
std::string line = reader.ReadLine(); std::string line = reader.ReadLine();
if (!line.empty()) { if (line.empty()) {
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); this->read = std::stoull(line, NULL, 16);
if (this->read == 0) { if (this->read == 0) {
@@ -105,20 +136,29 @@ size_t HttpStream::Read(uint8_t *buff, size_t len) {
} else { } else {
this->offset = 0; this->offset = 0;
len = len = std::min((size_t)(this->read - this->offset), len);
std::min((size_t)(this->read - this->offset), len);
if (len > 0) if (len > 0)
len = this->strm->Read(buff, len); 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->offset += len;
this->position += len; this->position += len;
return len; return len;
} }
}
return 0; return 0;
} }
} else { } 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); this->strm->WriteBlock(buff, len);
writer.WriteLine(); writer.WriteLine();
this->position += len;
return len; return len;
} else { } else {
this->position += len;
return this->strm->Write(buff, len); return this->strm->Write(buff, len);
} }
} }
+441 -220
View File
@@ -21,14 +21,19 @@
#include "TessesFramework/Http/HttpUtils.hpp" #include "TessesFramework/Http/HttpUtils.hpp"
#include "TessesFramework/Filesystem/VFS.hpp" #include "TessesFramework/Filesystem/VFS.hpp"
#include "TessesFramework/Serialization/BitConverter.hpp"
#include "TessesFramework/Text/StringConverter.hpp"
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
using VFSPath = Tesses::Framework::Filesystem::VFSPath; using VFSPath = Tesses::Framework::Filesystem::VFSPath;
namespace Tesses::Framework::Http { namespace Tesses::Framework::Http {
bool Uri::Relative(std::string url, Uri &uri) { bool Uri::Relative(std::string_view url, Uri &uri) {
auto index = url.find_first_of("//"); auto path = this->path;
if (path.empty())
path = "/";
auto index = url.find("//");
if (index != std::string::npos) { if (index != std::string::npos) {
if (Uri::TryParse(url, uri)) { if (Uri::TryParse(url, uri)) {
if (index == 0) if (index == 0)
@@ -63,7 +68,10 @@ bool Uri::Relative(std::string url, Uri &uri) {
auto fourthPart = HttpUtils::SplitString(thirdPart[1], "?", 2); auto fourthPart = HttpUtils::SplitString(thirdPart[1], "?", 2);
VFSPath p = VFSPath(this->path, fourthPart[0]); VFSPath p =
VFSPath(path, (path.back() == '/' || fourthPart[0].empty())
? fourthPart[0]
: "../" + fourthPart[0]);
uri.path = uri.path =
p.CollapseRelativeParents().ToString(); // this should be safe p.CollapseRelativeParents().ToString(); // this should be safe
if (fourthPart.size() == 2) { if (fourthPart.size() == 2) {
@@ -98,21 +106,33 @@ uint16_t Uri::GetPort() {
return 69; return 69;
return 0; return 0;
} }
bool Uri::TryParse(std::string url, Uri &uri) { bool Uri::TryParse(std::string_view url, Uri &uri) {
uri.scheme = ""; uri.scheme = "";
uri.port = 0; uri.port = 0;
auto firstPart = HttpUtils::SplitString(url, "//", 2); auto firstPart = HttpUtils::SplitString(url, "//", 2);
if (firstPart.size() == 2) if (firstPart.size() == 2)
uri.scheme = firstPart[0]; uri.scheme = firstPart[0];
else if (firstPart.empty()) else
return false; return false;
auto secondPart = HttpUtils::SplitString( auto secondPart = HttpUtils::SplitString(firstPart[1], "/", 2);
firstPart.size() == 2 ? firstPart[1] : firstPart[0], "/", 2);
if (secondPart.size() == 1) { if (secondPart.size() == 1) {
uri.path = "/"; uri.path = "/";
auto hp = secondPart[0];
auto fragPart = HttpUtils::SplitString(hp, "#", 2);
if (fragPart.size() == 2) {
uri.hash = fragPart[1];
hp = fragPart[0];
}
auto queryPart = HttpUtils::SplitString(hp, "?", 2);
if (queryPart.size() == 2) {
HttpUtils::QueryParamsDecode(uri.query, queryPart[1]);
hp = queryPart[0];
}
secondPart[0] = hp;
} else if (secondPart.size() == 2) { } else if (secondPart.size() == 2) {
auto thirdPart = HttpUtils::SplitString(secondPart[1], "#", 2); auto thirdPart = HttpUtils::SplitString(secondPart[1], "#", 2);
if (thirdPart.empty()) if (thirdPart.empty())
@@ -135,14 +155,25 @@ bool Uri::TryParse(std::string url, Uri &uri) {
if (hostPortPart.empty()) if (hostPortPart.empty())
return false; return false;
if (hostPortPart.size() == 2) { if (hostPortPart.size() == 2) {
uri.port = (uint16_t)std::stoul(hostPortPart[1]); uint64_t portNum;
if (!Serialization::BitConverter::TryParseUnsigned(hostPortPart[1],
portNum))
return false;
if (portNum > 65535)
return false;
uri.port = static_cast<uint16_t>(portNum);
} }
uri.host = hostPortPart[0]; uri.host = hostPortPart[0];
return true; return true;
} }
Uri::Uri() : query(true) {} Uri::Uri() : query(true) {}
std::string Uri::GetPathAndQuery() { return this->path + this->GetQuery(); } std::string Uri::GetPathAndQuery() {
if (this->path.empty() || this->path.front() != '/')
return '/' + this->path + this->GetQuery();
return this->path + this->GetQuery();
}
std::string Uri::GetQuery() { std::string Uri::GetQuery() {
if (this->query.kvp.empty()) if (this->query.kvp.empty())
return ""; return "";
@@ -162,17 +193,19 @@ std::string Uri::ToString() {
uri.append(this->GetPathAndQuery()); uri.append(this->GetPathAndQuery());
return uri; return uri;
} }
std::string HttpUtils::Replace(std::string text, std::string find, std::string HttpUtils::Replace(std::string_view text, std::string_view find,
std::string replace) { std::string_view replace) {
if (find.empty())
return std::string(text);
std::string dest; std::string dest;
while (text.length() > 0) { while (text.length() > 0) {
std::size_t index = text.find(find); std::size_t index = text.find(find);
if (index == std::string::npos) { if (index == std::string_view::npos) {
dest.append(text); dest.append(text);
break; break;
} else { } else {
std::string left = text.substr(0, index); std::string_view left = text.substr(0, index);
text = text.substr(index + find.size()); text = text.substr(index + find.size());
dest.append(left); dest.append(left);
@@ -182,12 +215,12 @@ std::string HttpUtils::Replace(std::string text, std::string find,
return dest; return dest;
} }
std::string HttpUtils::LeftPad(std::string text, int count, char c) { std::string HttpUtils::LeftPad(std::string_view text, int count, char c) {
if (text.size() >= (size_t)count) if (text.size() >= count)
return text; return std::string(text);
std::string newStr(count - text.size(), c);
text.insert(text.begin(), (size_t)count - text.size(), c); newStr.append(text);
return text; return newStr;
} }
char HttpUtils::NibbleToHex(uint8_t b, bool isUppercase) { char HttpUtils::NibbleToHex(uint8_t b, bool isUppercase) {
if (isUppercase) { if (isUppercase) {
@@ -221,74 +254,105 @@ uint8_t HttpUtils::HexToNibble(char c) {
return 0; return 0;
} }
std::string HttpUtils::MimeType(std::filesystem::path p) { struct mimetype_reg {
std::string ext = p.extension().string(); Threading::Mutex mtx;
if (ext == ".html" || ext == ".htm") { std::unordered_map<std::string, std::string> map = {
return "text/html"; {".html", "text/html"},
} {".htm", "text/html"},
if (ext == ".txt" || ext == ".log" || ext == ".twss") { {".txt", "text/plain"},
return "text/plain"; {".log", "text/plain"},
} {".twss", "text/twss"},
if (ext == ".woff") { {".css", "text/css"},
return "application/x-font-woff"; {".js", "text/javascript"},
} {".mjs", "text/javascript"},
if (ext == ".vtt") { {".json", "application/json"},
return "text/vtt"; {".map", "application/json"},
} {".pdf", "application/pdf"},
if (ext == ".svg") { {".png", "image/png"},
return "image/svg+xml"; {".jpg", "image/jpeg"},
} {".jpeg", "image/jpeg"},
if (ext == ".webp") { {".gif", "image/gif"},
return "image/webp"; {".webp", "image/webp"},
} {".svg", "image/svg+xml"},
if (ext == ".vcf") { {".ico", "image/x-icon"},
return "text/v-card"; {".bmp", "image/bmp"},
} {".avif", "image/avif"},
if (ext == ".rss" || ext == ".xml" || ext == ".atom" || ext == ".rdf") { {".heic", "image/heic"},
return "application/xml"; {".tif", "image/tiff"},
} {".tiff", "image/tiff"},
if (ext == ".js") { {".mp4", "video/mp4"},
return "text/javascript"; {".webm", "video/webm"},
} {".mov", "video/quicktime"},
if (ext == ".json") { {".mkv", "video/x-matroska"},
return "application/json"; {".avi", "video/x-msvideo"},
} {".ts", "video/mp2t"},
if (ext == ".wasm") { {".mp3", "audio/mpeg"},
return "application/wasm"; {".m4a", "audio/mp4"},
} {".ogg", "audio/ogg"},
if (ext == ".png") { {".oga", "audio/ogg"},
return "image/png"; {".opus", "audio/ogg"},
} {".wav", "audio/wav"},
if (ext == ".jpg" || ext == ".jpeg") { {".flac", "audio/flac"},
return "image/jpeg"; {".aac", "audio/aac"},
} {".weba", "audio/webm"},
if (ext == ".css") { {".woff", "font/woff"},
return "text/css"; {".woff2", "font/woff2"},
} {".ttf", "font/ttf"},
if (ext == ".gif") { {".zip", "application/zip"},
return "image/gif"; {".gz", "application/gzip"},
} {".7z", "application/x-7z-compressed"},
if (ext == ".mp4") { {".rar", "application/vnd.rar"},
return "video/mp4"; {".csv", "text/csv"},
} {".vtt", "text/vtt"},
if (ext == ".mov") { {".vcf", "text/v-card"},
return "video/quicktime"; {".rss", "application/xml"},
} {".xml", "application/xml"},
if (ext == ".m4a") { {".atom", "application/xml"},
return "audio/mp4"; {".rdf", "application/xml"},
} {".wasm", "application/wasm"},
if (ext == ".webm") { {".webmanifest", "application/manifest+json"},
return "video/webm"; {".m3u8", "application/vnd.apple.mpegurl"},
} {".mpd", "application/dash+xml"},
if (ext == ".webmanifest") { {".md", "text/markdown"},
return "application/manifest+json"; {".epub", "application/epub+zip"},
} {".ics", "text/calendar"},
if (ext == ".ico") { {".apk", "application/vnd.android.package-archive"},
return "image/x-icon"; {".crvm", "application/crvm"},
} {".tcross", "text/tcross"},
};
std::string Get(const std::string &ext) {
Threading::LockGuard lg(mtx);
std::string mime;
if (map.count(ext) != 0)
mime = map[ext];
if (mime.empty())
return "application/octet-stream"; 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) { bool HttpUtils::Invalid(char c) {
// just do windows because it is the strictist when it comes to windows, mac // just do windows because it is the strictist when it comes to windows, mac
// and linux // and linux
@@ -316,7 +380,7 @@ bool HttpUtils::Invalid(char c) {
return true; return true;
return false; return false;
} }
std::string HttpUtils::Sanitise(std::string text) { std::string HttpUtils::Sanitise(std::string_view text) {
std::string myStr = {}; std::string myStr = {};
for (auto item : text) { for (auto item : text) {
if (Invalid(item)) if (Invalid(item))
@@ -326,7 +390,8 @@ std::string HttpUtils::Sanitise(std::string text) {
return myStr; return myStr;
} }
void HttpUtils::QueryParamsDecode(HttpDictionary &dict, std::string query) { void HttpUtils::QueryParamsDecode(HttpDictionary &dict,
std::string_view query) {
for (auto item : SplitString(query, "&")) { for (auto item : SplitString(query, "&")) {
std::vector<std::string> ss = SplitString(item, "=", 2); std::vector<std::string> ss = SplitString(item, "=", 2);
if (ss.size() >= 1) { if (ss.size() >= 1) {
@@ -338,7 +403,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::vector<std::string> ents) {
std::string str = {}; std::string str = {};
bool first = true; bool first = true;
@@ -368,41 +433,43 @@ std::string HttpUtils::QueryParamsEncode(HttpDictionary &dict) {
return s; return s;
} }
std::string HttpUtils::UrlDecode(std::string v) { std::string HttpUtils::UrlDecode(std::string_view v) {
std::string s = {}; std::string s = {};
for (size_t i = 0; i < v.size(); i++) { for (size_t i = 0; i < v.size(); i++) {
if (v[i] == '+') if (v[i] == '+')
s.push_back(' '); s.push_back(' ');
else if (v[i] == '%') { else if (v[i] == '%') {
i++; if (i + 2 >= v.size())
uint8_t n = HexToNibble(v[i]) << 4; break;
i++;
n |= HexToNibble(v[i]); uint8_t n = (HexToNibble(v[i + 1]) << 4) | (HexToNibble(v[i + 2]));
s.push_back((char)n); i += 2;
s.push_back(static_cast<char>(n));
} else } else
s.push_back(v[i]); s.push_back(v[i]);
} }
return s; return s;
} }
std::string HttpUtils::UrlPathEncode(std::string v, bool ignoreSpace) { std::string HttpUtils::UrlPathEncode(std::string_view v, bool ignoreSpace) {
std::string s = {}; std::string s = {};
for (auto item : v) { 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); s.push_back(item);
else if (item >= 'a' && item <= 'z') else if (byte >= 'a' && byte <= 'z')
s.push_back(item); s.push_back(item);
else if (item >= '0' && item <= '9') else if (byte >= '0' && byte <= '9')
s.push_back(item); s.push_back(item);
else if (item == '-' || item == '_' || item == '.' || item == '~' || else if (byte == '-' || byte == '_' || byte == '.' || byte == '~' ||
item == '/') byte == '/')
s.push_back(item); s.push_back(item);
else { else {
if (item != ' ' || !ignoreSpace) { if (byte != ' ' || !ignoreSpace) {
s.push_back('%'); s.push_back('%');
s.push_back(NibbleToHex((item >> 4) & 0xF)); s.push_back(NibbleToHex((byte >> 4) & 0xF));
s.push_back(NibbleToHex((item) & 0xF)); s.push_back(NibbleToHex((byte) & 0xF));
} else { } else {
s.push_back(' '); s.push_back(' ');
} }
@@ -410,142 +477,102 @@ std::string HttpUtils::UrlPathEncode(std::string v, bool ignoreSpace) {
} }
return s; return s;
} }
std::string HttpUtils::UrlPathDecode(std::string v) { std::string HttpUtils::UrlPathDecode(std::string_view v) {
std::string s = {}; std::string s = {};
for (size_t i = 0; i < v.size(); i++) { for (size_t i = 0; i < v.size(); i++) {
if (v[i] == '%') { if (v[i] == '%') {
i++; if (i + 2 >= v.size())
uint8_t n = HexToNibble(v[i]) << 4; break;
i++;
n |= HexToNibble(v[i]); uint8_t n = (HexToNibble(v[i + 1]) << 4) | (HexToNibble(v[i + 2]));
s.push_back((char)n); i += 2;
s.push_back(static_cast<char>(n));
} else } else
s.push_back(v[i]); s.push_back(v[i]);
} }
return s; return s;
} }
std::string HttpUtils::UrlEncode(std::string v) { std::string HttpUtils::UrlEncode(std::string_view v) {
std::string s = {}; std::string s = {};
for (auto item : v) { for (auto item : v) {
if (item == ' ') uint8_t byte = static_cast<uint8_t>(item);
if (byte == ' ')
s.push_back('+'); s.push_back('+');
else if (item >= 'A' && item <= 'Z') else if (byte >= 'A' && byte <= 'Z')
s.push_back(item); s.push_back(item);
else if (item >= 'a' && item <= 'z') else if (byte >= 'a' && byte <= 'z')
s.push_back(item); s.push_back(item);
else if (item >= '0' && item <= '9') else if (byte >= '0' && byte <= '9')
s.push_back(item); s.push_back(item);
else if (item == '-' || item == '_' || item == '.' || item == '~') else if (byte == '-' || byte == '_' || byte == '.' || byte == '~')
s.push_back(item); s.push_back(item);
else { else {
s.push_back('%'); s.push_back('%');
s.push_back(NibbleToHex((item >> 4) & 0xF)); s.push_back(NibbleToHex((byte >> 4) & 0xF));
s.push_back(NibbleToHex((item) & 0xF)); s.push_back(NibbleToHex((byte) & 0xF));
} }
} }
return s; return s;
} }
void HttpUtils::SplitString(std::vector<std::string> &strs,
std::vector<std::string> HttpUtils::SplitString(std::string text, std::string_view text, std::string_view delimiter,
std::string delimiter,
std::size_t maxCnt) { std::size_t maxCnt) {
std::vector<std::string> strs; if (maxCnt == 0)
return;
if (delimiter.empty()) {
strs.push_back(std::string(text));
return;
}
std::size_t i = 1; std::size_t i = 1;
while (text.length() > 0) { while (true) {
if (i == maxCnt) { if (i == maxCnt) {
strs.push_back(text); strs.push_back(std::string(text));
break; break;
} }
std::size_t index = text.find(delimiter); std::size_t index = text.find(delimiter);
if (index == std::string::npos) { if (index == std::string_view::npos) {
strs.push_back(text); strs.push_back(std::string(text));
break; break;
} else { }
std::string left = text.substr(0, index); std::string_view left = text.substr(0, index);
text = text.substr(index + delimiter.size()); text = text.substr(index + delimiter.size());
strs.push_back(left); strs.push_back(std::string(left));
}
i++; 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; 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); std::string HttpUtils::HtmlP(std::string_view text) {
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 newText = ""; std::string newText = "";
std::string builder = ""; std::string builder = "";
auto flush = [&]() -> void { auto flush = [&]() -> void {
if (!builder.empty()) { if (!builder.empty()) {
// Collect trailing punctuation
std::string trailing;
static const char *punct = ".,;:!?'\"";
while (!builder.empty() && std::strchr(punct, builder.back())) {
trailing.insert(trailing.begin(), builder.back());
builder.pop_back();
}
if (builder.find("http://") == 0 || builder.find("https://") == 0 || if (builder.find("http://") == 0 || builder.find("https://") == 0 ||
builder.find("ftp://") == 0 || builder.find("ftps://") == 0 || builder.find("ftp://") == 0 || builder.find("ftps://") == 0 ||
builder.find("magnet:") == 0 || builder.find("btmh:") == 0) { builder.find("magnet:") == 0) {
newText += "<a href=\"" + HttpUtils::HtmlEncode(builder) + newText += "<a href=\"" + HttpUtils::HtmlEncode(builder) +
"\">" + HttpUtils::HtmlEncode(builder) + "</a>"; "\">" + HttpUtils::HtmlEncode(builder) + "</a>";
} else if (builder.find("mailto:") == 0) { } else if (builder.find("mailto:") == 0) {
@@ -559,6 +586,7 @@ std::string HttpUtils::HtmlP(std::string text) {
} else { } else {
newText += HttpUtils::HtmlEncode(builder); newText += HttpUtils::HtmlEncode(builder);
} }
newText += HttpUtils::HtmlEncode(trailing);
builder = ""; builder = "";
} }
}; };
@@ -575,7 +603,7 @@ std::string HttpUtils::HtmlP(std::string text) {
break; break;
case '\t': case '\t':
flush(); flush();
newText += "&tab;"; newText += "&nbsp;&nbsp;&nbsp;&nbsp;";
break; break;
case '\r': case '\r':
flush(); flush();
@@ -589,7 +617,107 @@ std::string HttpUtils::HtmlP(std::string text) {
return newText; return newText;
} }
std::string HttpUtils::HtmlEncode(std::string html) { static std::unordered_map<std::string, std::string> htmlencodings = {
{"&quot;", "\""},
{"&apos;", "'"},
{"&amp;", "&"},
{"&lt;", "<"},
{"&gt;", ">"},
{"&nbsp;", "\xC2\xA0"},
{"&copy;", "\xC2\xA9"},
{"&reg;", "\xC2\xAE"},
{"&trade;", "\xE2\x84\xA2"},
{"&mdash;", "\xE2\x80\x94"},
{"&ndash;", "\xE2\x80\x93"},
{"&hellip;", "\xE2\x80\xA6"},
{"&rsquo;", "\xE2\x80\x99"},
{"&lsquo;", "\xE2\x80\x98"},
{"&rdquo;", "\xE2\x80\x9D"},
{"&ldquo;", "\xE2\x80\x9C"},
{"&euro;", "\xE2\x82\xAC"},
{"&pound;", "\xC2\xA3"},
{"&yen;", "\xC2\xA5"},
{"&deg;", "\xC2\xB0"},
{"&plusmn;", "\xC2\xB1"},
{"&times;", "\xC3\x97"},
{"&divide;", "\xC3\xB7"},
{"&laquo;", "\xC2\xAB"},
{"&raquo;", "\xC2\xBB"},
{"&bull;", "\xE2\x80\xA2"},
{"&middot;", "\xC2\xB7"},
{"&frac12;", "\xC2\xBD"},
{"&frac14;", "\xC2\xBC"},
{"&frac34;", "\xC2\xBE"},
};
std::string HttpUtils::HtmlDecode(std::string_view view) {
std::string myHtml = {};
std::string tmp = {};
bool inEscaped = false;
for (size_t i = 0; i < view.size(); i++) {
if (inEscaped) {
if (view[i] == ';') {
inEscaped = false;
if (tmp.size() > 2 && tmp[1] == '#') //&#N
{
if (tmp[2] == 'x' || tmp[2] == 'X') {
auto res = tmp.substr(3);
uint64_t code;
if (Serialization::BitConverter::TryParseUnsigned(
res, code, 16)) {
if (code <= 0x10FFFF &&
(code < 0xD800 || code > 0xDFFF)) {
Text::StringConverter::UTF8::FromUTF32(
myHtml, {static_cast<char32_t>(code)});
} else {
myHtml += "\xEF\xBF\xBD";
}
} else {
myHtml += "\xEF\xBF\xBD";
}
} else {
auto res = tmp.substr(2);
uint64_t code;
if (Serialization::BitConverter::TryParseUnsigned(
res, code, 10)) {
if (code <= 0x10FFFF &&
(code < 0xD800 || code > 0xDFFF)) {
Text::StringConverter::UTF8::FromUTF32(
myHtml, {static_cast<char32_t>(code)});
} else {
myHtml += "\xEF\xBF\xBD";
}
} else {
myHtml += "\xEF\xBF\xBD";
}
}
} else {
tmp += ';';
auto result = htmlencodings.find(tmp);
if (result != htmlencodings.end()) {
myHtml += result->second;
} else {
myHtml += "\xEF\xBF\xBD";
}
}
} else {
tmp += view[i];
}
} else {
if (view[i] == '&') {
tmp = "&";
inEscaped = true;
} else {
myHtml += view[i];
}
}
}
if (inEscaped)
myHtml += tmp;
return myHtml;
}
std::string HttpUtils::HtmlEncode(std::string_view html) {
std::string myHtml = {}; std::string myHtml = {};
for (auto item : html) { for (auto item : html) {
if (item == '\"') { if (item == '\"') {
@@ -736,18 +864,12 @@ std::string HttpUtils::StatusCodeString(StatusCode code) {
return ""; 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) { bool HttpDictionary::AnyEquals(std::string key, std::string value) {
if (this->kvp.count(key) > 0) if (this->kvp.count(key) > 0)
for (auto v : this->kvp[key]) for (auto v : this->kvp[key])
@@ -756,6 +878,45 @@ bool HttpDictionary::AnyEquals(std::string key, std::string value) {
// for(auto v : item.second) if(v == value) return true; // for(auto v : item.second) if(v == value) return true;
return false; 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() { kvp.clear(); }
void HttpDictionary::Clear(std::string key, bool kvpExistsAfter) { void HttpDictionary::Clear(std::string key, bool kvpExistsAfter) {
@@ -816,16 +977,8 @@ bool HttpDictionary::TryGetFirstInt(std::string key, int64_t &value) {
std::string val; std::string val;
if (!TryGetFirst(key, val)) if (!TryGetFirst(key, val))
return false; return false;
try {
size_t off = 0; return Serialization::BitConverter::TryParseSigned(val, value);
auto v = std::stoll(val, &off);
if (off != val.size())
return false;
value = v;
} catch (std::exception &ex) {
return false;
}
return true;
} }
bool HttpDictionary::TryGetFirstDate(std::string key, Date::DateTime &dt) { bool HttpDictionary::TryGetFirstDate(std::string key, Date::DateTime &dt) {
std::string val; std::string val;
@@ -848,20 +1001,89 @@ bool HttpDictionary::TryGetFirstDouble(std::string key, double &value) {
} }
return true; 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, bool CaseInsensitiveLess::operator()(const std::string &s1,
const std::string &s2) const { const std::string &s2) const {
if (this->caseSensitive) if (this->caseSensitive)
return s1 == s2; return s1 < s2;
return HttpUtils::ToLower(s1) < HttpUtils::ToLower(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::string str1(str.length(), ' ');
std::transform(str.begin(), str.end(), str1.begin(), tolower); std::transform(str.begin(), str.end(), str1.begin(), tolower);
return str1; return str1;
} }
std::string HttpUtils::ToUpper(std::string str) { std::string HttpUtils::ToUpper(std::string_view str) {
std::string str1(str.length(), ' '); std::string str1(str.length(), ' ');
std::transform(str.begin(), str.end(), str1.begin(), toupper); std::transform(str.begin(), str.end(), str1.begin(), toupper);
return str1; return str1;
@@ -909,13 +1131,12 @@ void HttpUtils::BytesToHex(std::string &text, const std::vector<uint8_t> &data,
text[i * 2 + 1] += NibbleToHex(data[i], isUpper); 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; std::vector<uint8_t> data;
HexToBytes(data, text); HexToBytes(data, text);
return data; return data;
} }
void HttpUtils::HexToBytes(std::vector<uint8_t> &data, void HttpUtils::HexToBytes(std::vector<uint8_t> &data, std::string_view text) {
const std::string &text) {
if (text.empty()) { if (text.empty()) {
data.clear(); data.clear();
return; return;
+11 -1
View File
@@ -51,36 +51,46 @@ MountableServer::MountableServer(std::shared_ptr<IHttpServer> root) {
void MountableServer::Mount(std::string path, void MountableServer::Mount(std::string path,
std::shared_ptr<IHttpServer> server) { std::shared_ptr<IHttpServer> server) {
mtx.Lock();
this->servers.insert( this->servers.insert(
this->servers.begin(), this->servers.begin(),
std::pair<std::string, std::shared_ptr<IHttpServer>>(path, server)); std::pair<std::string, std::shared_ptr<IHttpServer>>(path, server));
mtx.Unlock();
} }
void MountableServer::Unmount(std::string path) { void MountableServer::Unmount(std::string path) {
mtx.Lock();
for (auto i = this->servers.begin(); i != this->servers.end(); i++) { for (auto i = this->servers.begin(); i != this->servers.end(); i++) {
auto &item = *i; auto &item = *i;
if (item.first == path) { if (item.first == path) {
this->servers.erase(i); this->servers.erase(i);
mtx.Unlock();
return; return;
} }
} }
mtx.Unlock();
} }
bool MountableServer::Handle(ServerContext &ctx) { bool MountableServer::Handle(ServerContext &ctx) {
std::string oldPath = ctx.path; std::string oldPath = ctx.path;
mtx.Lock();
bool needUnlock = true;
for (auto item : this->servers) { for (auto item : this->servers) {
if (StartsWith(oldPath, item.first)) { if (StartsWith(oldPath, item.first)) {
ctx.path = Subpath(oldPath, item.first); ctx.path = Subpath(oldPath, item.first);
mtx.Unlock();
if (item.second->Handle(ctx)) { if (item.second->Handle(ctx)) {
ctx.path = oldPath; ctx.path = oldPath;
return true; return true;
} }
ctx.path = oldPath; ctx.path = oldPath;
needUnlock = false;
break; break;
} }
} }
if (needUnlock)
mtx.Unlock();
ctx.path = oldPath; ctx.path = oldPath;
if (this->root && this->root->Handle(ctx)) if (this->root && this->root->Handle(ctx))
return true; return true;
return false; return false;
} }
MountableServer::~MountableServer() {}
} // namespace Tesses::Framework::Http } // namespace Tesses::Framework::Http
+21 -2
View File
@@ -58,22 +58,36 @@ RouteServer::RouteServer(std::shared_ptr<IHttpServer> root) : root(root) {}
void RouteServer::Add(std::string method, std::string pattern, void RouteServer::Add(std::string method, std::string pattern,
ServerRequestHandler handler) { ServerRequestHandler handler) {
mtx.Lock();
this->routes.emplace_back(pattern, method, handler); this->routes.emplace_back(pattern, method, handler);
mtx.Unlock();
} }
bool RouteServer::Handle(ServerContext &ctx) { bool RouteServer::Handle(ServerContext &ctx) {
auto pathArgs = ctx.pathArguments; auto pathArgs = ctx.pathArguments;
auto path = Tesses::Framework::Filesystem::VFSPath::ParseUriPath(ctx.path); auto path = Tesses::Framework::Filesystem::VFSPath::ParseUriPath(ctx.path);
bool mustUnlock = true;
mtx.Lock();
for (auto &svr : this->routes) { for (auto &svr : this->routes) {
if (svr.method != ctx.method && if (svr.method != ctx.method &&
!((svr.method == "GET" && ctx.method == "HEAD") || !((svr.method == "GET" && ctx.method == "HEAD") ||
(svr.method == "HEAD" && ctx.method == "GET"))) (svr.method == "HEAD" && ctx.method == "GET")))
continue; continue;
ctx.pathArguments = pathArgs; ctx.pathArguments = pathArgs;
if (svr.Equals(path, ctx.pathArguments) && svr.handler && if (svr.Equals(path, ctx.pathArguments) && svr.handler) {
svr.handler(ctx)) auto hndl = svr.handler;
mtx.Unlock();
if (hndl(ctx)) {
return true; return true;
} }
mustUnlock = false;
break;
}
}
if (mustUnlock)
mtx.Unlock();
ctx.pathArguments = pathArgs; ctx.pathArguments = pathArgs;
if (this->root) if (this->root)
@@ -104,4 +118,9 @@ void RouteServer::Query(std::string pattern, ServerRequestHandler handler) {
void RouteServer::Options(std::string pattern, ServerRequestHandler handler) { void RouteServer::Options(std::string pattern, ServerRequestHandler handler) {
Add("OPTIONS", pattern, handler); Add("OPTIONS", pattern, handler);
} }
void RouteServer::Clear() {
mtx.Lock();
this->routes.clear();
mtx.Unlock();
}
} // namespace Tesses::Framework::Http } // namespace Tesses::Framework::Http
+50 -6
View File
@@ -699,12 +699,56 @@ void ShellFileOrUrl(std::string fileOrUrl) {
#elif !defined(TESSESFRAMEWORK_ENABLE_PROCESS) #elif !defined(TESSESFRAMEWORK_ENABLE_PROCESS)
throw std::runtime_error("Process not enabled"); throw std::runtime_error("Process not enabled");
#elif defined(_WIN32) #elif defined(_WIN32)
auto exec = Tesses::Framework::Platform::Environment::GetRealExecutablePath( std::u16string wname;
(std::string) "cmd"); UTF16::FromUTF8(wname, fileOrUrl);
Process p(exec.ToString(), {"cmd", "/c", "start", fileOrUrl});
if (p.Start()) auto result = ShellExecuteW(NULL, L"open", (LPCWSTR)wname.c_str(), NULL,
if (p.WaitForExit() != 0) NULL, SW_SHOWNORMAL);
throw std::runtime_error("Exit code did not indicate success"); if ((INT_PTR)result <= 32) {
switch ((INT_PTR)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((intptr_t)result));
}
}
#elif defined(__APPLE__) #elif defined(__APPLE__)
auto exec = Tesses::Framework::Platform::Environment::GetRealExecutablePath( auto exec = Tesses::Framework::Platform::Environment::GetRealExecutablePath(
(std::string) "open"); (std::string) "open");
+57
View File
@@ -284,4 +284,61 @@ void BitConverter::FromSint16LE(uint8_t &b, int16_t v) {
memcpy(&dest, &v, sizeof(uint16_t)); memcpy(&dest, &v, sizeof(uint16_t));
FromUint16LE(b, dest); FromUint16LE(b, dest);
} }
bool BitConverter::TryParseSigned(const std::string &str, int64_t &val,
int base) {
try {
if (str.find_first_not_of(" \t") != 0)
return false;
size_t pos = 0;
int64_t v = std::stoll(str, &pos, base);
if (pos < str.size())
return false;
val = v;
return true;
} catch (std::out_of_range &arg) {
return false;
} catch (std::invalid_argument &arg) {
return false;
}
}
bool BitConverter::TryParseUnsigned(const std::string &str, uint64_t &val,
int base) {
try {
if (str.find_first_not_of(" \t") != 0)
return false;
if (str[0] == '-' || str[0] == '+')
return false;
size_t pos = 0;
uint64_t v = std::stoull(str, &pos, base);
if (pos < str.size())
return false;
val = v;
return true;
} catch (std::out_of_range &arg) {
return false;
} catch (std::invalid_argument &arg) {
return false;
}
}
bool BitConverter::TryParseDouble(const std::string &str, double &val) {
try {
if (str.find_first_not_of(" \t") != 0)
return false;
size_t pos = 0;
double v = std::stod(str, &pos);
if (pos < str.size())
return false;
val = v;
return true;
} catch (std::out_of_range &arg) {
return false;
} catch (std::invalid_argument &arg) {
return false;
}
}
} // namespace Tesses::Framework::Serialization } // namespace Tesses::Framework::Serialization
+429
View File
@@ -0,0 +1,429 @@
#include "TessesFramework/Serialization/Html.hpp"
#include "TessesFramework/Console.hpp"
#include "TessesFramework/Http/HttpUtils.hpp"
#include <unordered_set>
namespace Tesses::Framework::Serialization::Html {
static std::unordered_set<std::string> html_nodes_with_no_body = {
"area", "base", "br", "col", "embed", "hr", "img",
"input", "link", "meta", "source", "track", "wbr",
};
std::string HtmlNode::ToString() const {
std::string text;
HtmlNode::ToString(text);
return text;
}
void HtmlNode::InnerText(std::string &str) const {
if (this->isText) {
str += this->text_or_tag;
} else {
for (const auto &item : this->children)
item->InnerText(str);
}
}
std::string HtmlNode::InnerText() const {
std::string innerText;
InnerText(innerText);
return innerText;
}
void HtmlNode::ToString(std::string &str) const {
if (this->isText) {
str += Http::HttpUtils::HtmlEncode(this->text_or_tag);
} else {
str += "<";
str += this->text_or_tag;
for (auto &attr : this->attributes) {
str += ' ';
str += attr.first;
if (attr.second) {
str += "=\"";
str += Http::HttpUtils::HtmlEncode(attr.second.value());
str += "\"";
}
}
str += ">";
if (html_nodes_with_no_body.count(this->text_or_tag) == 0) {
for (auto &child : this->children)
child->ToString(str);
str += "</";
str += this->text_or_tag;
str += ">";
}
}
}
std::shared_ptr<HtmlNode> HtmlNode::FromText(std::string_view text) {
auto node = std::make_shared<HtmlNode>();
node->isText = true;
node->text_or_tag = std::string{text};
return node;
}
std::shared_ptr<HtmlNode> HtmlNode::FromNode(
std::string_view name,
std::initializer_list<std::pair<std::string, std::optional<std::string>>>
attrs,
std::initializer_list<std::shared_ptr<HtmlNode>> nodes) {
return HtmlNode::FromNode(name, attrs.begin(), attrs.end(), nodes.begin(),
nodes.end());
}
std::shared_ptr<HtmlNode> HtmlNode::FromNode(
std::string_view name,
std::vector<std::pair<std::string, std::optional<std::string>>> attrs,
std::vector<std::shared_ptr<HtmlNode>> nodes) {
return HtmlNode::FromNode(name, attrs.begin(), attrs.end(), nodes.begin(),
nodes.end());
}
void HtmlNode::FindNodes(
std::string_view tag,
std::vector<std::shared_ptr<const HtmlNode>> &nodes) const {
if (!this->isText) {
if (this->text_or_tag == tag)
nodes.push_back(this->shared_from_this());
for (const auto &node : this->children)
node->FindNodes(tag, nodes);
}
}
std::vector<std::shared_ptr<const HtmlNode>>
HtmlNode::FindNodes(std::string_view tag) const {
std::vector<std::shared_ptr<const HtmlNode>> nodes;
FindNodes(tag, nodes);
return nodes;
}
enum class HtmlLexTokenType { SYMBOL, TEXT, STRING };
struct HtmlLexToken {
std::string text;
HtmlLexTokenType type;
HtmlLexToken(HtmlLexTokenType type, std::string_view text)
: text(text), type(type) {}
};
struct HtmlLexer {
std::shared_ptr<TextStreams::TextReader> reader;
std::vector<HtmlLexToken> &tokens;
int32_t _peeked = -1;
std::string builder;
bool ignoreSpaceMode = false;
std::string nodeName = {};
HtmlLexer(std::shared_ptr<TextStreams::TextReader> reader,
std::vector<HtmlLexToken> &tokens)
: reader(std::move(reader)), tokens(tokens) {}
int32_t Peek() {
if (_peeked != -1)
return _peeked;
_peeked = Read();
return _peeked;
}
int64_t Read() {
if (_peeked != -1) {
auto tmp = _peeked;
_peeked = -1;
return tmp;
}
return reader->ReadChar();
}
void Flush() {
if (!builder.empty()) {
this->tokens.emplace_back(HtmlLexTokenType::TEXT, builder);
if (ignoreSpaceMode && nodeName.empty())
nodeName = builder;
builder = "";
}
}
void SkipTo(std::string_view fmt) {
if (fmt.empty())
return;
std::string window;
window.resize(fmt.size());
while (fmt != window) {
int32_t read = Read();
if (read == -1)
break;
for (size_t i = 0; i < window.size() - 1; i++) {
window[i] = window[i + 1];
}
window.back() = static_cast<char>(read);
}
}
void Lex() {
int read;
while ((read = Read()) != -1) {
int peek = Peek();
switch (read) {
case '"':
case '\'': {
if (ignoreSpaceMode) {
Flush();
char quote = static_cast<char>(read);
std::string val;
int c;
while ((c = Read()) != -1 && c != quote)
val += static_cast<char>(c);
tokens.emplace_back(HtmlLexTokenType::STRING, val);
} else {
builder += static_cast<char>(read);
}
break;
}
case '/': {
if (ignoreSpaceMode && peek == '>') {
Flush();
Read();
ignoreSpaceMode = false;
tokens.emplace_back(HtmlLexTokenType::SYMBOL, "/>");
} else if (ignoreSpaceMode) {
builder += '/';
} else {
builder += '/';
}
break;
}
case '>': {
if (ignoreSpaceMode) {
Flush();
ignoreSpaceMode = false;
tokens.emplace_back(HtmlLexTokenType::SYMBOL, ">");
if (nodeName == "script" || nodeName == "style") {
nodeName = "_";
std::string builder2 = "";
bool eof = false;
while ((read = Read()) != -1) {
if (read == '<') {
peek = Peek();
if (peek == '/') {
Read();
break;
} else if (peek == -1) {
eof = true;
break;
} else {
builder2 += '<';
}
} else {
builder2 += static_cast<char>(read);
}
}
if (read != -1 && !eof) {
tokens.emplace_back(HtmlLexTokenType::TEXT,
builder2);
ignoreSpaceMode = true;
tokens.emplace_back(HtmlLexTokenType::SYMBOL, "</");
} else {
return;
}
}
} else {
builder += '>';
}
} break;
case '<': {
Flush();
if (peek == '!') {
//<!-- -->
//<!DOCTYPE html>
Read();
peek = Peek();
if (peek == '-') {
Read();
peek = Peek();
if (peek == '-') {
Read();
SkipTo("-->");
} else {
SkipTo(">");
}
} else {
SkipTo(">");
}
} else if (peek == '/') {
Read();
ignoreSpaceMode = true;
tokens.emplace_back(HtmlLexTokenType::SYMBOL, "</");
} else {
ignoreSpaceMode = true;
tokens.emplace_back(HtmlLexTokenType::SYMBOL, "<");
nodeName = {};
}
} break;
case '=': {
if (!ignoreSpaceMode) {
builder += "=";
} else {
Flush();
tokens.emplace_back(HtmlLexTokenType::SYMBOL, "=");
}
} break;
case ' ':
case '\n':
case '\t':
case '\r':
if (!ignoreSpaceMode) {
builder += static_cast<char>(read);
} else {
Flush();
}
break;
default:
builder += static_cast<char>(read);
break;
}
}
Flush();
}
};
struct HtmlParser {
std::vector<HtmlLexToken> &tokens;
size_t index = 0;
HtmlParser(std::vector<HtmlLexToken> &tokens) : tokens(tokens) {}
bool IsSymbol(std::string_view type) {
if (Done())
return false;
auto &obj = Peak();
if (obj.type != HtmlLexTokenType::SYMBOL)
return false;
if (obj.text != type)
return false;
index++;
return true;
}
bool Done() { return index >= tokens.size(); }
HtmlLexToken &Read() { return tokens[index++]; }
HtmlLexToken &Peak() { return tokens[index]; }
void Parse(std::string_view name, std::shared_ptr<HtmlNode> &node,
bool root = false) {
while (!Done()) {
auto &curToken = Peak();
switch (curToken.type) {
case HtmlLexTokenType::TEXT: {
Read();
node->children.push_back(HtmlNode::FromText(
Http::HttpUtils::HtmlDecode(curToken.text)));
} break;
case HtmlLexTokenType::SYMBOL: {
if (curToken.text == "<") {
Read();
if (Done()) {
return;
}
auto &val = Peak();
if (val.type == HtmlLexTokenType::TEXT) {
Read();
std::shared_ptr<HtmlNode> newNode =
std::make_shared<HtmlNode>();
newNode->isText = false;
newNode->text_or_tag = val.text;
while (!Done()) {
auto &k = Peak();
if (k.type == HtmlLexTokenType::TEXT) {
Read();
if (IsSymbol("=")) {
if (!Done()) {
auto &v = Peak();
if (v.type ==
HtmlLexTokenType::STRING) {
Read();
newNode->attributes.emplace_back(
k.text,
Http::HttpUtils::HtmlDecode(
v.text));
continue;
}
}
}
newNode->attributes.emplace_back(k.text,
std::nullopt);
continue;
}
if (k.type == HtmlLexTokenType::SYMBOL &&
(k.text == ">" || k.text == "/>"))
break;
}
if (!IsSymbol("/>")) {
IsSymbol(">");
if (html_nodes_with_no_body.count(val.text) == 0)
Parse(val.text, newNode);
}
node->children.push_back(newNode);
}
} else if (curToken.text == "</") {
Read();
if (Done()) {
node->children.push_back(HtmlNode::FromText("</"));
return;
}
auto &val = Peak();
Read();
IsSymbol(">");
if (val.text == name && !root)
return;
}
} break;
}
}
}
std::shared_ptr<HtmlNode> Parse() {
std::shared_ptr<HtmlNode> node = std::make_shared<HtmlNode>();
node->isText = false;
node->text_or_tag = ":root";
Parse(":root", node, true);
return node;
}
};
std::shared_ptr<HtmlNode>
Parse(std::shared_ptr<TextStreams::TextReader> reader) {
std::vector<HtmlLexToken> tokens;
HtmlLexer lexer(reader, tokens);
lexer.Lex();
HtmlParser parser(tokens);
return parser.Parse();
}
} // namespace Tesses::Framework::Serialization::Html
+50 -2
View File
@@ -43,12 +43,15 @@ using HttpUtils = Tesses::Framework::Http::HttpUtils;
#else #else
#if defined(_WIN32) #if defined(_WIN32)
#include <iphlpapi.h> // clang-format off
#include <winsock2.h> #include <winsock2.h>
#include <iphlpapi.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#include <windows.h> #include <windows.h>
// clang-format on
#if defined __has_include #if defined __has_include
#if __has_include(<afunix.h>) #if __has_include(<afunix.h>)
#include <afunix.h> #include <afunix.h>
@@ -72,8 +75,8 @@ extern "C" {
!defined(__PS2__) !defined(__PS2__)
#include <sys/un.h> #include <sys/un.h>
#endif #endif
#include <poll.h> #include <poll.h>
#include <sys/time.h>
} }
#endif #endif
#if defined(GEKKO) #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_GETADDRINFO getaddrinfo
#define NETWORK_FREEADDRINFO freeaddrinfo #define NETWORK_FREEADDRINFO freeaddrinfo
#define NETWORK_GETSOCKNAME getsockname #define NETWORK_GETSOCKNAME getsockname
#define NETWORK_SHUTDOWN shutdown
#if defined(_WIN32) #if defined(_WIN32)
#define NETWORK_CLOSE closesocket #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 0;
return (size_t)sz2; 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() { void NetworkStream::Close() {
if (this->owns && this->success) if (this->owns && this->success)
NETWORK_CLOSE(this->sock); NETWORK_CLOSE(this->sock);
@@ -858,6 +889,22 @@ void NetworkStream::SetNoDelay(bool noDelay) {
(const char *)&noDelay2, (socklen_t)sizeof(noDelay2)); (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 } // namespace Tesses::Framework::Streams
#else #else
namespace Tesses::Framework::Streams { namespace Tesses::Framework::Streams {
@@ -911,6 +958,7 @@ NetworkStream::GetIPs(bool ipV6) {
} }
NetworkStream::~NetworkStream() {} NetworkStream::~NetworkStream() {}
void NetworkStream::SetNoDelay(bool noDelay) {} void NetworkStream::SetNoDelay(bool noDelay) {}
void NetworkStream::Shutdown(StreamShutdownMode mode) {}
void NetworkStream::Close() {} void NetworkStream::Close() {}
uint16_t NetworkStream::GetPort() { return 0; } uint16_t NetworkStream::GetPort() { return 0; }
uint16_t TcpServer::GetPort() { return 0; } uint16_t TcpServer::GetPort() { return 0; }
+18
View File
@@ -117,4 +117,22 @@ void Stream::CopyTo(std::shared_ptr<Stream> strm, size_t buffSize) {
strm->Flush(); strm->Flush();
} }
Stream::~Stream() {} 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 } // namespace Tesses::Framework::Streams
+5 -9
View File
@@ -521,7 +521,7 @@ std::string TF_GetExecutableName() {
if (sysctl(mib, 4, path.data(), &len, NULL, 0) < 0) { if (sysctl(mib, 4, path.data(), &len, NULL, 0) < 0) {
return ""; return "";
} }
path.resize(strlen(path.c_str())); path.resize(len - 1);
return path; return path;
#elif defined(__NetBSD__) #elif defined(__NetBSD__)
auto path = Filesystem::LocalFS->ReadLink( auto path = Filesystem::LocalFS->ReadLink(
@@ -534,18 +534,14 @@ std::string TF_GetExecutableName() {
return path.ToString(); return path.ToString();
#elif TARGET_OS_MAC && !TARGET_OS_IPHONE #elif TARGET_OS_MAC && !TARGET_OS_IPHONE
std::string path; std::string path;
path.resize(1025); uint32_t bufsize = 0;
uint32_t bufsize = (uint32_t)path.size(); _NSGetExecutablePath(NULL, &bufsize);
if (_NSGetExecutablePath(path.data(), &bufsize) == 0) {
path.resize(strlen(path.c_str()));
return path;
} else {
path.resize(bufsize); path.resize(bufsize);
if (_NSGetExecutablePath(path.data(), &bufsize) == 0) { if (_NSGetExecutablePath(path.data(), &bufsize) == 0) {
path.resize(strlen(path.c_str())); path.resize(bufsize - 1);
return path; return path;
} }
}
#endif #endif
return ""; return "";
+102 -7
View File
@@ -34,26 +34,45 @@ namespace Tesses::Framework::Threading {
class MutexHiddenFieldData : public HiddenFieldData { class MutexHiddenFieldData : public HiddenFieldData {
public: public:
#if defined(_WIN32) #if defined(_WIN32)
HANDLE mtx; CRITICAL_SECTION mtx;
#else #else
pthread_mutex_t mtx; pthread_mutex_t mtx;
pthread_mutexattr_t attr; pthread_mutexattr_t attr;
#endif #endif
~MutexHiddenFieldData() { ~MutexHiddenFieldData() {
#if defined(_WIN32) #if defined(_WIN32)
CloseHandle(mtx); DeleteCriticalSection(&mtx);
#else #else
pthread_mutex_destroy(&mtx); pthread_mutex_destroy(&mtx);
pthread_mutexattr_destroy(&attr); pthread_mutexattr_destroy(&attr);
#endif #endif
} }
}; };
class MutexCondHiddenFieldData : public HiddenFieldData {
public:
#if defined(_WIN32)
CONDITION_VARIABLE cond;
#else
pthread_cond_t cond;
#endif #endif
~MutexCondHiddenFieldData() {
#if defined(_WIN32)
#else
pthread_cond_destroy(&cond);
#endif
}
};
#endif
Mutex::Mutex() { Mutex::Mutex() {
#if defined(TESSESFRAMEWORK_ENABLE_THREADING) #if defined(TESSESFRAMEWORK_ENABLE_THREADING)
auto md = this->data.AllocField<MutexHiddenFieldData>(); auto md = this->data.AllocField<MutexHiddenFieldData>();
#if defined(_WIN32) #if defined(_WIN32)
md->mtx = CreateMutex(NULL, false, NULL); InitializeCriticalSection(&(md->mtx));
#else #else
pthread_mutexattr_init(&md->attr); pthread_mutexattr_init(&md->attr);
pthread_mutexattr_settype(&md->attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutexattr_settype(&md->attr, PTHREAD_MUTEX_RECURSIVE);
@@ -66,7 +85,7 @@ void Mutex::Lock() {
#if defined(TESSESFRAMEWORK_ENABLE_THREADING) #if defined(TESSESFRAMEWORK_ENABLE_THREADING)
auto md = this->data.GetField<MutexHiddenFieldData *>(); auto md = this->data.GetField<MutexHiddenFieldData *>();
#if defined(_WIN32) #if defined(_WIN32)
WaitForSingleObject(md->mtx, INFINITE); EnterCriticalSection(&(md->mtx));
#else #else
pthread_mutex_lock(&md->mtx); pthread_mutex_lock(&md->mtx);
@@ -77,7 +96,7 @@ void Mutex::Unlock() {
#if defined(TESSESFRAMEWORK_ENABLE_THREADING) #if defined(TESSESFRAMEWORK_ENABLE_THREADING)
auto md = this->data.GetField<MutexHiddenFieldData *>(); auto md = this->data.GetField<MutexHiddenFieldData *>();
#if defined(_WIN32) #if defined(_WIN32)
ReleaseMutex(md->mtx); LeaveCriticalSection(&(md->mtx));
#else #else
pthread_mutex_unlock(&md->mtx); pthread_mutex_unlock(&md->mtx);
#endif #endif
@@ -87,12 +106,88 @@ bool Mutex::TryLock() {
#if defined(TESSESFRAMEWORK_ENABLE_THREADING) #if defined(TESSESFRAMEWORK_ENABLE_THREADING)
auto md = this->data.GetField<MutexHiddenFieldData *>(); auto md = this->data.GetField<MutexHiddenFieldData *>();
#if defined(_WIN32) #if defined(_WIN32)
return WaitForSingleObject(md->mtx, 100) == WAIT_OBJECT_0; return TryEnterCriticalSection(&md->mtx) != FALSE;
#else #else
return pthread_mutex_trylock(&md->mtx) == 0; return pthread_mutex_trylock(&md->mtx) == 0;
#endif #endif
#endif #endif
} }
Mutex::~Mutex() {} 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 }; // namespace Tesses::Framework::Threading