Fix license compliance and fix things up, add reverse proxy, fix some security flaws with HttpUtils
Some checks failed
Build and Deploy on Tag / 🔨 Build for everything else (git.tesses.org/tesses50/linux-arm64:latest, aarch64-linux-gnu, arm64) (push) Successful in 2m37s
Build and Deploy on Tag / 🔨 Build for everything else (git.tesses.org/tesses50/linux-arm:latest, arm-linux-gnueabihf, arm7) (push) Successful in 2m37s
Build and Deploy on Tag / 🔨 Build for PowerPC (push) Successful in 2m58s
Build and Deploy on Tag / 🔨 Build win32 and update the tap 🍺 (push) Failing after 3m36s
Build and Deploy on Tag / 🔨 Build for everything else (git.tesses.org/tesses50/linux-riscv64:latest, riscv64-linux-gnu, riscv64) (push) Successful in 2m19s
Build and Deploy on Tag / 🔨 Build for everything else (git.tesses.org/tesses50/linux-x64:latest, x86_64-linux-gnu, amd64) (push) Successful in 2m21s
Build and Deploy on Tag / 🔨 Build for everything else (git.tesses.org/tesses50/linux-x86:latest, i386-linux-gnu, 386) (push) Successful in 2m6s

This commit is contained in:
2026-08-31 20:57:18 -05:00
parent 313e75b14c
commit 3bce736834
41 changed files with 2227 additions and 384 deletions

View File

@@ -11,7 +11,7 @@ env:
jobs: jobs:
build-ppc: build-ppc:
name: "🔨 Build for PowerPC" name: "🔨 Build for PowerPC"
runs-on: ubuntu-latest runs-on: fw13-ubuntu-latest
container: container:
image: git.tesses.org/tesses50/linux-ppc:latest image: git.tesses.org/tesses50/linux-ppc:latest
steps: steps:
@@ -24,7 +24,38 @@ jobs:
prerelease: true prerelease: true
files: |- files: |-
artifacts/** artifacts/**
update-tap-and-build-win32:
name: "🔨 Build win32 and update the tap 🍺"
runs-on: fw13-ubuntu-latest
container: git.tesses.org/tesses50/global-containers-mingw:latest
steps:
- uses: actions/checkout@v4
- run: |
mkdir artifacts
env -C Packaging/WinToolsIntel bash build.sh
- uses: akkuman/gitea-release-action@v1
env:
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
with:
prerelease: true
files: |-
artifacts/**
- uses: actions/checkout@v4
with:
ref: "master"
path: "tapdir"
repository: "tesses50/tesses-tap.git"
token: ${{ env.PACKAGE_AND_BREW }}
- run: |
cd tapdir
bash ../Packaging/edit-formula.sh
git config user.name "Tesses Gitea Bot"
git config user.email "noreply@tesses.net"
git add .
git commit -m "Push tessesframework=${{ env.VERSION }}"
git push
build-packages: build-packages:
name: "🔨 Build for everything else" name: "🔨 Build for everything else"
runs-on: fw13-ubuntu-latest runs-on: fw13-ubuntu-latest
@@ -61,34 +92,4 @@ jobs:
files: |- files: |-
artifacts/** artifacts/**
update-tap-and-build-win32:
name: "🔨 Build win32 and update the tap 🍺"
runs-on: global-container-mingw
steps:
- uses: actions/checkout@v4
- run: |
mkdir artifacts
env -C Packaging/WinToolsIntel bash build.sh
- uses: akkuman/gitea-release-action@v1
env:
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
with:
prerelease: true
files: |-
artifacts/**
- uses: actions/checkout@v4
with:
ref: "master"
path: "tapdir"
repository: "tesses50/tesses-tap.git"
token: ${{ env.PACKAGE_AND_BREW }}
- run: |
cd tapdir
bash ../Packaging/edit-formula.sh
git config user.name "Tesses Gitea Bot"
git config user.email "noreply@tesses.net"
git add .
git commit -m "Push tessesframework=${{ env.VERSION }}"
git push

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

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;
}

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;
} }

View File

@@ -1,7 +1,7 @@
# Changelog # Changelog
## 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

View File

@@ -66,7 +66,11 @@ 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}")
endif() endif()

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

View File

@@ -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();
}; };

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

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) {

View File

@@ -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);

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;
} }

View File

@@ -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

View File

@@ -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

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);
}; };

View File

@@ -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

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 {
@@ -169,30 +180,40 @@ 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); static std::string HtmlP(std::string_view text);
static std::vector<std::string> static void SplitString(std::vector<std::string> &out,
SplitString(std::string text, std::string delimiter, 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}; };

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"

View File

@@ -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

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

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)

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;

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
src/Http/DomainServer.cpp Normal file
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

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;
} }

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;
} }

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

View File

@@ -51,6 +51,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 +288,7 @@ class WSServer {
return; return;
} }
if (!ctx->requestHeaders.AnyEquals("Upgrade", "websocket")) { if (!ctx->requestHeaders.AnyEqualsCSV("Upgrade", "websocket")) {
return; return;
} }
@@ -647,7 +652,7 @@ void HttpServer::StartAccepting() {
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");
@@ -904,10 +909,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;
} }
@@ -985,6 +986,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 +1003,21 @@ ServerContext &ServerContext::WriteHeaders() {
return *this; return *this;
} }
static void sanitise_path(std::string &path) {
bool endsWithSlash = !path.empty() ? (path.back() == '/') : false;
Tesses::Framework::Filesystem::VFSPath path2 = path;
for (auto ittr = path2.path.begin(); ittr != path2.path.end(); ittr++) {
if (*ittr == ".." || *ittr == ".") {
path2.path.erase(ittr);
ittr--;
}
}
path = path2.ToString();
if (endsWithSlash)
path += '/';
}
void HttpServer::Process(std::shared_ptr<Stream> strm, 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,
@@ -1042,6 +1061,7 @@ void HttpServer::Process(std::shared_ptr<Stream> strm,
pp.resize(2); pp.resize(2);
ctx.originalPath = pp[0]; ctx.originalPath = pp[0];
sanitise_path(ctx.originalPath);
ctx.path = ctx.originalPath; ctx.path = ctx.originalPath;
TF_LOG(ctx.method + " with path " + ctx.path); TF_LOG(ctx.method + " with path " + ctx.path);
@@ -1074,6 +1094,29 @@ 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" &&

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);
} }
} }

View File

@@ -162,17 +162,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 +184,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 +223,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"},
{".txt", "text/plain"},
{".log", "text/plain"},
{".twss", "text/twss"},
{".css", "text/css"},
{".js", "text/javascript"},
{".mjs", "text/javascript"},
{".json", "application/json"},
{".map", "application/json"},
{".pdf", "application/pdf"},
{".png", "image/png"},
{".jpg", "image/jpeg"},
{".jpeg", "image/jpeg"},
{".gif", "image/gif"},
{".webp", "image/webp"},
{".svg", "image/svg+xml"},
{".ico", "image/x-icon"},
{".bmp", "image/bmp"},
{".avif", "image/avif"},
{".heic", "image/heic"},
{".tif", "image/tiff"},
{".tiff", "image/tiff"},
{".mp4", "video/mp4"},
{".webm", "video/webm"},
{".mov", "video/quicktime"},
{".mkv", "video/x-matroska"},
{".avi", "video/x-msvideo"},
{".ts", "video/mp2t"},
{".mp3", "audio/mpeg"},
{".m4a", "audio/mp4"},
{".ogg", "audio/ogg"},
{".oga", "audio/ogg"},
{".opus", "audio/ogg"},
{".wav", "audio/wav"},
{".flac", "audio/flac"},
{".aac", "audio/aac"},
{".weba", "audio/webm"},
{".woff", "font/woff"},
{".woff2", "font/woff2"},
{".ttf", "font/ttf"},
{".zip", "application/zip"},
{".gz", "application/gzip"},
{".7z", "application/x-7z-compressed"},
{".rar", "application/vnd.rar"},
{".csv", "text/csv"},
{".vtt", "text/vtt"},
{".vcf", "text/v-card"},
{".rss", "application/xml"},
{".xml", "application/xml"},
{".atom", "application/xml"},
{".rdf", "application/xml"},
{".wasm", "application/wasm"},
{".webmanifest", "application/manifest+json"},
{".m3u8", "application/vnd.apple.mpegurl"},
{".mpd", "application/dash+xml"},
{".md", "text/markdown"},
{".epub", "application/epub+zip"},
{".ics", "text/calendar"},
{".apk", "application/vnd.android.package-archive"},
{".crvm", "application/crvm"},
{".tcross", "text/tcross"},
};
std::string Get(const std::string &ext) {
Threading::LockGuard lg(mtx);
std::string mime;
if (map.count(ext) != 0)
mime = map[ext];
if (mime.empty())
return "application/octet-stream";
return mime;
} }
if (ext == ".txt" || ext == ".log" || ext == ".twss") { void Add(const std::string &ext, const std::string &mime) {
return "text/plain"; Threading::LockGuard lg(mtx);
map[ext] = mime;
} }
if (ext == ".woff") { };
return "application/x-font-woff";
static mimetype_reg mime_reg;
std::string HttpUtils::GetMimeType(const std::string &ext) {
return mime_reg.Get(ext);
} }
if (ext == ".vtt") { std::string HttpUtils::GetMimeTypePath(const Filesystem::VFSPath &p) {
return "text/vtt"; return GetMimeType(p.GetExtension());
} }
if (ext == ".svg") { void HttpUtils::AddMimeType(const std::string &ext, const std::string &mime) {
return "image/svg+xml"; mime_reg.Add(ext, mime);
} }
if (ext == ".webp") { void HttpUtils::AddMimeTypePath(const Filesystem::VFSPath &p,
return "image/webp"; const std::string &mime) {
} AddMimeType(p.GetExtension(), mime);
if (ext == ".vcf") {
return "text/v-card";
}
if (ext == ".rss" || ext == ".xml" || ext == ".atom" || ext == ".rdf") {
return "application/xml";
}
if (ext == ".js") {
return "text/javascript";
}
if (ext == ".json") {
return "application/json";
}
if (ext == ".wasm") {
return "application/wasm";
}
if (ext == ".png") {
return "image/png";
}
if (ext == ".jpg" || ext == ".jpeg") {
return "image/jpeg";
}
if (ext == ".css") {
return "text/css";
}
if (ext == ".gif") {
return "image/gif";
}
if (ext == ".mp4") {
return "video/mp4";
}
if (ext == ".mov") {
return "video/quicktime";
}
if (ext == ".m4a") {
return "audio/mp4";
}
if (ext == ".webm") {
return "video/webm";
}
if (ext == ".webmanifest") {
return "application/manifest+json";
}
if (ext == ".ico") {
return "image/x-icon";
} }
return "application/octet-stream";
}
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 +349,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 +359,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 +372,7 @@ void HttpUtils::QueryParamsDecode(HttpDictionary &dict, std::string query) {
} }
} }
} }
std::string HttpUtils::Join(std::string joinStr, std::string HttpUtils::Join(std::string_view joinStr,
std::vector<std::string> ents) { std::vector<std::string> ents) {
std::string str = {}; std::string str = {};
bool first = true; bool first = true;
@@ -368,41 +402,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,134 +446,87 @@ 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 = "";
@@ -589,7 +578,7 @@ std::string HttpUtils::HtmlP(std::string text) {
return newText; return newText;
} }
std::string HttpUtils::HtmlEncode(std::string html) { 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 +725,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 +739,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) {
@@ -848,20 +870,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 +1000,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;

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

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

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)result) {
case 0:
throw std::runtime_error(
"The operating system is out of memory or resources.");
case 2:
throw std::runtime_error(
"The specified file was not found (SE_ERR_FNF).");
case 3:
throw std::runtime_error(
"The specified path was not found (SE_ERR_PNF).");
case 5:
throw std::runtime_error("Access denied (SE_ERR_ACCESSDENIED).");
case 8:
throw std::runtime_error("Out of memory (SE_ERR_OOM).");
case 11:
throw std::runtime_error(
"Bad executable format (ERROR_BAD_FORMAT).");
case 26:
throw std::runtime_error(
"Sharing violation occurred (SE_ERR_SHARE).");
case 27:
throw std::runtime_error("File association incomplete or invalid "
"(SE_ERR_ASSOCINCOMPLETE).");
case 28:
throw std::runtime_error(
"DDE transaction timed out (SE_ERR_DDETIMEOUT).");
case 29:
throw std::runtime_error(
"DDE transaction failed (SE_ERR_DDEFAIL).");
case 30:
throw std::runtime_error("DDE transaction busy (SE_ERR_DDEBUSY).");
case 31:
throw std::runtime_error(
"No application associated with file extension "
"(SE_ERR_NOASSOC).");
case 32:
throw std::runtime_error("Dynamic-link library (DLL) not found "
"(SE_ERR_DLLNOTFOUND).");
default:
throw std::runtime_error("Unknown error: " +
std::to_string((int)result));
}
}
#elif defined(__APPLE__) #elif defined(__APPLE__)
auto exec = Tesses::Framework::Platform::Environment::GetRealExecutablePath( auto exec = Tesses::Framework::Platform::Environment::GetRealExecutablePath(
(std::string) "open"); (std::string) "open");

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; }

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

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 "";

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