commit 856373b396c52addc58821126ab77eb07be63bbb Author: Mike Nolan Date: Fri Dec 6 04:58:55 2024 -0600 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1899660 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +.vscode \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..eedcb0e --- /dev/null +++ b/CMakeLists.txt @@ -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 + "$" + "$" +) +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() \ No newline at end of file diff --git a/Config.cmake.in b/Config.cmake.in new file mode 100644 index 0000000..1836374 --- /dev/null +++ b/Config.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/TessesFrameworkTargets.cmake") + +check_required_components(TessesFramework) diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..04b87d5 --- /dev/null +++ b/LICENSE.md @@ -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. \ No newline at end of file diff --git a/OTHERS_CODE.md b/OTHERS_CODE.md new file mode 100644 index 0000000..9d2d272 --- /dev/null +++ b/OTHERS_CODE.md @@ -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) \ No newline at end of file diff --git a/cmake/bin2h.cmake b/cmake/bin2h.cmake new file mode 100644 index 0000000..c7a99e3 --- /dev/null +++ b/cmake/bin2h.cmake @@ -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() \ No newline at end of file diff --git a/examples/copyfile.cpp b/examples/copyfile.cpp new file mode 100644 index 0000000..96ed7c8 --- /dev/null +++ b/examples/copyfile.cpp @@ -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; +} \ No newline at end of file diff --git a/examples/download.cpp b/examples/download.cpp new file mode 100644 index 0000000..9c27bb0 --- /dev/null +++ b/examples/download.cpp @@ -0,0 +1,22 @@ +#include "TessesFramework/TessesFramework.hpp" +#include +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 \n",argv[0]); + return 1; + } + FileStream strm(argv[2],"wb"); + + HttpRequest req; + req.url = argv[1]; + HttpResponse resp(req); + resp.CopyToStream(&strm); + + + return 0; +} \ No newline at end of file diff --git a/examples/fileserver.cpp b/examples/fileserver.cpp new file mode 100644 index 0000000..5c42c39 --- /dev/null +++ b/examples/fileserver.cpp @@ -0,0 +1,23 @@ +#include "TessesFramework/TessesFramework.hpp" +#include +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 \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; +} \ No newline at end of file diff --git a/examples/mountabletest.cpp b/examples/mountabletest.cpp new file mode 100644 index 0000000..b5428c3 --- /dev/null +++ b/examples/mountabletest.cpp @@ -0,0 +1,63 @@ +#include "TessesFramework/TessesFramework.hpp" +#include + +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 \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 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; + } + } + } + +} \ No newline at end of file diff --git a/examples/safesubpath.cpp b/examples/safesubpath.cpp new file mode 100644 index 0000000..9b74be2 --- /dev/null +++ b/examples/safesubpath.cpp @@ -0,0 +1,19 @@ +#include "TessesFramework/TessesFramework.hpp" +#include + +using namespace Tesses::Framework::Filesystem; + +int main(int argc, char** argv) +{ + if(argc < 3) + { + std::cout << "USAGE: " << argv[0] << " \n"; + return 1; + } + + VFSPath parent(argv[1]); + VFSPath subpath(argv[2]); + VFSPath newPath(parent,subpath.CollapseRelativeParents()); + + std::cout << newPath.ToString() << "\n"; +} \ No newline at end of file diff --git a/examples/webserverex.cpp b/examples/webserverex.cpp new file mode 100644 index 0000000..fa04880 --- /dev/null +++ b/examples/webserverex.cpp @@ -0,0 +1,100 @@ +#include "TessesFramework/TessesFramework.hpp" +#include +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(""); + writer.WriteLine("Streaming"); + writer.WriteLine(""); + + writer.WriteLine("

Streaming

"); + + writer.WriteLine("
    "); + + for(size_t i=0;i<10000; i++) + { + writer.WriteLine("
  • " + std::to_string(i) + "
  • "); + + } + + writer.WriteLine("
"); + + writer.WriteLine(""); + writer.WriteLine(""); + 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(); + } + +} \ No newline at end of file diff --git a/include/TessesFramework/Common.hpp b/include/TessesFramework/Common.hpp new file mode 100644 index 0000000..e3342b7 --- /dev/null +++ b/include/TessesFramework/Common.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Tesses::Framework +{ + void TF_Init(); + void TF_ConnectToSelf(uint16_t port); + void TF_RunEventLoop(); + void TF_RunEventLoopItteration(); + bool TF_IsRunning(); + void TF_Quit(); +} \ No newline at end of file diff --git a/include/TessesFramework/Crypto/ClientTLSStream.hpp b/include/TessesFramework/Crypto/ClientTLSStream.hpp new file mode 100644 index 0000000..becbd22 --- /dev/null +++ b/include/TessesFramework/Crypto/ClientTLSStream.hpp @@ -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(); + }; + +} \ No newline at end of file diff --git a/include/TessesFramework/Filesystem/LocalFS.hpp b/include/TessesFramework/Filesystem/LocalFS.hpp new file mode 100644 index 0000000..c28d6e5 --- /dev/null +++ b/include/TessesFramework/Filesystem/LocalFS.hpp @@ -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& 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); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Filesystem/MountableFilesystem.hpp b/include/TessesFramework/Filesystem/MountableFilesystem.hpp new file mode 100644 index 0000000..15a3429 --- /dev/null +++ b/include/TessesFramework/Filesystem/MountableFilesystem.hpp @@ -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 dirs; + void GetFS(VFSPath srcPath, VFSPath curDir, VFSPath& destRoot, VFSPath& destPath, VFS*& vfs); + ~MountableDirectory(); + }; + + class MountableFilesystem : public VFS + { + bool owns; + VFS* root; + + std::vector 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& 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(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Filesystem/NullFilesystem.hpp b/include/TessesFramework/Filesystem/NullFilesystem.hpp new file mode 100644 index 0000000..e8da42a --- /dev/null +++ b/include/TessesFramework/Filesystem/NullFilesystem.hpp @@ -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& paths); + void MoveFile(VFSPath src, VFSPath dest); + std::string VFSPathToSystem(VFSPath path); + VFSPath SystemToVFSPath(std::string path); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Filesystem/SubdirFilesystem.hpp b/include/TessesFramework/Filesystem/SubdirFilesystem.hpp new file mode 100644 index 0000000..3d341bf --- /dev/null +++ b/include/TessesFramework/Filesystem/SubdirFilesystem.hpp @@ -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& 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(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Filesystem/VFS.hpp b/include/TessesFramework/Filesystem/VFS.hpp new file mode 100644 index 0000000..36eb76d --- /dev/null +++ b/include/TessesFramework/Filesystem/VFS.hpp @@ -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 SplitPath(std::string path,bool native=false); + std::vector path; + VFSPath(); + VFSPath(std::vector 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& 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(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Http/ContentDisposition.hpp b/include/TessesFramework/Http/ContentDisposition.hpp new file mode 100644 index 0000000..638c71d --- /dev/null +++ b/include/TessesFramework/Http/ContentDisposition.hpp @@ -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(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Http/FileServer.hpp b/include/TessesFramework/Http/FileServer.hpp new file mode 100644 index 0000000..9f96e16 --- /dev/null +++ b/include/TessesFramework/Http/FileServer.hpp @@ -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 defaultNames; + FileServer(std::filesystem::path path,bool allowListing,bool spa); + FileServer(std::filesystem::path path,bool allowListing, bool spa, std::vector 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 defaultNames); + bool Handle(ServerContext& ctx); + ~FileServer(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Http/HttpClient.hpp b/include/TessesFramework/Http/HttpClient.hpp new file mode 100644 index 0000000..6291c0c --- /dev/null +++ b/include/TessesFramework/Http/HttpClient.hpp @@ -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(); + }; + + +} \ No newline at end of file diff --git a/include/TessesFramework/Http/HttpServer.hpp b/include/TessesFramework/Http/HttpServer.hpp new file mode 100644 index 0000000..ffb8500 --- /dev/null +++ b/include/TessesFramework/Http/HttpServer.hpp @@ -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 cb); + void ReadStream(Tesses::Framework::Streams::Stream& strm); + void ReadStream(Tesses::Framework::Streams::Stream* strm); + std::string ReadString(); + void SendBytes(std::vector 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(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Http/HttpStream.hpp b/include/TessesFramework/Http/HttpStream.hpp new file mode 100644 index 0000000..a94bd12 --- /dev/null +++ b/include/TessesFramework/Http/HttpStream.hpp @@ -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(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Http/HttpUtils.hpp b/include/TessesFramework/Http/HttpUtils.hpp new file mode 100644 index 0000000..8cc2b5a --- /dev/null +++ b/include/TessesFramework/Http/HttpUtils.hpp @@ -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> 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 value); + template + 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 value); + + template + 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 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 SplitString(std::string text, std::string delimiter,std::size_t maxCnt = std::string::npos); + static std::string StatusCodeString(StatusCode code); + + }; + + + + + +} \ No newline at end of file diff --git a/include/TessesFramework/Streams/BufferedStream.hpp b/include/TessesFramework/Streams/BufferedStream.hpp new file mode 100644 index 0000000..2e35069 --- /dev/null +++ b/include/TessesFramework/Streams/BufferedStream.hpp @@ -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(); + }; + +} \ No newline at end of file diff --git a/include/TessesFramework/Streams/FileStream.hpp b/include/TessesFramework/Streams/FileStream.hpp new file mode 100644 index 0000000..330acab --- /dev/null +++ b/include/TessesFramework/Streams/FileStream.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "Stream.hpp" +#include +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(); + + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Streams/MemoryStream.hpp b/include/TessesFramework/Streams/MemoryStream.hpp new file mode 100644 index 0000000..8cc8199 --- /dev/null +++ b/include/TessesFramework/Streams/MemoryStream.hpp @@ -0,0 +1,22 @@ +#pragma once +#include "Stream.hpp" +namespace Tesses::Framework::Streams +{ + class MemoryStream : public Stream { + std::vector buffer; + size_t offset; + bool writable; + public: + MemoryStream(bool isWritable); + std::vector& 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); + + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Streams/NetworkStream.hpp b/include/TessesFramework/Streams/NetworkStream.hpp new file mode 100644 index 0000000..49045d0 --- /dev/null +++ b/include/TessesFramework/Streams/NetworkStream.hpp @@ -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> GetIPs(bool ipV6=false); + ~NetworkStream(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Streams/Stream.hpp b/include/TessesFramework/Streams/Stream.hpp new file mode 100644 index 0000000..4ac6944 --- /dev/null +++ b/include/TessesFramework/Streams/Stream.hpp @@ -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(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/TessesFramework.hpp b/include/TessesFramework/TessesFramework.hpp new file mode 100644 index 0000000..b8937b9 --- /dev/null +++ b/include/TessesFramework/TessesFramework.hpp @@ -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" \ No newline at end of file diff --git a/include/TessesFramework/TextStreams/StreamReader.hpp b/include/TessesFramework/TextStreams/StreamReader.hpp new file mode 100644 index 0000000..bc97ee6 --- /dev/null +++ b/include/TessesFramework/TextStreams/StreamReader.hpp @@ -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(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/TextStreams/StreamWriter.hpp b/include/TessesFramework/TextStreams/StreamWriter.hpp new file mode 100644 index 0000000..2fc5d9d --- /dev/null +++ b/include/TessesFramework/TextStreams/StreamWriter.hpp @@ -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(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/TextStreams/TextReader.hpp b/include/TessesFramework/TextStreams/TextReader.hpp new file mode 100644 index 0000000..521cbfd --- /dev/null +++ b/include/TessesFramework/TextStreams/TextReader.hpp @@ -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(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/TextStreams/TextWriter.hpp b/include/TessesFramework/TextStreams/TextWriter.hpp new file mode 100644 index 0000000..50bdc82 --- /dev/null +++ b/include/TessesFramework/TextStreams/TextWriter.hpp @@ -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(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Threading/Mutex.hpp b/include/TessesFramework/Threading/Mutex.hpp new file mode 100644 index 0000000..abfaab9 --- /dev/null +++ b/include/TessesFramework/Threading/Mutex.hpp @@ -0,0 +1,22 @@ +#pragma once +#if defined(GEKKO) +#include +#else +#include +#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(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Threading/Thread.hpp b/include/TessesFramework/Threading/Thread.hpp new file mode 100644 index 0000000..f1218b8 --- /dev/null +++ b/include/TessesFramework/Threading/Thread.hpp @@ -0,0 +1,27 @@ +#pragma once +#include +#if defined(GEKKO) +#include +#else +#include +#endif +#include +namespace Tesses::Framework::Threading +{ + class Thread + { + std::atomic hasInvoked; + #if defined(GEKKO) + lwp_t thrd; + static void* cb(void* ptr); + #else + thrd_t thrd; + static int cb(void* ptr); + #endif + std::function fn; + public: + Thread(std::function fn); + void Join(); + void Detach(); + }; +} \ No newline at end of file diff --git a/src/Crypto/ClientTLSStream.cpp b/src/Crypto/ClientTLSStream.cpp new file mode 100644 index 0000000..2e13cc7 --- /dev/null +++ b/src/Crypto/ClientTLSStream.cpp @@ -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 +#include +#include +#include +#include +#include +#endif +#include +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(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(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(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(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(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(ctx); + return (int)priv->strm->Read(buf, len); + #else + return 0; + #endif + } + bool ClientTLSStream::CanRead() + { + #if defined(TESSESFRAMEWORK_ENABLE_MBED) + return !(!static_cast(this->privateData)->success || static_cast(this->privateData)->eos); + #else + return false; + #endif + } + bool ClientTLSStream::CanWrite() + { + + #if defined(TESSESFRAMEWORK_ENABLE_MBED) + return !(!static_cast(this->privateData)->success || static_cast(this->privateData)->eos); + #else + return false; + #endif + } + bool ClientTLSStream::EndOfStream() + { + #if defined(TESSESFRAMEWORK_ENABLE_MBED) + return !static_cast(this->privateData)->success || static_cast(this->privateData)->eos; + #else + return true; + #endif + } + ClientTLSStream::~ClientTLSStream() + { + #if defined(TESSESFRAMEWORK_ENABLE_MBED) + delete static_cast(this->privateData); + #endif + } +} \ No newline at end of file diff --git a/src/Filesystem/LocalFS.cpp b/src/Filesystem/LocalFS.cpp new file mode 100644 index 0000000..42e7807 --- /dev/null +++ b/src/Filesystem/LocalFS.cpp @@ -0,0 +1,122 @@ +#include "TessesFramework/Filesystem/LocalFS.hpp" +#include "TessesFramework/Streams/FileStream.hpp" +#include +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& paths) + { + for(auto item : std::filesystem::directory_iterator(VFSPathToSystem(path))) + { + paths.push_back(VFSPath(path, item.path().filename().string())); + } + } +} + +// C:/Users/Jim/Joel \ No newline at end of file diff --git a/src/Filesystem/MountableFilesystem.cpp b/src/Filesystem/MountableFilesystem.cpp new file mode 100644 index 0000000..eaa9f64 --- /dev/null +++ b/src/Filesystem/MountableFilesystem.cpp @@ -0,0 +1,553 @@ +#include "TessesFramework/Filesystem/MountableFilesystem.hpp" +#include "TessesFramework/Filesystem/NullFilesystem.hpp" +#include +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& paths) + { + path = path.CollapseRelativeParents(); + bool mydirs = path.path.empty(); + std::vector* 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 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); + } +} \ No newline at end of file diff --git a/src/Filesystem/NullFilesystem.cpp b/src/Filesystem/NullFilesystem.cpp new file mode 100644 index 0000000..b556f0b --- /dev/null +++ b/src/Filesystem/NullFilesystem.cpp @@ -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& 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); + } +} \ No newline at end of file diff --git a/src/Filesystem/SubdirFilesystem.cpp b/src/Filesystem/SubdirFilesystem.cpp new file mode 100644 index 0000000..f1f52b4 --- /dev/null +++ b/src/Filesystem/SubdirFilesystem.cpp @@ -0,0 +1,132 @@ +#include "TessesFramework/Filesystem/SubdirFilesystem.hpp" +#include +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& paths) + { + std::vector 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; + } +} \ No newline at end of file diff --git a/src/Filesystem/VFS.cpp b/src/Filesystem/VFS.cpp new file mode 100644 index 0000000..7ac5f10 --- /dev/null +++ b/src/Filesystem/VFS.cpp @@ -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 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 VFSPath::SplitPath(std::string path,bool nativePath) + { + std::vector 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 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 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 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 paths; + GetPaths(path, paths); + + for(auto item : paths) + { + if(DirectoryExists(item)) + { + DeleteDirectoryRecurse(item); + } + else + { + DeleteFile(item); + } + } + DeleteDirectory(path); + } +} \ No newline at end of file diff --git a/src/Http/ContentDisposition.cpp b/src/Http/ContentDisposition.cpp new file mode 100644 index 0000000..f70f547 --- /dev/null +++ b/src/Http/ContentDisposition.cpp @@ -0,0 +1,77 @@ +#include "TessesFramework/Http/ContentDisposition.hpp" +#include "TessesFramework/Http/HttpUtils.hpp" +#include +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 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); + } +} \ No newline at end of file diff --git a/src/Http/FileServer.cpp b/src/Http/FileServer.cpp new file mode 100644 index 0000000..a675031 --- /dev/null +++ b/src/Http/FileServer.cpp @@ -0,0 +1,131 @@ +#include "TessesFramework/Http/FileServer.hpp" +#include "TessesFramework/Filesystem/LocalFS.hpp" +#include "TessesFramework/Filesystem/SubdirFilesystem.hpp" +#include +#include +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 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 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 = "Index of "; + html.append(p); + html.append("

Index of "); + html.append(p); + html.append("


../\r\n");
+                std::vector ents;
+                vfs->GetPaths(path, ents);
+
+                for(auto item : ents)
+                {
+                    if(vfs->DirectoryExists(item))
+                    {
+                        //is dir
+                        std::string path = item.GetFileName();
+                        html.append("");
+                        html.append(HttpUtils::HtmlEncode(path) + "/");
+                        html.append("\r\n");
+                    }
+                    else
+                    {
+                        //is file
+                        std::string path = item.GetFileName();
+                        html.append("");
+                        html.append(HttpUtils::HtmlEncode(path));
+                        html.append("\r\n");
+                    }
+                }
+
+                html.append("

"); + + 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; + } +} \ No newline at end of file diff --git a/src/Http/HttpClient.cpp b/src/Http/HttpClient.cpp new file mode 100644 index 0000000..a40121e --- /dev/null +++ b/src/Http/HttpClient.cpp @@ -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 +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"); + } +} \ No newline at end of file diff --git a/src/Http/HttpServer.cpp b/src/Http/HttpServer.cpp new file mode 100644 index 0000000..0bb4c37 --- /dev/null +++ b/src/Http/HttpServer.cpp @@ -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 +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(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(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(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(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(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 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 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 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 ? ("File " + HttpUtils::HtmlEncode(this->originalPath) + " " + HttpUtils::StatusCodeString(this->statusCode) + "

" + std::to_string((int)this->statusCode) + " " + HttpUtils::StatusCodeString(this->statusCode) + "

" + HttpUtils::HtmlEncode(this->originalPath) + "

") : ""; + + 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) + { + /* + + + + + Internal Server Error at / + + +

Internal Server Error at /

+

what(): std::exception

+ +*/ + this->WithMimeType("text/html").SendText(" Internal Server Error at " + HttpUtils::HtmlEncode(this->originalPath) + "

Internal Server Error at " + HttpUtils::HtmlEncode(this->originalPath) + "

what(): " + HttpUtils::HtmlEncode(ex.what()) + "

"); + } + 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; + } + } +} \ No newline at end of file diff --git a/src/Http/HttpStream.cpp b/src/Http/HttpStream.cpp new file mode 100644 index 0000000..35c58e3 --- /dev/null +++ b/src/Http/HttpStream.cpp @@ -0,0 +1,166 @@ +#include "TessesFramework/Http/HttpStream.hpp" +#include "TessesFramework/TextStreams/StreamWriter.hpp" +#include "TessesFramework/TextStreams/StreamReader.hpp" +#include +#include +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; + } +} \ No newline at end of file diff --git a/src/Http/HttpUtils.cpp b/src/Http/HttpUtils.cpp new file mode 100644 index 0000000..6148388 --- /dev/null +++ b/src/Http/HttpUtils.cpp @@ -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 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 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= '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= '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 HttpUtils::SplitString(std::string text, std::string delimiter,std::size_t maxCnt) + { + std::vector 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("""); + } + else if(item == '\'') + { + myHtml.append("'"); + } + else if(item == '&') + { + myHtml.append("&"); + } + else if(item == '<') + { + myHtml.append("<"); + } + else if(item == '>') + { + myHtml.append(">"); + } + 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 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 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"; + } + +} \ No newline at end of file diff --git a/src/Streams/BufferedStream.cpp b/src/Streams/BufferedStream.cpp new file mode 100644 index 0000000..0d1c356 --- /dev/null +++ b/src/Streams/BufferedStream.cpp @@ -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; + } +} \ No newline at end of file diff --git a/src/Streams/FileStream.cpp b/src/Streams/FileStream.cpp new file mode 100644 index 0000000..ca52b74 --- /dev/null +++ b/src/Streams/FileStream.cpp @@ -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); + } +} \ No newline at end of file diff --git a/src/Streams/MemoryStream.cpp b/src/Streams/MemoryStream.cpp new file mode 100644 index 0000000..de613ac --- /dev/null +++ b/src/Streams/MemoryStream.cpp @@ -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& 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; + } + } +} \ No newline at end of file diff --git a/src/Streams/NetworkStream.cpp b/src/Streams/NetworkStream.cpp new file mode 100644 index 0000000..1a24b17 --- /dev/null +++ b/src/Streams/NetworkStream.cpp @@ -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 +#define NETWORK_RECV net_recv +#define sockaddr_storage sockaddr_in +#error "Not supported yet" +#else + + + +extern "C" { +#include +#include +#include +#include +#include +#include +} +#if defined(GEKKO) + +extern "C" uint32_t if_config( char *local_ip, char *netmask, char *gateway,bool use_dhcp, int max_retries); +#else +#include +#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> NetworkStream::GetIPs(bool ipV6) + { + std::vector> 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("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(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(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); + } +} \ No newline at end of file diff --git a/src/Streams/Stream.cpp b/src/Streams/Stream.cpp new file mode 100644 index 0000000..b0de9cf --- /dev/null +++ b/src/Streams/Stream.cpp @@ -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() + { + + } +} \ No newline at end of file diff --git a/src/TF_Init.cpp b/src/TF_Init.cpp new file mode 100644 index 0000000..74b1659 --- /dev/null +++ b/src/TF_Init.cpp @@ -0,0 +1,114 @@ +#include "TessesFramework/Common.hpp" +#include "TessesFramework/Streams/NetworkStream.hpp" +#include +#include +#if defined(GEKKO) +#include +#include +#include +#include +#include + + +#if defined(HW_RVL) +#if defined(TESSESFRAMEWORK_USE_WII_SOCKET) +#include +#endif +#include +#endif + +static void *xfb = NULL; +static GXRModeObj *rmode = NULL; + +#endif + +namespace Tesses::Framework +{ + volatile static bool isRunningSig=true; + volatile static std::atomic 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 + } +} \ No newline at end of file diff --git a/src/TextStreams/StreamReader.cpp b/src/TextStreams/StreamReader.cpp new file mode 100644 index 0000000..09495b5 --- /dev/null +++ b/src/TextStreams/StreamReader.cpp @@ -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; + } +}; \ No newline at end of file diff --git a/src/TextStreams/StreamWriter.cpp b/src/TextStreams/StreamWriter.cpp new file mode 100644 index 0000000..990f033 --- /dev/null +++ b/src/TextStreams/StreamWriter.cpp @@ -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; + } +} \ No newline at end of file diff --git a/src/TextStreams/TextReader.cpp b/src/TextStreams/TextReader.cpp new file mode 100644 index 0000000..8546023 --- /dev/null +++ b/src/TextStreams/TextReader.cpp @@ -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() + { + + } +} \ No newline at end of file diff --git a/src/TextStreams/TextWriter.cpp b/src/TextStreams/TextWriter.cpp new file mode 100644 index 0000000..d27b335 --- /dev/null +++ b/src/TextStreams/TextWriter.cpp @@ -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() + { + + } +} \ No newline at end of file diff --git a/src/Threading/Mutex.cpp b/src/Threading/Mutex.cpp new file mode 100644 index 0000000..b53d804 --- /dev/null +++ b/src/Threading/Mutex.cpp @@ -0,0 +1,49 @@ +#include "TessesFramework/Threading/Mutex.hpp" +#include +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 + + } +}; \ No newline at end of file diff --git a/src/Threading/Thread.cpp b/src/Threading/Thread.cpp new file mode 100644 index 0000000..30f6438 --- /dev/null +++ b/src/Threading/Thread.cpp @@ -0,0 +1,53 @@ +#include "TessesFramework/Threading/Thread.hpp" +#include +namespace Tesses::Framework::Threading +{ + #if defined(GEKKO) + void* Thread::cb(void* data) + #else + int Thread::cb(void* data) + #endif + { + auto thrd = static_cast(data); + + auto fn = thrd->fn; + thrd->hasInvoked=true; + fn(); + #if defined(GEKKO) + return NULL; + #else + return 0; + #endif + } + Thread::Thread(std::function fn) + { + this->hasInvoked=false; + this->fn = fn; + + #if defined(GEKKO) + thrd = LWP_THREAD_NULL; + LWP_CreateThread(&thrd, cb, static_cast(this), NULL, 12000, LWP_PRIO_HIGHEST); + #else + thrd_create(&thrd, cb, static_cast(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 + + } +} \ No newline at end of file