Add both Basic auth and CGI support

This commit is contained in:
2025-12-04 13:24:23 -06:00
parent abe444d22b
commit 2861fba6f2
15 changed files with 315 additions and 18 deletions

View File

@@ -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<IHttpServer> server, std::function<bool(std::string username, std::string password)> 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;
}
}

135
src/Http/CGIServer.cpp Normal file
View File

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

View File

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

View File

@@ -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<Stream> strm, std::shared_ptr<IHttpServer> server, std::string ip, uint16_t port, bool encrypted)
void HttpServer::Process(std::shared_ptr<Stream> strm, std::shared_ptr<IHttpServer> 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();
}
}

View File

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

View File

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

View File

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

View File

@@ -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<std::string,std::string>(ifa->ifa_name, StringifyIP(ifa->ifa_addr)));

View File

@@ -1,4 +1,5 @@
#include "TessesFramework/Streams/Stream.hpp"
#include <iostream>
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);