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
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:
@@ -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
|
|
||||||
|
|||||||
2
Doxyfile
2
Doxyfile
@@ -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
|
||||||
|
|||||||
33
apps/tsimplereverseproxy.cpp
Normal file
33
apps/tsimplereverseproxy.cpp
Normal 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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -26,9 +26,23 @@
|
|||||||
|
|
||||||
namespace Tesses::Framework::Date {
|
namespace Tesses::Framework::Date {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the time zone in seconds from UTC
|
||||||
|
*
|
||||||
|
* @return int the utc distance in seconds, west
|
||||||
|
*/
|
||||||
int GetTimeZone();
|
int GetTimeZone();
|
||||||
|
/**
|
||||||
|
* @brief Does the timezone support daylight savings
|
||||||
|
*
|
||||||
|
* @return true yes
|
||||||
|
* @return false no
|
||||||
|
*/
|
||||||
bool TimeZoneSupportDST();
|
bool TimeZoneSupportDST();
|
||||||
|
/**
|
||||||
|
* @brief Stores a DateTime
|
||||||
|
*
|
||||||
|
*/
|
||||||
class DateTime {
|
class DateTime {
|
||||||
int year = 1970;
|
int year = 1970;
|
||||||
int month = 1;
|
int month = 1;
|
||||||
@@ -41,83 +55,413 @@ class DateTime {
|
|||||||
void FromEpochNoConvert(int64_t gmt);
|
void FromEpochNoConvert(int64_t gmt);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Date Time object with Jan 1, 1970 at 12:00AM UTC
|
||||||
|
*
|
||||||
|
*/
|
||||||
DateTime();
|
DateTime();
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Date Time object
|
||||||
|
*
|
||||||
|
* @param year the year
|
||||||
|
* @param month the month (1-12)
|
||||||
|
* @param day the day (1-31)
|
||||||
|
* @param hour the hour (0-23)
|
||||||
|
* @param minute the minute (0-59)
|
||||||
|
* @param seconds the second (0-59)
|
||||||
|
* @param isLocal true: your timezone, false: utc
|
||||||
|
*/
|
||||||
DateTime(int year, int month, int day, int hour, int minute, int seconds,
|
DateTime(int year, int month, int day, int hour, int minute, int seconds,
|
||||||
bool isLocal = true);
|
bool isLocal = true);
|
||||||
|
/**
|
||||||
|
* @brief Construct a new DateTime object
|
||||||
|
*
|
||||||
|
* @param epoch seconds since Jan 1, 1970 at 12:00AM UTC
|
||||||
|
*/
|
||||||
DateTime(int64_t epoch);
|
DateTime(int64_t epoch);
|
||||||
|
/**
|
||||||
|
* @brief Get the year
|
||||||
|
*
|
||||||
|
* @return int ex 1992 means the year is 1992
|
||||||
|
*/
|
||||||
int Year() const;
|
int Year() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the month (1-12)
|
||||||
|
*
|
||||||
|
* @return int ex 8 means august
|
||||||
|
*/
|
||||||
int Month() const;
|
int Month() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the day (1-31)
|
||||||
|
*
|
||||||
|
* @return int ex 20 means the day is the 20th
|
||||||
|
*/
|
||||||
int Day() const;
|
int Day() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the hour (0-23)
|
||||||
|
*
|
||||||
|
* @return int ex 12 means noon, 0 means midnight, 18 means 6 PM
|
||||||
|
*/
|
||||||
int Hour() const;
|
int Hour() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the minute (0-59)
|
||||||
|
*
|
||||||
|
* @return int ex 15 means quarter after the hour
|
||||||
|
*/
|
||||||
int Minute() const;
|
int Minute() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the second (0-59)
|
||||||
|
*
|
||||||
|
* @return int the seconds value
|
||||||
|
*/
|
||||||
int Second() const;
|
int Second() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the day of week (0-6)
|
||||||
|
*
|
||||||
|
* @retval 0 Sunday
|
||||||
|
* @retval 1 Monday
|
||||||
|
* @retval 2 Tuesday
|
||||||
|
* @retval 3 Wednesday
|
||||||
|
* @retval 4 Thursday
|
||||||
|
* @retval 5 Friday
|
||||||
|
* @retval 6 Saturday
|
||||||
|
*/
|
||||||
int DayOfWeek() const;
|
int DayOfWeek() const;
|
||||||
|
/**
|
||||||
|
* @brief Is local timezone
|
||||||
|
*
|
||||||
|
* @return true yes
|
||||||
|
* @return false no
|
||||||
|
*/
|
||||||
bool IsLocal() const;
|
bool IsLocal() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the time as epoch
|
||||||
|
*
|
||||||
|
* @return int64_t seconds since Jan 1, 1970 at 12:00AM UTC
|
||||||
|
*/
|
||||||
int64_t ToEpoch() const;
|
int64_t ToEpoch() const;
|
||||||
|
/**
|
||||||
|
* @brief Convert this time to local time
|
||||||
|
*
|
||||||
|
* @return DateTime this time in local time
|
||||||
|
*/
|
||||||
DateTime ToLocal() const;
|
DateTime ToLocal() const;
|
||||||
|
/**
|
||||||
|
* @brief Convert this time to utc time
|
||||||
|
*
|
||||||
|
* @return DateTime this time in UTC time
|
||||||
|
*/
|
||||||
DateTime ToUTC() const;
|
DateTime ToUTC() const;
|
||||||
|
/**
|
||||||
|
* @brief Set this time to local
|
||||||
|
*
|
||||||
|
*/
|
||||||
void SetToLocal();
|
void SetToLocal();
|
||||||
|
/**
|
||||||
|
* @brief Set this time to UTC
|
||||||
|
*
|
||||||
|
*/
|
||||||
void SetToUTC();
|
void SetToUTC();
|
||||||
|
/**
|
||||||
|
* @brief Set the current year
|
||||||
|
*
|
||||||
|
* @param y year
|
||||||
|
*/
|
||||||
void SetYear(int y);
|
void SetYear(int y);
|
||||||
|
/**
|
||||||
|
* @brief Set the current month (1-12)
|
||||||
|
*
|
||||||
|
* @param m month
|
||||||
|
*/
|
||||||
void SetMonth(int m);
|
void SetMonth(int m);
|
||||||
|
/**
|
||||||
|
* @brief Set the current day (1-31)
|
||||||
|
*
|
||||||
|
* @param d day
|
||||||
|
*/
|
||||||
void SetDay(int d);
|
void SetDay(int d);
|
||||||
|
/**
|
||||||
|
* @brief Set the current hour (0-23)
|
||||||
|
*
|
||||||
|
* @param h hour
|
||||||
|
*/
|
||||||
void SetHour(int h);
|
void SetHour(int h);
|
||||||
|
/**
|
||||||
|
* @brief Set the current minute (0-59)
|
||||||
|
*
|
||||||
|
* @param m minute
|
||||||
|
*/
|
||||||
void SetMinute(int m);
|
void SetMinute(int m);
|
||||||
|
/**
|
||||||
|
* @brief Set the current second (0-59)
|
||||||
|
*
|
||||||
|
* @param s second
|
||||||
|
*/
|
||||||
void SetSecond(int s);
|
void SetSecond(int s);
|
||||||
|
/**
|
||||||
|
* @brief Set whether this time is local or utc
|
||||||
|
*
|
||||||
|
* @param local true set this time to localtime or false set this time to
|
||||||
|
* utc
|
||||||
|
*/
|
||||||
void SetLocal(bool local);
|
void SetLocal(bool local);
|
||||||
|
/**
|
||||||
|
* @brief Set the seconds since Jan 1, 1970 at 12:00AM UTC
|
||||||
|
*
|
||||||
|
* @param epoch seconds since Jan 1, 1970 at 12:00AM UTC
|
||||||
|
*/
|
||||||
void Set(int64_t epoch);
|
void Set(int64_t epoch);
|
||||||
|
/**
|
||||||
|
* @brief Set the time of this DateTime object
|
||||||
|
*
|
||||||
|
* @param year the year
|
||||||
|
* @param month the month (1-12)
|
||||||
|
* @param day the day (1-31)
|
||||||
|
* @param hour the hour (0-23)
|
||||||
|
* @param minute the minute (0-59)
|
||||||
|
* @param seconds the second (0-59)
|
||||||
|
* @param isLocal true: your timezone, false: utc
|
||||||
|
*/
|
||||||
void Set(int year, int month, int day, int hour, int minute, int seconds,
|
void Set(int year, int month, int day, int hour, int minute, int seconds,
|
||||||
bool isLocal = true);
|
bool isLocal = true);
|
||||||
|
/**
|
||||||
|
* @brief Set this DateTime to right now in your timezone
|
||||||
|
*
|
||||||
|
*/
|
||||||
void SetToNow();
|
void SetToNow();
|
||||||
|
/**
|
||||||
|
* @brief Set this DateTime to right now in utc
|
||||||
|
*
|
||||||
|
*/
|
||||||
void SetToNowUTC();
|
void SetToNowUTC();
|
||||||
|
/**
|
||||||
|
* @brief Construct a datetime from right now in your timezone
|
||||||
|
*
|
||||||
|
* @return DateTime the current time object
|
||||||
|
*/
|
||||||
static DateTime Now();
|
static DateTime Now();
|
||||||
|
/**
|
||||||
|
* @brief Construct a datetime from right now in your timezone
|
||||||
|
*
|
||||||
|
* @return DateTime the current time object
|
||||||
|
*/
|
||||||
static DateTime NowUTC();
|
static DateTime NowUTC();
|
||||||
|
/**
|
||||||
|
* @brief Convert date to string same as ToString("%Y/%m/%d %H:%M:%S")
|
||||||
|
*
|
||||||
|
* @return std::string the date as string
|
||||||
|
*/
|
||||||
std::string ToString() const;
|
std::string ToString() const;
|
||||||
|
/**
|
||||||
|
* @brief Convert date to string with your own fmt
|
||||||
|
*
|
||||||
|
* @param fmt the format, see:
|
||||||
|
* https://git.tesses.org/tesses50/tessesframework/wiki/DateTime_ToString_Formating
|
||||||
|
* for more details
|
||||||
|
* @return std::string the date as a string based on fmt
|
||||||
|
*/
|
||||||
std::string ToString(std::string fmt) const;
|
std::string ToString(std::string fmt) const;
|
||||||
|
/**
|
||||||
|
* @brief Format as IMF-fixdate (RFC 9110 §5.6.7)
|
||||||
|
*
|
||||||
|
* Example: "Tue, 01 Sep 2026 18:07:05 GMT"
|
||||||
|
*/
|
||||||
std::string ToHttpDate() const;
|
std::string ToHttpDate() const;
|
||||||
static bool TryParseHttpDate(std::string txt, DateTime &dt);
|
/**
|
||||||
|
* @brief Tries to parse an IMF-fixdate (RFC 9110 §5.6.7) date into a
|
||||||
|
* DateTime
|
||||||
|
*
|
||||||
|
* @param txt example "Tue, 01 Sep 2026 18:07:05 GMT"
|
||||||
|
* @param dt a reference to a datetime
|
||||||
|
* @return true we did parse the date correctly
|
||||||
|
* @return false we didn't parse the date correctly
|
||||||
|
*/
|
||||||
|
static bool TryParseHttpDate(std::string_view txt, DateTime &dt);
|
||||||
|
/**
|
||||||
|
* @brief Tries to parse an IMF-fixdate (RFC 9110 §5.6.7) date into the
|
||||||
|
* DateTime
|
||||||
|
*
|
||||||
|
* @param txt example "Tue, 01 Sep 2026 18:07:05 GMT"
|
||||||
|
* @return true we did parse the date correctly
|
||||||
|
* @return false we didn't parse the date correctly
|
||||||
|
*/
|
||||||
|
bool TryParseHttpDate(std::string_view txt);
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* @brief Stores a time offset in seconds
|
||||||
|
*
|
||||||
|
*/
|
||||||
class TimeSpan {
|
class TimeSpan {
|
||||||
int64_t totalSeconds;
|
int64_t totalSeconds;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a new TimeSpan with 0 seconds
|
||||||
|
*
|
||||||
|
*/
|
||||||
TimeSpan();
|
TimeSpan();
|
||||||
|
/**
|
||||||
|
* @brief Construct a new TimeSpan object with totalSeconds
|
||||||
|
*
|
||||||
|
*/
|
||||||
TimeSpan(int64_t totalSeconds);
|
TimeSpan(int64_t totalSeconds);
|
||||||
|
/**
|
||||||
|
* @brief Construct a new TimeSpan object with hours, minutes and seconds
|
||||||
|
*
|
||||||
|
*/
|
||||||
TimeSpan(int hours, int minutes, int seconds);
|
TimeSpan(int hours, int minutes, int seconds);
|
||||||
|
/**
|
||||||
|
* @brief Construct a new TimeSpan object with days, hours, minutes and
|
||||||
|
* seconds
|
||||||
|
*
|
||||||
|
*/
|
||||||
TimeSpan(int days, int hours, int minutes, int seconds);
|
TimeSpan(int days, int hours, int minutes, int seconds);
|
||||||
|
/**
|
||||||
|
* @brief Set the timespan with new days, hours, minutes and seconds
|
||||||
|
*
|
||||||
|
*/
|
||||||
void Set(int days, int hours, int minutes, int seconds);
|
void Set(int days, int hours, int minutes, int seconds);
|
||||||
|
/**
|
||||||
|
* @brief Set the timespan with new hours, minutes and seconds
|
||||||
|
*
|
||||||
|
*/
|
||||||
void Set(int hours, int minutes, int seconds);
|
void Set(int hours, int minutes, int seconds);
|
||||||
|
/**
|
||||||
|
* @brief Set the days component (hours, minutes, seconds unchanged)
|
||||||
|
*/
|
||||||
void SetDays(int d);
|
void SetDays(int d);
|
||||||
|
/**
|
||||||
|
* @brief Set the hours component (days, minutes, seconds unchanged)
|
||||||
|
*/
|
||||||
void SetHours(int h);
|
void SetHours(int h);
|
||||||
|
/**
|
||||||
|
* @brief Set the minutes component (days, hours, seconds unchanged)
|
||||||
|
*/
|
||||||
void SetMinutes(int m);
|
void SetMinutes(int m);
|
||||||
|
/**
|
||||||
|
* @brief Set the seconds component (days, hours, minutes unchanged)
|
||||||
|
*/
|
||||||
void SetSeconds(int s);
|
void SetSeconds(int s);
|
||||||
|
/**
|
||||||
|
* @brief Get the days of the timespan
|
||||||
|
*
|
||||||
|
*/
|
||||||
int Days() const;
|
int Days() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the hours of the timespan
|
||||||
|
*
|
||||||
|
*/
|
||||||
int Hours() const;
|
int Hours() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the minutes of the timespan
|
||||||
|
*
|
||||||
|
*/
|
||||||
int Minutes() const;
|
int Minutes() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the seconds of the timespan
|
||||||
|
*
|
||||||
|
*/
|
||||||
int Seconds() const;
|
int Seconds() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the total seconds of the timespan
|
||||||
|
*
|
||||||
|
*/
|
||||||
int64_t TotalSeconds() const;
|
int64_t TotalSeconds() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the total minutes of the timespan
|
||||||
|
*
|
||||||
|
*/
|
||||||
int64_t TotalMinutes() const;
|
int64_t TotalMinutes() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the total hours of the timespan
|
||||||
|
*
|
||||||
|
*/
|
||||||
int64_t TotalHours() const;
|
int64_t TotalHours() const;
|
||||||
|
/**
|
||||||
|
* @brief Set the total seconds of the timespan, removing any old value
|
||||||
|
*
|
||||||
|
*/
|
||||||
void SetTotalSeconds(int64_t totalSeconds);
|
void SetTotalSeconds(int64_t totalSeconds);
|
||||||
|
/**
|
||||||
|
* @brief Set the total minutes of the timespan, removing any old value
|
||||||
|
*
|
||||||
|
*/
|
||||||
void SetTotalMinutes(int64_t totalMinutes);
|
void SetTotalMinutes(int64_t totalMinutes);
|
||||||
|
/**
|
||||||
|
* @brief Set the total hours of the timespan, removing any old value
|
||||||
|
*
|
||||||
|
*/
|
||||||
void SetTotalHours(int64_t totalHours);
|
void SetTotalHours(int64_t totalHours);
|
||||||
|
/**
|
||||||
|
* @brief Set the total days of the timespan, removing any old value
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void SetTotalDays(int64_t totalHours);
|
||||||
|
/**
|
||||||
|
* @brief Add seconds to the timespan
|
||||||
|
*
|
||||||
|
*/
|
||||||
void AddSeconds(int64_t seconds);
|
void AddSeconds(int64_t seconds);
|
||||||
|
/**
|
||||||
|
* @brief Add minutes to the timespan
|
||||||
|
*
|
||||||
|
*/
|
||||||
void AddMinutes(int64_t minutes);
|
void AddMinutes(int64_t minutes);
|
||||||
|
/**
|
||||||
|
* @brief Add hours to the timespan
|
||||||
|
*
|
||||||
|
*/
|
||||||
void AddHours(int64_t hours);
|
void AddHours(int64_t hours);
|
||||||
|
/**
|
||||||
|
* @brief Add days to the timespan
|
||||||
|
*
|
||||||
|
*/
|
||||||
void AddDays(int64_t days);
|
void AddDays(int64_t days);
|
||||||
|
/**
|
||||||
|
* @brief Converts the timespan to string
|
||||||
|
*
|
||||||
|
* @param slim If true, omits leading zeros (e.g. "1:00", "10:00",
|
||||||
|
* "1:00:00"). If false, always zero-pads (e.g. "00:00:00") unless there are
|
||||||
|
* days.
|
||||||
|
*/
|
||||||
std::string ToString(bool slim = true) const;
|
std::string ToString(bool slim = true) const;
|
||||||
|
/**
|
||||||
static bool TryParse(std::string text, TimeSpan &span);
|
* @brief Try to parse a TimeSpan from a string
|
||||||
|
*
|
||||||
|
* @param text the string to parse
|
||||||
|
* @param span receives the parsed TimeSpan on success
|
||||||
|
* @return true we parsed successfully
|
||||||
|
* @return false we failed to parse
|
||||||
|
*/
|
||||||
|
static bool TryParse(std::string_view text, TimeSpan &span);
|
||||||
|
/**
|
||||||
|
* @brief Try to parse this timespan from string
|
||||||
|
*
|
||||||
|
* @param text the string to parse
|
||||||
|
* @return true we parsed successfully
|
||||||
|
* @return false we failed to parse
|
||||||
|
*/
|
||||||
|
bool TryParse(std::string_view text);
|
||||||
|
/**
|
||||||
|
* @brief Create a timespan with seconds, same as ctor
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static TimeSpan FromSeconds(int64_t seconds);
|
||||||
|
/**
|
||||||
|
* @brief Create a timespan with minutes
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static TimeSpan FromMinutes(int64_t minutes);
|
||||||
|
/**
|
||||||
|
* @brief Create a timespan with hours
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static TimeSpan FromHours(int64_t hours);
|
||||||
|
/**
|
||||||
|
* @brief Create a timespan with days
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static TimeSpan FromDays(int64_t days);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline DateTime operator+(const DateTime &dt, const TimeSpan &ts) {
|
inline DateTime operator+(const DateTime &dt, const TimeSpan &ts) {
|
||||||
|
|||||||
@@ -24,13 +24,47 @@
|
|||||||
#include "VFSFix.hpp"
|
#include "VFSFix.hpp"
|
||||||
|
|
||||||
namespace Tesses::Framework::Filesystem::Helpers {
|
namespace Tesses::Framework::Filesystem::Helpers {
|
||||||
|
/** @brief Read all of the text from a file
|
||||||
|
* @param vfs the VFS you want to use
|
||||||
|
* @param path the path to the file in the VFS
|
||||||
|
* @param text the file's contents
|
||||||
|
*/
|
||||||
void ReadAllText(std::shared_ptr<VFS> vfs, VFSPath path, std::string &text);
|
void ReadAllText(std::shared_ptr<VFS> vfs, VFSPath path, std::string &text);
|
||||||
|
/**
|
||||||
|
* @brief Read all of the lines from a file
|
||||||
|
*
|
||||||
|
* @param vfs the VFS you want to use
|
||||||
|
* @param path the path to the file in the VFS
|
||||||
|
* @param lines the file's lines
|
||||||
|
*/
|
||||||
void ReadAllLines(std::shared_ptr<VFS> vfs, VFSPath path,
|
void ReadAllLines(std::shared_ptr<VFS> vfs, VFSPath path,
|
||||||
std::vector<std::string> &lines);
|
std::vector<std::string> &lines);
|
||||||
|
/**
|
||||||
|
* @brief Read all of the bytes from a file
|
||||||
|
*
|
||||||
|
* @param vfs the VFS you want to use
|
||||||
|
* @param path the path to the file in the VFS
|
||||||
|
* @param array the file's contents
|
||||||
|
*/
|
||||||
void ReadAllBytes(std::shared_ptr<VFS> vfs, VFSPath path,
|
void ReadAllBytes(std::shared_ptr<VFS> vfs, VFSPath path,
|
||||||
std::vector<uint8_t> &array);
|
std::vector<uint8_t> &array);
|
||||||
|
/**
|
||||||
|
* @brief Read all of the text from a file
|
||||||
|
*
|
||||||
|
* @param vfs the VFS you want to use
|
||||||
|
* @param path the path to the file in the VFS
|
||||||
|
* @return std::string the file's contents
|
||||||
|
*/
|
||||||
std::string ReadAllText(std::shared_ptr<VFS> vfs, VFSPath path);
|
std::string ReadAllText(std::shared_ptr<VFS> vfs, VFSPath path);
|
||||||
|
/**
|
||||||
|
* @brief Read all of the lines from a file
|
||||||
|
*
|
||||||
|
* @param vfs the VFS you want to use
|
||||||
|
* @param path the path to the file in the VFS
|
||||||
|
* @return std::vector<std::string> the file's lines
|
||||||
|
*/
|
||||||
std::vector<std::string> ReadAllLines(std::shared_ptr<VFS> vfs, VFSPath path);
|
std::vector<std::string> ReadAllLines(std::shared_ptr<VFS> vfs, VFSPath path);
|
||||||
|
|
||||||
std::vector<uint8_t> ReadAllBytes(std::shared_ptr<VFS> vfs, VFSPath path);
|
std::vector<uint8_t> ReadAllBytes(std::shared_ptr<VFS> vfs, VFSPath path);
|
||||||
void WriteAllText(std::shared_ptr<VFS> vfs, VFSPath path,
|
void WriteAllText(std::shared_ptr<VFS> vfs, VFSPath path,
|
||||||
const std::string &text);
|
const std::string &text);
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ class HiddenField {
|
|||||||
HiddenField(HiddenFieldData *data);
|
HiddenField(HiddenFieldData *data);
|
||||||
void SetField(HiddenFieldData *data);
|
void SetField(HiddenFieldData *data);
|
||||||
template <typename T> T GetField() { return dynamic_cast<T>(ptr); }
|
template <typename T> T GetField() { return dynamic_cast<T>(ptr); }
|
||||||
template <typename T> T *AllocField() {
|
template <typename T, typename... TArgs> T *AllocField(TArgs &&...args) {
|
||||||
auto v = new T();
|
auto v = new T(std::forward<TArgs>(args)...);
|
||||||
SetField(v);
|
SetField(v);
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,15 @@
|
|||||||
|
|
||||||
namespace Tesses::Framework::Http {
|
namespace Tesses::Framework::Http {
|
||||||
class ChangeableServer {
|
class ChangeableServer {
|
||||||
|
std::shared_ptr<IHttpServer> server;
|
||||||
|
Tesses::Framework::Threading::Mutex mtx;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ChangeableServer();
|
ChangeableServer();
|
||||||
ChangeableServer(std::shared_ptr<IHttpServer> original);
|
ChangeableServer(std::shared_ptr<IHttpServer> original);
|
||||||
std::shared_ptr<IHttpServer> server;
|
|
||||||
bool Handle(ServerContext &ctx);
|
bool Handle(ServerContext &ctx);
|
||||||
|
void SetServer(std::shared_ptr<IHttpServer> server);
|
||||||
|
std::shared_ptr<IHttpServer> GetServer();
|
||||||
~ChangeableServer();
|
~ChangeableServer();
|
||||||
};
|
};
|
||||||
} // namespace Tesses::Framework::Http
|
} // namespace Tesses::Framework::Http
|
||||||
41
include/TessesFramework/Http/DomainServer.hpp
Normal file
41
include/TessesFramework/Http/DomainServer.hpp
Normal 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
|
||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
84
include/TessesFramework/Http/HttpReverseProxy.hpp
Normal file
84
include/TessesFramework/Http/HttpReverseProxy.hpp
Normal 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
|
||||||
@@ -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 void SplitString(std::vector<std::string> &out,
|
||||||
|
std::string_view text, std::string_view delimiter,
|
||||||
|
std::size_t maxCnt = std::string::npos);
|
||||||
static std::vector<std::string>
|
static std::vector<std::string>
|
||||||
SplitString(std::string text, std::string delimiter,
|
SplitString(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::string Replace(std::string_view str, std::string_view find,
|
||||||
std::string replace);
|
std::string_view replace);
|
||||||
static std::string StatusCodeString(StatusCode code);
|
static std::string StatusCodeString(StatusCode code);
|
||||||
static std::string ToLower(std::string str);
|
static std::string ToLower(std::string_view str);
|
||||||
static std::string ToUpper(std::string str);
|
static std::string ToUpper(std::string_view str);
|
||||||
static std::string LeftPad(std::string text, int count, char c);
|
static std::string LeftPad(std::string_view text, int count, char c);
|
||||||
|
static bool CaseInsensitiveCompare(std::string_view left,
|
||||||
|
std::string_view right);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Tesses::Framework::Http
|
} // namespace Tesses::Framework::Http
|
||||||
@@ -32,6 +32,7 @@ class MountableServer : public IHttpServer {
|
|||||||
Filesystem::VFSPath offsetPath);
|
Filesystem::VFSPath offsetPath);
|
||||||
bool StartsWith(Filesystem::VFSPath fullPath,
|
bool StartsWith(Filesystem::VFSPath fullPath,
|
||||||
Filesystem::VFSPath offsetPath);
|
Filesystem::VFSPath offsetPath);
|
||||||
|
Tesses::Framework::Threading::Mutex mtx;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MountableServer();
|
MountableServer();
|
||||||
@@ -39,6 +40,5 @@ class MountableServer : public IHttpServer {
|
|||||||
void Mount(std::string path, std::shared_ptr<IHttpServer> server);
|
void Mount(std::string path, std::shared_ptr<IHttpServer> server);
|
||||||
void Unmount(std::string path);
|
void Unmount(std::string path);
|
||||||
bool Handle(ServerContext &ctx);
|
bool Handle(ServerContext &ctx);
|
||||||
~MountableServer();
|
|
||||||
};
|
};
|
||||||
} // namespace Tesses::Framework::Http
|
} // namespace Tesses::Framework::Http
|
||||||
@@ -42,6 +42,7 @@ class RouteServer : public IHttpServer {
|
|||||||
};
|
};
|
||||||
std::vector<RouteServerRoute> routes;
|
std::vector<RouteServerRoute> routes;
|
||||||
std::shared_ptr<IHttpServer> root;
|
std::shared_ptr<IHttpServer> root;
|
||||||
|
Tesses::Framework::Threading::Mutex mtx;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RouteServer() = default;
|
RouteServer() = default;
|
||||||
@@ -58,5 +59,6 @@ class RouteServer : public IHttpServer {
|
|||||||
void Add(std::string method, std::string pattern,
|
void Add(std::string method, std::string pattern,
|
||||||
ServerRequestHandler handler);
|
ServerRequestHandler handler);
|
||||||
bool Handle(ServerContext &ctx);
|
bool Handle(ServerContext &ctx);
|
||||||
|
void Clear();
|
||||||
};
|
};
|
||||||
} // namespace Tesses::Framework::Http
|
} // namespace Tesses::Framework::Http
|
||||||
@@ -53,7 +53,7 @@ class NetworkStream : public Stream {
|
|||||||
int32_t sock;
|
int32_t sock;
|
||||||
bool owns;
|
bool owns;
|
||||||
bool success;
|
bool success;
|
||||||
bool endOfStream;
|
std::atomic<bool> endOfStream;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool DataAvailable(int timeout = 0);
|
bool DataAvailable(int timeout = 0);
|
||||||
@@ -85,6 +85,9 @@ class NetworkStream : public Stream {
|
|||||||
|
|
||||||
~NetworkStream();
|
~NetworkStream();
|
||||||
void SetNoDelay(bool noDelay);
|
void SetNoDelay(bool noDelay);
|
||||||
|
void Shutdown(StreamShutdownMode mode);
|
||||||
|
void SetSendTimeout(uint64_t seconds);
|
||||||
|
void SetRecvTimeout(uint64_t seconds);
|
||||||
void Close();
|
void Close();
|
||||||
};
|
};
|
||||||
} // namespace Tesses::Framework::Streams
|
} // namespace Tesses::Framework::Streams
|
||||||
|
|||||||
@@ -21,8 +21,11 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "../Common.hpp"
|
#include "../Common.hpp"
|
||||||
|
#include "../Date/Date.hpp"
|
||||||
namespace Tesses::Framework::Streams {
|
namespace Tesses::Framework::Streams {
|
||||||
enum class SeekOrigin : uint8_t { Begin = 0, Current = 1, End = 2 };
|
enum class SeekOrigin : uint8_t { Begin = 0, Current = 1, End = 2 };
|
||||||
|
enum class StreamShutdownMode { Read = 0, Write = 1, ReadWrite = 2 };
|
||||||
|
|
||||||
class Stream {
|
class Stream {
|
||||||
public:
|
public:
|
||||||
int32_t ReadByte();
|
int32_t ReadByte();
|
||||||
@@ -42,6 +45,12 @@ class Stream {
|
|||||||
void CopyTo(std::shared_ptr<Stream> strm, size_t buffSize = 1024);
|
void CopyTo(std::shared_ptr<Stream> strm, size_t buffSize = 1024);
|
||||||
void CopyToLimit(std::shared_ptr<Stream> strm, uint64_t len,
|
void CopyToLimit(std::shared_ptr<Stream> strm, uint64_t len,
|
||||||
size_t buffSize = 1024);
|
size_t buffSize = 1024);
|
||||||
|
virtual void Shutdown(StreamShutdownMode mode);
|
||||||
|
void SetSendTimeout(Tesses::Framework::Date::TimeSpan ts);
|
||||||
|
void SetRecvTimeout(Tesses::Framework::Date::TimeSpan ts);
|
||||||
|
virtual void SetSendTimeout(uint64_t seconds);
|
||||||
|
virtual void SetRecvTimeout(uint64_t seconds);
|
||||||
|
|
||||||
virtual void Close();
|
virtual void Close();
|
||||||
virtual ~Stream();
|
virtual ~Stream();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
40
src/Http/DomainServer.cpp
Normal 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
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,10 +65,12 @@ StreamHttpRequestBody::StreamHttpRequestBody(std::shared_ptr<Stream> strm,
|
|||||||
this->mimeType = mimeType;
|
this->mimeType = mimeType;
|
||||||
}
|
}
|
||||||
void StreamHttpRequestBody::HandleHeaders(HttpDictionary &dict) {
|
void StreamHttpRequestBody::HandleHeaders(HttpDictionary &dict) {
|
||||||
dict.AddValue("Content-Type", this->mimeType);
|
dict.SetValue("Content-Type", this->mimeType);
|
||||||
auto len = this->strm->GetLength();
|
auto len = this->strm->GetLength();
|
||||||
if (len > -1)
|
if (len > -1)
|
||||||
dict.AddValue("Content-Length", std::to_string(len));
|
dict.SetValue("Content-Length", std::to_string(len));
|
||||||
|
else
|
||||||
|
dict.SetValue("Transfer-Encoding", "chunked");
|
||||||
}
|
}
|
||||||
void StreamHttpRequestBody::Write(
|
void StreamHttpRequestBody::Write(
|
||||||
std::shared_ptr<Tesses::Framework::Streams::Stream> strm) {
|
std::shared_ptr<Tesses::Framework::Streams::Stream> strm) {
|
||||||
@@ -96,6 +98,7 @@ void HttpRequest::SendRequest(
|
|||||||
|
|
||||||
std::string request = method + " " + uri.GetPathAndQuery() +
|
std::string request = method + " " + uri.GetPathAndQuery() +
|
||||||
" HTTP/1.1\r\nHost: " + uri.HostPort() + "\r\n";
|
" HTTP/1.1\r\nHost: " + uri.HostPort() + "\r\n";
|
||||||
|
|
||||||
for (auto headers : requestHeaders.kvp) {
|
for (auto headers : requestHeaders.kvp) {
|
||||||
for (auto item : headers.second) {
|
for (auto item : headers.second) {
|
||||||
request.append(headers.first);
|
request.append(headers.first);
|
||||||
@@ -106,7 +109,6 @@ void HttpRequest::SendRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
request.append("\r\n");
|
request.append("\r\n");
|
||||||
|
|
||||||
StreamWriter writer(strm);
|
StreamWriter writer(strm);
|
||||||
writer.Write(request);
|
writer.Write(request);
|
||||||
|
|
||||||
@@ -114,9 +116,9 @@ void HttpRequest::SendRequest(
|
|||||||
body->Write(strm);
|
body->Write(strm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::shared_ptr<Stream>
|
std::shared_ptr<Stream> HttpRequest::EstablishConnection(
|
||||||
HttpRequest::EstablishConnection(Uri uri, bool ignoreSSLErrors,
|
Uri uri, bool ignoreSSLErrors, std::string trusted_root_cert_bundle,
|
||||||
std::string trusted_root_cert_bundle) {
|
std::optional<Crypto::CertificateKeyStore> mTLS_keyStore) {
|
||||||
if (uri.scheme == "http:" || uri.scheme == "ws:") {
|
if (uri.scheme == "http:" || uri.scheme == "ws:") {
|
||||||
return std::make_shared<NetworkStream>(uri.host, uri.GetPort(), false,
|
return std::make_shared<NetworkStream>(uri.host, uri.GetPort(), false,
|
||||||
false, false);
|
false, false);
|
||||||
@@ -125,24 +127,34 @@ HttpRequest::EstablishConnection(Uri uri, bool ignoreSSLErrors,
|
|||||||
false, false, false);
|
false, false, false);
|
||||||
if (netStrm == nullptr)
|
if (netStrm == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
return std::make_shared<ClientTLSStream>(
|
return mTLS_keyStore
|
||||||
netStrm, !ignoreSSLErrors, uri.host, trusted_root_cert_bundle);
|
? std::make_shared<ClientTLSStream>(
|
||||||
|
netStrm, !ignoreSSLErrors, uri.host,
|
||||||
|
trusted_root_cert_bundle, mTLS_keyStore.value())
|
||||||
|
: std::make_shared<ClientTLSStream>(
|
||||||
|
netStrm, !ignoreSSLErrors, uri.host,
|
||||||
|
trusted_root_cert_bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Stream>
|
std::shared_ptr<Stream> HttpRequest::EstablishUnixPathConnection(
|
||||||
HttpRequest::EstablishUnixPathConnection(std::string unixPath, Uri uri,
|
std::string unixPath, Uri uri, bool ignoreSSLErrors,
|
||||||
bool ignoreSSLErrors,
|
std::string trusted_root_cert_bundle,
|
||||||
std::string trusted_root_cert_bundle) {
|
std::optional<Crypto::CertificateKeyStore> mTLS_keyStore) {
|
||||||
if (uri.scheme == "http:" || uri.scheme == "ws:") {
|
if (uri.scheme == "http:" || uri.scheme == "ws:") {
|
||||||
return std::make_shared<NetworkStream>(unixPath, false);
|
return std::make_shared<NetworkStream>(unixPath, false);
|
||||||
} else if (uri.scheme == "https:" || uri.scheme == "wss:") {
|
} else if (uri.scheme == "https:" || uri.scheme == "wss:") {
|
||||||
auto netStrm = std::make_shared<NetworkStream>(unixPath, false);
|
auto netStrm = std::make_shared<NetworkStream>(unixPath, false);
|
||||||
if (netStrm == nullptr)
|
if (netStrm == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
return std::make_shared<ClientTLSStream>(
|
return mTLS_keyStore
|
||||||
netStrm, !ignoreSSLErrors, uri.host, trusted_root_cert_bundle);
|
? std::make_shared<ClientTLSStream>(
|
||||||
|
netStrm, !ignoreSSLErrors, uri.host,
|
||||||
|
trusted_root_cert_bundle, mTLS_keyStore.value())
|
||||||
|
: std::make_shared<ClientTLSStream>(
|
||||||
|
netStrm, !ignoreSSLErrors, uri.host,
|
||||||
|
trusted_root_cert_bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -186,13 +198,13 @@ HttpResponse::HttpResponse(HttpRequest &req) : responseHeaders(false) {
|
|||||||
std::string url = req.url;
|
std::string url = req.url;
|
||||||
Uri uri;
|
Uri uri;
|
||||||
while (Uri::TryParse(url, uri)) {
|
while (Uri::TryParse(url, uri)) {
|
||||||
auto strm =
|
auto strm = req.unixSocket.empty()
|
||||||
req.unixSocket.empty()
|
? HttpRequest::EstablishConnection(
|
||||||
? HttpRequest::EstablishConnection(uri, req.ignoreSSLErrors,
|
uri, req.ignoreSSLErrors,
|
||||||
req.trusted_root_cert_bundle)
|
req.trusted_root_cert_bundle, req.mTLS_keyStore)
|
||||||
: HttpRequest::EstablishUnixPathConnection(
|
: HttpRequest::EstablishUnixPathConnection(
|
||||||
req.unixSocket, uri, req.ignoreSSLErrors,
|
req.unixSocket, uri, req.ignoreSSLErrors,
|
||||||
req.trusted_root_cert_bundle);
|
req.trusted_root_cert_bundle, req.mTLS_keyStore);
|
||||||
if (strm == nullptr)
|
if (strm == nullptr)
|
||||||
return;
|
return;
|
||||||
auto reqHeaders = req.requestHeaders;
|
auto reqHeaders = req.requestHeaders;
|
||||||
@@ -628,7 +640,7 @@ void WebSocketUnixSocketClient(std::string unixSocket, std::string url,
|
|||||||
if (resp.statusCode != 101 ||
|
if (resp.statusCode != 101 ||
|
||||||
!resp.responseHeaders.TryGetFirst("Sec-WebSocket-Accept", accept) ||
|
!resp.responseHeaders.TryGetFirst("Sec-WebSocket-Accept", accept) ||
|
||||||
!resp.responseHeaders.AnyEquals("Connection", "Upgrade") ||
|
!resp.responseHeaders.AnyEquals("Connection", "Upgrade") ||
|
||||||
!resp.responseHeaders.AnyEquals("Upgrade", "websocket")) {
|
!resp.responseHeaders.AnyEqualsCSV("Upgrade", "websocket")) {
|
||||||
cb(resp.responseHeaders, false);
|
cb(resp.responseHeaders, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
525
src/Http/HttpReverseProxy.cpp
Normal file
525
src/Http/HttpReverseProxy.cpp
Normal 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
|
||||||
@@ -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" &&
|
||||||
|
|||||||
@@ -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,30 +117,48 @@ 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()) {
|
||||||
this->read = std::stoull(line, NULL, 16);
|
|
||||||
|
|
||||||
if (this->read == 0) {
|
if (this->position > 0) {
|
||||||
reader.ReadLine();
|
throw std::runtime_error(
|
||||||
this->done = true;
|
"Incomplete Read (chunked): connection closed "
|
||||||
return 0;
|
"before final 0 chunk");
|
||||||
} else {
|
|
||||||
this->offset = 0;
|
|
||||||
|
|
||||||
len =
|
|
||||||
std::min((size_t)(this->read - this->offset), len);
|
|
||||||
if (len > 0)
|
|
||||||
len = this->strm->Read(buff, len);
|
|
||||||
this->offset += len;
|
|
||||||
this->position += len;
|
|
||||||
return len;
|
|
||||||
}
|
}
|
||||||
|
this->done = true;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
this->read = std::stoull(line, NULL, 16);
|
||||||
|
|
||||||
|
if (this->read == 0) {
|
||||||
|
reader.ReadLine();
|
||||||
|
this->done = true;
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
this->offset = 0;
|
||||||
|
|
||||||
|
len = std::min((size_t)(this->read - this->offset), len);
|
||||||
|
if (len > 0)
|
||||||
|
len = this->strm->Read(buff, len);
|
||||||
|
if (len == 0 && this->read > 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Incomplete Read (chunked): connection closed "
|
||||||
|
"after chunk size, 0 of " +
|
||||||
|
std::to_string(this->read) + " bytes");
|
||||||
|
}
|
||||||
|
this->offset += len;
|
||||||
|
this->position += len;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"},
|
||||||
if (ext == ".txt" || ext == ".log" || ext == ".twss") {
|
{".txt", "text/plain"},
|
||||||
return "text/plain";
|
{".log", "text/plain"},
|
||||||
}
|
{".twss", "text/twss"},
|
||||||
if (ext == ".woff") {
|
{".css", "text/css"},
|
||||||
return "application/x-font-woff";
|
{".js", "text/javascript"},
|
||||||
}
|
{".mjs", "text/javascript"},
|
||||||
if (ext == ".vtt") {
|
{".json", "application/json"},
|
||||||
return "text/vtt";
|
{".map", "application/json"},
|
||||||
}
|
{".pdf", "application/pdf"},
|
||||||
if (ext == ".svg") {
|
{".png", "image/png"},
|
||||||
return "image/svg+xml";
|
{".jpg", "image/jpeg"},
|
||||||
}
|
{".jpeg", "image/jpeg"},
|
||||||
if (ext == ".webp") {
|
{".gif", "image/gif"},
|
||||||
return "image/webp";
|
{".webp", "image/webp"},
|
||||||
}
|
{".svg", "image/svg+xml"},
|
||||||
if (ext == ".vcf") {
|
{".ico", "image/x-icon"},
|
||||||
return "text/v-card";
|
{".bmp", "image/bmp"},
|
||||||
}
|
{".avif", "image/avif"},
|
||||||
if (ext == ".rss" || ext == ".xml" || ext == ".atom" || ext == ".rdf") {
|
{".heic", "image/heic"},
|
||||||
return "application/xml";
|
{".tif", "image/tiff"},
|
||||||
}
|
{".tiff", "image/tiff"},
|
||||||
if (ext == ".js") {
|
{".mp4", "video/mp4"},
|
||||||
return "text/javascript";
|
{".webm", "video/webm"},
|
||||||
}
|
{".mov", "video/quicktime"},
|
||||||
if (ext == ".json") {
|
{".mkv", "video/x-matroska"},
|
||||||
return "application/json";
|
{".avi", "video/x-msvideo"},
|
||||||
}
|
{".ts", "video/mp2t"},
|
||||||
if (ext == ".wasm") {
|
{".mp3", "audio/mpeg"},
|
||||||
return "application/wasm";
|
{".m4a", "audio/mp4"},
|
||||||
}
|
{".ogg", "audio/ogg"},
|
||||||
if (ext == ".png") {
|
{".oga", "audio/ogg"},
|
||||||
return "image/png";
|
{".opus", "audio/ogg"},
|
||||||
}
|
{".wav", "audio/wav"},
|
||||||
if (ext == ".jpg" || ext == ".jpeg") {
|
{".flac", "audio/flac"},
|
||||||
return "image/jpeg";
|
{".aac", "audio/aac"},
|
||||||
}
|
{".weba", "audio/webm"},
|
||||||
if (ext == ".css") {
|
{".woff", "font/woff"},
|
||||||
return "text/css";
|
{".woff2", "font/woff2"},
|
||||||
}
|
{".ttf", "font/ttf"},
|
||||||
if (ext == ".gif") {
|
{".zip", "application/zip"},
|
||||||
return "image/gif";
|
{".gz", "application/gzip"},
|
||||||
}
|
{".7z", "application/x-7z-compressed"},
|
||||||
if (ext == ".mp4") {
|
{".rar", "application/vnd.rar"},
|
||||||
return "video/mp4";
|
{".csv", "text/csv"},
|
||||||
}
|
{".vtt", "text/vtt"},
|
||||||
if (ext == ".mov") {
|
{".vcf", "text/v-card"},
|
||||||
return "video/quicktime";
|
{".rss", "application/xml"},
|
||||||
}
|
{".xml", "application/xml"},
|
||||||
if (ext == ".m4a") {
|
{".atom", "application/xml"},
|
||||||
return "audio/mp4";
|
{".rdf", "application/xml"},
|
||||||
}
|
{".wasm", "application/wasm"},
|
||||||
if (ext == ".webm") {
|
{".webmanifest", "application/manifest+json"},
|
||||||
return "video/webm";
|
{".m3u8", "application/vnd.apple.mpegurl"},
|
||||||
}
|
{".mpd", "application/dash+xml"},
|
||||||
if (ext == ".webmanifest") {
|
{".md", "text/markdown"},
|
||||||
return "application/manifest+json";
|
{".epub", "application/epub+zip"},
|
||||||
}
|
{".ics", "text/calendar"},
|
||||||
if (ext == ".ico") {
|
{".apk", "application/vnd.android.package-archive"},
|
||||||
return "image/x-icon";
|
{".crvm", "application/crvm"},
|
||||||
}
|
{".tcross", "text/tcross"},
|
||||||
|
};
|
||||||
|
std::string Get(const std::string &ext) {
|
||||||
|
Threading::LockGuard lg(mtx);
|
||||||
|
std::string mime;
|
||||||
|
|
||||||
return "application/octet-stream";
|
if (map.count(ext) != 0)
|
||||||
|
mime = map[ext];
|
||||||
|
|
||||||
|
if (mime.empty())
|
||||||
|
return "application/octet-stream";
|
||||||
|
return mime;
|
||||||
|
}
|
||||||
|
void Add(const std::string &ext, const std::string &mime) {
|
||||||
|
Threading::LockGuard lg(mtx);
|
||||||
|
map[ext] = mime;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static mimetype_reg mime_reg;
|
||||||
|
|
||||||
|
std::string HttpUtils::GetMimeType(const std::string &ext) {
|
||||||
|
return mime_reg.Get(ext);
|
||||||
}
|
}
|
||||||
|
std::string HttpUtils::GetMimeTypePath(const Filesystem::VFSPath &p) {
|
||||||
|
return GetMimeType(p.GetExtension());
|
||||||
|
}
|
||||||
|
void HttpUtils::AddMimeType(const std::string &ext, const std::string &mime) {
|
||||||
|
mime_reg.Add(ext, mime);
|
||||||
|
}
|
||||||
|
void HttpUtils::AddMimeTypePath(const Filesystem::VFSPath &p,
|
||||||
|
const std::string &mime) {
|
||||||
|
AddMimeType(p.GetExtension(), mime);
|
||||||
|
}
|
||||||
|
|
||||||
bool HttpUtils::Invalid(char c) {
|
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) {
|
if (maxCnt == 0)
|
||||||
std::vector<std::string> strs;
|
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);
|
|
||||||
|
|
||||||
text = text.substr(index + delimiter.size());
|
|
||||||
|
|
||||||
strs.push_back(left);
|
|
||||||
}
|
}
|
||||||
|
std::string_view left = text.substr(0, index);
|
||||||
|
|
||||||
|
text = text.substr(index + delimiter.size());
|
||||||
|
|
||||||
|
strs.push_back(std::string(left));
|
||||||
|
|
||||||
i++;
|
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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
return true;
|
mtx.Unlock();
|
||||||
|
|
||||||
|
if (hndl(ctx)) {
|
||||||
|
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
|
||||||
@@ -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");
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
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;
|
||||||
} else {
|
|
||||||
path.resize(bufsize);
|
|
||||||
if (_NSGetExecutablePath(path.data(), &bufsize) == 0) {
|
|
||||||
path.resize(strlen(path.c_str()));
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user