first commit

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

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
build
.vscode

149
CMakeLists.txt Normal file
View File

@ -0,0 +1,149 @@
cmake_minimum_required(VERSION 3.20)
project(TessesFramework VERSION 1.0)
set(CMAKE_CXX_STANDARD 20)
list(APPEND TESSESFRAMEWORK_SOURCE
src/Http/FileServer.cpp
src/Http/HttpServer.cpp
src/Http/HttpUtils.cpp
src/Http/HttpClient.cpp
src/Http/HttpStream.cpp
src/Http/ContentDisposition.cpp
src/Streams/FileStream.cpp
src/Streams/MemoryStream.cpp
src/Streams/NetworkStream.cpp
src/Streams/Stream.cpp
src/Streams/BufferedStream.cpp
src/TextStreams/StreamReader.cpp
src/TextStreams/StreamWriter.cpp
src/TextStreams/TextReader.cpp
src/TextStreams/TextWriter.cpp
src/Threading/Thread.cpp
src/Threading/Mutex.cpp
src/Filesystem/VFS.cpp
src/Filesystem/LocalFS.cpp
src/Filesystem/SubdirFilesystem.cpp
src/Filesystem/NullFilesystem.cpp
src/Filesystem/MountableFilesystem.cpp
src/Crypto/ClientTLSStream.cpp
src/TF_Init.cpp
)
set(TESSESFRAMEWORK_CERT_BUNDLE_FILE "/etc/ssl/certs/ca-certificates.crt")
option(TESSESFRAMEWORK_EMBED_CERT_BUNDLE "Embed the certificate chain bundle" ON)
option(TESSESFRAMEWORK_ENABLE_MBED "Enable Tesses Framework mbedtls" ON)
option(TESSESFRAMEWORK_ENABLE_EXAMPLES "Enable Tesses Framework examples" ON)
option(TESSESFRAMEWORK_ENABLE_STATIC "Enable Tesses Framework Static Libraries" ON)
option(TESSESFRAMEWORK_ENABLE_SHARED "Enable Tesses Framework Shared Libraries" ON)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include)
if(TESSESFRAMEWORK_ENABLE_MBED)
if(TESSESFRAMEWORK_EMBED_CERT_BUNDLE)
include(cmake/bin2h.cmake)
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/include/CertificateChain.h "#pragma once\n")
#target_compile_definitions(TessesFramework PUBLIC TESSESFRAMEWORK_EMBED_CERT_BUNDLE)
bin2h(SOURCE_FILE ${TESSESFRAMEWORK_CERT_BUNDLE_FILE} HEADER_FILE ${CMAKE_CURRENT_BINARY_DIR}/include/CertificateChain.h VARIABLE_NAME CertificateChain APPEND NULL_TERMINATE)
file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/include/CertificateChain.h "\n")
#else()
#target_compile_definitions(TessesFramework PUBLIC TESSESFRAMEWORK_CERT_BUNDLE_FILE=${TESSESFRAMEWORK_CERT_BUNDLE_FILE})
endif()
endif()
set(MBEDTLS_DIR "")
function(link_deps TessesFramework_TARGET)
if(TESSESFRAMEWORK_ENABLE_MBED)
target_compile_definitions(${TessesFramework_TARGET} PUBLIC TESSESFRAMEWORK_ENABLE_MBED)
if(TESSESFRAMEWORK_EMBED_CERT_BUNDLE)
target_compile_definitions(${TessesFramework_TARGET} PUBLIC TESSESFRAMEWORK_EMBED_CERT_BUNDLE)
else()
target_compile_definitions(TessesFramework PUBLIC TESSESFRAMEWORK_CERT_BUNDLE_FILE=${TESSESFRAMEWORK_CERT_BUNDLE_FILE})
endif()
if(MBEDTLS_DIR STREQUAL "")
else()
target_include_directories(${TessesFramework_TARGET} PUBLIC ${MBEDTLS_DIR}/include)
target_link_directories(${TessesFramework_TARGET} PUBLIC ${MBEDTLS_DIR}/lib)
endif()
target_link_libraries(${TessesFramework_TARGET} PUBLIC mbedtls mbedx509 mbedcrypto)
target_include_directories(${TessesFramework_TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include)
endif()
target_include_directories(${TessesFramework_TARGET}
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
if("${CMAKE_SYSTEM_NAME}" STREQUAL "NintendoWii" OR "${CMAKE_SYSTEM_NAME}" STREQUAL "NintendoGameCube")
target_link_libraries(${TessesFramework_TARGET} PUBLIC fat)
endif()
if("${CMAKE_SYSTEM_NAME}" STREQUAL "NintendoWii")
target_link_libraries(${TessesFramework_TARGET} PUBLIC wiisocket)
target_compile_definitions(${TessesFramework_TARGET} PUBLIC TESSESFRAMEWORK_USE_WII_SOCKET)
endif()
endfunction()
include(GNUInstallDirs)
if(TESSESFRAMEWORK_ENABLE_STATIC)
add_library(tessesframework STATIC ${TESSESFRAMEWORK_SOURCE})
link_deps(tessesframework)
list(APPEND TessesFrameworkLibs tessesframework)
endif()
if(TESSESFRAMEWORK_ENABLE_SHARED)
add_library(tessesframework_shared SHARED ${TESSESFRAMEWORK_SOURCE})
link_deps(tessesframework_shared)
list(APPEND TessesFrameworkLibs tessesframework_shared)
endif()
install(TARGETS ${TessesFrameworkLibs}
EXPORT TessesFrameworkTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(DIRECTORY include/TessesFramework DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(EXPORT TessesFrameworkTargets
FILE TessesFrameworkTargets.cmake
NAMESPACE TessesFramework::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TessesFramework
)
include(CMakePackageConfigHelpers)
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/TessesFrameworkConfig.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TessesFramework)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/TessesFrameworkConfig.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TessesFramework)
if(TESSESFRAMEWORK_ENABLE_EXAMPLES)
add_executable(copyfile examples/copyfile.cpp)
target_link_libraries(copyfile PUBLIC tessesframework)
add_executable(fileserver examples/fileserver.cpp)
target_link_libraries(fileserver PUBLIC tessesframework)
add_executable(webserverex examples/webserverex.cpp)
target_link_libraries(webserverex PUBLIC tessesframework)
add_executable(safesubpath examples/safesubpath.cpp)
target_link_libraries(safesubpath PUBLIC tessesframework)
add_executable(mountabletest examples/mountabletest.cpp)
target_link_libraries(mountabletest PUBLIC tessesframework)
add_executable(download examples/download.cpp)
target_link_libraries(download PUBLIC tessesframework)
endif()

5
Config.cmake.in Normal file
View File

@ -0,0 +1,5 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/TessesFrameworkTargets.cmake")
check_required_components(TessesFramework)

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Mike Nolan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

6
OTHERS_CODE.md Normal file
View File

@ -0,0 +1,6 @@
Other peoples code
==================
- cmake/bin2h.cmake, from: https://github.com/sivachandran/cmake-bin2h (MIT)
- Cppified the multipart/form-data code from: https://github.com/dajuric/simple-http (MIT)
- mbedtls ssl code from mbedtls examples (Apache 2.0)

84
cmake/bin2h.cmake Normal file
View File

@ -0,0 +1,84 @@
#Released under MIT from https://github.com/sivachandran/cmake-bin2h
include(CMakeParseArguments)
# Function to wrap a given string into multiple lines at the given column position.
# Parameters:
# VARIABLE - The name of the CMake variable holding the string.
# AT_COLUMN - The column position at which string will be wrapped.
function(WRAP_STRING)
set(oneValueArgs VARIABLE AT_COLUMN)
cmake_parse_arguments(WRAP_STRING "${options}" "${oneValueArgs}" "" ${ARGN})
string(LENGTH ${${WRAP_STRING_VARIABLE}} stringLength)
math(EXPR offset "0")
while(stringLength GREATER 0)
if(stringLength GREATER ${WRAP_STRING_AT_COLUMN})
math(EXPR length "${WRAP_STRING_AT_COLUMN}")
else()
math(EXPR length "${stringLength}")
endif()
string(SUBSTRING ${${WRAP_STRING_VARIABLE}} ${offset} ${length} line)
set(lines "${lines}\n${line}")
math(EXPR stringLength "${stringLength} - ${length}")
math(EXPR offset "${offset} + ${length}")
endwhile()
set(${WRAP_STRING_VARIABLE} "${lines}" PARENT_SCOPE)
endfunction()
# Function to embed contents of a file as byte array in C/C++ header file(.h). The header file
# will contain a byte array and integer variable holding the size of the array.
# Parameters
# SOURCE_FILE - The path of source file whose contents will be embedded in the header file.
# VARIABLE_NAME - The name of the variable for the byte array. The string "_SIZE" will be append
# to this name and will be used a variable name for size variable.
# HEADER_FILE - The path of header file.
# APPEND - If specified appends to the header file instead of overwriting it
# NULL_TERMINATE - If specified a null byte(zero) will be append to the byte array. This will be
# useful if the source file is a text file and we want to use the file contents
# as string. But the size variable holds size of the byte array without this
# null byte.
# Usage:
# bin2h(SOURCE_FILE "Logo.png" HEADER_FILE "Logo.h" VARIABLE_NAME "LOGO_PNG")
function(BIN2H)
set(options APPEND NULL_TERMINATE)
set(oneValueArgs SOURCE_FILE VARIABLE_NAME HEADER_FILE)
cmake_parse_arguments(BIN2H "${options}" "${oneValueArgs}" "" ${ARGN})
# reads source file contents as hex string
file(READ ${BIN2H_SOURCE_FILE} hexString HEX)
string(LENGTH ${hexString} hexStringLength)
# appends null byte if asked
if(BIN2H_NULL_TERMINATE)
set(hexString "${hexString}00")
endif()
# wraps the hex string into multiple lines at column 32(i.e. 16 bytes per line)
wrap_string(VARIABLE hexString AT_COLUMN 32)
math(EXPR arraySize "${hexStringLength} / 2")
# adds '0x' prefix and comma suffix before and after every byte respectively
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " arrayValues ${hexString})
# removes trailing comma
string(REGEX REPLACE ", $" "" arrayValues ${arrayValues})
# converts the variable name into proper C identifier
string(MAKE_C_IDENTIFIER "${BIN2H_VARIABLE_NAME}" BIN2H_VARIABLE_NAME)
string(TOUPPER "${BIN2H_VARIABLE_NAME}" BIN2H_VARIABLE_NAME)
# declares byte array and the length variables
set(arrayDefinition "const unsigned char ${BIN2H_VARIABLE_NAME}[] = { ${arrayValues} };")
set(arraySizeDefinition "const size_t ${BIN2H_VARIABLE_NAME}_SIZE = ${arraySize};")
set(declarations "${arrayDefinition}\n\n${arraySizeDefinition}\n\n")
if(BIN2H_APPEND)
file(APPEND ${BIN2H_HEADER_FILE} "${declarations}")
else()
file(WRITE ${BIN2H_HEADER_FILE} "${declarations}")
endif()
endfunction()

16
examples/copyfile.cpp Normal file
View File

@ -0,0 +1,16 @@
#include "TessesFramework/TessesFramework.hpp"
using namespace Tesses::Framework::Filesystem;
int main(int argc, char** argv)
{
LocalFilesystem fs;
VFSPath src = fs.SystemToVFSPath(argv[1]);
VFSPath dest = fs.SystemToVFSPath(argv[2]);
auto srcs = fs.OpenFile(src,"rb");
auto dests = fs.OpenFile(dest,"wb");
srcs->CopyTo(*dests);
delete srcs;
delete dests;
}

22
examples/download.cpp Normal file
View File

@ -0,0 +1,22 @@
#include "TessesFramework/TessesFramework.hpp"
#include <iostream>
using namespace Tesses::Framework;
using namespace Tesses::Framework::Streams;
using namespace Tesses::Framework::Http;
int main(int argc, char** argv)
{
if(argc < 3)
{
printf("USAGE: %s <url> <path>\n",argv[0]);
return 1;
}
FileStream strm(argv[2],"wb");
HttpRequest req;
req.url = argv[1];
HttpResponse resp(req);
resp.CopyToStream(&strm);
return 0;
}

23
examples/fileserver.cpp Normal file
View File

@ -0,0 +1,23 @@
#include "TessesFramework/TessesFramework.hpp"
#include <iostream>
using namespace Tesses::Framework;
using namespace Tesses::Framework::Http;
using namespace Tesses::Framework::Streams;
using namespace Tesses::Framework::TextStreams;
using namespace Tesses::Framework::Threading;
int main(int argc, char** argv)
{
TF_Init();
if(argc < 1)
{
printf("USAGE: %s <dir>\n",argv[0]);
return 0;
}
FileServer fs(argv[1],true,false);
HttpServer server(10000U,fs);
server.StartAccepting();
TF_RunEventLoop();
std::cout << "Closing server" << std::endl;
return 0;
}

View File

@ -0,0 +1,63 @@
#include "TessesFramework/TessesFramework.hpp"
#include <iostream>
using namespace Tesses::Framework;
using namespace Tesses::Framework::Filesystem;
using namespace Tesses::Framework::Streams;
int main(int argc, char** argv)
{
if(argc < 2)
{
printf("USAGE: %s <command> <args...>\n",argv[0]);
return 1;
}
LocalFilesystem lfs;
std::string root = "./root";
std::string mountDemi = "./demi";
std::string mountJoelSlashJim = "./joelslashjim";
SubdirFilesystem rootdir(&lfs,root,false);
SubdirFilesystem mountDemidir(&lfs,mountDemi,false);
SubdirFilesystem mountjohnslashjim(&lfs,mountJoelSlashJim,false);
MountableFilesystem fs(&rootdir,false);
fs.Mount(std::string("/demi"), &mountDemidir,false);
fs.Mount(std::string("/joel/jim"), &mountjohnslashjim,false);
std::string command = argv[1];
if(command == "ls")
{
std::string dir = "/";
if(argc > 2) dir = argv[2];
std::vector<VFSPath> paths;
fs.GetPaths(dir, paths);
for(auto item : paths)
{
std::cout << item.GetFileName() << std::endl;
}
}
else if(command == "cat")
{
FileStream strm(stdout, false,"wb",false);
for(int a = 2; a < argc; a++)
{
std::string path = argv[a];
auto f = fs.OpenFile(path,"rb");
if(f != nullptr)
{
f->CopyTo(strm);
delete f;
}
}
}
}

19
examples/safesubpath.cpp Normal file
View File

@ -0,0 +1,19 @@
#include "TessesFramework/TessesFramework.hpp"
#include <iostream>
using namespace Tesses::Framework::Filesystem;
int main(int argc, char** argv)
{
if(argc < 3)
{
std::cout << "USAGE: " << argv[0] << " <parent> <subpath>\n";
return 1;
}
VFSPath parent(argv[1]);
VFSPath subpath(argv[2]);
VFSPath newPath(parent,subpath.CollapseRelativeParents());
std::cout << newPath.ToString() << "\n";
}

100
examples/webserverex.cpp Normal file
View File

@ -0,0 +1,100 @@
#include "TessesFramework/TessesFramework.hpp"
#include <iostream>
using namespace Tesses::Framework;
using namespace Tesses::Framework::Http;
using namespace Tesses::Framework::Streams;
using namespace Tesses::Framework::TextStreams;
using namespace Tesses::Framework::Threading;
class MyWebServer : public IHttpServer {
public:
bool Handle(ServerContext& ctx)
{
if(ctx.path == "/")
{
FileStream fs("index.html","rb");
ctx
.WithMimeType("text/html")
.SendStream(fs);
return true;
}
else if(ctx.path == "/streaming.html")
{
StreamWriter writer(ctx.OpenResponseStream(),true);
writer.WriteLine("<html>");
writer.WriteLine("<head><title>Streaming</title></head>");
writer.WriteLine("<body>");
writer.WriteLine("<h1>Streaming</h1>");
writer.WriteLine("<ul>");
for(size_t i=0;i<10000; i++)
{
writer.WriteLine("<li>" + std::to_string(i) + "</li>");
}
writer.WriteLine("</ul>");
writer.WriteLine("</body>");
writer.WriteLine("</html>");
return true;
}
else if(ctx.path == "/main.js")
{
FileStream fs("main.js","rb");
ctx
.WithMimeType("text/js")
.SendStream(fs);
return true;
}
else if(ctx.path == "/upload")
{
ctx.ParseFormData([](std::string mime, std::string filename, std::string name)->Tesses::Framework::Streams::Stream*{
return new FileStream(filename,"wb");
});
}
return false;
}
bool Handle1(ServerContext& ctx)
{
std::string name;
if(ctx.queryParams.TryGetFirst("name",name))
{
std::cout << name << std::endl;
ctx
.WithMimeType("text/plain")
.WithContentDisposition(HttpUtils::Sanitise(name) + ".txt",false)
.SendText(name + " is cool.");
//do something with q
return true;
}
return false;
}
};
int main(int argc, char** argv)
{
TF_Init();
TcpServer server(9985U,10);
MyWebServer svr;
while(true)
{
std::string ip;
uint16_t port;
auto res = server.GetStream(ip, port);
if(res == nullptr) return 0;
std::cout << "Got from " << ip << ":" << port << std::endl;
Thread thrd([ip,port,res,&svr]()->void {
HttpServer::Process(*res, svr, ip,port, false);
delete res;
});
thrd.Detach();
}
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <cstring>
#include <cstdint>
#include <cstddef>
#include <string>
#include <filesystem>
#include <map>
#include <vector>
namespace Tesses::Framework
{
void TF_Init();
void TF_ConnectToSelf(uint16_t port);
void TF_RunEventLoop();
void TF_RunEventLoopItteration();
bool TF_IsRunning();
void TF_Quit();
}

View File

@ -0,0 +1,24 @@
#pragma once
#include "../Streams/Stream.hpp"
namespace Tesses::Framework::Crypto
{
class ClientTLSStream : public Tesses::Framework::Streams::Stream {
void* privateData;
static int strm_send(void* ctx,const unsigned char* buf,size_t len);
static int strm_recv(void* ctx,unsigned char* buf,size_t len);
public:
static std::string GetCertChain();
ClientTLSStream(Tesses::Framework::Streams::Stream* innerStream, bool owns, bool verify, std::string domain);
ClientTLSStream(Tesses::Framework::Streams::Stream* innerStream, bool owns, bool verify, std::string domain, std::string cert);
size_t Read(uint8_t* buff, size_t sz);
size_t Write(const uint8_t* buff, size_t sz);
bool CanRead();
bool CanWrite();
bool EndOfStream();
~ClientTLSStream();
};
}

View File

@ -0,0 +1,28 @@
#pragma once
#include "VFS.hpp"
namespace Tesses::Framework::Filesystem
{
class LocalFilesystem : public VFS
{
public:
Tesses::Framework::Streams::Stream* OpenFile(VFSPath path, std::string mode);
void CreateDirectory(VFSPath path);
void DeleteDirectory(VFSPath path);
bool RegularFileExists(VFSPath path);
bool SymlinkExists(VFSPath path);
bool CharacterDeviceExists(VFSPath path);
bool BlockDeviceExists(VFSPath path);
bool SocketFileExists(VFSPath path);
bool FIFOFileExists(VFSPath path);
bool DirectoryExists(VFSPath path);
void DeleteFile(VFSPath path);
void CreateSymlink(VFSPath existingFile, VFSPath symlinkFile);
void GetPaths(VFSPath path, std::vector<VFSPath>& paths);
void CreateHardlink(VFSPath existingFile, VFSPath newName);
void MoveFile(VFSPath src, VFSPath dest);
void MoveDirectory(VFSPath src, VFSPath dest);
VFSPath ReadLink(VFSPath path);
std::string VFSPathToSystem(VFSPath path);
VFSPath SystemToVFSPath(std::string path);
};
}

View File

@ -0,0 +1,53 @@
#pragma once
#include "VFS.hpp"
namespace Tesses::Framework::Filesystem
{
class MountableDirectory {
public:
std::string name;
VFS* vfs;
bool owns;
std::vector<MountableDirectory*> dirs;
void GetFS(VFSPath srcPath, VFSPath curDir, VFSPath& destRoot, VFSPath& destPath, VFS*& vfs);
~MountableDirectory();
};
class MountableFilesystem : public VFS
{
bool owns;
VFS* root;
std::vector<MountableDirectory*> directories;
void GetFS(VFSPath srcPath, VFSPath& destRoot, VFSPath& destPath, VFS*& vfs);
public:
MountableFilesystem();
MountableFilesystem(VFS* root, bool owns);
void Mount(VFSPath path, VFS* fs, bool owns);
void Unmount(VFSPath path);
Tesses::Framework::Streams::Stream* OpenFile(VFSPath path, std::string mode);
void CreateDirectory(VFSPath path);
void DeleteDirectory(VFSPath path);
bool SpecialFileExists(VFSPath path);
bool FileExists(VFSPath path);
bool RegularFileExists(VFSPath path);
bool SymlinkExists(VFSPath path);
bool CharacterDeviceExists(VFSPath path);
bool BlockDeviceExists(VFSPath path);
bool SocketFileExists(VFSPath path);
bool FIFOFileExists(VFSPath path);
bool DirectoryExists(VFSPath path);
void DeleteFile(VFSPath path);
void CreateSymlink(VFSPath existingFile, VFSPath symlinkFile);
void GetPaths(VFSPath path, std::vector<VFSPath>& paths);
void CreateHardlink(VFSPath existingFile, VFSPath newName);
void MoveFile(VFSPath src, VFSPath dest);
void MoveDirectory(VFSPath src, VFSPath dest);
VFSPath ReadLink(VFSPath path);
std::string VFSPathToSystem(VFSPath path);
VFSPath SystemToVFSPath(std::string path);
~MountableFilesystem();
};
}

View File

@ -0,0 +1,19 @@
#pragma once
#include "VFS.hpp"
namespace Tesses::Framework::Filesystem
{
class NullFilesystem : public VFS
{
public:
Tesses::Framework::Streams::Stream* OpenFile(VFSPath path, std::string mode);
void CreateDirectory(VFSPath path);
void DeleteDirectory(VFSPath path);
bool RegularFileExists(VFSPath path);
bool DirectoryExists(VFSPath path);
void DeleteFile(VFSPath path);
void GetPaths(VFSPath path, std::vector<VFSPath>& paths);
void MoveFile(VFSPath src, VFSPath dest);
std::string VFSPathToSystem(VFSPath path);
VFSPath SystemToVFSPath(std::string path);
};
}

View File

@ -0,0 +1,38 @@
#pragma once
#include "VFS.hpp"
namespace Tesses::Framework::Filesystem
{
class SubdirFilesystem : public VFS
{
bool owns;
VFS* parent;
VFSPath path;
VFSPath ToParent(VFSPath path);
VFSPath FromParent(VFSPath path);
public:
SubdirFilesystem(VFS* parent, VFSPath path, bool owns);
Tesses::Framework::Streams::Stream* OpenFile(VFSPath path, std::string mode);
void CreateDirectory(VFSPath path);
void DeleteDirectory(VFSPath path);
bool SpecialFileExists(VFSPath path);
bool FileExists(VFSPath path);
bool RegularFileExists(VFSPath path);
bool SymlinkExists(VFSPath path);
bool CharacterDeviceExists(VFSPath path);
bool BlockDeviceExists(VFSPath path);
bool SocketFileExists(VFSPath path);
bool FIFOFileExists(VFSPath path);
bool DirectoryExists(VFSPath path);
void DeleteFile(VFSPath path);
void CreateSymlink(VFSPath existingFile, VFSPath symlinkFile);
void GetPaths(VFSPath path, std::vector<VFSPath>& paths);
void CreateHardlink(VFSPath existingFile, VFSPath newName);
void MoveFile(VFSPath src, VFSPath dest);
void MoveDirectory(VFSPath src, VFSPath dest);
void DeleteDirectoryRecurse(VFSPath path);
VFSPath ReadLink(VFSPath path);
std::string VFSPathToSystem(VFSPath path);
VFSPath SystemToVFSPath(std::string path);
~SubdirFilesystem();
};
}

View File

@ -0,0 +1,50 @@
#pragma once
#include "../Common.hpp"
#include "../Streams/Stream.hpp"
namespace Tesses::Framework::Filesystem
{
class VFSPath {
public:
static VFSPath RelativeCurrentDirectory();
bool relative;
static std::vector<std::string> SplitPath(std::string path,bool native=false);
std::vector<std::string> path;
VFSPath();
VFSPath(std::vector<std::string> path);
VFSPath(std::string path);
VFSPath(VFSPath p, std::string subent);
VFSPath(VFSPath p, VFSPath p2);
VFSPath GetParent();
VFSPath CollapseRelativeParents();
std::string GetFileName();
std::string ToString();
};
class VFS {
public:
virtual Tesses::Framework::Streams::Stream* OpenFile(VFSPath path, std::string mode)=0;
virtual void CreateDirectory(VFSPath path)=0;
virtual void DeleteDirectory(VFSPath path)=0;
virtual bool RegularFileExists(VFSPath path)=0;
virtual bool SymlinkExists(VFSPath path);
virtual bool CharacterDeviceExists(VFSPath path);
virtual bool BlockDeviceExists(VFSPath path);
virtual bool SocketFileExists(VFSPath path);
virtual bool FIFOFileExists(VFSPath path);
virtual bool FileExists(VFSPath path);
virtual bool SpecialFileExists(VFSPath path);
virtual void CreateSymlink(VFSPath existingFile, VFSPath symlinkFile);
virtual void CreateHardlink(VFSPath existingFile, VFSPath newName);
virtual bool DirectoryExists(VFSPath path)=0;
virtual void DeleteFile(VFSPath path)=0;
virtual void DeleteDirectoryRecurse(VFSPath path);
virtual void GetPaths(VFSPath path, std::vector<VFSPath>& paths)=0;
virtual void MoveFile(VFSPath src, VFSPath dest)=0;
virtual void MoveDirectory(VFSPath src, VFSPath dest);
virtual VFSPath ReadLink(VFSPath path);
virtual std::string VFSPathToSystem(VFSPath path)=0;
virtual VFSPath SystemToVFSPath(std::string path)=0;
virtual ~VFS();
};
}

View File

@ -0,0 +1,13 @@
#pragma once
#include "../Common.hpp"
namespace Tesses::Framework::Http
{
class ContentDisposition {
public:
std::string filename;
std::string type;
std::string fieldName;
static bool TryParse(std::string str, ContentDisposition& cd);
std::string ToString();
};
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "../Filesystem/VFS.hpp"
#include "HttpServer.hpp"
namespace Tesses::Framework::Http
{
class FileServer : public IHttpServer
{
Tesses::Framework::Filesystem::VFS* vfs;
bool ownsVFS;
bool SendFile(ServerContext& ctx,Tesses::Framework::Filesystem::VFSPath path);
public:
bool allowListing;
bool spa;
std::vector<std::string> defaultNames;
FileServer(std::filesystem::path path,bool allowListing,bool spa);
FileServer(std::filesystem::path path,bool allowListing, bool spa, std::vector<std::string> defaultNames);
FileServer(Tesses::Framework::Filesystem::VFS* fs, bool owns, bool allowListing, bool spa);
FileServer(Tesses::Framework::Filesystem::VFS* fs, bool owns, bool allowListing, bool spa, std::vector<std::string> defaultNames);
bool Handle(ServerContext& ctx);
~FileServer();
};
}

View File

@ -0,0 +1,63 @@
#pragma once
#include "../Streams/Stream.hpp"
#include "HttpUtils.hpp"
namespace Tesses::Framework::Http
{
class HttpRequestBody {
public:
virtual void HandleHeaders(HttpDictionary& dict);
virtual void Write(Tesses::Framework::Streams::Stream* strm)=0;
};
class StreamHttpRequestBody : public HttpRequestBody {
Tesses::Framework::Streams::Stream* strm;
bool owns;
std::string mimeType;
public:
StreamHttpRequestBody(Tesses::Framework::Streams::Stream* strm, bool owns, std::string mimeType);
void HandleHeaders(HttpDictionary& dict);
void Write(Tesses::Framework::Streams::Stream* strm);
~StreamHttpRequestBody();
};
class HttpRequest {
public:
HttpRequest();
std::string trusted_root_cert_bundle;
bool ignoreSSLErrors;
bool followRedirects;
std::string method;
std::string url;
HttpDictionary requestHeaders;
HttpRequestBody* body;
static Tesses::Framework::Streams::Stream* EstablishConnection(Uri uri,bool ignoreSSLErrors,std::string trusted_root_cert_bundle);
void SendRequest(Tesses::Framework::Streams::Stream* strm);
};
class HttpResponse {
private:
bool owns;
Tesses::Framework::Streams::Stream* handleStrm;
public:
HttpResponse(Tesses::Framework::Streams::Stream* strm, bool owns);
HttpResponse(HttpRequest& request);
std::string version;
StatusCode statusCode;
HttpDictionary responseHeaders;
std::string ReadAsString();
Tesses::Framework::Streams::Stream* ReadAsStream();
void CopyToStream(Tesses::Framework::Streams::Stream* strm);
~HttpResponse();
};
}

View File

@ -0,0 +1,70 @@
#pragma once
#include "../Streams/NetworkStream.hpp"
#include "HttpUtils.hpp"
#include "../Threading/Thread.hpp"
namespace Tesses::Framework::Http
{
class ServerContext {
bool sent;
Tesses::Framework::Streams::Stream* strm;
public:
HttpDictionary requestHeaders;
HttpDictionary responseHeaders;
HttpDictionary queryParams;
std::string path;
std::string originalPath;
std::string method;
StatusCode statusCode;
std::string ip;
uint16_t port;
std::string version;
bool encrypted;
ServerContext(Tesses::Framework::Streams::Stream* strm);
Tesses::Framework::Streams::Stream& GetStream();
std::string GetOriginalPathWithQuery();
std::string GetUrlWithQuery();
bool Sent();
bool NeedToParseFormData();
void ParseFormData(std::function<Tesses::Framework::Streams::Stream*(std::string mime, std::string filename, std::string name)> cb);
void ReadStream(Tesses::Framework::Streams::Stream& strm);
void ReadStream(Tesses::Framework::Streams::Stream* strm);
std::string ReadString();
void SendBytes(std::vector<uint8_t> buffer);
void SendText(std::string text);
void SendStream(Tesses::Framework::Streams::Stream& strm);
void SendStream(Tesses::Framework::Streams::Stream* strm);
void SendErrorPage(bool showPath);
void SendNotFound();
void SendBadRequest();
void SendException(std::exception& ex);
Tesses::Framework::Streams::Stream* OpenResponseStream();
Tesses::Framework::Streams::Stream* OpenRequestStream();
ServerContext& WithHeader(std::string key, std::string value);
ServerContext& WithSingleHeader(std::string key, std::string value);
ServerContext& WithMimeType(std::string mime);
ServerContext& WithContentDisposition(std::string filename, bool isInline);
ServerContext& WriteHeaders();
};
class IHttpServer {
public:
virtual bool Handle(ServerContext& ctx)=0;
virtual ~IHttpServer();
};
class HttpServer {
Tesses::Framework::Streams::TcpServer* server;
IHttpServer* http;
Tesses::Framework::Threading::Thread* thrd;
bool owns;
uint16_t port;
public:
HttpServer(uint16_t port, IHttpServer& http);
HttpServer(uint16_t port, IHttpServer* http, bool owns);
void StartAccepting();
static void Process(Tesses::Framework::Streams::Stream& strm, IHttpServer& server, std::string ip, uint16_t port, bool encrypted);
~HttpServer();
};
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "../Streams/Stream.hpp"
namespace Tesses::Framework::Http
{
class HttpStream : public Tesses::Framework::Streams::Stream {
Tesses::Framework::Streams::Stream* strm;
size_t offset;
size_t read;
int64_t length;
int64_t position;
bool owns;
bool recv;
bool http1_1;
bool done;
public:
HttpStream(Tesses::Framework::Streams::Stream* strm, bool owns, int64_t length, bool recv, bool http1_1);
HttpStream(Tesses::Framework::Streams::Stream& strm, int64_t length, bool recv,bool http1_1);
bool CanRead();
bool CanWrite();
bool EndOfStream();
int64_t GetLength();
int64_t GetPosition();
size_t Read(uint8_t* buffer, size_t len);
size_t Write(const uint8_t* buffer, size_t len);
~HttpStream();
};
}

View File

@ -0,0 +1,144 @@
#pragma once
#include "../Common.hpp"
namespace Tesses::Framework::Http
{
typedef enum StatusCode {
Continue=100,
SwitchingProtocols=101,
Processing=102,
EarlyHints=103,
OK=200,
Created=201,
Accepted=202,
NonAuthoritativeInformation=203,
NoContent=204,
ResetContent=205,
PartialContent=206,
MultiStatus=207,
AlreadyReported=208,
IMUsed=226,
MultipleChoices=300,
MovedPermanently=301,
Found=302,
SeeOther=303,
NotModified=304,
UseProxy=305,
TemporaryRedirect=307,
PermanentRedirect=308,
BadRequest=400,
Unauthorized=401,
PaymentRequired=402,
Forbidden=403,
NotFound=404,
MethodNotAllowed=405,
NotAcceptable=406,
ProxyAuthenticationRequired=407,
RequestTimeout=408,
Conflict=409,
Gone=410,
LengthRequired=411,
PreconditionFailed=412,
PayloadTooLarge=413,
URITooLong=414,
UnsupportedMediaType=415,
RangeNotSatisfiable=416,
ExpectationFailed=417,
ImATeapot=418,
MisdirectedRequest=421,
UnprocessableContent=422,
Locked=423,
FailedDependency=424,
TooEarly=425,
UpgradeRequired=426,
PreconditionRequired=428,
TooManyRequests=429,
RequestHeaderFieldsTooLarge=431,
UnavailableForLegalReasons=451,
InternalServerError=500,
NotImplemented=501,
BadGateway=502,
ServiceUnavailable=503,
GatewayTimeout=504,
HTTPVersionNotSupported=505,
VariantAlsoNegotiates=506,
InsufficientStorage=507,
LoopDetected=508,
NotExtended=510,
NetworkAuthenticationRequired=511
} StatusCode;
class HttpDictionary {
public:
std::map<std::string,std::vector<std::string>> kvp;
void Clear();
void Clear(std::string key, bool kvpExistsAfter);
void SetValue(std::string key, std::string value);
void SetValue(std::string key, std::vector<std::string> value);
template<typename Itterator>
void SetValue(std::string key, Itterator begin, Itterator end)
{
Clear(key,true);
AddValue(key, begin, end);
}
void AddValue(std::string key, std::string value);
void AddValue(std::string key, std::vector<std::string> value);
template<typename Itterator>
void AddValue(std::string key, Itterator begin, Itterator end)
{
auto& ls = kvp[key];
ls.insert(ls.end(), begin, end);
}
bool TryGetFirst(std::string key, std::string& value);
bool TryGetFirstInt(std::string key, int64_t& value);
bool TryGetFirstDouble(std::string key, double& value);
bool GetFirstBoolean(std::string key);
};
class Uri {
public:
std::string GetQuery();
std::string GetPathAndQuery();
uint16_t GetPort();
std::string HostPort();
bool Relative(std::string url, Uri& uri);
std::string ToString();
static bool TryParse(std::string url, Uri& uri);
std::string scheme;
std::string host;
uint16_t port;
std::string path;
HttpDictionary query;
std::string hash;
};
class HttpUtils
{
public:
static char NibbleToHex(uint8_t nibble);
static uint8_t HexToNibble(char c);
static std::string MimeType(std::filesystem::path p);
static bool Invalid(char c);
static std::string Sanitise(std::string text);
static void QueryParamsDecode(HttpDictionary& dict,std::string query);
static std::string Join(std::string joinStr, std::vector<std::string> ents);
static std::string QueryParamsEncode(HttpDictionary& dict);
static std::string UrlDecode(std::string v);
static std::string UrlEncode(std::string v);
static std::string UrlPathDecode(std::string v);
static std::string UrlPathEncode(std::string v, bool ignoreSpace=false);
static std::string HtmlEncode(std::string v);
static std::vector<std::string> SplitString(std::string text, std::string delimiter,std::size_t maxCnt = std::string::npos);
static std::string StatusCodeString(StatusCode code);
};
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "Stream.hpp"
namespace Tesses::Framework::Streams
{
class BufferedStream : public Stream
{
private:
Stream* strm;
bool owns;
uint8_t* buffer;
size_t bufferSize;
size_t offset;
size_t read;
public:
BufferedStream(Stream* strm, bool owns, size_t bufferSize=1024);
BufferedStream(Stream& strm, size_t bufferSize=1024);
bool EndOfStream();
bool CanRead();
bool CanWrite();
size_t Read(uint8_t* buff, size_t sz);
size_t Write(const uint8_t* buff, size_t sz);
~BufferedStream();
};
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "Stream.hpp"
#include <cstdio>
namespace Tesses::Framework::Streams
{
class FileStream : public Stream {
bool canRead;
bool canWrite;
bool canSeek;
bool owns;
FILE* f;
void SetMode(std::string mode);
public:
FileStream(std::filesystem::path p, std::string mode);
FileStream(FILE* f, bool owns, std::string mode , bool canSeek=true);
size_t Read(uint8_t* buff, size_t sz);
size_t Write(const uint8_t* buff, size_t sz);
bool CanRead();
bool CanWrite();
bool CanSeek();
int64_t GetPosition();
void Flush();
void Seek(int64_t pos, SeekOrigin whence);
~FileStream();
};
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "Stream.hpp"
namespace Tesses::Framework::Streams
{
class MemoryStream : public Stream {
std::vector<uint8_t> buffer;
size_t offset;
bool writable;
public:
MemoryStream(bool isWritable);
std::vector<uint8_t>& GetBuffer();
size_t Read(uint8_t* buff, size_t sz);
size_t Write(const uint8_t* buff, size_t sz);
bool CanRead();
bool CanWrite();
bool CanSeek();
int64_t GetLength();
int64_t GetPosition();
void Seek(int64_t pos, SeekOrigin whence);
};
}

View File

@ -0,0 +1,43 @@
#pragma once
#include "Stream.hpp"
namespace Tesses::Framework::Streams
{
class NetworkStream;
class TcpServer {
int32_t sock;
bool owns;
bool valid;
public:
TcpServer(int32_t sock,bool owns);
TcpServer(uint16_t port, int32_t backlog);
TcpServer(std::string ip, uint16_t port, int32_t backlog);
NetworkStream* GetStream(std::string& ip, uint16_t& port);
~TcpServer();
void Close();
};
class NetworkStream : public Stream {
int32_t sock;
bool owns;
bool success;
bool endOfStream;
public:
bool EndOfStream();
bool CanRead();
bool CanWrite();
NetworkStream(bool ipV6,bool datagram);
NetworkStream(std::string ipOrFqdn, uint16_t port, bool datagram,bool broadcast,bool supportIPv6);
NetworkStream(int32_t sock, bool owns);
void Listen(int32_t backlog);
void Bind(std::string ip, uint16_t port);
void SetBroadcast(bool bC);
NetworkStream* Accept(std::string& ip, uint16_t& port);
size_t Read(uint8_t* buff, size_t sz);
size_t Write(const uint8_t* buff, size_t sz);
size_t ReadFrom(uint8_t* buff, size_t sz, std::string& ip, uint16_t& port);
size_t WriteTo(const uint8_t* buff, size_t sz, std::string ip, uint16_t port);
static std::vector<std::pair<std::string,std::string>> GetIPs(bool ipV6=false);
~NetworkStream();
};
}

View File

@ -0,0 +1,31 @@
#pragma once
#include "../Common.hpp"
namespace Tesses::Framework::Streams
{
enum class SeekOrigin : uint8_t {
Begin=0,
Current=1,
End=2
};
class Stream {
public:
int32_t ReadByte();
void WriteByte(uint8_t b);
virtual bool EndOfStream();
virtual size_t Read(uint8_t* buff, size_t sz);
virtual size_t Write(const uint8_t* buff, size_t sz);
size_t ReadBlock(uint8_t* buff, size_t sz);
void WriteBlock(const uint8_t* buff, size_t sz);
virtual bool CanRead();
virtual bool CanWrite();
virtual bool CanSeek();
virtual int64_t GetPosition();
virtual int64_t GetLength();
virtual void Flush();
virtual void Seek(int64_t pos, SeekOrigin whence);
void CopyTo(Stream* strm, size_t buffSize=1024);
void CopyTo(Stream& strm, size_t buffSize=1024);
virtual ~Stream();
};
}

View File

@ -0,0 +1,17 @@
#pragma once
#include "Http/HttpServer.hpp"
#include "Http/HttpClient.hpp"
#include "Http/FileServer.hpp"
#include "Http/ContentDisposition.hpp"
#include "Streams/FileStream.hpp"
#include "Streams/MemoryStream.hpp"
#include "Streams/NetworkStream.hpp"
#include "Streams/BufferedStream.hpp"
#include "TextStreams/StreamReader.hpp"
#include "TextStreams/StreamWriter.hpp"
#include "Threading/Thread.hpp"
#include "Filesystem/LocalFS.hpp"
#include "Filesystem/SubdirFilesystem.hpp"
#include "Filesystem/NullFilesystem.hpp"
#include "Filesystem/MountableFilesystem.hpp"
#include "Crypto/ClientTLSStream.hpp"

View File

@ -0,0 +1,19 @@
#pragma once
#include "TextReader.hpp"
#include "../Streams/Stream.hpp"
namespace Tesses::Framework::TextStreams
{
class StreamReader : public TextReader
{
private:
Tesses::Framework::Streams::Stream* strm;
bool owns;
public:
Tesses::Framework::Streams::Stream& GetStream();
StreamReader(Tesses::Framework::Streams::Stream& strm);
StreamReader(Tesses::Framework::Streams::Stream* strm, bool owns);
StreamReader(std::filesystem::path filename);
bool ReadBlock(std::string& str,size_t sz);
~StreamReader();
};
}

View File

@ -0,0 +1,19 @@
#pragma once
#include "../Streams/Stream.hpp"
#include "TextWriter.hpp"
namespace Tesses::Framework::TextStreams
{
class StreamWriter : public TextWriter {
private:
Tesses::Framework::Streams::Stream* strm;
bool owns;
public:
Tesses::Framework::Streams::Stream& GetStream();
StreamWriter(Tesses::Framework::Streams::Stream& strm);
StreamWriter(Tesses::Framework::Streams::Stream* strm, bool owns);
StreamWriter(std::filesystem::path filename, bool append=false);
void WriteData(const char* text, size_t len);
~StreamWriter();
};
}

View File

@ -0,0 +1,18 @@
#pragma once
#include "../Common.hpp"
#include "TextWriter.hpp"
namespace Tesses::Framework::TextStreams
{
class TextReader
{
public:
virtual bool ReadBlock(std::string& str,size_t sz)=0;
int32_t ReadChar();
std::string ReadLine();
bool ReadLine(std::string& str);
std::string ReadToEnd();
void ReadToEnd(std::string& str);
void CopyTo(TextWriter& writer, size_t bufSz=1024);
virtual ~TextReader();
};
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "../Common.hpp"
namespace Tesses::Framework::TextStreams
{
class TextWriter {
public:
TextWriter();
std::string newline;
virtual void WriteData(const char* text, size_t len)=0;
void Write(std::string txt);
void WriteLine(std::string txt);
void WriteLine();
virtual ~TextWriter();
};
}

View File

@ -0,0 +1,22 @@
#pragma once
#if defined(GEKKO)
#include <ogc/mutex.h>
#else
#include <threads.h>
#endif
namespace Tesses::Framework::Threading
{
class Mutex {
#if defined(GEKKO)
mutex_t mtx;
#else
mtx_t mtx;
#endif
public:
Mutex();
void Lock();
void Unlock();
bool TryLock();
~Mutex();
};
}

View File

@ -0,0 +1,27 @@
#pragma once
#include <functional>
#if defined(GEKKO)
#include <ogc/lwp.h>
#else
#include <threads.h>
#endif
#include <atomic>
namespace Tesses::Framework::Threading
{
class Thread
{
std::atomic<bool> hasInvoked;
#if defined(GEKKO)
lwp_t thrd;
static void* cb(void* ptr);
#else
thrd_t thrd;
static int cb(void* ptr);
#endif
std::function<void()> fn;
public:
Thread(std::function<void()> fn);
void Join();
void Detach();
};
}

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