diff --git a/CMakeLists.txt b/CMakeLists.txt
index b3e037c..1a356fe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,6 +16,8 @@ src/Http/HttpStream.cpp
src/Http/ContentDisposition.cpp
src/Http/WebSocket.cpp
src/Http/ChangeableServer.cpp
+src/Http/BasicAuthServer.cpp
+src/Http/CGIServer.cpp
src/Mail/Smtp.cpp
src/Serialization/Json.cpp
src/Serialization/SQLite.cpp
diff --git a/apps/tfileserver.cpp b/apps/tfileserver.cpp
index b247bf9..1116d67 100644
--- a/apps/tfileserver.cpp
+++ b/apps/tfileserver.cpp
@@ -9,7 +9,7 @@ using namespace Tesses::Framework::Threading;
void print_help(const char* name)
{
printf("Tesses FileServer\nUSAGE: %s [OPTIONS]
\n",name);
- printf("OPTIONS:\n-p PORT, --port PORT: Change port from 9852\n-l, --listing: Enable listing\n-s, --spa: Enable SPA mode (send \"/\" body instead of not found)\n-h, --help: This Screen\n");
+ printf("OPTIONS:\n-p PORT, --port PORT: Change port from 9852\n-l, --listing: Enable listing\n-s, --spa: Enable SPA mode (send \"/\" body instead of not found)\n-c, --cgi-bin: Enable cgi (common gateway interface) support (specify a folder like /cgi-bin)\n-a, --cgi-admin: cgi admin email\n-w, --cgi-working: working directory for cgi scripts\n-h, --help: This Screen\n");
exit(1);
}
int main(int argc, char** argv)
@@ -21,6 +21,9 @@ int main(int argc, char** argv)
const char* directory = "wwwroot";
bool spa=false;
bool allowListing = false;
+ std::optional cgi_bin;
+ std::optional cgi_admin;
+ std::optional cgi_workdir;
uint16_t port = 9852L;
for(int i = 1; i < argc; i++)
@@ -29,6 +32,41 @@ int main(int argc, char** argv)
{
print_help(argv[0]);
}
+ else if(strcmp(argv[i], "-c") == 0 || strcmp(argv[i],"--cgi-bin") == 0)
+ {
+ if(i+1>=argc)
+ {
+ printf("ERROR: Not enough arguments for cgi-bin\n");
+ print_help(argv[0]);
+ }
+ else {
+ printf("CGI is enabled\n");
+ cgi_bin = argv[++i];
+ }
+ }
+ else if(strcmp(argv[i], "-a") == 0 || strcmp(argv[i],"--cgi-admin") == 0)
+ {
+ if(i+1>=argc)
+ {
+ printf("ERROR: Not enough arguments for cgi-admin\n");
+ print_help(argv[0]);
+ }
+ else {
+ cgi_admin = argv[++i];
+ }
+ }
+ else if(strcmp(argv[i], "-w") == 0 || strcmp(argv[i],"--cgi-working") == 0)
+ {
+ if(i+1>=argc)
+ {
+ printf("ERROR: Not enough arguments for cgi-working\n");
+ print_help(argv[0]);
+ }
+ else {
+ cgi_workdir = (Tesses::Framework::Filesystem::VFSPath)argv[++i];
+ }
+ }
+
else if(strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--listing") == 0)
{
allowListing = true;
@@ -57,10 +95,25 @@ int main(int argc, char** argv)
}
std::cout << "In folder: " << std::filesystem::absolute(directory).string() << std::endl;
-
-
+ std::shared_ptr http;
+
auto fs = std::make_shared(directory,allowListing, spa);
- HttpServer server(port,fs);
+ if(cgi_bin)
+ {
+ Tesses::Framework::Filesystem::VFSPath dir = *cgi_bin;
+ dir.relative = true;
+ auto svr = std::make_shared(fs);
+ auto cgi = std::make_shared(std::filesystem::absolute(directory).string() / dir);
+ cgi->adminEmail = cgi_admin;
+ cgi->workingDirectory = cgi_workdir;
+ svr->Mount(*cgi_bin, cgi);
+ http = svr;
+ }
+ else {
+ http = fs;
+ }
+
+ HttpServer server(port,http);
server.StartAccepting();
TF_RunEventLoop();
std::cout << "Closing server" << std::endl;
diff --git a/include/TessesFramework/Http/BasicAuthServer.hpp b/include/TessesFramework/Http/BasicAuthServer.hpp
new file mode 100644
index 0000000..ed475c9
--- /dev/null
+++ b/include/TessesFramework/Http/BasicAuthServer.hpp
@@ -0,0 +1,18 @@
+#pragma once
+#include "HttpServer.hpp"
+namespace Tesses::Framework::Http {
+ class BasicAuthServer : public Tesses::Framework::Http::IHttpServer
+ {
+ public:
+ std::shared_ptr server;
+ std::function authorization;
+ std::string realm;
+
+ BasicAuthServer();
+ BasicAuthServer(std::shared_ptr server, std::function auth,std::string realm="Protected Content");
+ bool Handle(ServerContext& ctx);
+
+
+ static bool GetCreds(ServerContext& ctx, std::string& user, std::string& pass);
+ };
+}
\ No newline at end of file
diff --git a/include/TessesFramework/Http/CGIServer.hpp b/include/TessesFramework/Http/CGIServer.hpp
new file mode 100644
index 0000000..18e4b9d
--- /dev/null
+++ b/include/TessesFramework/Http/CGIServer.hpp
@@ -0,0 +1,23 @@
+#pragma once
+#include "../Filesystem/VFS.hpp"
+#include "../Filesystem/VFSFix.hpp"
+#include "HttpServer.hpp"
+#include
+namespace Tesses::Framework::Http {
+ struct CGIParams {
+ std::optional document_root;
+ Tesses::Framework::Filesystem::VFSPath program;
+ std::optional adminEmail;
+ std::optional workingDirectory;
+ };
+ class CGIServer : public Tesses::Framework::Http::IHttpServer {
+ Tesses::Framework::Filesystem::VFSPath dir;
+ public:
+ std::optional document_root;
+ std::optional adminEmail;
+ std::optional workingDirectory;
+ CGIServer(Tesses::Framework::Filesystem::VFSPath dir);
+ bool Handle(ServerContext& ctx);
+ static bool ServeCGIRequest(ServerContext& ctx, CGIParams& params);
+ };
+}
\ No newline at end of file
diff --git a/include/TessesFramework/Http/HttpServer.hpp b/include/TessesFramework/Http/HttpServer.hpp
index c2d75db..c85ed43 100644
--- a/include/TessesFramework/Http/HttpServer.hpp
+++ b/include/TessesFramework/Http/HttpServer.hpp
@@ -29,6 +29,7 @@ namespace Tesses::Framework::Http
StatusCode statusCode;
std::string ip;
uint16_t port;
+ uint16_t serverPort;
std::string version;
bool encrypted;
ServerContext(std::shared_ptr strm);
@@ -99,7 +100,7 @@ namespace Tesses::Framework::Http
HttpServer(std::string unixPath, std::shared_ptr http);
uint16_t GetPort();
void StartAccepting();
- static void Process(std::shared_ptr strm, std::shared_ptr server, std::string ip, uint16_t port, bool encrypted);
+ static void Process(std::shared_ptr strm, std::shared_ptr server, std::string ip, uint16_t port,uint16_t serverPort, bool encrypted);
~HttpServer();
};
}
\ No newline at end of file
diff --git a/include/TessesFramework/TessesFramework.hpp b/include/TessesFramework/TessesFramework.hpp
index 07dbaf7..0a6a0de 100644
--- a/include/TessesFramework/TessesFramework.hpp
+++ b/include/TessesFramework/TessesFramework.hpp
@@ -7,6 +7,8 @@
#include "Http/MountableServer.hpp"
#include "Http/ContentDisposition.hpp"
#include "Http/ChangeableServer.hpp"
+#include "Http/CGIServer.hpp"
+#include "Http/BasicAuthServer.hpp"
#include "Streams/FileStream.hpp"
#include "Streams/MemoryStream.hpp"
#include "Streams/NetworkStream.hpp"
diff --git a/src/Http/BasicAuthServer.cpp b/src/Http/BasicAuthServer.cpp
new file mode 100644
index 0000000..166f807
--- /dev/null
+++ b/src/Http/BasicAuthServer.cpp
@@ -0,0 +1,47 @@
+#include "TessesFramework/Http/BasicAuthServer.hpp"
+#include "TessesFramework/Crypto/Crypto.hpp"
+namespace Tesses::Framework::Http {
+
+ BasicAuthServer::BasicAuthServer()
+ {
+
+ }
+ BasicAuthServer::BasicAuthServer(std::shared_ptr server, std::function auth,std::string realm) : server(server), authorization(auth), realm(realm)
+ {
+
+ }
+ bool BasicAuthServer::Handle(ServerContext& ctx)
+ {
+ std::string www_authenticate = "Basic realm=\"" + this->realm + "\", charset=\"UTF-8\"";
+ std::string user;
+ std::string pass;
+ if(!GetCreds(ctx,user,pass) || !this->authorization(user,pass)) {
+ ctx.responseHeaders.SetValue("WWW-Authenticate",www_authenticate);
+ ctx.statusCode = Unauthorized;
+ return false;
+ }
+
+ if(this->server)
+ return this->server->Handle(ctx);
+ ctx.statusCode = InternalServerError;
+ return false;
+ }
+
+
+ bool BasicAuthServer::GetCreds(ServerContext& ctx, std::string& user, std::string& pass)
+ {
+ std::string auth;
+ if(!ctx.requestHeaders.TryGetFirst("Authorization", auth)) return false;
+
+ auto security = HttpUtils::SplitString(auth," ",2);
+ if(security.size() < 2) return false;
+ if(security[0] != "Basic") return false;
+ auto decoded = Crypto::Base64_Decode(security[0]);
+ std::string decoded_str(decoded.begin(),decoded.end());
+ security = HttpUtils::SplitString(auth,":",2);
+ if(security.size() < 2) return false;
+ user = security[0];
+ pass = security[1];
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/Http/CGIServer.cpp b/src/Http/CGIServer.cpp
new file mode 100644
index 0000000..8a6fff6
--- /dev/null
+++ b/src/Http/CGIServer.cpp
@@ -0,0 +1,135 @@
+#include "TessesFramework/Http/CGIServer.hpp"
+#include "TessesFramework/Filesystem/LocalFS.hpp"
+#include "TessesFramework/Platform/Process.hpp"
+#include "TessesFramework/Http/BasicAuthServer.hpp"
+#include "TessesFramework/TextStreams/StreamReader.hpp"
+#include
+namespace Tesses::Framework::Http {
+ CGIServer::CGIServer(Tesses::Framework::Filesystem::VFSPath dir)
+ {
+ this->dir = dir;
+ }
+ bool CGIServer::Handle(ServerContext& ctx)
+ {
+ Tesses::Framework::Filesystem::VFSPath execPath = ctx.path;
+ execPath.relative=true;
+ CGIParams params;
+ params.document_root = this->document_root ? *this->document_root : this->dir;
+ params.adminEmail = this->adminEmail;
+ params.workingDirectory = this->workingDirectory;
+ params.program = this->dir / execPath.CollapseRelativeParents();
+
+ return ServeCGIRequest(ctx,params);
+ }
+ bool CGIServer::ServeCGIRequest(ServerContext& ctx, CGIParams& params)
+ {
+ using namespace Tesses::Framework::Filesystem;
+ auto program = params.program.MakeAbsolute();
+ if(!LocalFS->FileExists(program)) return false;
+ Tesses::Framework::Platform::Process p;
+
+ Tesses::Framework::Filesystem::VFSPath p0=ctx.originalPath;
+
+ p.env.emplace_back("SCRIPT_FILENAME",LocalFS->VFSPathToSystem(program));
+ p.env.emplace_back("SCRIPT_NAME",p0.CollapseRelativeParents().ToString());
+ if(ctx.encrypted)
+ p.env.emplace_back("HTTPS","on");
+
+ std::string query;
+ for(auto& item : ctx.queryParams.kvp)
+ {
+ for(auto& val : item.second)
+ {
+ if(!query.empty()) query += "&";
+
+ query += HttpUtils::UrlEncode(item.first);
+ query += "=";
+ query += HttpUtils::UrlEncode(val);
+ }
+ }
+ p.env.emplace_back("QUERY_STRING",query);
+ p.env.emplace_back("REQUEST_URI",ctx.GetOriginalPathWithQuery());
+ p.env.emplace_back("REQUEST_METHOD",ctx.method);
+ p.env.emplace_back("REMOTE_HOST",ctx.ip);
+ p.env.emplace_back("REMOTE_ADDR",ctx.ip);
+ p.env.emplace_back("REMOTE_PORT",std::to_string(ctx.port));
+ std::string user;
+ std::string pass;
+ if(BasicAuthServer::GetCreds(ctx,user,pass))
+ p.env.emplace_back("REMOTE_USER",user);
+ p.env.emplace_back("SERVER_SOFTWARE","TessesFrameworkWebServer");
+ p.env.emplace_back("SERVER_PORT",std::to_string(ctx.serverPort));
+ p.env.emplace_back("GATEWAY_INTERFACE","CGI/1.1");
+ p.env.emplace_back("SERVER_PROTOCOL",ctx.version);
+
+ if(params.document_root)
+ p.env.emplace_back("DOCUMENT_ROOT",params.document_root->ToString());
+ if(params.adminEmail)
+ p.env.emplace_back("SERVER_ADMIN",*params.adminEmail);
+
+ for(auto& hdr : ctx.requestHeaders.kvp)
+ {
+ std::string hdr_name = HttpUtils::ToUpper(hdr.first);
+ if(hdr_name == "CONTENT-LENGTH")
+ {
+ if(!hdr.second.empty())
+ p.env.emplace_back("CONTENT_LENGTH",hdr.second.front());
+ }
+ else if(hdr_name == "CONTENT-TYPE")
+ {
+ if(!hdr.second.empty())
+ p.env.emplace_back("CONTENT_LENGTH",hdr.second.front());
+ }
+ else {
+
+ if(!hdr.second.empty())
+ p.env.emplace_back("HTTP_"+hdr.first,hdr.second.front());
+ }
+ }
+ p.redirectStdIn=true;
+ p.redirectStdOut=true;
+ p.name = program.ToString();
+
+
+ if(params.workingDirectory)
+ {
+ p.workingDirectory = params.workingDirectory->MakeAbsolute().ToString();
+ }
+
+ if(p.Start())
+ {
+ auto strm = p.GetStdinStream();
+ if(ctx.method != "GET") ctx.ReadStream(strm);
+ p.CloseStdInNow();
+ auto stout = p.GetStdoutStream();
+ Tesses::Framework::TextStreams::StreamReader reader(stout);
+ std::string line;
+ while(reader.ReadLine(line))
+ {
+ auto v = HttpUtils::SplitString(line,": ", 2);
+ if(v.size() == 2)
+ {
+ if(HttpUtils::ToLower(v[0]) == "status")
+ {
+ auto v2 = HttpUtils::SplitString(v[1]," ",2);
+ if(v2.empty())
+ {
+ ctx.statusCode = StatusCode::InternalServerError;
+ throw std::runtime_error("Status response is empty");
+ }
+ ctx.statusCode= (StatusCode)std::stoi(v2[0]);
+ }
+ else {
+ ctx.responseHeaders.AddValue(v[0],v[1]);
+ }
+ }
+ else throw std::runtime_error("Corrupted header: " + line);
+ line.clear();
+ }
+
+ ctx.SendStream(stout);
+ return true;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/Http/FileServer.cpp b/src/Http/FileServer.cpp
index 0bcd407..ad94f7e 100644
--- a/src/Http/FileServer.cpp
+++ b/src/Http/FileServer.cpp
@@ -51,7 +51,8 @@ namespace Tesses::Framework::Http
bool FileServer::Handle(ServerContext& ctx)
{
- auto path = HttpUtils::UrlPathDecode(ctx.path);
+ auto path = ((VFSPath)HttpUtils::UrlPathDecode(ctx.path)).CollapseRelativeParents();
+
if(this->vfs->DirectoryExists(path))
{
diff --git a/src/Http/HttpServer.cpp b/src/Http/HttpServer.cpp
index ed3c03d..5481ea0 100644
--- a/src/Http/HttpServer.cpp
+++ b/src/Http/HttpServer.cpp
@@ -515,8 +515,7 @@ namespace Tesses::Framework::Http
{
if(ct.find("multipart/form-data") != 0)
{
- std::cout << "Not form data" << std::endl;
- return;
+ throw std::runtime_error("Not form data");
}
auto res = ct.find("boundary=");
if(res == std::string::npos) return;
@@ -581,9 +580,10 @@ namespace Tesses::Framework::Http
fflush(stdout);
if(http == nullptr || server == nullptr) return;
auto svr=this->server;
+ auto serverPort = this->server->GetPort();
auto http = this->http;
TF_LOG("Before Creating Thread");
- thrd = new Threading::Thread([svr,http]()->void {
+ thrd = new Threading::Thread([svr,http,serverPort]()->void {
while(TF_IsRunning())
{
TF_LOG("after TF_IsRunning");
@@ -599,9 +599,9 @@ namespace Tesses::Framework::Http
return;
}
TF_LOG("Before entering socket thread");
- Threading::Thread thrd2([sock,http,ip,port]()->void {
+ Threading::Thread thrd2([sock,http,ip,port,serverPort]()->void {
TF_LOG("In thread to process");
- HttpServer::Process(sock,http,ip,port,false);
+ HttpServer::Process(sock,http,ip,port,serverPort,false);
TF_LOG("In thread after process");
});
@@ -829,7 +829,8 @@ namespace Tesses::Framework::Http
else
{
auto chunkedStream = this->OpenResponseStream();
- this->strm->CopyTo(chunkedStream);
+
+ strm->CopyTo(chunkedStream);
}
@@ -928,7 +929,7 @@ namespace Tesses::Framework::Http
return *this;
}
- void HttpServer::Process(std::shared_ptr strm, std::shared_ptr server, std::string ip, uint16_t port, bool encrypted)
+ void HttpServer::Process(std::shared_ptr strm, std::shared_ptr server, std::string ip, uint16_t port,uint16_t serverPort, bool encrypted)
{
TF_LOG("In process");
while(true)
@@ -939,6 +940,7 @@ namespace Tesses::Framework::Http
ctx.ip = ip;
ctx.port = port;
ctx.encrypted = encrypted;
+ ctx.serverPort = serverPort;
try{
bool firstLine = true;
std::string line;
@@ -1093,4 +1095,7 @@ namespace Tesses::Framework::Http
path2 = path2 / path;
return path2.CollapseRelativeParents().ToString();
}
+
+
}
+
diff --git a/src/Http/HttpStream.cpp b/src/Http/HttpStream.cpp
index dcaf289..f4f65c8 100644
--- a/src/Http/HttpStream.cpp
+++ b/src/Http/HttpStream.cpp
@@ -124,6 +124,7 @@ namespace Tesses::Framework::Http
{
len = std::min((size_t)(this->length - this->position), len);
+
if(len > 0)
len = this->strm->Write(buff,len);
this->position += len;
@@ -131,6 +132,7 @@ namespace Tesses::Framework::Http
}
else
{
+ if(len == 0) return 0;
if(this->http1_1)
{
std::stringstream strm;
diff --git a/src/Http/MountableServer.cpp b/src/Http/MountableServer.cpp
index 1d26c65..331d43c 100644
--- a/src/Http/MountableServer.cpp
+++ b/src/Http/MountableServer.cpp
@@ -65,7 +65,7 @@ bool MountableServer::Handle(ServerContext& ctx)
}
}
ctx.path=oldPath;
- if(this->root != nullptr && this->root->Handle(ctx)) return true;
+ if(this->root && this->root->Handle(ctx)) return true;
return false;
}
MountableServer::~MountableServer()
diff --git a/src/Platform/Process.cpp b/src/Platform/Process.cpp
index 0476622..c2fc1ec 100644
--- a/src/Platform/Process.cpp
+++ b/src/Platform/Process.cpp
@@ -180,8 +180,9 @@ namespace Tesses::Framework::Platform {
#elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) || !defined(TESSESFRAMEWORK_ENABLE_PROCESS)
return 0;
#else
- if(this->strm < 0 || this->eos && writing) return 0;
+ if(this->strm < 0 || this->eos && writing) return 0;
+
auto r = read(this->strm,buff,sz);
if(r == -1) return 0;
if(r == 0 && sz != 0) { this->eos=true; return 0;}
diff --git a/src/Streams/NetworkStream.cpp b/src/Streams/NetworkStream.cpp
index 87ba692..c41cc5f 100644
--- a/src/Streams/NetworkStream.cpp
+++ b/src/Streams/NetworkStream.cpp
@@ -129,9 +129,16 @@ namespace Tesses::Framework::Streams {
#else
struct ifaddrs *ifAddrStruct = NULL;
- getifaddrs(&ifAddrStruct);
+ errno = 0;
+ if(getifaddrs(&ifAddrStruct) == -1)
+ {
+ freeifaddrs(ifAddrStruct);
+ return {};
+ }
for (struct ifaddrs *ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == NULL)
+ continue;
if (ifa->ifa_addr->sa_family == AF_INET) { // IPv4
ipConfig.push_back(std::pair(ifa->ifa_name, StringifyIP(ifa->ifa_addr)));
diff --git a/src/Streams/Stream.cpp b/src/Streams/Stream.cpp
index e7540d2..2194ba9 100644
--- a/src/Streams/Stream.cpp
+++ b/src/Streams/Stream.cpp
@@ -1,4 +1,5 @@
#include "TessesFramework/Streams/Stream.hpp"
+#include
namespace Tesses::Framework::Streams {
int32_t Stream::ReadByte()
@@ -108,6 +109,7 @@ namespace Tesses::Framework::Streams {
read = (size_t)std::min(len-offset,(uint64_t)buffSize);
read = this->Read(buffer,read);
+
strm->WriteBlock(buffer, read);
offset += read;
@@ -122,8 +124,6 @@ namespace Tesses::Framework::Streams {
{
size_t read;
uint8_t* buffer = new uint8_t[buffSize];
-
-
do {
read = this->Read(buffer,buffSize);
strm->WriteBlock(buffer, read);