first commit

This commit is contained in:
2024-12-06 04:58:55 -06:00
commit 856373b396
61 changed files with 5920 additions and 0 deletions

View File

@ -0,0 +1,263 @@
#include "TessesFramework/Crypto/ClientTLSStream.hpp"
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
#if defined(TESSESFRAMEWORK_EMBED_CERT_BUNDLE)
#include "CertificateChain.h"
#else
#include "TessesFramework/TextStreams/StreamReader.hpp"
using StreamReader = Tesses::Framework::TextStreams::StreamReader;
#endif
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/x509.h>
#include <mbedtls/ssl.h>
#include <mbedtls/net_sockets.h>
#include <mbedtls/error.h>
#endif
#include <cstring>
using namespace Tesses::Framework::Streams;
namespace Tesses::Framework::Crypto
{
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
class ClientTLSPrivateData {
public:
bool eos;
bool owns;
bool success;
Stream* strm;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
mbedtls_x509_crt cachain;
~ClientTLSPrivateData()
{
mbedtls_x509_crt_free(&cachain);
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
mbedtls_ssl_config_free(&conf);
mbedtls_ssl_free(&ssl);
if(this->owns) delete strm;
}
};
#endif
std::string ClientTLSStream::GetCertChain()
{
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
#if defined(TESSESFRAMEWORK_EMBED_CERT_BUNDLE)
return std::string((const char*)CERTIFICATECHAIN,CERTIFICATECHAIN_SIZE);
#else
#if defined(TESSESFRAMEWORK_CERT_BUNDLE_FILE)
StreamReader sr(TESSESFRAMEWORK_CERT_BUNDLE_FILE);
return sr.ReadToEnd();
#endif
#endif
#endif
return "";
}
ClientTLSStream::ClientTLSStream(Tesses::Framework::Streams::Stream* innerStream, bool owns, bool verify, std::string domain) : ClientTLSStream(innerStream,owns,verify,domain,"")
{
}
ClientTLSStream::ClientTLSStream(Tesses::Framework::Streams::Stream* innerStream, bool owns, bool verify, std::string domain, std::string cert)
{
#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->owns = owns;
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);
const char* pers = "TessesFramework";
int ret=0;
/*
#if defined(MBEDTLS_USE_PSA_CRYPTO)
psa_status_t status = psa_crypto_init();
if (status != PSA_SUCCESS) {
mbedtls_fprintf(stderr, "Failed to initialize PSA Crypto implementation: %d\n",
(int) status);
return;
}
#endif
*/
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;
}
if(ret != 0) { printf("FAILED mbedtls_x509_crt_parse cert %i\n",ret); 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;}
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_OPTIONAL);
mbedtls_ssl_conf_ca_chain(&data->conf, &data->cachain, NULL);
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
}
size_t ClientTLSStream::Read(uint8_t* buffer, size_t len)
{
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
auto priv = static_cast<ClientTLSPrivateData*>(this->privateData);
if(!priv->success) return 0;
if(priv->eos) return 0;
int r = mbedtls_ssl_read(&priv->ssl,buffer,len);
if(r == -30848)
{
priv->eos = true;
return 0;
}
return (size_t)r;
#else
return (size_t)0;
#endif
}
size_t ClientTLSStream::Write(const uint8_t* buffer, size_t len)
{
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
auto priv = static_cast<ClientTLSPrivateData*>(this->privateData);
if(!priv->success) return 0;
int r = mbedtls_ssl_write(&priv->ssl,buffer,len);
return (size_t)r;
#else
return (size_t)0;
#endif
}
int ClientTLSStream::strm_send(void* ctx,const unsigned char* buf,size_t len)
{
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
auto priv = static_cast<ClientTLSPrivateData*>(ctx);
return (int)priv->strm->Write(buf, len);
#else
return 0;
#endif
}
int ClientTLSStream::strm_recv(void* ctx,unsigned char* buf,size_t len)
{
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
auto priv = static_cast<ClientTLSPrivateData*>(ctx);
return (int)priv->strm->Read(buf, len);
#else
return 0;
#endif
}
bool ClientTLSStream::CanRead()
{
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
return !(!static_cast<ClientTLSPrivateData*>(this->privateData)->success || static_cast<ClientTLSPrivateData*>(this->privateData)->eos);
#else
return false;
#endif
}
bool ClientTLSStream::CanWrite()
{
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
return !(!static_cast<ClientTLSPrivateData*>(this->privateData)->success || static_cast<ClientTLSPrivateData*>(this->privateData)->eos);
#else
return false;
#endif
}
bool ClientTLSStream::EndOfStream()
{
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
return !static_cast<ClientTLSPrivateData*>(this->privateData)->success || static_cast<ClientTLSPrivateData*>(this->privateData)->eos;
#else
return true;
#endif
}
ClientTLSStream::~ClientTLSStream()
{
#if defined(TESSESFRAMEWORK_ENABLE_MBED)
delete static_cast<ClientTLSPrivateData*>(this->privateData);
#endif
}
}

122
src/Filesystem/LocalFS.cpp Normal file
View File

@ -0,0 +1,122 @@
#include "TessesFramework/Filesystem/LocalFS.hpp"
#include "TessesFramework/Streams/FileStream.hpp"
#include <iostream>
namespace Tesses::Framework::Filesystem
{
VFSPath LocalFilesystem::ReadLink(VFSPath path)
{
return this->SystemToVFSPath(std::filesystem::read_symlink(this->VFSPathToSystem(path)));
}
Tesses::Framework::Streams::Stream* LocalFilesystem::OpenFile(VFSPath path, std::string mode)
{
return new Tesses::Framework::Streams::FileStream(VFSPathToSystem(path), mode);
}
void LocalFilesystem::DeleteDirectory(VFSPath path)
{
std::filesystem::remove(VFSPathToSystem(path));
}
void LocalFilesystem::DeleteFile(VFSPath path)
{
std::filesystem::remove(VFSPathToSystem(path));
}
void LocalFilesystem::CreateDirectory(VFSPath path)
{
std::filesystem::create_directories(VFSPathToSystem(path));
}
bool LocalFilesystem::DirectoryExists(VFSPath path)
{
return std::filesystem::is_directory(VFSPathToSystem(path));
}
bool LocalFilesystem::RegularFileExists(VFSPath path)
{
return std::filesystem::is_regular_file(VFSPathToSystem(path));
}
bool LocalFilesystem::SymlinkExists(VFSPath path)
{
return std::filesystem::is_symlink(VFSPathToSystem(path));
}
bool LocalFilesystem::BlockDeviceExists(VFSPath path)
{
return std::filesystem::is_block_file(VFSPathToSystem(path));
}
bool LocalFilesystem::CharacterDeviceExists(VFSPath path)
{
return std::filesystem::is_character_file(VFSPathToSystem(path));
}
bool LocalFilesystem::SocketFileExists(VFSPath path)
{
return std::filesystem::is_socket(VFSPathToSystem(path));
}
bool LocalFilesystem::FIFOFileExists(VFSPath path)
{
return std::filesystem::is_fifo(VFSPathToSystem(path));
}
void LocalFilesystem::CreateSymlink(VFSPath existingFile, VFSPath symlinkFile)
{
if(std::filesystem::is_directory(VFSPathToSystem(existingFile)))
{
std::filesystem::create_directory_symlink(VFSPathToSystem(existingFile),VFSPathToSystem(symlinkFile));
}
else
{
std::filesystem::create_symlink(VFSPathToSystem(existingFile),VFSPathToSystem(symlinkFile));
}
}
void LocalFilesystem::CreateHardlink(VFSPath existingFile, VFSPath newName)
{
std::filesystem::create_hard_link(VFSPathToSystem(existingFile),VFSPathToSystem(newName));
}
void LocalFilesystem::MoveFile(VFSPath src, VFSPath dest)
{
std::filesystem::rename(VFSPathToSystem(src),VFSPathToSystem(dest));
}
void LocalFilesystem::MoveDirectory(VFSPath src, VFSPath dest)
{
std::filesystem::rename(VFSPathToSystem(src),VFSPathToSystem(dest));
}
std::string LocalFilesystem::VFSPathToSystem(VFSPath path)
{
#if defined(WIN32)
bool first=true;
std::string p = {};
for(auto item : path.path)
{
if(!(first && !item.empty() && item.back()==':') && !(first && this->relative))
p.push_back('\\');
p.append(item);
first=false;
}
return p;
#else
return path.ToString();
#endif
}
VFSPath LocalFilesystem::SystemToVFSPath(std::string path)
{
VFSPath p;
p.path = VFSPath::SplitPath(path,true);
p.relative=true;
if(!path.empty())
{
if(path.front() == '/') p.relative=false;
if(!p.path.empty())
{
auto firstPartPath = p.path.front();
if(!firstPartPath.empty() && firstPartPath.back() == '/') p.relative=false;
}
}
return p;
}
void LocalFilesystem::GetPaths(VFSPath path, std::vector<VFSPath>& paths)
{
for(auto item : std::filesystem::directory_iterator(VFSPathToSystem(path)))
{
paths.push_back(VFSPath(path, item.path().filename().string()));
}
}
}
// C:/Users/Jim/Joel

View File

@ -0,0 +1,553 @@
#include "TessesFramework/Filesystem/MountableFilesystem.hpp"
#include "TessesFramework/Filesystem/NullFilesystem.hpp"
#include <iostream>
namespace Tesses::Framework::Filesystem
{
MountableFilesystem::MountableFilesystem() : MountableFilesystem(new NullFilesystem(),true)
{
}
MountableFilesystem::MountableFilesystem(VFS* root, bool owns)
{
this->root = root;
this->owns = owns;
}
MountableDirectory::~MountableDirectory()
{
if(this->owns && this->vfs) delete this->vfs;
for(auto dir : this->dirs) delete dir;
}
MountableFilesystem::~MountableFilesystem()
{
if(this->owns && this->root) delete root;
for(auto item : this->directories) delete item;
}
void MountableFilesystem::GetFS(VFSPath srcPath, VFSPath& destRoot, VFSPath& destPath, VFS*& vfs)
{
if(srcPath.path.empty()) return;
for(auto item : this->directories)
{
if(srcPath.path.front() == item->name)
{
if(item->vfs != nullptr)
{
vfs = item->vfs;
VFSPath srcPath1(std::vector(srcPath.path.begin()+1,srcPath.path.end()));
srcPath1.relative=false;
destPath = srcPath1;
destRoot = VFSPath(VFSPath(),item->name);
}
VFSPath srcPath2(std::vector(srcPath.path.begin()+1,srcPath.path.end()));
srcPath2.relative=false;
item->GetFS(srcPath2,VFSPath(VFSPath(),item->name), destRoot,destPath,vfs);
return;
}
}
}
void MountableDirectory::GetFS(VFSPath srcPath, VFSPath curDir, VFSPath& destRoot, VFSPath& destPath, VFS*& vfs)
{
if(srcPath.path.empty()) return;
for(auto item : this->dirs)
{
if(!srcPath.path.empty() && srcPath.path.front() == item->name)
{
if(item->vfs != nullptr)
{
vfs = item->vfs;
VFSPath srcPath1(std::vector(srcPath.path.begin()+1,srcPath.path.end()));
srcPath1.relative=false;
destPath = srcPath1;
destRoot = curDir;
}
VFSPath srcPath2(std::vector(srcPath.path.begin()+1,srcPath.path.end()));
srcPath2.relative=false;
item->GetFS(srcPath2,VFSPath(curDir,item->name), destRoot,destPath,vfs);
return;
}
}
}
VFSPath MountableFilesystem::ReadLink(VFSPath path)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(vfs != nullptr)
return VFSPath(destRoot,vfs->ReadLink(destPath));
return VFSPath();
}
Tesses::Framework::Streams::Stream* MountableFilesystem::OpenFile(VFSPath path, std::string mode)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(vfs != nullptr)
return vfs->OpenFile(destPath,mode);
return nullptr;
}
void MountableFilesystem::CreateDirectory(VFSPath path)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(destPath.path.empty()) return;
if(vfs != nullptr)
vfs->CreateDirectory(destPath);
}
void MountableFilesystem::DeleteDirectory(VFSPath path)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(destPath.path.empty()) return;
if(vfs != nullptr)
vfs->DeleteDirectory(destPath);
}
bool MountableFilesystem::SpecialFileExists(VFSPath path)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(vfs != nullptr)
return vfs->SpecialFileExists(destPath);
return false;
}
bool MountableFilesystem::FileExists(VFSPath path)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(vfs != nullptr)
return vfs->FileExists(destPath);
return false;
}
bool MountableFilesystem::RegularFileExists(VFSPath path)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(vfs != nullptr)
return vfs->RegularFileExists(destPath);
return false;
}
bool MountableFilesystem::SymlinkExists(VFSPath path)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(vfs != nullptr)
return vfs->SymlinkExists(destPath);
return false;
}
bool MountableFilesystem::CharacterDeviceExists(VFSPath path)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(vfs != nullptr)
return vfs->CharacterDeviceExists(destPath);
return false;
}
bool MountableFilesystem::BlockDeviceExists(VFSPath path)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(vfs != nullptr)
return vfs->BlockDeviceExists(destPath);
return false;
}
bool MountableFilesystem::SocketFileExists(VFSPath path)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(vfs != nullptr)
return vfs->SocketFileExists(destPath);
return false;
}
bool MountableFilesystem::FIFOFileExists(VFSPath path)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(vfs != nullptr)
return vfs->FIFOFileExists(destPath);
return false;
}
bool MountableFilesystem::DirectoryExists(VFSPath path)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(destPath.path.empty()) return true;
if(vfs != nullptr)
return vfs->DirectoryExists(destPath);
return false;
}
void MountableFilesystem::DeleteFile(VFSPath path)
{
path = path.CollapseRelativeParents();
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(vfs != nullptr)
vfs->DeleteFile(destPath);
}
void MountableFilesystem::CreateSymlink(VFSPath existingFile, VFSPath symlinkFile)
{
existingFile = existingFile.CollapseRelativeParents();
symlinkFile = existingFile.CollapseRelativeParents();
VFSPath existingDestRoot;
VFSPath existingDestPath = existingFile;
VFS* existingVFS = root;
VFSPath symlinkDestRoot;
VFSPath symlinkDestPath = symlinkFile;
VFS* symlinkVFS = root;
GetFS(existingFile, existingDestRoot, existingDestPath, existingVFS);
GetFS(symlinkFile, symlinkDestRoot, symlinkDestPath, symlinkVFS);
if(existingVFS != nullptr && existingVFS == symlinkVFS)
existingVFS->CreateSymlink(existingDestPath, symlinkDestPath);
}
void MountableFilesystem::MoveDirectory(VFSPath src, VFSPath dest)
{
src = src.CollapseRelativeParents();
dest = dest.CollapseRelativeParents();
VFSPath srcDestRoot;
VFSPath srcDestPath = src;
VFS* srcVFS = root;
VFSPath destDestRoot;
VFSPath destDestPath = dest;
VFS* destVFS = root;
GetFS(src, srcDestRoot, srcDestPath, srcVFS);
GetFS(dest, destDestRoot, destDestPath, destVFS);
if(srcVFS != nullptr && srcVFS == destVFS)
srcVFS->MoveDirectory(srcDestPath, destDestPath);
}
void MountableFilesystem::MoveFile(VFSPath src, VFSPath dest)
{
src = src.CollapseRelativeParents();
dest = dest.CollapseRelativeParents();
VFSPath srcDestRoot;
VFSPath srcDestPath = src;
VFS* srcVFS = root;
VFSPath destDestRoot;
VFSPath destDestPath = dest;
VFS* destVFS = root;
GetFS(src, srcDestRoot, srcDestPath, srcVFS);
GetFS(dest, destDestRoot, destDestPath, destVFS);
if(srcVFS != nullptr && srcVFS == destVFS)
srcVFS->MoveFile(srcDestPath, destDestPath);
}
void MountableFilesystem::CreateHardlink(VFSPath existingFile, VFSPath newName)
{
existingFile = existingFile.CollapseRelativeParents();
newName = existingFile.CollapseRelativeParents();
VFSPath existingDestRoot;
VFSPath existingDestPath = existingFile;
VFS* existingVFS = root;
VFSPath newNameRoot;
VFSPath newNamePath = newName;
VFS* newNameVFS = root;
GetFS(existingFile, existingDestRoot, existingDestPath, existingVFS);
GetFS(newName, newNameRoot, newNamePath, newNameVFS);
if(existingVFS != nullptr && existingVFS == newNameVFS)
existingVFS->CreateHardlink(existingDestPath, newNamePath);
}
void MountableFilesystem::GetPaths(VFSPath path, std::vector<VFSPath>& paths)
{
path = path.CollapseRelativeParents();
bool mydirs = path.path.empty();
std::vector<MountableDirectory*>* dirs = &this->directories;
if(!path.path.empty())
for(auto p : path.path)
{
mydirs=true;
bool hasSet=false;
for(auto itm : *dirs)
{
if(itm->name == p)
{
hasSet=true;
dirs = &itm->dirs;
break;
}
}
if(!hasSet)
{
mydirs=false;
break;
}
}
VFSPath destRoot;
VFSPath destPath = path;
VFS* vfs = root;
GetFS(path, destRoot, destPath, vfs);
if(vfs != nullptr)
{
std::vector<VFSPath> paths2;
if(vfs->DirectoryExists(destPath) || !mydirs)
vfs->GetPaths(destPath,paths2);
for(auto item : paths2)
{
paths.push_back(VFSPath(destPath, item.GetFileName()));
}
if(mydirs)
for(auto item : *dirs)
{
bool cantAdd=false;
for(auto d : paths) {
if(d.GetFileName() == item->name)
{
cantAdd=true;
break;
}
}
if(!cantAdd) paths.push_back(VFSPath(destPath, item->name));
}
}
}
void MountableFilesystem::Mount(VFSPath path, VFS* fs, bool owns)
{
path = path.CollapseRelativeParents();
if(path.path.empty())
{
if(owns) delete fs;
return;
}
auto* fsLs = &this->directories;
bool needToCreate=true;
for(auto index = path.path.begin(); index < path.path.end()-1; index++)
{
needToCreate=true;
for(auto item : *fsLs)
{
if(item->name == *index)
{
needToCreate=false;
fsLs = &(item->dirs);
break;
}
}
if(needToCreate)
{
MountableDirectory* dir = new MountableDirectory();
dir->name = *index;
dir->owns=false;
dir->vfs=NULL;
fsLs->push_back(dir);
fsLs = &(dir->dirs);
}
}
needToCreate=true;
std::string lastDir = path.GetFileName();
for(auto item : *fsLs)
{
if(item->name == lastDir)
{
needToCreate=false;
if(item->owns && item->vfs) delete item->vfs;
item->vfs = fs;
break;
}
}
if(needToCreate)
{
MountableDirectory* dir = new MountableDirectory();
dir->name = lastDir;
dir->owns=owns;
dir->vfs=fs;
fsLs->push_back(dir);
}
}
static bool myumount(MountableDirectory* dir,VFSPath path)
{
if(path.path.empty())
{
if(dir->owns && dir->vfs) delete dir->vfs;
dir->vfs = nullptr;
}
if(dir->dirs.empty())
{
delete dir;
return true;
}
for(auto index = dir->dirs.begin(); index < dir->dirs.end(); index++)
{
auto item = *index;
if(!path.path.empty() && path.path.front() == item->name)
{
VFSPath srcPath2(std::vector(path.path.begin()+1,path.path.end()));
if(myumount(item,srcPath2))
{
dir->dirs.erase(index);
}
if(dir->dirs.empty())
{
delete dir;
return true;
}
return false;
}
}
return false;
}
void MountableFilesystem::Unmount(VFSPath path)
{
path = path.CollapseRelativeParents();
for(auto index = this->directories.begin(); index < this->directories.end(); index++)
{
auto item = *index;
if(!path.path.empty() && path.path.front() == item->name)
{
VFSPath srcPath2(std::vector(path.path.begin()+1,path.path.end()));
if(myumount(item,srcPath2))
{
this->directories.erase(index);
}
return;
}
}
}
std::string MountableFilesystem::VFSPathToSystem(VFSPath path)
{
return path.ToString();
}
VFSPath MountableFilesystem::SystemToVFSPath(std::string path)
{
return VFSPath(path);
}
}

View File

@ -0,0 +1,45 @@
#include "TessesFramework/Filesystem/NullFilesystem.hpp"
namespace Tesses::Framework::Filesystem
{
Tesses::Framework::Streams::Stream* NullFilesystem::OpenFile(VFSPath path, std::string mode)
{
return nullptr;
}
void NullFilesystem::CreateDirectory(VFSPath path)
{
}
void NullFilesystem::DeleteDirectory(VFSPath path)
{
}
bool NullFilesystem::RegularFileExists(VFSPath path)
{
return false;
}
bool NullFilesystem::DirectoryExists(VFSPath path)
{
return false;
}
void NullFilesystem::DeleteFile(VFSPath path)
{
}
void NullFilesystem::GetPaths(VFSPath path, std::vector<VFSPath>& paths)
{
}
void NullFilesystem::MoveFile(VFSPath src, VFSPath dest)
{
}
std::string NullFilesystem::VFSPathToSystem(VFSPath path)
{
return path.ToString();
}
VFSPath NullFilesystem::SystemToVFSPath(std::string path)
{
return VFSPath(path);
}
}

View File

@ -0,0 +1,132 @@
#include "TessesFramework/Filesystem/SubdirFilesystem.hpp"
#include <iostream>
namespace Tesses::Framework::Filesystem
{
VFSPath SubdirFilesystem::ReadLink(VFSPath path)
{
return FromParent(this->parent->ReadLink(ToParent(path)));
}
VFSPath SubdirFilesystem::FromParent(VFSPath path)
{
// /a/b/c
// /a/b/c
VFSPath newPath;
newPath.relative=false;
if(path.path.size() >= this->path.path.size())
{
newPath.path.reserve(path.path.size()-this->path.path.size());
for(size_t i = this->path.path.size(); i < path.path.size();i++)
{
newPath.path.push_back(path.path[i]);
}
}
return newPath;
}
VFSPath SubdirFilesystem::ToParent(VFSPath path)
{
return VFSPath(this->path, path.CollapseRelativeParents());
}
SubdirFilesystem::SubdirFilesystem(VFS* parent, VFSPath path, bool owns)
{
this->parent = parent;
this->path = path;
this->owns=owns;
}
Tesses::Framework::Streams::Stream* SubdirFilesystem::OpenFile(VFSPath path, std::string mode)
{
return this->parent->OpenFile(ToParent(path),mode);
}
void SubdirFilesystem::CreateDirectory(VFSPath path)
{
this->parent->CreateDirectory(ToParent(path));
}
void SubdirFilesystem::DeleteDirectory(VFSPath path)
{
this->parent->DeleteDirectory(ToParent(path));
}
bool SubdirFilesystem::RegularFileExists(VFSPath path)
{
return this->parent->RegularFileExists(ToParent(path));
}
bool SubdirFilesystem::SymlinkExists(VFSPath path)
{
return this->parent->SymlinkExists(ToParent(path));
}
bool SubdirFilesystem::CharacterDeviceExists(VFSPath path)
{
return this->parent->CharacterDeviceExists(ToParent(path));
}
bool SubdirFilesystem::BlockDeviceExists(VFSPath path)
{
return this->parent->BlockDeviceExists(ToParent(path));
}
bool SubdirFilesystem::SocketFileExists(VFSPath path)
{
return this->parent->SocketFileExists(ToParent(path));
}
bool SubdirFilesystem::FIFOFileExists(VFSPath path)
{
return this->parent->FIFOFileExists(ToParent(path));
}
bool SubdirFilesystem::DirectoryExists(VFSPath path)
{
return this->parent->DirectoryExists(ToParent(path));
}
void SubdirFilesystem::DeleteFile(VFSPath path)
{
this->parent->DeleteFile(ToParent(path));
}
void SubdirFilesystem::CreateSymlink(VFSPath existingFile, VFSPath symlinkFile)
{
this->parent->CreateSymlink(ToParent(existingFile),ToParent(symlinkFile));
}
void SubdirFilesystem::GetPaths(VFSPath path, std::vector<VFSPath>& paths)
{
std::vector<VFSPath> paths2;
this->parent->GetPaths(ToParent(path),paths2);
for(auto item : paths2)
{
paths.push_back(FromParent(item));
}
}
void SubdirFilesystem::CreateHardlink(VFSPath existingFile, VFSPath newName)
{
this->parent->CreateHardlink(ToParent(existingFile),ToParent(newName));
}
void SubdirFilesystem::MoveFile(VFSPath src, VFSPath dest)
{
this->parent->MoveFile(ToParent(src),ToParent(dest));
}
void SubdirFilesystem::MoveDirectory(VFSPath src, VFSPath dest)
{
this->parent->MoveDirectory(ToParent(src),ToParent(dest));
}
std::string SubdirFilesystem::VFSPathToSystem(VFSPath path)
{
return this->parent->VFSPathToSystem(ToParent(path));
}
VFSPath SubdirFilesystem::SystemToVFSPath(std::string path)
{
return FromParent(this->parent->SystemToVFSPath(path));
}
void SubdirFilesystem::DeleteDirectoryRecurse(VFSPath path)
{
this->parent->DeleteDirectoryRecurse(ToParent(path));
}
bool SubdirFilesystem::SpecialFileExists(VFSPath path)
{
return this->parent->SpecialFileExists(ToParent(path));
}
bool SubdirFilesystem::FileExists(VFSPath path)
{
return this->parent->FileExists(ToParent(path));
}
SubdirFilesystem::~SubdirFilesystem()
{
if(this->owns)
delete this->parent;
}
}

208
src/Filesystem/VFS.cpp Normal file
View File

@ -0,0 +1,208 @@
#include "TessesFramework/Filesystem/VFS.hpp"
#include "TessesFramework/Http/HttpUtils.hpp"
namespace Tesses::Framework::Filesystem
{
VFSPath VFS::ReadLink(VFSPath path)
{
return VFSPath("/");
}
VFSPath VFSPath::CollapseRelativeParents()
{
std::vector<std::string> parts;
for(auto item : this->path)
{
if(item == "..")
{
if(!parts.empty())
{
parts.erase(parts.end()-1);
}
}
else if(item == ".")
{
//do nothing but don't emit this
}
else
{
parts.push_back(item);
}
}
VFSPath newpath;
newpath.relative = relative;
newpath.path = parts;
return newpath;
}
VFSPath VFSPath::RelativeCurrentDirectory()
{
VFSPath path;
path.relative=true;
return path;
}
std::vector<std::string> VFSPath::SplitPath(std::string path,bool nativePath)
{
std::vector<std::string> parts;
std::string curPath = {};
for(auto c : path)
{
if(c == '/')
{
if(!curPath.empty())
{
parts.push_back(curPath);
curPath = {};
}
}
#if defined(WIN32)
else if(c == '\\' && nativePath)
{
if(!curPath.empty())
{
parts.push_back(curPath);
curPath = {};
}
}
#endif
else
{
curPath.push_back(c);
}
}
if(!curPath.empty())
{
parts.push_back(curPath);
curPath = {};
}
return parts;
}
VFSPath::VFSPath()
{
this->relative=false;
}
VFSPath::VFSPath(std::vector<std::string> p)
{
this->path = p;
}
VFSPath::VFSPath(std::string str)
{
this->path = SplitPath(str);
this->relative=true;
if(!str.empty())
{
if(str.front() == '/') this->relative=false;
if(!this->path.empty())
{
auto firstPartPath = this->path.front();
if(!firstPartPath.empty() && firstPartPath.back() == '/') this->relative=false;
}
}
}
VFSPath::VFSPath(VFSPath p1, VFSPath p2)
{
this->relative = p1.relative;
this->path.insert(this->path.end(),p1.path.begin(),p1.path.end());
this->path.insert(this->path.end(),p2.path.begin(),p2.path.end());
}
VFSPath::VFSPath(VFSPath p1, std::string subpath) : VFSPath(p1, VFSPath(subpath))
{
}
VFSPath VFSPath::GetParent()
{
std::vector<std::string> paths;
if(this->path.empty()) return VFSPath();
paths.insert(paths.begin(), this->path.begin(), this->path.end()-1);
return VFSPath(paths);
}
std::string VFSPath::GetFileName()
{
if(this->path.empty()) return "";
return this->path.back();
}
std::string VFSPath::ToString()
{
if(this->path.empty() && !this->relative) return "/";
bool first=true;
std::string p = {};
for(auto item : this->path)
{
if(!(first && !item.empty() && item.back()==':') && !(first && this->relative))
p.push_back('/');
p.append(item);
first=false;
}
return p;
}
VFS::~VFS()
{
}
bool VFS::FIFOFileExists(VFSPath path) {return false;}
bool VFS::SocketFileExists(VFSPath path) {return false;}
bool VFS::CharacterDeviceExists(VFSPath path) {return false;}
bool VFS::BlockDeviceExists(VFSPath path) {return false;}
bool VFS::SymlinkExists(VFSPath path) {return false;}
void VFS::MoveDirectory(VFSPath src, VFSPath dest)
{
std::vector<VFSPath> paths;
GetPaths(src, paths);
for(auto item : paths)
{
if(DirectoryExists(item))
{
MoveDirectory(item, VFSPath(dest, item.GetFileName()));
}
else
{
MoveFile(item, VFSPath(dest, item.GetFileName()));
}
}
DeleteDirectory(src);
}
void VFS::CreateSymlink(VFSPath existingFile, VFSPath symlinkFile)
{
}
void VFS::CreateHardlink(VFSPath existingFile, VFSPath newName)
{
}
bool VFS::SpecialFileExists(VFSPath path)
{
return SymlinkExists(path) || BlockDeviceExists(path) || CharacterDeviceExists(path) || SocketFileExists(path) || FIFOFileExists(path);
}
bool VFS::FileExists(VFSPath path)
{
return RegularFileExists(path) || SpecialFileExists(path);
}
void VFS::DeleteDirectoryRecurse(VFSPath path)
{
if(!DirectoryExists(path)) return;
std::vector<VFSPath> paths;
GetPaths(path, paths);
for(auto item : paths)
{
if(DirectoryExists(item))
{
DeleteDirectoryRecurse(item);
}
else
{
DeleteFile(item);
}
}
DeleteDirectory(path);
}
}

View File

@ -0,0 +1,77 @@
#include "TessesFramework/Http/ContentDisposition.hpp"
#include "TessesFramework/Http/HttpUtils.hpp"
#include <iostream>
namespace Tesses::Framework::Http
{
bool ContentDisposition::TryParse(std::string str, ContentDisposition& cd)
{
auto res = HttpUtils::SplitString(str,"; ");
if(res.empty()) return false;
cd.type = res[0];
bool hasFileNameStar = false;
for(size_t i = 1; i < res.size(); i++)
{
auto res2 = HttpUtils::SplitString(res[i],"=",2);
if(res2.size() == 2)
{
if(res2[0] == "filename*")
{
//cd.filename = res2[1];
//UTF-8''
std::string p = res2[1];
if(p.find("UTF-8''") == 0)
{
hasFileNameStar = true;
p = HttpUtils::UrlPathDecode(p.substr(7));
cd.filename = p;
}
}
else if(res2[0] == "filename" && !hasFileNameStar)
{
std::string p = res2[1];
if(!p.empty() && p[0] == '\"')
{
p = p.substr(1, p.size()-2);
}
p = HttpUtils::UrlPathDecode(p);
cd.filename = p;
}
else if(res2[0] == "name")
{
std::string p = res2[1];
if(!p.empty() && p[0] == '\"')
{
p = p.substr(1, p.size()-2);
}
cd.fieldName = HttpUtils::UrlPathDecode(p);
}
}
}
return true;
}
std::string ContentDisposition::ToString()
{
std::vector<std::string> parts;
parts.push_back(this->type);
if(!this->fieldName.empty())
{
parts.push_back("name=\"" + HttpUtils::UrlPathEncode(this->fieldName,true) + "\"");
}
if(!this->filename.empty())
{
parts.push_back("filename=\"" + HttpUtils::UrlPathEncode(this->filename,true) + "\"");
}
return HttpUtils::Join("; ", parts);
}
}

131
src/Http/FileServer.cpp Normal file
View File

@ -0,0 +1,131 @@
#include "TessesFramework/Http/FileServer.hpp"
#include "TessesFramework/Filesystem/LocalFS.hpp"
#include "TessesFramework/Filesystem/SubdirFilesystem.hpp"
#include <iostream>
#include <unistd.h>
using LocalFilesystem = Tesses::Framework::Filesystem::LocalFilesystem;
using SubdirFilesystem = Tesses::Framework::Filesystem::SubdirFilesystem;
using VFSPath = Tesses::Framework::Filesystem::VFSPath;
using VFS = Tesses::Framework::Filesystem::VFS;
namespace Tesses::Framework::Http
{
FileServer::FileServer(std::filesystem::path path,bool allowListing,bool spa) : FileServer(path,allowListing,spa,{"index.html","default.html","index.htm","default.htm"})
{
}
FileServer::FileServer(std::filesystem::path path,bool allowListing, bool spa, std::vector<std::string> defaultNames)
{
LocalFilesystem* lfs=new LocalFilesystem;
SubdirFilesystem* sdfs=new SubdirFilesystem(lfs,lfs->SystemToVFSPath(path),true);
this->vfs = sdfs;
this->spa = spa;
this->ownsVFS=true;
this->allowListing = allowListing;
this->defaultNames = defaultNames;
}
FileServer::FileServer(Tesses::Framework::Filesystem::VFS* fs, bool owns, bool allowListing,bool spa) : FileServer(fs,owns,allowListing,spa,{"index.html","default.html","index.htm","default.htm"})
{
}
FileServer::FileServer(Tesses::Framework::Filesystem::VFS* fs, bool owns, bool allowListing, bool spa, std::vector<std::string> defaultNames)
{
this->vfs = fs;
this->ownsVFS = owns;
this->allowListing = allowListing;
this->defaultNames = defaultNames;
this->spa = spa;
}
bool FileServer::SendFile(ServerContext& ctx,VFSPath path)
{
auto strm = this->vfs->OpenFile(path,"rb");
bool retVal = false;
if(strm != nullptr)
{
ctx.WithMimeType(HttpUtils::MimeType(path.GetFileName())).SendStream(strm);
retVal = true;
}
delete strm;
return retVal;
}
bool FileServer::Handle(ServerContext& ctx)
{
auto path = HttpUtils::UrlPathDecode(ctx.path);
if(this->vfs->DirectoryExists(path))
{
for(auto f : defaultNames)
{
VFSPath p(path,f);
if(this->vfs->FileExists(p))
return SendFile(ctx,p);
}
if(this->allowListing)
{
std::string p = HttpUtils::HtmlEncode(ctx.originalPath);
std::string html = "<!DOCTYPE html><html><head><title>Index of ";
html.append(p);
html.append("</title></head><body><h1>Index of ");
html.append(p);
html.append("</h1><hr><pre><a href=\"../\">../</a>\r\n");
std::vector<VFSPath> ents;
vfs->GetPaths(path, ents);
for(auto item : ents)
{
if(vfs->DirectoryExists(item))
{
//is dir
std::string path = item.GetFileName();
html.append("<a href=\"");
html.append(HttpUtils::UrlPathEncode(path) + "/");
html.append("\">");
html.append(HttpUtils::HtmlEncode(path) + "/");
html.append("</a>\r\n");
}
else
{
//is file
std::string path = item.GetFileName();
html.append("<a href=\"");
html.append(HttpUtils::UrlPathEncode(path));
html.append("\">");
html.append(HttpUtils::HtmlEncode(path));
html.append("</a>\r\n");
}
}
html.append("</pre><hr></body></html>");
ctx.WithMimeType("text/html").SendText(html);
return true;
}
}
else if(this->vfs->FileExists(path))
{
return SendFile(ctx,path);
}
else if(this->spa)
{
for(auto f : defaultNames)
{
VFSPath p(f);
p.relative=false;
if(this->vfs->FileExists(p))
return SendFile(ctx,p);
}
}
return false;
}
FileServer::~FileServer()
{
if(this->ownsVFS)
delete this->vfs;
}
}

226
src/Http/HttpClient.cpp Normal file
View File

@ -0,0 +1,226 @@
#include "TessesFramework/Http/HttpClient.hpp"
#include "TessesFramework/Crypto/ClientTLSStream.hpp"
#include "TessesFramework/Streams/NetworkStream.hpp"
#include "TessesFramework/TextStreams/StreamWriter.hpp"
#include "TessesFramework/TextStreams/StreamReader.hpp"
#include "TessesFramework/Http/HttpStream.hpp"
#include "TessesFramework/Streams/BufferedStream.hpp"
#include <iostream>
using Stream = Tesses::Framework::Streams::Stream;
using NetworkStream = Tesses::Framework::Streams::NetworkStream;
using ClientTLSStream = Tesses::Framework::Crypto::ClientTLSStream;
using StreamReader = Tesses::Framework::TextStreams::StreamReader;
using StreamWriter = Tesses::Framework::TextStreams::StreamWriter;
using BufferedStream = Tesses::Framework::Streams::BufferedStream;
using HttpStream = Tesses::Framework::Http::HttpStream;
namespace Tesses::Framework::Http
{
HttpRequest::HttpRequest()
{
this->body=nullptr;
this->followRedirects=true;
this->ignoreSSLErrors=false;
this->trusted_root_cert_bundle="";
this->method = "GET";
this->requestHeaders.SetValue("Connection","close");
}
void HttpRequest::SendRequest(Tesses::Framework::Streams::Stream* strm)
{
Uri uri;
if(!Uri::TryParse(this->url, uri)) return;
if(body != nullptr)
{
body->HandleHeaders(requestHeaders);
}
std::string request = method + " " + uri.GetPathAndQuery() + " HTTP/1.1\r\nHost: " + uri.HostPort() + "\r\n";
for(auto headers : requestHeaders.kvp)
{
for(auto item : headers.second)
{
request.append(headers.first);
request.append(": ");
request.append(item);
request.append("\r\n");
}
}
request.append("\r\n");
StreamWriter writer(strm, false);
writer.Write(request);
if(body != nullptr)
{
body->Write(strm);
}
}
Stream* HttpRequest::EstablishConnection(Uri uri, bool ignoreSSLErrors, std::string trusted_root_cert_bundle)
{
if(uri.scheme == "http:")
{
return new NetworkStream(uri.host,uri.GetPort(),false,false,false);
}
else if(uri.scheme == "https:")
{
auto netStrm = new NetworkStream(uri.host,uri.GetPort(),false,false,false);
if(netStrm == nullptr) return netStrm;
return new ClientTLSStream(netStrm, true, ignoreSSLErrors,uri.host, trusted_root_cert_bundle);
}
return nullptr;
}
HttpResponse::~HttpResponse()
{
if(this->owns)
delete this->handleStrm;
}
HttpResponse::HttpResponse(Stream* strm, bool owns)
{
this->handleStrm=nullptr;
this->owns=owns;
StreamReader reader(strm, false);
std::string statusLine;
if(!reader.ReadLine(statusLine)) return;
auto statusLinesPart = HttpUtils::SplitString(statusLine," ",3);
if(statusLinesPart.size() >= 2)
{
this->version = statusLinesPart[0];
this->statusCode = (StatusCode)std::stoi(statusLinesPart[1]);
}
std::string line;
while(reader.ReadLine(line))
{
if(line.empty()) break;
auto v = HttpUtils::SplitString(line,": ", 2);
if(v.size() != 2)
{
delete strm;
return;
}
this->responseHeaders.AddValue(v[0],v[1]);
line.clear();
}
this->handleStrm = strm;
}
HttpResponse::HttpResponse(HttpRequest& req)
{
this->owns=true;
this->handleStrm = nullptr;
std::string url = req.url;
Uri uri;
while(Uri::TryParse(url, uri))
{
auto strm = HttpRequest::EstablishConnection(uri, req.ignoreSSLErrors,req.trusted_root_cert_bundle);
if(strm == nullptr) return;
auto reqHeaders = req.requestHeaders;
if(req.body != nullptr)
{
req.body->HandleHeaders(reqHeaders);
}
std::string request = req.method + " " + uri.GetPathAndQuery() + " HTTP/1.1\r\nHost: " + uri.HostPort() + "\r\n";
for(auto headers : reqHeaders.kvp)
{
for(auto item : headers.second)
{
request.append(headers.first);
request.append(": ");
request.append(item);
request.append("\r\n");
}
}
request.append("\r\n");
StreamWriter writer(strm, false);
writer.Write(request);
if(req.body != nullptr)
{
req.body->Write(strm);
}
StreamReader reader(strm, false);
std::string statusLine;
if(!reader.ReadLine(statusLine)) break;
auto statusLinesPart = HttpUtils::SplitString(statusLine," ",3);
if(statusLinesPart.size() >= 2)
{
this->version = statusLinesPart[0];
this->statusCode = (StatusCode)std::stoi(statusLinesPart[1]);
}
std::string line;
while(reader.ReadLine(line))
{
if(line.empty()) break;
auto v = HttpUtils::SplitString(line,": ", 2);
if(v.size() != 2)
{
delete strm;
return;
}
this->responseHeaders.AddValue(v[0],v[1]);
line.clear();
}
std::string location;
Uri uri2;
if(req.followRedirects && (this->statusCode == MovedPermanently || this->statusCode == PermanentRedirect || this->statusCode == TemporaryRedirect) && this->responseHeaders.TryGetFirst("Location",location) && uri.Relative(location, uri2))
{
this->responseHeaders.Clear();
url = uri2.ToString();
delete strm;
continue;
}
this->handleStrm = strm;
break;
}
}
std::string HttpResponse::ReadAsString()
{
auto strm = ReadAsStream();
if(strm == nullptr) return {};
StreamReader r(strm, true);
return r.ReadToEnd();
}
void HttpResponse::CopyToStream(Stream* strm)
{
if(strm == nullptr) return;
auto src = ReadAsStream();
if(src == nullptr) return;
src->CopyTo(*strm);
delete src;
}
Stream* HttpResponse::ReadAsStream()
{
if(this->handleStrm == nullptr) return nullptr;
int64_t length = -1;
if(!this->responseHeaders.TryGetFirstInt("Content-Length",length))
{
length = -1;
}
return new HttpStream(this->handleStrm,false,length,true,version=="HTTP/1.1");
}
}

651
src/Http/HttpServer.cpp Normal file
View File

@ -0,0 +1,651 @@
#include "TessesFramework/Http/HttpServer.hpp"
#include "TessesFramework/Streams/FileStream.hpp"
#include "TessesFramework/Streams/MemoryStream.hpp"
#include "TessesFramework/TextStreams/StreamWriter.hpp"
#include "TessesFramework/TextStreams/StreamReader.hpp"
#include "TessesFramework/Http/ContentDisposition.hpp"
#include "TessesFramework/Streams/BufferedStream.hpp"
#include "TessesFramework/Http/HttpStream.hpp"
#include <iostream>
using FileStream = Tesses::Framework::Streams::FileStream;
using Stream = Tesses::Framework::Streams::Stream;
using SeekOrigin = Tesses::Framework::Streams::SeekOrigin;
using MemoryStream = Tesses::Framework::Streams::MemoryStream;
using StreamReader = Tesses::Framework::TextStreams::StreamReader;
using StreamWriter = Tesses::Framework::TextStreams::StreamWriter;
using TcpServer = Tesses::Framework::Streams::TcpServer;
using NetworkStream = Tesses::Framework::Streams::NetworkStream;
using BufferedStream = Tesses::Framework::Streams::BufferedStream;
namespace Tesses::Framework::Http
{
/*
static int _header_field(multipart_parser* p, const char *at, size_t length)
{
UploadData* data = static_cast<UploadData*>(multipart_parser_get_data(p));
data->currentHeaderKey = std::string(at,length);
return 0;
}
static int _header_value(multipart_parser* p, const char *at, size_t length)
{
UploadData* data = static_cast<UploadData*>(multipart_parser_get_data(p));
data->currentHeaders.AddValue(data->currentHeaderKey, std::string(at,length));
std::cout << data->currentHeaderKey << ": " << std::string(at,length) << std::endl;
return 0;
}
static int _part_begin(multipart_parser* p)
{
UploadData* data = static_cast<UploadData*>(multipart_parser_get_data(p));
std::string cd0;
ContentDisposition cd1;
std::string ct;
if(!data->currentHeaders.TryGetFirst("Content-Type",ct))
ct = "application/octet-stream";
if(data->currentHeaders.TryGetFirst("Content-Disposition", cd0) && ContentDisposition::TryParse(cd0,cd1))
{
if(cd1.filename.empty())
{
data->isFile=false;
data->key = cd1.fieldName;
data->currentBody = new MemoryStream(true);
}
else
{
data->isFile = true;
data->currentBody = data->cb(ct, cd1.filename, cd1.fieldName);
}
}
return 0;
}
static int _part_end(multipart_parser* p)
{
UploadData* data = static_cast<UploadData*>(multipart_parser_get_data(p));
if(data->currentBody == nullptr) return 0;
if(data->isFile)
{
delete data->currentBody;
data->currentBody = nullptr;
}
else
{
MemoryStream* ms = dynamic_cast<MemoryStream*>(data->currentBody);
if(ms != nullptr)
{
ms->Seek(0, SeekOrigin::Begin);
auto& buff = ms->GetBuffer();
data->ctx->queryParams.AddValue(data->key, std::string(buff.begin(),buff.end()));
}
delete data->currentBody;
data->currentBody=nullptr;
}
data->currentHeaders.Clear();
return 0;
}*/
bool ServerContext::NeedToParseFormData()
{
std::string ct;
if(this->requestHeaders.TryGetFirst("Content-Type",ct))
{
if(ct.find("multipart/form-data") == 0)
{
return true;
}
}
return false;
}
static bool parseUntillBoundaryEnd(Tesses::Framework::Streams::Stream* src, Tesses::Framework::Streams::Stream* dest, std::string boundary)
{
bool hasMore=true;
uint8_t checkBuffer[boundary.size()];
int b;
size_t i = 0;
size_t i2 = 0;
uint8_t buffer[1024];
size_t offsetInMem = 0;
while((b = src->ReadByte()) != -1)
{
if(i == boundary.size())
{
if (b == '-')
{
i2++;
if(i2 == 2) hasMore=false;
}
if (b == '\n') break;
else if(b != '-')
i2 = 0;
continue;
}
i2=0;
if (b == boundary[i]) //start filling the check buffer
{
checkBuffer[i] = (uint8_t)b;
i++;
}
else
{
size_t idx = 0;
while (idx < i) //write the buffer data to stream
{
if(offsetInMem >= sizeof(buffer))
{
dest->Write(buffer, sizeof(buffer));
offsetInMem=0;
}
buffer[offsetInMem++] = checkBuffer[idx];
idx++;
}
i = 0;
if(offsetInMem >= sizeof(buffer))
{
dest->Write(buffer, sizeof(buffer));
offsetInMem=0;
}
buffer[offsetInMem++] = (uint8_t)b;
}
}
if(offsetInMem > 0)
{
dest->Write(buffer,offsetInMem);
}
return hasMore;
}
static bool parseSection(ServerContext* ctx, std::string boundary, std::function<Tesses::Framework::Streams::Stream*(std::string mime, std::string filename, std::string name)> cb)
{
HttpDictionary req;
StreamReader reader(ctx->GetStream());
std::string line;
while(reader.ReadLine(line))
{
auto v = HttpUtils::SplitString(line,": ", 2);
if(v.size() == 2)
req.AddValue(v[0],v[1]);
line.clear();
}
std::string cd0;
ContentDisposition cd1;
std::string ct;
if(!req.TryGetFirst("Content-Type",ct))
ct = "application/octet-stream";
if(req.TryGetFirst("Content-Disposition", cd0) && ContentDisposition::TryParse(cd0,cd1))
{
if(cd1.filename.empty())
{
MemoryStream ms(true);
bool retVal = parseUntillBoundaryEnd(&ctx->GetStream(),&ms,boundary);
auto& buff = ms.GetBuffer();
ctx->queryParams.AddValue(cd1.fieldName, std::string(buff.begin(),buff.end()));
return retVal;
}
else
{
auto strm = cb(ct, cd1.filename, cd1.fieldName);
bool retVal = parseUntillBoundaryEnd(&ctx->GetStream(),strm,boundary);
delete strm;
return retVal;
}
}
return false;
}
void ServerContext::ParseFormData(std::function<Tesses::Framework::Streams::Stream*(std::string mime, std::string filename, std::string name)> cb)
{
std::string ct;
if(this->requestHeaders.TryGetFirst("Content-Type",ct))
{
if(ct.find("multipart/form-data") != 0)
{
std::cout << "Not form data" << std::endl;
return;
}
auto res = ct.find("boundary=");
if(res == std::string::npos) return;
ct = "--" + ct.substr(res+9);
}
Stream nullStrm;
parseUntillBoundaryEnd(this->strm,&nullStrm, ct);
while(parseSection(this, ct, cb));
}
HttpServer::HttpServer(uint16_t port, IHttpServer* http, bool owns)
{
this->server = new TcpServer(port, 10);
this->http = http;
this->owns = owns;
this->thrd=nullptr;
this->port = port;
}
HttpServer::HttpServer(uint16_t port, IHttpServer& http) : HttpServer(port,&http,false)
{
}
Stream* ServerContext::OpenResponseStream()
{
if(sent) return nullptr;
int64_t length = -1;
if(!this->responseHeaders.TryGetFirstInt("Content-Length",length))
length = -1;
if(this->version == "HTTP/1.1" && length == -1)
this->responseHeaders.SetValue("Transfer-Encoding","chunked");
this->WriteHeaders();
return new HttpStream(this->strm,false,length,false,version == "HTTP/1.1");
}
Stream* ServerContext::OpenRequestStream()
{
int64_t length = -1;
if(!this->requestHeaders.TryGetFirstInt("Content-Length",length))
length = -1;
return new HttpStream(this->strm,false,length,true,version == "HTTP/1.1");
}
void HttpServer::StartAccepting()
{
fflush(stdout);
if(http == nullptr) return;
auto svr=this->server;
auto http = this->http;
thrd = new Threading::Thread([svr,http]()->void {
while(TF_IsRunning())
{
std::string ip;
uint16_t port;
auto sock =svr->GetStream(ip,port);
if(sock == nullptr) return;
Threading::Thread thrd2([sock,http,ip,port]()->void {
HttpServer::Process(*sock,*http,ip,port,false);
});
thrd2.Detach();
}
});
std::cout << "\e[34mInterfaces:\n";
for(auto _ip : NetworkStream::GetIPs())
{
std::cout << "\e[32m";
std::cout << _ip.first << ": ";
std::cout << "\e[35mhttp://";
std::cout << _ip.second << ":" << std::to_string(this->port) << "/\n";
}
std::cout << "\e[31mAlmost Ready to Listen\e[39m\n";
}
HttpServer::~HttpServer()
{
this->server->Close();
TF_ConnectToSelf(this->port);
if(this->thrd != nullptr)
{
this->thrd->Join();
delete this->thrd;
}
if(this->owns)
delete http;
delete this->server;
}
IHttpServer::~IHttpServer()
{
}
ServerContext::ServerContext(Stream* strm)
{
this->statusCode = OK;
this->strm = strm;
this->sent = false;
this->responseHeaders.AddValue("Server","TessesFrameworkWebServer");
}
Stream& ServerContext::GetStream()
{
return *this->strm;
}
void ServerContext::SendBytes(std::vector<uint8_t> buff)
{
MemoryStream strm(false);
strm.GetBuffer() = buff;
SendStream(strm);
}
void ServerContext::SendText(std::string text)
{
MemoryStream strm(false);
auto& buff= strm.GetBuffer();
buff.insert(buff.end(),text.begin(),text.end());
SendStream(strm);
}
void ServerContext::SendErrorPage(bool showPath)
{
if(sent) return;
std::string errorHtml = showPath ? ("<html><head><title>File " + HttpUtils::HtmlEncode(this->originalPath) + " " + HttpUtils::StatusCodeString(this->statusCode) + "</title></head><body><h1>" + std::to_string((int)this->statusCode) + " " + HttpUtils::StatusCodeString(this->statusCode) + "</h1><h4>" + HttpUtils::HtmlEncode(this->originalPath) + "</h4></body></html>") : "";
WithMimeType("text/html").SendText(errorHtml);
}
void ServerContext::SendStream(Stream* strm)
{
if(strm == nullptr) return;
SendStream(*strm);
}
void ServerContext::SendStream(Stream& strm)
{
if(sent) return;
if(strm.CanSeek())
{
int64_t len=strm.GetLength();
std::string range={};
if(this->requestHeaders.TryGetFirst("Range",range))
{
auto res = HttpUtils::SplitString(range,"=",2);
if(res.size() == 2)
{
if(res[0] != "bytes")
{
this->statusCode = BadRequest;
this->WriteHeaders();
return;
}
res = HttpUtils::SplitString(res[1],", ",2);
if(res.size() != 1)
{
this->statusCode = BadRequest;
this->WriteHeaders();
return;
}
auto dash=HttpUtils::SplitString(res[0],"-",2);
int64_t begin = 0;
int64_t end = -1;
if(dash.size() == 1 && res[0].find_first_of('-') != std::string::npos)
{
//NUMBER-
begin = std::stoll(dash[0]);
}
else if(dash.size() == 2)
{
//NUMBER-NUMBER
//or
//-NUMBER
if(dash[0].size() > 0)
{
//NUMBER-NUMBER
begin = std::stoll(dash[0]);
end = std::stoll(dash[1]);
}
else
{
//-NUMBER
end = std::stoll(dash[1]);
}
}
else
{
this->statusCode = BadRequest;
this->WriteHeaders();
return;
}
if(end == -1)
{
end = len-1;
}
if(end > len-1)
{
this->statusCode = RangeNotSatisfiable;
this->WithSingleHeader("Content-Range","bytes */" + std::to_string(len));
this->WriteHeaders();
return;
}
if(begin >= end) {
this->statusCode = RangeNotSatisfiable;
this->WithSingleHeader("Content-Range","bytes */" + std::to_string(len));
this->WriteHeaders();
return;
}
int64_t myLen = (end - begin)+1;
this->WithSingleHeader("Accept-Ranges","bytes");
this->WithSingleHeader("Content-Length",std::to_string(myLen));
this->WithSingleHeader("Content-Range","bytes " + std::to_string(begin) + "-" + std::to_string(end) + "/" + std::to_string(len));
this->statusCode = PartialContent;
this->WriteHeaders();
strm.Seek(begin,SeekOrigin::Begin);
uint8_t buffer[1024];
size_t read=0;
do {
read = sizeof(buffer);
myLen = (end - begin)+1;
if(myLen < read) read = (size_t)myLen;
if(read == 0) break;
read = strm.Read(buffer,read);
if(read == 0) break;
this->strm->WriteBlock(buffer,read);
begin += read;
} while(read > 0);
}
else
{
this->statusCode = BadRequest;
this->SendErrorPage(true);
return;
}
}
else
{
if(len > -1)
{
this->WithSingleHeader("Accept-Range","bytes");
this->WithSingleHeader("Content-Length",std::to_string(len));
this->WriteHeaders();
strm.CopyTo(*this->strm);
}
}
}
else
{
auto chunkedStream = this->OpenResponseStream();
this->strm->CopyTo(chunkedStream);
delete chunkedStream;
}
}
ServerContext& ServerContext::WithHeader(std::string key, std::string value)
{
this->responseHeaders.AddValue(key, value);
return *this;
}
ServerContext& ServerContext::WithSingleHeader(std::string key, std::string value)
{
this->responseHeaders.SetValue(key, value);
return *this;
}
ServerContext& ServerContext::WithMimeType(std::string mime)
{
this->responseHeaders.SetValue("Content-Type",mime);
return *this;
}
ServerContext& ServerContext::WithContentDisposition(std::string filename, bool isInline)
{
ContentDisposition cd;
cd.type = isInline ? "inline" : "attachment";
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());
return *this;
}
void ServerContext::SendNotFound()
{
if(sent) return;
statusCode = StatusCode::NotFound;
SendErrorPage(true);
}
void ServerContext::SendBadRequest()
{
if(sent) return;
statusCode = StatusCode::BadRequest;
SendErrorPage(false);
}
void ServerContext::SendException(std::exception& ex)
{
/*<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Internal Server Error at /</title>
</head>
<body>
<h1>Internal Server Error at /</h1>
<p>what(): std::exception</p>
</body>
</html>*/
this->WithMimeType("text/html").SendText("<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Internal Server Error at " + HttpUtils::HtmlEncode(this->originalPath) + "</title></head><body><h1>Internal Server Error at " + HttpUtils::HtmlEncode(this->originalPath) + "</h1><p>what(): " + HttpUtils::HtmlEncode(ex.what()) + "</p></body></html>");
}
ServerContext& ServerContext::WriteHeaders()
{
if(this->sent) return *this;
this->sent = true;
StreamWriter writer(this->strm,false);
writer.newline = "\r\n";
writer.WriteLine("HTTP/1.1 " + std::to_string((int)statusCode) + " " + HttpUtils::StatusCodeString(statusCode));
for(auto& hdr : responseHeaders.kvp)
{
auto& key = hdr.first;
for(auto& val : hdr.second)
{
writer.WriteLine(key + ": " + val);
}
}
writer.WriteLine();
return *this;
}
void HttpServer::Process(Stream& strm, IHttpServer& server, std::string ip, uint16_t port, bool encrypted)
{
while(true)
{
BufferedStream bStrm(strm);
StreamReader reader(bStrm);
ServerContext ctx(&bStrm);
ctx.ip = ip;
ctx.port = port;
ctx.encrypted = encrypted;
try{
bool firstLine = true;
std::string line;
while(reader.ReadLine(line))
{
if(firstLine)
{
auto v = HttpUtils::SplitString(line, " ", 3);
if(v.size() != 3) {
ctx.statusCode = BadRequest;
ctx.WithMimeType("text/plain").SendText("First line is not 3 elements");
return;
}
ctx.method = v[0];
auto pp = HttpUtils::SplitString(v[1],"?", 2);
pp.resize(2);
ctx.originalPath = pp[0];
ctx.path = ctx.originalPath;
auto queryPart = pp[1];
if(!queryPart.empty())
{
HttpUtils::QueryParamsDecode(ctx.queryParams, queryPart);
}
ctx.version = v[2];
}
else
{
auto v = HttpUtils::SplitString(line,": ", 2);
if(v.size() != 2) {
ctx.statusCode = BadRequest;
ctx.WithMimeType("text/plain").SendText("Header line is not 2 elements");
return;
}
ctx.requestHeaders.AddValue(v[0],v[1]);
}
line.clear();
firstLine=false;
}
if(firstLine) return;
std::string type;
int64_t length;
if(ctx.requestHeaders.TryGetFirst("Content-Type",type) && type == "application/x-www-form-urlencoded" && ctx.requestHeaders.TryGetFirstInt("Content-Length",length))
{
size_t len = (size_t)length;
uint8_t* buffer = new uint8_t[len];
len = bStrm.ReadBlock(buffer,len);
std::string query((const char*)buffer,len);
delete buffer;
HttpUtils::QueryParamsDecode(ctx.queryParams, query);
}
if(!server.Handle(ctx))
{
ctx.SendNotFound();
}
}
catch(std::exception& ex)
{
ctx.SendException(ex);
}
if(ctx.version != "HTTP/1.1" ) return;
std::string connection;
if(ctx.requestHeaders.TryGetFirst("Connection", connection))
{
if(connection != "keep-alive") return;
}
if(ctx.responseHeaders.TryGetFirst("Connection", connection))
{
if(connection != "keep-alive") return;
}
if(bStrm.EndOfStream()) return;
}
}
}

166
src/Http/HttpStream.cpp Normal file
View File

@ -0,0 +1,166 @@
#include "TessesFramework/Http/HttpStream.hpp"
#include "TessesFramework/TextStreams/StreamWriter.hpp"
#include "TessesFramework/TextStreams/StreamReader.hpp"
#include <sstream>
#include <iostream>
using StreamWriter = Tesses::Framework::TextStreams::StreamWriter;
using StreamReader = Tesses::Framework::TextStreams::StreamReader;
namespace Tesses::Framework::Http
{
HttpStream::HttpStream(Tesses::Framework::Streams::Stream* strm, bool owns, int64_t length, bool recv, bool http1_1)
{
this->strm = strm;
this->owns = owns;
this->length = length;
this->recv = recv;
this->http1_1 = http1_1;
this->offset = 0;
this->read = 0;
this->position = 0;
this->done=false;
}
HttpStream::HttpStream(Tesses::Framework::Streams::Stream& strm, int64_t length, bool recv,bool http1_1) : HttpStream(&strm,false,length,recv,http1_1)
{
}
bool HttpStream::CanRead()
{
if(this->done) return false;
if(!this->recv) return false;
if(this->offset < this->read) return true;
return this->strm->CanRead();
}
bool HttpStream::CanWrite()
{
if(this->recv) return false;
return this->strm->CanWrite();
}
bool HttpStream::EndOfStream()
{
if(this->done) return true;
if(!this->recv) return true;
if(this->offset < this->read) return false;
return this->strm->EndOfStream();
}
int64_t HttpStream::GetLength()
{
return this->length;
}
int64_t HttpStream::GetPosition()
{
return this->position;
}
size_t HttpStream::Read(uint8_t* buff, size_t len)
{
if(this->done) return 0;
if(!this->recv) return 0;
if(this->length == 0) return 0;
if(this->length > 0)
{
len = std::min((size_t)(this->length - this->position), len);
if(len > 0)
len = this->strm->Read(buff,len);
this->position += len;
return len;
}
else
{
if(this->http1_1)
{
if(this->offset < this->read)
{
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;
if(this->offset >= this->read)
{
StreamReader reader(this->strm, false);
reader.ReadLine();
}
return len;
}
else
{
StreamReader reader(this->strm, false);
std::string line = reader.ReadLine();
if(!line.empty())
{
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);
this->offset += len;
this->position += len;
return len;
}
}
return 0;
}
}
else
{
return this->strm->Read(buff,len);
}
}
}
size_t HttpStream::Write(const uint8_t* buff, size_t len)
{
if(this->recv) return 0;
if(this->length == 0) return 0;
if(this->length > 0)
{
len = std::min((size_t)(this->length - this->position), len);
if(len > 0)
len = this->strm->Write(buff,len);
this->position += len;
return len;
}
else
{
if(this->http1_1)
{
std::stringstream strm;
strm << std::hex << len;
StreamWriter writer(this->strm,false);
writer.newline = "\r\n";
writer.WriteLine(strm.str());
this->strm->WriteBlock(buff, len);
writer.WriteLine();
return len;
}
else
{
return this->strm->Write(buff,len);
}
}
}
HttpStream::~HttpStream()
{
if(this->length == -1 && this->http1_1)
{
StreamWriter writer(this->strm,false);
writer.newline = "\r\n";
writer.WriteLine("0");
writer.WriteLine();
}
if(this->owns) delete this->strm;
}
}

732
src/Http/HttpUtils.cpp Normal file
View File

@ -0,0 +1,732 @@
#include "TessesFramework/Http/HttpUtils.hpp"
#include "TessesFramework/Filesystem/VFS.hpp"
using VFSPath = Tesses::Framework::Filesystem::VFSPath;
namespace Tesses::Framework::Http {
bool Uri::Relative(std::string url, Uri& uri)
{
auto index = url.find_first_of("//");
if(index != std::string::npos)
{
if(Uri::TryParse(url,uri))
{
if(index == 0)
uri.scheme = this->scheme;
return true;
}
}
else if(!url.empty())
{
if(url[0] == '/')
{
auto thirdPart = HttpUtils::SplitString(url,"#",2);
if(thirdPart.empty()) return false;
if(thirdPart.size() == 2)
{
uri.hash=thirdPart[1];
}
auto fourthPart = HttpUtils::SplitString(thirdPart[1],"?",2);
VFSPath p = fourthPart[0];
uri.path = p.CollapseRelativeParents().ToString(); //this should be safe
if(fourthPart.size() == 2)
{
HttpUtils::QueryParamsDecode(uri.query, fourthPart[1]);
}
}
else
{
auto thirdPart = HttpUtils::SplitString(url,"#",2);
if(thirdPart.empty()) return false;
if(thirdPart.size() == 2)
{
uri.hash=thirdPart[1];
}
auto fourthPart = HttpUtils::SplitString(thirdPart[1],"?",2);
VFSPath p = VFSPath(this->path,fourthPart[0]);
uri.path = p.CollapseRelativeParents().ToString(); //this should be safe
if(fourthPart.size() == 2)
{
HttpUtils::QueryParamsDecode(uri.query, fourthPart[1]);
}
}
uri.scheme = this->scheme;
uri.host = this->host;
uri.port = this->port;
return true;
}
return false;
}
std::string Uri::HostPort()
{
if(this->port != 0) return this->host + ":" + std::to_string(this->port);
return this->host;
}
uint16_t Uri::GetPort()
{
if(this->port != 0) return this->port;
if(this->scheme == "http:")
return 80;
if(this->scheme == "https:")
return 443;
if(this->scheme == "sftp:")
return 22;
if(this->scheme == "ftp:")
return 21;
if(this->scheme == "tftp:")
return 69;
return 0;
}
bool Uri::TryParse(std::string url, Uri& uri)
{
uri.scheme = "";
uri.port=0;
auto firstPart = HttpUtils::SplitString(url,"//",2);
if(firstPart.size() == 2)
uri.scheme=firstPart[0];
else if(firstPart.empty())
return false;
auto secondPart = HttpUtils::SplitString(firstPart.size() == 2 ? firstPart[1] : firstPart[0] ,"/",2);
if(secondPart.size() == 1)
{
uri.path = "/";
}
else if(secondPart.size() == 2)
{
auto thirdPart = HttpUtils::SplitString(secondPart[1],"#",2);
if(thirdPart.empty()) return false;
if(thirdPart.size() == 2)
{
uri.hash=thirdPart[1];
}
auto fourthPart = HttpUtils::SplitString(thirdPart[0],"?",2);
uri.path = "/" + fourthPart[0]; //this should be safe
if(fourthPart.size() == 2)
{
HttpUtils::QueryParamsDecode(uri.query, fourthPart[1]);
}
}
else
{
return false;
}
auto hostPortPart = HttpUtils::SplitString(secondPart[0],":",2);
if(hostPortPart.empty()) return false;
if(hostPortPart.size() == 2)
{
uri.port = (uint16_t)std::stoul(hostPortPart[1]);
}
uri.host = hostPortPart[0];
return true;
}
std::string Uri::GetPathAndQuery()
{
return this->path + this->GetQuery();
}
std::string Uri::GetQuery()
{
if(this->query.kvp.empty()) return "";
std::string queryStr = "?";
queryStr.append(HttpUtils::QueryParamsEncode(query));
return queryStr;
}
std::string Uri::ToString()
{
std::string uri = this->scheme;
uri.append("//");
uri.append(this->host);
if(this->port > 0)
{
uri.push_back(':');
uri.append(std::to_string(this->port));
}
uri.append(this->GetPathAndQuery());
return uri;
}
char HttpUtils::NibbleToHex(uint8_t b)
{
b %= 16;
if(b >= 0 && b <= 9)
return b + '0';
if(b >= 10 && b <= 15)
return b + ('a' - 10);
return 0;
}
uint8_t HttpUtils::HexToNibble(char c)
{
if(c >= '0' && c <= '9')
return (uint8_t)(c - '0');
if(c >= 'A' && c <= 'F')
return (uint8_t)(c - 55);
if(c >= 'a' && c <= 'f')
return (uint8_t)(c - 87);
return 0;
}
std::string HttpUtils::MimeType(std::filesystem::path p)
{
std::string ext = p.extension().string();
if(ext == ".html" || ext == ".htm")
{
return "text/html";
}
if(ext == ".txt" || ext == ".log" || ext == ".twss")
{
return "text/plain";
}
if(ext == ".woff")
{
return "application/x-font-woff";
}
if(ext == ".vtt")
{
return "text/vtt";
}
if(ext == ".svg")
{
return "image/svg+xml";
}
if(ext == ".webp")
{
return "image/webp";
}
if(ext == ".vcf")
{
return "text/v-card";
}
if(ext == ".rss" || ext == ".xml" || ext == ".atom" || ext == ".rdf")
{
return "application/xml";
}
if(ext == ".js")
{
return "text/javascript";
}
if(ext == ".json")
{
return "application/json";
}
if(ext == ".wasm")
{
return "application/wasm";
}
if(ext == ".png")
{
return "image/png";
}
if(ext == ".jpg" || ext == ".jpeg")
{
return "image/jpeg";
}
if(ext == ".css")
{
return "text/css";
}
if(ext == ".gif")
{
return "image/gif";
}
if(ext == ".mp4")
{
return "video/mp4";
}
if(ext == ".mov")
{
return "video/quicktime";
}
if(ext == ".m4a")
{
return "audio/mp4";
}
if(ext == ".webm")
{
return "video/webm";
}
if(ext == ".webmanifest")
{
return "application/manifest+json";
}
if(ext == ".ico")
{
return "image/x-icon";
}
return "application/octet-stream";
}
bool HttpUtils::Invalid(char c)
{
//just do windows because it is the strictist when it comes to windows, mac and linux
if(c >= 0 && c < 32) return true;
if(c == 127) return true;
if(c == '\\') return true;
if(c == '*') return true;
if(c == '/') return true;
if(c == '|') return true;
if(c == ':') return true;
if(c == '<') return true;
if(c == '>') return true;
if(c == '\"') return true;
if(c == '?') return true;
return false;
}
std::string HttpUtils::Sanitise(std::string text)
{
std::string myStr={};
for(auto item : text)
{
if(Invalid(item)) continue;
myStr.push_back(item);
}
return myStr;
}
void HttpUtils::QueryParamsDecode(HttpDictionary& dict,std::string query)
{
for(auto item : SplitString(query,"&"))
{
std::vector<std::string> ss=SplitString(item,"=",2);
if(ss.size() >= 1)
{
std::string value = {};
if(ss.size() == 2)
{
value = UrlDecode(ss[1]);
}
dict.AddValue(UrlDecode(ss[0]),value);
}
}
}
std::string HttpUtils::Join(std::string joinStr, std::vector<std::string> ents)
{
std::string str={};
bool first=true;
for(auto item : ents)
{
if(!first) str.append(joinStr);
str.append(item);
first=false;
}
return str;
}
std::string HttpUtils::QueryParamsEncode(HttpDictionary& dict)
{
std::string s={};
bool first = true;
for(auto item : dict.kvp)
{
for(auto item2 : item.second)
{
if(!first)
{
s.push_back('&');
}
s.insert(s.size(),UrlEncode(item.first));
s.push_back('=');
s.insert(s.size(),UrlEncode(item2));
first=false;
}
}
return s;
}
std::string HttpUtils::UrlDecode(std::string v)
{
std::string s = {};
for(size_t i = 0;i<v.size();i++)
{
if(v[i] == '+')
s.push_back(' ');
else if(v[i] == '%')
{
i++;
uint8_t n = HexToNibble(v[i])<<4;
i++;
n |= HexToNibble(v[i]);
s.push_back((char)n);
}
else
s.push_back(v[i]);
}
return s;
}
std::string HttpUtils::UrlPathEncode(std::string v,bool ignoreSpace)
{
std::string s = {};
for(auto item : v)
{
if(item >= 'A' && item <= 'Z')
s.push_back(item);
else if(item >= 'a' && item <= 'z')
s.push_back(item);
else if(item >= '0' && item <= '9')
s.push_back(item);
else if(item == '-' || item == '_' || item == '.' || item == '~')
s.push_back(item);
else
{
if(item != ' ' || !ignoreSpace)
{
s.push_back('%');
s.push_back(NibbleToHex((item >> 4) & 0xF));
s.push_back(NibbleToHex((item) & 0xF));
}
else
{
s.push_back(' ');
}
}
}
return s;
}
std::string HttpUtils::UrlPathDecode(std::string v)
{
std::string s = {};
for(size_t i = 0;i<v.size();i++)
{
if(v[i] == '%')
{
i++;
uint8_t n = HexToNibble(v[i])<<4;
i++;
n |= HexToNibble(v[i]);
s.push_back((char)n);
}
else
s.push_back(v[i]);
}
return s;
}
std::string HttpUtils::UrlEncode(std::string v)
{
std::string s = {};
for(auto item : v)
{
if(item == ' ')
s.push_back('+');
else if(item >= 'A' && item <= 'Z')
s.push_back(item);
else if(item >= 'a' && item <= 'z')
s.push_back(item);
else if(item >= '0' && item <= '9')
s.push_back(item);
else if(item == '-' || item == '_' || item == '.' || item == '~')
s.push_back(item);
else
{
s.push_back('%');
s.push_back(NibbleToHex((item >> 4) & 0xF));
s.push_back(NibbleToHex((item) & 0xF));
}
}
return s;
}
std::vector<std::string> HttpUtils::SplitString(std::string text, std::string delimiter,std::size_t maxCnt)
{
std::vector<std::string> strs;
std::size_t i = 1;
while(text.length() > 0)
{
if(i == maxCnt)
{
strs.push_back(text);
break;
}
std::size_t index= text.find(delimiter);
if(index == std::string::npos)
{
strs.push_back(text);
break;
}
else
{
std::string left = text.substr(0,index);
text = text.substr(index+delimiter.size());
strs.push_back(left);
}
i++;
}
return strs;
}
std::string HttpUtils::HtmlEncode(std::string html)
{
std::string myHtml = {};
for(auto item : html)
{
if(item == '\"')
{
myHtml.append("&quot;");
}
else if(item == '\'')
{
myHtml.append("&apos;");
}
else if(item == '&')
{
myHtml.append("&amp;");
}
else if(item == '<')
{
myHtml.append("&lt;");
}
else if(item == '>')
{
myHtml.append("&gt;");
}
else
{
myHtml.push_back(item);
}
}
return myHtml;
}
std::string HttpUtils::StatusCodeString(StatusCode code)
{
switch(code)
{
case StatusCode::Continue:
return "Continue";
case StatusCode::SwitchingProtocols:
return "Switching Protocols";
case StatusCode::Processing:
return "Processing";
case StatusCode::EarlyHints:
return "Early Hints";
case StatusCode::OK:
return "OK";
case StatusCode::Created:
return "Created";
case StatusCode::Accepted:
return "Accepted";
case StatusCode::NonAuthoritativeInformation:
return "Non-Authoritative Information";
case StatusCode::NoContent:
return "No Content";
case StatusCode::ResetContent:
return "Reset Content";
case StatusCode::PartialContent:
return "PartialContent";
case StatusCode::MultiStatus:
return "Multi-Status";
case StatusCode::AlreadyReported:
return "Already Reported";
case StatusCode::IMUsed:
return "IM Used";
case StatusCode::MultipleChoices:
return "Multiple Choices";
case StatusCode::MovedPermanently:
return "Moved Permanently";
case StatusCode::Found:
return "Found";
case StatusCode::SeeOther:
return "See Other";
case StatusCode::NotModified:
return "Not Modified";
case StatusCode::UseProxy:
return "Use Proxy";
case StatusCode::TemporaryRedirect:
return "Temporary Redirect";
case StatusCode::PermanentRedirect:
return "Permanent Redirect";
case StatusCode::BadRequest:
return "Bad Request";
case StatusCode::Unauthorized:
return "Unauthorized";
case StatusCode::PaymentRequired:
return "Payment Required";
case StatusCode::Forbidden:
return "Forbidden";
case StatusCode::NotFound:
return "Not Found";
case StatusCode::MethodNotAllowed:
return "Method Not Allowed";
case StatusCode::NotAcceptable:
return "Not Acceptable";
case StatusCode::ProxyAuthenticationRequired:
return "Proxy Authentication Required";
case StatusCode::RequestTimeout:
return "Request Timeout";
case StatusCode::Conflict:
return "Conflict";
case StatusCode::Gone:
return "Gone";
case StatusCode::LengthRequired:
return "Length Required";
case StatusCode::PreconditionFailed:
return "Precondition Failed";
case StatusCode::PayloadTooLarge:
return "Payload Too Large";
case StatusCode::URITooLong:
return "URI Too Long";
case StatusCode::UnsupportedMediaType:
return "Unsupported Media Type";
case StatusCode::RangeNotSatisfiable:
return "Range Not Satisfiable";
case StatusCode::ExpectationFailed:
return "Expectation Failed";
case StatusCode::ImATeapot:
return "I'm a teapot";
case StatusCode::MisdirectedRequest:
return "Misdirected Request";
case StatusCode::UnprocessableContent:
return "Unprocessable Content";
case StatusCode::Locked:
return "Locked";
case StatusCode::FailedDependency:
return "Failed Dependency";
case StatusCode::TooEarly:
return "Too Early";
case StatusCode::UpgradeRequired:
return "Upgrade Required";
case StatusCode::PreconditionRequired:
return "Precondition Required";
case StatusCode::TooManyRequests:
return "Too Many Requests";
case StatusCode::RequestHeaderFieldsTooLarge:
return "Request Header Fields Too Large";
case StatusCode::UnavailableForLegalReasons:
return "Unavailable For Legal Reasons";
case StatusCode::InternalServerError:
return "Internal Server Error";
case StatusCode::NotImplemented:
return "Not Implemented";
case StatusCode::ServiceUnavailable:
return "Service Unavailable";
case StatusCode::GatewayTimeout:
return "Gateway Timeout";
case StatusCode::HTTPVersionNotSupported:
return "HTTP Version Not Supported";
case StatusCode::VariantAlsoNegotiates:
return "Variant Also Negotiates";
case StatusCode::InsufficientStorage:
return "Insufficient Storage";
case StatusCode::LoopDetected:
return "Loop Detected";
case StatusCode::NotExtended:
return "Not Extended";
case StatusCode::NetworkAuthenticationRequired:
return "Network Authentication Required";
default:
return "";
}
}
void HttpDictionary::Clear()
{
kvp.clear();
}
void HttpDictionary::Clear(std::string key, bool kvpExistsAfter)
{
if(kvpExistsAfter)
{
kvp[key].clear();
}
else
{
if(kvp.count(key) == 0) return;
kvp[key].clear();
kvp.erase(key);
}
}
void HttpDictionary::SetValue(std::string key, std::string value)
{
kvp[key] = {value};
}
void HttpDictionary::SetValue(std::string key, std::vector<std::string> value)
{
kvp[key] = value;
}
void HttpDictionary::AddValue(std::string key, std::string value)
{
kvp[key].push_back(value);
}
void HttpDictionary::AddValue(std::string key, std::vector<std::string> value)
{
auto& ls = kvp[key];
ls.insert(ls.end(), value.begin(), value.end());
}
bool HttpDictionary::TryGetFirst(std::string key, std::string& value)
{
if(kvp.count(key) == 0) return false;
auto& ls = kvp[key];
if(ls.empty()) return false;
value = ls.front();
return true;
}
bool HttpDictionary::TryGetFirstInt(std::string key, int64_t& value)
{
std::string val;
if(!TryGetFirst(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::TryGetFirstDouble(std::string key, double& value)
{
std::string val;
if(!TryGetFirst(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::GetFirstBoolean(std::string key)
{
std::string val;
if(!TryGetFirst(key,val)) return false;
return val == "true" || val == "on";
}
}

View File

@ -0,0 +1,65 @@
#include "TessesFramework/Streams/BufferedStream.hpp"
namespace Tesses::Framework::Streams {
BufferedStream::BufferedStream(Stream* strm, bool owns, size_t bufferSize)
{
this->strm = strm;
this->owns = owns;
this->bufferSize = bufferSize;
this->buffer = new uint8_t[bufferSize];
this->read = 0;
this->offset = 0;
}
BufferedStream::BufferedStream(Stream& strm, size_t bufferSize) : BufferedStream(&strm,false, bufferSize)
{
}
bool BufferedStream::EndOfStream()
{
if(this->offset < this->read) return false;
return this->strm->EndOfStream();
}
bool BufferedStream::CanRead()
{
if(this->offset < this->read) return true;
return this->strm->CanRead();
}
bool BufferedStream::CanWrite()
{
return this->strm->CanWrite();
}
size_t BufferedStream::Read(uint8_t* buff, size_t sz)
{
if(this->offset < this->read)
{
sz = std::min(sz,this->read-this->offset);
memcpy(buff, this->buffer+this->offset, sz);
this->offset+=sz;
return sz;
}
if(sz < this->bufferSize)
{
this->read = this->strm->Read(this->buffer, this->bufferSize);
this->offset=0;
sz = std::min(sz,this->read-this->offset);
memcpy(buff, this->buffer+this->offset, sz);
this->offset+=sz;
return sz;
}
else
{
return this->strm->Read(buff, sz);
}
}
size_t BufferedStream::Write(const uint8_t* buff, size_t sz)
{
return this->strm->Write(buff,sz);
}
BufferedStream::~BufferedStream()
{
if(this->owns)
delete this->strm;
delete buffer;
}
}

View File

@ -0,0 +1,84 @@
#include "TessesFramework/Streams/FileStream.hpp"
namespace Tesses::Framework::Streams
{
void FileStream::SetMode(std::string mode)
{
this->canRead = false;
this->canWrite = false;
this->canSeek = true;
if(mode.size() >= 1)
{
if(mode[0] == 'r')
{
this->canRead = true;
}
else if(mode[0] == 'w')
{
this->canWrite = true;
}
else if(mode[0] == 'a')
{
this->canSeek = false;
this->canWrite = true;
}
}
if(((mode.size() >= 2 && mode[1] == '+') || (mode.size() >= 2 && mode[1] == 'b' && mode[2] == '+')))
{
this->canRead = true;
this->canWrite = true;
}
}
FileStream::FileStream(std::filesystem::path p, std::string mode)
{
this->f = fopen(p.c_str(),mode.c_str());
this->canSeek = true;
this->owns=true;
this->SetMode(mode);
}
FileStream::FileStream(FILE* f, bool owns, std::string mode , bool canSeek)
{
this->f = f;
this->owns = owns;
this->SetMode(mode);
this->canSeek = canSeek;
}
size_t FileStream::Read(uint8_t* buff, size_t sz)
{
return fread(buff,1, sz, this->f);
}
size_t FileStream::Write(const uint8_t* buff, size_t sz)
{
return fwrite(buff,1, sz, f);
}
bool FileStream::CanRead()
{
return this->canRead;
}
bool FileStream::CanWrite()
{
return this->canWrite;
}
bool FileStream::CanSeek()
{
return this->canSeek;
}
int64_t FileStream::GetPosition()
{
return (int64_t)ftello(this->f);
}
void FileStream::Flush()
{
fflush(this->f);
}
void FileStream::Seek(int64_t pos, SeekOrigin whence)
{
fseeko(this->f,(off_t)pos,whence == SeekOrigin::Begin ? SEEK_SET : whence == SeekOrigin::Current ? SEEK_CUR : SEEK_END);
}
FileStream::~FileStream()
{
if(this->owns)
fclose(this->f);
}
}

View File

@ -0,0 +1,68 @@
#include "TessesFramework/Streams/MemoryStream.hpp"
namespace Tesses::Framework::Streams
{
MemoryStream::MemoryStream(bool isWritable)
{
this->offset=0;
this->writable = isWritable;
}
std::vector<uint8_t>& MemoryStream::GetBuffer()
{
return this->buffer;
}
size_t MemoryStream::Read(uint8_t* buff, size_t sz)
{
if(this->offset >= this->buffer.size()) return 0;
size_t toRead = std::min(sz, this->buffer.size()-this->offset);
memcpy(buff, this->buffer.data() + this->offset, sz);
this->offset += toRead;
return toRead;
}
size_t MemoryStream::Write(const uint8_t* buff, size_t sz)
{
if(!this->writable) return 0;
if(this->offset > this->buffer.size())
{
this->buffer.resize(this->offset+sz);
}
this->buffer.insert(this->buffer.begin()+this->offset, buff, buff+sz);
this->offset+=sz;
return sz;
}
bool MemoryStream::CanRead()
{
return true;
}
bool MemoryStream::CanWrite()
{
return this->writable;
}
bool MemoryStream::CanSeek()
{
return true;
}
int64_t MemoryStream::GetLength()
{
return this->buffer.size();
}
int64_t MemoryStream::GetPosition()
{
return (int64_t)this->offset;
}
void MemoryStream::Seek(int64_t pos, SeekOrigin whence)
{
switch(whence)
{
case SeekOrigin::Begin:
this->offset = (size_t)pos;
break;
case SeekOrigin::Current:
this->offset += (size_t)pos;
break;
case SeekOrigin::End:
this->offset = (size_t)(this->buffer.size() + pos);
break;
}
}
}

View File

@ -0,0 +1,528 @@
#include "TessesFramework/Streams/NetworkStream.hpp"
#include "TessesFramework/Http/HttpUtils.hpp"
using HttpUtils = Tesses::Framework::Http::HttpUtils;
#if defined(GEKKO)
#define ss_family sin_family
#endif
#if defined(GEKKO) && !(defined(TESSESFRAMEWORK_USE_WII_SOCKET) && defined(HW_RVL))
#include <network.h>
#define NETWORK_RECV net_recv
#define sockaddr_storage sockaddr_in
#error "Not supported yet"
#else
extern "C" {
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
}
#if defined(GEKKO)
extern "C" uint32_t if_config( char *local_ip, char *netmask, char *gateway,bool use_dhcp, int max_retries);
#else
#include <ifaddrs.h>
#endif
#define NETWORK_SEND send
#define NETWORK_RECV recv
#define NETWORK_SENDTO sendto
#define NETWORK_RECVFROM recvfrom
#define NETWORK_SOCKET socket
#define NETWORK_SETSOCKOPT setsockopt
#define NETWORK_CONNECT connect
#define NETWORK_BIND bind
#define NETWORK_LISTEN listen
#define NETWORK_ACCEPT accept
#define NETWORK_GETADDRINFO getaddrinfo
#define NETWORK_FREEADDRINFO freeaddrinfo
#define NETWORK_CLOSE close
#endif
#undef AF_INET6
namespace Tesses::Framework::Streams {
std::string StringifyIP(struct sockaddr* addr);
std::vector<std::pair<std::string,std::string>> NetworkStream::GetIPs(bool ipV6)
{
std::vector<std::pair<std::string, std::string>> ipConfig;
#if defined(GEKKO)
//if_config( char *local_ip, char *netmask, char *gateway,bool use_dhcp, int max_retries);
char localIp[16];
char netmask[16];
char gateway[16];
if_config(localIp,netmask, gateway, true, 1);
ipConfig.push_back(std::pair<std::string,std::string>("net",localIp));
#else
struct ifaddrs *ifAddrStruct = NULL;
getifaddrs(&ifAddrStruct);
for (struct ifaddrs *ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
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)));
}
#if defined(AF_INET6)
if (ifa->ifa_addr->sa_family == AF_INET6 && ipV6) { // IPv6
ipConfig.push_back(std::pair<std::string,std::string>(ifa->ifa_name, StringifyIP(ifa->ifa_addr)));
}
#endif
}
freeifaddrs(ifAddrStruct);
#endif
return ipConfig;
}
void SetPort(struct sockaddr* addr, uint16_t port)
{
if(addr->sa_family == AF_INET)
{
struct sockaddr_in* a = (struct sockaddr_in*)addr;\
a->sin_port = htons(port);
}
#if defined(AF_INET6)
if(addr->sa_family == AF_INET6)
{
struct sockaddr_in6* a = (struct sockaddr_in6*)addr;\
a->sin6_port = htons(port);
}
#endif
}
uint16_t GetPort(struct sockaddr* addr)
{
if(addr->sa_family == AF_INET)
{
struct sockaddr_in* a = (struct sockaddr_in*)addr;\
return ntohs(a->sin_port);
}
#if defined(AF_INET6)
if(addr->sa_family == AF_INET6)
{
struct sockaddr_in6* a = (struct sockaddr_in6*)addr;\
return ntohs(a->sin6_port);
}
#endif
return 0;
}
bool IPParse(std::string str,struct sockaddr_storage* addr)
{
memset(addr,0,sizeof(struct sockaddr_storage));
uint8_t ip[16];
if(inet_pton(AF_INET,str.c_str(),ip)==1)
{
struct sockaddr_in* inAddr = (struct sockaddr_in*)addr;
inAddr->sin_family = AF_INET;
memcpy(&inAddr->sin_addr,ip,4);
return true;
}
#if defined(AF_INET6)
if(inet_pton(AF_INET6,str.c_str(),ip)==1)
{
struct sockaddr_in6* inAddr = (struct sockaddr_in6*)addr;
inAddr->sin6_family = AF_INET6;
memcpy(&inAddr->sin6_addr,ip,16);
return 6;
}
#endif
return false;
}
std::string StringifyIP(struct sockaddr* addr)
{
if(addr->sa_family == AF_INET)
{
uint8_t* ip = (uint8_t*)&(((struct sockaddr_in*)addr)->sin_addr);
return std::to_string((uint32_t)ip[0]) + "." + std::to_string((uint32_t)ip[1]) + "." + std::to_string((uint32_t)ip[2]) + "." + std::to_string((uint32_t)ip[3]);
}
#if defined(AF_INET6)
if(addr->sa_family == AF_INET6)
{
uint8_t* ip = (uint8_t*)&(((struct sockaddr_in6*)addr)->sin6_addr);
return std::string({
HttpUtils::NibbleToHex((ip[0] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[0] & 0x0F),
HttpUtils::NibbleToHex((ip[1] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[1] & 0x0F),
':',
HttpUtils::NibbleToHex((ip[2] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[2] & 0x0F),
HttpUtils::NibbleToHex((ip[3] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[3] & 0x0F),
':',
HttpUtils::NibbleToHex((ip[4] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[4] & 0x0F),
HttpUtils::NibbleToHex((ip[5] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[5] & 0x0F),
':',
HttpUtils::NibbleToHex((ip[6] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[6] & 0x0F),
HttpUtils::NibbleToHex((ip[7] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[7] & 0x0F),
':',
HttpUtils::NibbleToHex((ip[8] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[8] & 0x0F),
HttpUtils::NibbleToHex((ip[9] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[9] & 0x0F),
':',
HttpUtils::NibbleToHex((ip[10] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[10] & 0x0F),
HttpUtils::NibbleToHex((ip[11] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[11] & 0x0F),
':',
HttpUtils::NibbleToHex((ip[12] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[12] & 0x0F),
HttpUtils::NibbleToHex((ip[13] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[13] & 0x0F),
':',
HttpUtils::NibbleToHex((ip[14] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[14] & 0x0F),
HttpUtils::NibbleToHex((ip[15] >> 4) & 0x0F),
HttpUtils::NibbleToHex(ip[15] & 0x0F),
});
}
#endif
return "";
}
TcpServer::TcpServer(uint16_t port, int32_t backlog)
{
this->owns=true;
this->sock = NETWORK_SOCKET(AF_INET, SOCK_STREAM, 0);
if(this->sock < 0)
{
this->valid=false;
return;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if(NETWORK_BIND(this->sock, (const sockaddr*)&addr, (socklen_t)sizeof(addr)) != 0)
{
this->valid = false;
return;
}
if(NETWORK_LISTEN(this->sock, backlog) != 0)
{
this->valid = false;
return;
}
this->valid = true;
}
TcpServer::TcpServer(std::string ip, uint16_t port, int32_t backlog)
{
this->owns=true;
struct sockaddr_storage addr;
memset(&addr, 0, sizeof(addr));
uint8_t ipBytes[16];
bool success = IPParse(ip, &addr);
if(!success)
{
this->valid=false;
return;
}
SetPort((struct sockaddr*)&addr, port);
this->sock = NETWORK_SOCKET((int)addr.ss_family, SOCK_STREAM, 0);
if(this->sock < 0)
{
this->valid=false;
return;
}
if(NETWORK_BIND(this->sock, (const sockaddr*)&addr, (socklen_t)sizeof(addr)) != 0)
{
this->valid = false;
return;
}
if(NETWORK_LISTEN(this->sock, backlog) != 0)
{
this->valid = false;
return;
}
this->valid = true;
}
TcpServer::TcpServer(int32_t sock, bool owns)
{
this->valid = sock >= 0;
this->sock = sock;
this->owns = owns;
}
TcpServer::~TcpServer()
{
if(this->valid && this->owns)
Close();
}
void TcpServer::Close()
{
if(this->valid)
NETWORK_CLOSE(this->sock);
this->valid=false;
}
NetworkStream* TcpServer::GetStream(std::string& ip, uint16_t& port)
{
struct sockaddr_storage storage;
memset(&storage,0, sizeof(storage));
socklen_t addrlen=(socklen_t)sizeof(storage);
int s = NETWORK_ACCEPT(this->sock, (struct sockaddr*)&storage, &addrlen);
if(s < 0)
{
return nullptr;
}
ip = StringifyIP((struct sockaddr*)&storage);
port = GetPort((struct sockaddr*)&storage);
return new NetworkStream(s,true);
}
bool NetworkStream::CanRead()
{
return this->success;
}
bool NetworkStream::CanWrite()
{
return this->success;
}
NetworkStream::NetworkStream(bool ipV6,bool datagram)
{
this->endOfStream=false;
this->owns = true;
this->success=false;
if(ipV6)
{
#if defined(AF_INET6)
this->sock = socket(AF_INET6, datagram ? SOCK_DGRAM : SOCK_STREAM, 0);
if(this->sock >= 0) this->success=true;
#endif
}
else
{
#if defined(AF_INET)
this->sock = socket(AF_INET, datagram ? SOCK_DGRAM : SOCK_STREAM, 0);
if(this->sock >= 0) this->success=true;
#endif
}
}
NetworkStream::NetworkStream(std::string ipOrFqdn, uint16_t port, bool datagram, bool broadcast, bool supportIPv6)
{
this->endOfStream = false;
this->owns=true;
this->success=false;
std::string portStr = std::to_string((uint32_t)port);
struct addrinfo hint;
memset(&hint, 0, sizeof(hint));
#if defined(AF_INET6)
hint.ai_family = supportIPv6 ? AF_UNSPEC : AF_INET;
#else
hint.ai_family = AF_INET;
#endif
hint.ai_socktype = datagram ? SOCK_DGRAM : SOCK_STREAM;
struct addrinfo* result;
int status = NETWORK_GETADDRINFO(ipOrFqdn.c_str(),portStr.c_str(), &hint, &result);
if(status != 0)
{
return;
}
struct addrinfo* tmp=result;
while(tmp != nullptr)
{
sock = NETWORK_SOCKET(tmp->ai_family,tmp->ai_socktype,tmp->ai_protocol);
if(broadcast)
{
int broadcast = 1;
if (NETWORK_SETSOCKOPT(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)) != 0) {
this->success=false;
NETWORK_CLOSE(this->sock);
continue;
}
}
if(sock != -1)
{
this->success=true;
if(NETWORK_CONNECT(this->sock,(const sockaddr*)tmp->ai_addr,tmp->ai_addrlen) == 0) break;
this->success=false;
NETWORK_CLOSE(this->sock);
}
tmp = tmp->ai_next;
}
NETWORK_FREEADDRINFO(result);
}
NetworkStream::NetworkStream(int32_t sock, bool owns)
{
this->endOfStream = false;
this->success = sock >= 0;
this->sock = sock;
this->owns = owns;
}
bool NetworkStream::EndOfStream()
{
return this->endOfStream;
}
void NetworkStream::Listen(int32_t backlog)
{
if(this->success)
NETWORK_LISTEN(this->sock, backlog);
}
void NetworkStream::Bind(std::string ip, uint16_t port)
{
if(!this->success) return;
struct sockaddr_storage addr;
memset(&addr, 0, sizeof(addr));
uint8_t ipBytes[16];
bool success = IPParse(ip, &addr);
if(!success)
{
this->success=false;
if(this->owns)
NETWORK_CLOSE(this->sock);
return;
}
SetPort((struct sockaddr*)&addr, port);
int r = NETWORK_BIND(this->sock, (struct sockaddr*)&addr, sizeof(addr));
if(r != 0)
{
this->success=false;
if(this->owns)
NETWORK_CLOSE(this->sock);
return;
}
}
void NetworkStream::SetBroadcast(bool bC)
{
if(!this->success) return;
int broadcast = 1;
if (NETWORK_SETSOCKOPT(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)) != 0)
{
this->success=false;
if(this->owns)
NETWORK_CLOSE(this->sock);
}
}
NetworkStream* NetworkStream::Accept(std::string& ip, uint16_t& port)
{
if(!this->success) return nullptr;
struct sockaddr_storage storage;
socklen_t addrlen=(socklen_t)sizeof(storage);
int s = NETWORK_ACCEPT(this->sock, (struct sockaddr*)&storage, (socklen_t*)&addrlen);
if(s < 0)
{
return nullptr;
}
ip = StringifyIP((struct sockaddr*)&storage);
port = GetPort((struct sockaddr*)&storage);
return new NetworkStream(s,true);
}
size_t NetworkStream::Read(uint8_t* buff, size_t sz)
{
if(!this->success) return 0;
ssize_t r = NETWORK_RECV(this->sock,buff,sz,0);
if(r <= 0)
{
this->endOfStream=true;
return 0;
}
return (size_t)r;
}
size_t NetworkStream::Write(const uint8_t* buff, size_t sz)
{
if(!this->success) return 0;
ssize_t sz2 = NETWORK_SEND(this->sock,buff,sz, 0);
if(sz2 < 0) return 0;
return (size_t)sz;
}
size_t NetworkStream::ReadFrom(uint8_t* buff, size_t sz, std::string& ip, uint16_t& port)
{
if(!this->success) return 0;
struct sockaddr_storage storage;
socklen_t addrlen=(socklen_t)sizeof(storage);
ssize_t r = NETWORK_RECVFROM(this->sock,buff,sz,0, (struct sockaddr*)&storage, (socklen_t*)&addrlen);
ip = StringifyIP((struct sockaddr*)&storage);
port = GetPort((struct sockaddr*)&storage);
if(r < 0) return 0;
return (size_t)r;
}
size_t NetworkStream::WriteTo(const uint8_t* buff, size_t sz, std::string ip, uint16_t port)
{
if(!this->success) return 0;
struct sockaddr_storage addr;
memset(&addr, 0, sizeof(addr));
uint8_t ipBytes[16];
bool success = IPParse(ip, &addr);
if(!success)
{
this->success=false;
if(this->owns)
NETWORK_CLOSE(this->sock);
return 0;
}
SetPort((struct sockaddr*)&addr, port);
ssize_t sz2 = NETWORK_SENDTO(this->sock,buff,sz, 0, (const sockaddr*)&addr, (socklen_t)sizeof(addr));
if(sz2 < 0) return 0;
return (size_t)sz2;
}
NetworkStream::~NetworkStream()
{
if(this->owns && this->success)
NETWORK_CLOSE(this->sock);
}
}

115
src/Streams/Stream.cpp Normal file
View File

@ -0,0 +1,115 @@
#include "TessesFramework/Streams/Stream.hpp"
namespace Tesses::Framework::Streams {
int32_t Stream::ReadByte()
{
uint8_t b;
if(Read(&b, 1) == 0) return -1;
return b;
}
void Stream::WriteByte(uint8_t b)
{
Write(&b, 1);
}
size_t Stream::Read(uint8_t* buffer, size_t count)
{
return 0;
}
size_t Stream::Write(const uint8_t* buffer, size_t count)
{
return 0;
}
size_t Stream::ReadBlock(uint8_t* buffer,size_t len)
{
size_t read;
size_t readTotal = 0;
do{
read = 1024;
if(len < 1024)
read = len;
if(read > 0)
read=this->Read(buffer,read);
buffer += read;
len -= read;
readTotal += read;
} while(read > 0);
return readTotal;
}
void Stream::WriteBlock(const uint8_t* buffer,size_t len)
{
size_t read;
do{
read = 1024;
if(len < 1024)
read = len;
if(read > 0)
read=this->Write(buffer,read);
buffer += read;
len -= read;
} while(read > 0);
}
bool Stream::CanRead()
{
return false;
}
bool Stream::CanWrite()
{
return false;
}
bool Stream::CanSeek()
{
return false;
}
bool Stream::EndOfStream()
{
return false;
}
int64_t Stream::GetPosition()
{
return 0;
}
int64_t Stream::GetLength()
{
if(!CanSeek()) return 0;
int64_t curPos = GetPosition();
Seek(0, SeekOrigin::End);
int64_t len = GetPosition();
Seek(curPos, SeekOrigin::Begin);
return len;
}
void Stream::Flush()
{
}
void Stream::Seek(int64_t pos, SeekOrigin whence)
{
}
void Stream::CopyTo(Stream* strm, size_t buffSize)
{
if(strm == nullptr)
strm->CopyTo(strm, buffSize);
}
void Stream::CopyTo(Stream& strm, size_t buffSize)
{
size_t read;
uint8_t buffer[buffSize];
do {
read = this->Read(buffer,buffSize);
strm.WriteBlock(buffer, read);
} while(read > 0);
strm.Flush();
}
Stream::~Stream()
{
}
}

114
src/TF_Init.cpp Normal file
View File

@ -0,0 +1,114 @@
#include "TessesFramework/Common.hpp"
#include "TessesFramework/Streams/NetworkStream.hpp"
#include <atomic>
#include <csignal>
#if defined(GEKKO)
#include <stdio.h>
#include <stdlib.h>
#include <gccore.h>
#include <fat.h>
#include <ogc/pad.h>
#if defined(HW_RVL)
#if defined(TESSESFRAMEWORK_USE_WII_SOCKET)
#include <wiisocket.h>
#endif
#include <wiiuse/wpad.h>
#endif
static void *xfb = NULL;
static GXRModeObj *rmode = NULL;
#endif
namespace Tesses::Framework
{
volatile static bool isRunningSig=true;
volatile static std::atomic<bool> isRunning;
void TF_ConnectToSelf(uint16_t port)
{
Tesses::Framework::Streams::NetworkStream ns("127.0.0.1",port,false,false,false);
}
bool TF_IsRunning()
{
return isRunning;
}
static void _sigInt(int c)
{
isRunningSig=false;
}
void TF_RunEventLoop()
{
while(isRunning)
{
TF_RunEventLoopItteration();
}
}
void TF_RunEventLoopItteration()
{
if(!isRunningSig) isRunning=false;
#if defined(GEKKO)
PAD_ScanPads();
if(PAD_ButtonsDown(0) & PAD_BUTTON_START) isRunning=false;
#endif
}
void TF_Quit()
{
isRunning=false;
}
void TF_Init()
{
isRunning=true;
#if defined(GEKKO)
fatInitDefault();
VIDEO_Init();
PAD_Init();
#if defined(HW_RVL)
#if defined(TESSESFRAMEWORK_USE_WII_SOCKET)
wiisocket_init();
#endif
WPAD_Init();
#endif
rmode = VIDEO_GetPreferredMode(NULL);
// Allocate memory for the display in the uncached region
xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
// Initialise the console, required for printf
console_init(xfb,20,20,rmode->fbWidth,rmode->xfbHeight,rmode->fbWidth*VI_DISPLAY_PIX_SZ);
//SYS_STDIO_Report(true);
// Set up the video registers with the chosen mode
VIDEO_Configure(rmode);
// Tell the video hardware where our display memory is
VIDEO_SetNextFramebuffer(xfb);
// Make the display visible
VIDEO_SetBlack(false);
// Flush the video register changes to the hardware
VIDEO_Flush();
// Wait for Video setup to complete
VIDEO_WaitVSync();
if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();
// The console understands VT terminal escape codes
// This positions the cursor on row 2, column 0
// we can use variables for this with format codes too
// e.g. printf ("\x1b[%d;%dH", row, column );
printf("\x1b[2;0H");
#else
signal(SIGPIPE,SIG_IGN);
signal(SIGINT,_sigInt);
#endif
}
}

View File

@ -0,0 +1,39 @@
#include "TessesFramework/TextStreams/StreamReader.hpp"
#include "TessesFramework/Streams/FileStream.hpp"
using Stream = Tesses::Framework::Streams::Stream;
using FileStream = Tesses::Framework::Streams::FileStream;
namespace Tesses::Framework::TextStreams {
StreamReader::StreamReader(Stream& strm) : StreamReader(&strm, false)
{
}
StreamReader::StreamReader(std::filesystem::path path) : StreamReader(new FileStream(path,"rb"),true)
{
}
StreamReader::StreamReader(Stream* strm, bool owns) : TextReader()
{
this->strm = strm;
this->owns = owns;
}
Stream& StreamReader::GetStream()
{
return *(this->strm);
}
bool StreamReader::ReadBlock(std::string& str, size_t len)
{
uint8_t buff[len];
len = strm->ReadBlock(buff,len);
if(len == 0) return false;
str.append((const char*)buff, len);
return true;
}
StreamReader::~StreamReader()
{
if(this->owns)
delete this->strm;
}
};

View File

@ -0,0 +1,34 @@
#include "TessesFramework/Streams/FileStream.hpp"
#include "TessesFramework/TextStreams/StreamWriter.hpp"
using Stream = Tesses::Framework::Streams::Stream;
using FileStream = Tesses::Framework::Streams::FileStream;
namespace Tesses::Framework::TextStreams
{
Stream& StreamWriter::GetStream()
{
return *(this->strm);
}
StreamWriter::StreamWriter(Stream& strm) : StreamWriter(&strm, false)
{
}
StreamWriter::StreamWriter(Stream* strm, bool owns) : TextWriter()
{
this->strm = strm;
this->owns = owns;
}
StreamWriter::StreamWriter(std::filesystem::path filename, bool append) : StreamWriter(new FileStream(filename, append ? "ab" : "wb"),true)
{
}
void StreamWriter::WriteData(const char* text, size_t len)
{
this->strm->WriteBlock((const uint8_t*)text, len);
}
StreamWriter::~StreamWriter()
{
if(this->owns)
delete this->strm;
}
}

View File

@ -0,0 +1,58 @@
#include "TessesFramework/TextStreams/TextReader.hpp"
namespace Tesses::Framework::TextStreams
{
int32_t TextReader::ReadChar()
{
std::string txt;
this->ReadBlock(txt,1);
if(txt.empty()) return -1;
return (uint8_t)txt[0];
}
std::string TextReader::ReadLine()
{
std::string str = {};
ReadLine(str);
return str;
}
bool TextReader::ReadLine(std::string& str)
{
bool ret = false;
int32_t r = -1;
do {
r = ReadChar();
if(r == -1) break;
if(r == '\r') continue;
if(r == '\n') break;
str.push_back((char)(uint8_t)r);
ret = true;
} while(r != -1);
return ret;
}
std::string TextReader::ReadToEnd()
{
std::string str = {};
ReadToEnd(str);
return str;
}
void TextReader::ReadToEnd(std::string& str)
{
while(ReadBlock(str,1024));
}
void TextReader::CopyTo(TextWriter& writer, size_t buffSz)
{
std::string str = {};
while(ReadBlock(str,buffSz))
{
writer.Write(str);
str.clear();
}
}
TextReader::~TextReader()
{
}
}

View File

@ -0,0 +1,31 @@
#include "TessesFramework/TextStreams/TextWriter.hpp"
namespace Tesses::Framework::TextStreams
{
TextWriter::TextWriter()
{
#if defined(WIN32) || defined(_WIN32)
newline = "\r\n";
#else
newline = "\n";
#endif
}
void TextWriter::Write(std::string txt)
{
WriteData(txt.c_str(),txt.size());
}
void TextWriter::WriteLine(std::string txt)
{
std::string str = txt;
str.append(newline);
Write(str);
}
void TextWriter::WriteLine()
{
Write(newline);
}
TextWriter::~TextWriter()
{
}
}

49
src/Threading/Mutex.cpp Normal file
View File

@ -0,0 +1,49 @@
#include "TessesFramework/Threading/Mutex.hpp"
#include <cstring>
namespace Tesses::Framework::Threading
{
Mutex::Mutex()
{
#if defined(GEKKO)
mtx = LWP_MUTEX_NULL;
LWP_MutexInit(&mtx, true);
#else
memset(&mtx, 0, sizeof(mtx_t));
mtx_init(&mtx, mtx_recursive);
#endif
}
void Mutex::Lock()
{
#if defined(GEKKO)
LWP_MutexLock(mtx);
#else
mtx_lock(&mtx);
#endif
}
void Mutex::Unlock()
{
#if defined(GEKKO)
LWP_MutexUnlock(mtx);
#else
mtx_unlock(&mtx);
#endif
}
bool Mutex::TryLock()
{
#if defined(GEKKO)
return LWP_MutexTryLock(mtx) == 0;
#else
return mtx_trylock(&mtx) == thrd_success;
#endif
}
Mutex::~Mutex()
{
#if defined(GEKKO)
LWP_MutexDestroy(mtx);
#else
mtx_destroy(&mtx);
#endif
}
};

53
src/Threading/Thread.cpp Normal file
View File

@ -0,0 +1,53 @@
#include "TessesFramework/Threading/Thread.hpp"
#include <iostream>
namespace Tesses::Framework::Threading
{
#if defined(GEKKO)
void* Thread::cb(void* data)
#else
int Thread::cb(void* data)
#endif
{
auto thrd = static_cast<Thread*>(data);
auto fn = thrd->fn;
thrd->hasInvoked=true;
fn();
#if defined(GEKKO)
return NULL;
#else
return 0;
#endif
}
Thread::Thread(std::function<void()> fn)
{
this->hasInvoked=false;
this->fn = fn;
#if defined(GEKKO)
thrd = LWP_THREAD_NULL;
LWP_CreateThread(&thrd, cb, static_cast<void*>(this), NULL, 12000, LWP_PRIO_HIGHEST);
#else
thrd_create(&thrd, cb, static_cast<void*>(this));
#endif
while(!this->hasInvoked);
}
void Thread::Detach()
{
#if !defined(GEKKO)
thrd_detach(thrd);
#endif
}
void Thread::Join()
{
#if defined(GEKKO)
void* res;
LWP_JoinThread(thrd,&res);
#else
int res;
thrd_join(thrd,&res);
#endif
}
}