first commit
This commit is contained in:
		
						commit
						856373b396
					
				|  | @ -0,0 +1,2 @@ | |||
| build | ||||
| .vscode | ||||
|  | @ -0,0 +1,149 @@ | |||
| cmake_minimum_required(VERSION 3.20) | ||||
| 
 | ||||
| project(TessesFramework VERSION 1.0) | ||||
| 
 | ||||
| set(CMAKE_CXX_STANDARD 20) | ||||
| 
 | ||||
| list(APPEND TESSESFRAMEWORK_SOURCE  | ||||
| src/Http/FileServer.cpp | ||||
| src/Http/HttpServer.cpp | ||||
| src/Http/HttpUtils.cpp | ||||
| src/Http/HttpClient.cpp | ||||
| src/Http/HttpStream.cpp | ||||
| src/Http/ContentDisposition.cpp | ||||
| src/Streams/FileStream.cpp | ||||
| src/Streams/MemoryStream.cpp | ||||
| src/Streams/NetworkStream.cpp | ||||
| src/Streams/Stream.cpp | ||||
| src/Streams/BufferedStream.cpp | ||||
| src/TextStreams/StreamReader.cpp | ||||
| src/TextStreams/StreamWriter.cpp | ||||
| src/TextStreams/TextReader.cpp | ||||
| src/TextStreams/TextWriter.cpp | ||||
| src/Threading/Thread.cpp | ||||
| src/Threading/Mutex.cpp | ||||
| src/Filesystem/VFS.cpp | ||||
| src/Filesystem/LocalFS.cpp | ||||
| src/Filesystem/SubdirFilesystem.cpp | ||||
| src/Filesystem/NullFilesystem.cpp | ||||
| src/Filesystem/MountableFilesystem.cpp | ||||
| src/Crypto/ClientTLSStream.cpp | ||||
| src/TF_Init.cpp | ||||
| ) | ||||
| 
 | ||||
| set(TESSESFRAMEWORK_CERT_BUNDLE_FILE "/etc/ssl/certs/ca-certificates.crt") | ||||
| option(TESSESFRAMEWORK_EMBED_CERT_BUNDLE "Embed the certificate chain bundle" ON) | ||||
| option(TESSESFRAMEWORK_ENABLE_MBED "Enable Tesses Framework mbedtls" ON) | ||||
| option(TESSESFRAMEWORK_ENABLE_EXAMPLES "Enable Tesses Framework examples" ON) | ||||
| option(TESSESFRAMEWORK_ENABLE_STATIC "Enable Tesses Framework Static Libraries" ON) | ||||
| option(TESSESFRAMEWORK_ENABLE_SHARED "Enable Tesses Framework Shared Libraries" ON) | ||||
| 
 | ||||
| file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include) | ||||
| 
 | ||||
| if(TESSESFRAMEWORK_ENABLE_MBED) | ||||
| if(TESSESFRAMEWORK_EMBED_CERT_BUNDLE) | ||||
| include(cmake/bin2h.cmake) | ||||
| file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/include/CertificateChain.h "#pragma once\n") | ||||
| #target_compile_definitions(TessesFramework PUBLIC TESSESFRAMEWORK_EMBED_CERT_BUNDLE) | ||||
| bin2h(SOURCE_FILE ${TESSESFRAMEWORK_CERT_BUNDLE_FILE} HEADER_FILE ${CMAKE_CURRENT_BINARY_DIR}/include/CertificateChain.h VARIABLE_NAME CertificateChain APPEND NULL_TERMINATE) | ||||
| file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/include/CertificateChain.h "\n") | ||||
| #else() | ||||
| #target_compile_definitions(TessesFramework PUBLIC TESSESFRAMEWORK_CERT_BUNDLE_FILE=${TESSESFRAMEWORK_CERT_BUNDLE_FILE}) | ||||
| 
 | ||||
| endif() | ||||
| endif() | ||||
| set(MBEDTLS_DIR "") | ||||
| 
 | ||||
| function(link_deps TessesFramework_TARGET) | ||||
| if(TESSESFRAMEWORK_ENABLE_MBED) | ||||
| target_compile_definitions(${TessesFramework_TARGET} PUBLIC TESSESFRAMEWORK_ENABLE_MBED) | ||||
| if(TESSESFRAMEWORK_EMBED_CERT_BUNDLE) | ||||
| target_compile_definitions(${TessesFramework_TARGET} PUBLIC TESSESFRAMEWORK_EMBED_CERT_BUNDLE) | ||||
| else() | ||||
| target_compile_definitions(TessesFramework PUBLIC TESSESFRAMEWORK_CERT_BUNDLE_FILE=${TESSESFRAMEWORK_CERT_BUNDLE_FILE}) | ||||
| endif() | ||||
| if(MBEDTLS_DIR STREQUAL "") | ||||
| else() | ||||
| target_include_directories(${TessesFramework_TARGET} PUBLIC ${MBEDTLS_DIR}/include) | ||||
| target_link_directories(${TessesFramework_TARGET} PUBLIC ${MBEDTLS_DIR}/lib) | ||||
| endif() | ||||
| target_link_libraries(${TessesFramework_TARGET} PUBLIC mbedtls mbedx509 mbedcrypto) | ||||
| target_include_directories(${TessesFramework_TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include) | ||||
| endif() | ||||
| target_include_directories(${TessesFramework_TARGET} | ||||
|     PUBLIC | ||||
|     "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>" | ||||
|     "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" | ||||
| ) | ||||
| if("${CMAKE_SYSTEM_NAME}" STREQUAL "NintendoWii" OR "${CMAKE_SYSTEM_NAME}" STREQUAL "NintendoGameCube") | ||||
| target_link_libraries(${TessesFramework_TARGET} PUBLIC fat) | ||||
| endif() | ||||
| if("${CMAKE_SYSTEM_NAME}" STREQUAL "NintendoWii") | ||||
| target_link_libraries(${TessesFramework_TARGET} PUBLIC wiisocket) | ||||
| target_compile_definitions(${TessesFramework_TARGET} PUBLIC TESSESFRAMEWORK_USE_WII_SOCKET) | ||||
| endif() | ||||
| endfunction() | ||||
| 
 | ||||
| include(GNUInstallDirs) | ||||
| 
 | ||||
| if(TESSESFRAMEWORK_ENABLE_STATIC) | ||||
| 
 | ||||
| add_library(tessesframework STATIC ${TESSESFRAMEWORK_SOURCE}) | ||||
| link_deps(tessesframework) | ||||
| list(APPEND TessesFrameworkLibs tessesframework) | ||||
| endif() | ||||
| 
 | ||||
| 
 | ||||
| if(TESSESFRAMEWORK_ENABLE_SHARED) | ||||
| 
 | ||||
| add_library(tessesframework_shared SHARED ${TESSESFRAMEWORK_SOURCE}) | ||||
| link_deps(tessesframework_shared) | ||||
| list(APPEND TessesFrameworkLibs tessesframework_shared) | ||||
| endif() | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| install(TARGETS ${TessesFrameworkLibs}  | ||||
|     EXPORT TessesFrameworkTargets | ||||
|     LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} | ||||
|     ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} | ||||
|     RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} | ||||
|     INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} | ||||
| ) | ||||
| 
 | ||||
| install(DIRECTORY include/TessesFramework DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) | ||||
| 
 | ||||
| install(EXPORT TessesFrameworkTargets | ||||
|     FILE TessesFrameworkTargets.cmake | ||||
|     NAMESPACE TessesFramework:: | ||||
|     DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TessesFramework | ||||
| ) | ||||
| 
 | ||||
| include(CMakePackageConfigHelpers) | ||||
| configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/TessesFrameworkConfig.cmake" | ||||
| INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TessesFramework) | ||||
| 
 | ||||
| install(FILES "${CMAKE_CURRENT_BINARY_DIR}/TessesFrameworkConfig.cmake" | ||||
| DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TessesFramework) | ||||
| 
 | ||||
| if(TESSESFRAMEWORK_ENABLE_EXAMPLES) | ||||
|         add_executable(copyfile examples/copyfile.cpp) | ||||
|         target_link_libraries(copyfile PUBLIC tessesframework) | ||||
| 
 | ||||
|         | ||||
|         add_executable(fileserver examples/fileserver.cpp) | ||||
|         target_link_libraries(fileserver PUBLIC tessesframework) | ||||
|         add_executable(webserverex examples/webserverex.cpp) | ||||
|         target_link_libraries(webserverex PUBLIC tessesframework) | ||||
| 
 | ||||
|         add_executable(safesubpath examples/safesubpath.cpp) | ||||
|         target_link_libraries(safesubpath PUBLIC tessesframework) | ||||
| 
 | ||||
|         add_executable(mountabletest examples/mountabletest.cpp) | ||||
|         target_link_libraries(mountabletest PUBLIC tessesframework) | ||||
| 
 | ||||
|         add_executable(download examples/download.cpp) | ||||
|         target_link_libraries(download PUBLIC tessesframework) | ||||
| endif() | ||||
|  | @ -0,0 +1,5 @@ | |||
| @PACKAGE_INIT@ | ||||
| 
 | ||||
| include("${CMAKE_CURRENT_LIST_DIR}/TessesFrameworkTargets.cmake") | ||||
| 
 | ||||
| check_required_components(TessesFramework) | ||||
|  | @ -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. | ||||
|  | @ -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) | ||||
|  | @ -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() | ||||
|  | @ -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; | ||||
| } | ||||
|  | @ -0,0 +1,22 @@ | |||
| #include "TessesFramework/TessesFramework.hpp" | ||||
| #include <iostream> | ||||
| using namespace Tesses::Framework; | ||||
| using namespace Tesses::Framework::Streams; | ||||
| using namespace Tesses::Framework::Http; | ||||
| int main(int argc, char** argv) | ||||
| { | ||||
|     if(argc < 3) | ||||
|     { | ||||
|         printf("USAGE: %s <url> <path>\n",argv[0]); | ||||
|         return 1; | ||||
|     } | ||||
|     FileStream strm(argv[2],"wb"); | ||||
| 
 | ||||
|     HttpRequest req; | ||||
|     req.url = argv[1]; | ||||
|     HttpResponse resp(req); | ||||
|     resp.CopyToStream(&strm); | ||||
| 
 | ||||
|      | ||||
|     return 0; | ||||
| } | ||||
|  | @ -0,0 +1,23 @@ | |||
| #include "TessesFramework/TessesFramework.hpp" | ||||
| #include <iostream> | ||||
| using namespace Tesses::Framework; | ||||
| using namespace Tesses::Framework::Http; | ||||
| using namespace Tesses::Framework::Streams; | ||||
| using namespace Tesses::Framework::TextStreams; | ||||
| using namespace Tesses::Framework::Threading; | ||||
| 
 | ||||
| int main(int argc, char** argv) | ||||
| { | ||||
|     TF_Init(); | ||||
|     if(argc < 1) | ||||
|     { | ||||
|         printf("USAGE: %s <dir>\n",argv[0]); | ||||
|         return 0; | ||||
|     } | ||||
|     FileServer fs(argv[1],true,false); | ||||
|     HttpServer server(10000U,fs); | ||||
|     server.StartAccepting(); | ||||
|     TF_RunEventLoop(); | ||||
|     std::cout << "Closing server" << std::endl; | ||||
|     return 0; | ||||
| } | ||||
|  | @ -0,0 +1,63 @@ | |||
| #include "TessesFramework/TessesFramework.hpp" | ||||
| #include <iostream> | ||||
| 
 | ||||
| using namespace Tesses::Framework; | ||||
| using namespace Tesses::Framework::Filesystem; | ||||
| using namespace Tesses::Framework::Streams; | ||||
| 
 | ||||
| int main(int argc, char** argv) | ||||
| { | ||||
|     if(argc < 2) | ||||
|     { | ||||
|         printf("USAGE: %s <command> <args...>\n",argv[0]); | ||||
|         return 1; | ||||
|     } | ||||
| 
 | ||||
|     LocalFilesystem lfs; | ||||
| 
 | ||||
|     std::string root = "./root"; | ||||
|     std::string mountDemi = "./demi"; | ||||
|     std::string mountJoelSlashJim = "./joelslashjim"; | ||||
| 
 | ||||
|     SubdirFilesystem rootdir(&lfs,root,false); | ||||
| 
 | ||||
|     SubdirFilesystem mountDemidir(&lfs,mountDemi,false); | ||||
| 
 | ||||
| 
 | ||||
|     SubdirFilesystem mountjohnslashjim(&lfs,mountJoelSlashJim,false); | ||||
| 
 | ||||
|     MountableFilesystem fs(&rootdir,false); | ||||
|     fs.Mount(std::string("/demi"), &mountDemidir,false); | ||||
| 
 | ||||
|     fs.Mount(std::string("/joel/jim"), &mountjohnslashjim,false); | ||||
| 
 | ||||
|     std::string command = argv[1]; | ||||
| 
 | ||||
| 
 | ||||
|     if(command == "ls") | ||||
|     { | ||||
|         std::string dir = "/"; | ||||
|         if(argc > 2) dir = argv[2]; | ||||
|         std::vector<VFSPath> paths; | ||||
|         fs.GetPaths(dir, paths); | ||||
|         for(auto item : paths) | ||||
|         { | ||||
|             std::cout << item.GetFileName() << std::endl; | ||||
|         } | ||||
|     } | ||||
|     else if(command == "cat") | ||||
|     { | ||||
|         FileStream strm(stdout, false,"wb",false); | ||||
|         for(int a = 2; a < argc; a++) | ||||
|         { | ||||
|             std::string path = argv[a]; | ||||
|             auto f = fs.OpenFile(path,"rb"); | ||||
|             if(f != nullptr) | ||||
|             { | ||||
|                 f->CopyTo(strm); | ||||
|                 delete f; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
| } | ||||
|  | @ -0,0 +1,19 @@ | |||
| #include "TessesFramework/TessesFramework.hpp" | ||||
| #include <iostream> | ||||
| 
 | ||||
| using namespace Tesses::Framework::Filesystem; | ||||
| 
 | ||||
| int main(int argc, char** argv) | ||||
| { | ||||
|     if(argc < 3) | ||||
|     { | ||||
|         std::cout << "USAGE: " << argv[0] << " <parent> <subpath>\n"; | ||||
|         return 1; | ||||
|     } | ||||
| 
 | ||||
|     VFSPath parent(argv[1]); | ||||
|     VFSPath subpath(argv[2]); | ||||
|     VFSPath newPath(parent,subpath.CollapseRelativeParents()); | ||||
| 
 | ||||
|     std::cout << newPath.ToString() << "\n"; | ||||
| } | ||||
|  | @ -0,0 +1,100 @@ | |||
| #include "TessesFramework/TessesFramework.hpp" | ||||
| #include <iostream> | ||||
| using namespace Tesses::Framework; | ||||
| using namespace Tesses::Framework::Http; | ||||
| using namespace Tesses::Framework::Streams; | ||||
| using namespace Tesses::Framework::TextStreams; | ||||
| using namespace Tesses::Framework::Threading; | ||||
| 
 | ||||
| class MyWebServer : public IHttpServer { | ||||
|     public: | ||||
|         bool Handle(ServerContext& ctx) | ||||
|         { | ||||
|             if(ctx.path == "/") | ||||
|             { | ||||
|                 FileStream fs("index.html","rb"); | ||||
|                  | ||||
|                 ctx | ||||
|                 .WithMimeType("text/html") | ||||
|                 .SendStream(fs); | ||||
|                 return true; | ||||
|             } | ||||
|             else if(ctx.path == "/streaming.html") | ||||
|             { | ||||
|                 StreamWriter writer(ctx.OpenResponseStream(),true); | ||||
|                 writer.WriteLine("<html>"); | ||||
|                 writer.WriteLine("<head><title>Streaming</title></head>"); | ||||
|                 writer.WriteLine("<body>"); | ||||
| 
 | ||||
|                 writer.WriteLine("<h1>Streaming</h1>"); | ||||
| 
 | ||||
|                 writer.WriteLine("<ul>"); | ||||
| 
 | ||||
|                 for(size_t i=0;i<10000; i++) | ||||
|                 { | ||||
|                     writer.WriteLine("<li>" + std::to_string(i) + "</li>"); | ||||
|                      | ||||
|                 } | ||||
| 
 | ||||
|                 writer.WriteLine("</ul>"); | ||||
| 
 | ||||
|                 writer.WriteLine("</body>"); | ||||
|                 writer.WriteLine("</html>"); | ||||
|                 return true; | ||||
|             } | ||||
|             else if(ctx.path == "/main.js") | ||||
|             { | ||||
| 
 | ||||
|                 FileStream fs("main.js","rb"); | ||||
|                  | ||||
|                 ctx | ||||
|                 .WithMimeType("text/js") | ||||
|                 .SendStream(fs); | ||||
|                 return true; | ||||
|             } | ||||
|             else if(ctx.path == "/upload") | ||||
|             { | ||||
|                 ctx.ParseFormData([](std::string mime, std::string filename, std::string name)->Tesses::Framework::Streams::Stream*{ | ||||
|                     return new FileStream(filename,"wb"); | ||||
|                 }); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|         bool Handle1(ServerContext& ctx) | ||||
|         { | ||||
|             std::string name; | ||||
|             if(ctx.queryParams.TryGetFirst("name",name)) | ||||
|             { | ||||
|                 std::cout << name << std::endl; | ||||
|                 ctx | ||||
|                 .WithMimeType("text/plain") | ||||
|                 .WithContentDisposition(HttpUtils::Sanitise(name) + ".txt",false) | ||||
|                 .SendText(name + " is cool."); | ||||
|                 //do something with q
 | ||||
|                  | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
| }; | ||||
| 
 | ||||
| int main(int argc, char** argv) | ||||
| { | ||||
|     TF_Init(); | ||||
|     TcpServer server(9985U,10); | ||||
|     MyWebServer svr; | ||||
|     while(true) | ||||
|     { | ||||
|         std::string ip; | ||||
|         uint16_t port; | ||||
|         auto res = server.GetStream(ip, port); | ||||
|         if(res == nullptr) return 0; | ||||
|         std::cout << "Got from " << ip << ":" << port << std::endl; | ||||
|         Thread thrd([ip,port,res,&svr]()->void { | ||||
|             HttpServer::Process(*res, svr, ip,port, false); | ||||
|             delete res; | ||||
|         }); | ||||
|         thrd.Detach(); | ||||
|     } | ||||
|      | ||||
| } | ||||
|  | @ -0,0 +1,19 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <cstring> | ||||
| #include <cstdint> | ||||
| #include <cstddef> | ||||
| #include <string> | ||||
| #include <filesystem> | ||||
| #include <map> | ||||
| #include <vector> | ||||
| 
 | ||||
| namespace Tesses::Framework | ||||
| { | ||||
|     void TF_Init(); | ||||
|     void TF_ConnectToSelf(uint16_t port); | ||||
|     void TF_RunEventLoop(); | ||||
|     void TF_RunEventLoopItteration(); | ||||
|     bool TF_IsRunning(); | ||||
|     void TF_Quit(); | ||||
| } | ||||
|  | @ -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(); | ||||
|     }; | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,28 @@ | |||
| #pragma once | ||||
| #include "VFS.hpp" | ||||
| namespace Tesses::Framework::Filesystem | ||||
| { | ||||
|     class LocalFilesystem : public VFS | ||||
|     { | ||||
|         public: | ||||
|             Tesses::Framework::Streams::Stream* OpenFile(VFSPath path, std::string mode); | ||||
|             void CreateDirectory(VFSPath path); | ||||
|             void DeleteDirectory(VFSPath path); | ||||
|             bool RegularFileExists(VFSPath path); | ||||
|             bool SymlinkExists(VFSPath path); | ||||
|             bool CharacterDeviceExists(VFSPath path); | ||||
|             bool BlockDeviceExists(VFSPath path); | ||||
|             bool SocketFileExists(VFSPath path); | ||||
|             bool FIFOFileExists(VFSPath path); | ||||
|             bool DirectoryExists(VFSPath path); | ||||
|             void DeleteFile(VFSPath path); | ||||
|             void CreateSymlink(VFSPath existingFile, VFSPath symlinkFile); | ||||
|             void GetPaths(VFSPath path, std::vector<VFSPath>& paths); | ||||
|             void CreateHardlink(VFSPath existingFile, VFSPath newName); | ||||
|             void MoveFile(VFSPath src, VFSPath dest); | ||||
|             void MoveDirectory(VFSPath src, VFSPath dest); | ||||
|             VFSPath ReadLink(VFSPath path); | ||||
|             std::string VFSPathToSystem(VFSPath path); | ||||
|             VFSPath SystemToVFSPath(std::string path); | ||||
|     }; | ||||
| } | ||||
|  | @ -0,0 +1,53 @@ | |||
| #pragma once | ||||
| #include "VFS.hpp" | ||||
| namespace Tesses::Framework::Filesystem | ||||
| { | ||||
|     class MountableDirectory { | ||||
|         public: | ||||
|             std::string name; | ||||
|             VFS* vfs; | ||||
|             bool owns; | ||||
|             std::vector<MountableDirectory*> dirs; | ||||
|             void GetFS(VFSPath srcPath, VFSPath curDir, VFSPath& destRoot, VFSPath& destPath, VFS*& vfs); | ||||
|             ~MountableDirectory(); | ||||
|     }; | ||||
| 
 | ||||
|     class MountableFilesystem : public VFS | ||||
|     { | ||||
|         bool owns; | ||||
|         VFS* root; | ||||
| 
 | ||||
|         std::vector<MountableDirectory*> directories; | ||||
|          | ||||
|         void GetFS(VFSPath srcPath, VFSPath& destRoot, VFSPath& destPath, VFS*& vfs); | ||||
| 
 | ||||
|      | ||||
|         public: | ||||
|             MountableFilesystem(); | ||||
|             MountableFilesystem(VFS* root, bool owns); | ||||
|             void Mount(VFSPath path, VFS* fs, bool owns); | ||||
|             void Unmount(VFSPath path); | ||||
|             Tesses::Framework::Streams::Stream* OpenFile(VFSPath path, std::string mode); | ||||
|             void CreateDirectory(VFSPath path); | ||||
|             void DeleteDirectory(VFSPath path); | ||||
|             bool SpecialFileExists(VFSPath path); | ||||
|             bool FileExists(VFSPath path); | ||||
|             bool RegularFileExists(VFSPath path); | ||||
|             bool SymlinkExists(VFSPath path); | ||||
|             bool CharacterDeviceExists(VFSPath path); | ||||
|             bool BlockDeviceExists(VFSPath path); | ||||
|             bool SocketFileExists(VFSPath path); | ||||
|             bool FIFOFileExists(VFSPath path); | ||||
|             bool DirectoryExists(VFSPath path); | ||||
|             void DeleteFile(VFSPath path); | ||||
|             void CreateSymlink(VFSPath existingFile, VFSPath symlinkFile); | ||||
|             void GetPaths(VFSPath path, std::vector<VFSPath>& paths); | ||||
|             void CreateHardlink(VFSPath existingFile, VFSPath newName); | ||||
|             void MoveFile(VFSPath src, VFSPath dest); | ||||
|             void MoveDirectory(VFSPath src, VFSPath dest); | ||||
|             VFSPath ReadLink(VFSPath path); | ||||
|             std::string VFSPathToSystem(VFSPath path); | ||||
|             VFSPath SystemToVFSPath(std::string path); | ||||
|             ~MountableFilesystem(); | ||||
|     }; | ||||
| } | ||||
|  | @ -0,0 +1,19 @@ | |||
| #pragma once | ||||
| #include "VFS.hpp" | ||||
| namespace Tesses::Framework::Filesystem | ||||
| { | ||||
|     class NullFilesystem : public VFS | ||||
|     { | ||||
|         public:   | ||||
|             Tesses::Framework::Streams::Stream* OpenFile(VFSPath path, std::string mode); | ||||
|             void CreateDirectory(VFSPath path); | ||||
|             void DeleteDirectory(VFSPath path); | ||||
|             bool RegularFileExists(VFSPath path); | ||||
|             bool DirectoryExists(VFSPath path); | ||||
|             void DeleteFile(VFSPath path); | ||||
|             void GetPaths(VFSPath path, std::vector<VFSPath>& paths); | ||||
|             void MoveFile(VFSPath src, VFSPath dest); | ||||
|             std::string VFSPathToSystem(VFSPath path); | ||||
|             VFSPath SystemToVFSPath(std::string path); | ||||
|     }; | ||||
| } | ||||
|  | @ -0,0 +1,38 @@ | |||
| #pragma once | ||||
| #include "VFS.hpp" | ||||
| namespace Tesses::Framework::Filesystem | ||||
| { | ||||
|     class SubdirFilesystem : public VFS | ||||
|     { | ||||
|         bool owns; | ||||
|         VFS* parent; | ||||
|         VFSPath path; | ||||
|         VFSPath ToParent(VFSPath path); | ||||
|         VFSPath FromParent(VFSPath path); | ||||
|         public: | ||||
|             SubdirFilesystem(VFS* parent, VFSPath path, bool owns); | ||||
|             Tesses::Framework::Streams::Stream* OpenFile(VFSPath path, std::string mode); | ||||
|             void CreateDirectory(VFSPath path); | ||||
|             void DeleteDirectory(VFSPath path); | ||||
|             bool SpecialFileExists(VFSPath path); | ||||
|             bool FileExists(VFSPath path); | ||||
|             bool RegularFileExists(VFSPath path); | ||||
|             bool SymlinkExists(VFSPath path); | ||||
|             bool CharacterDeviceExists(VFSPath path); | ||||
|             bool BlockDeviceExists(VFSPath path); | ||||
|             bool SocketFileExists(VFSPath path); | ||||
|             bool FIFOFileExists(VFSPath path); | ||||
|             bool DirectoryExists(VFSPath path); | ||||
|             void DeleteFile(VFSPath path); | ||||
|             void CreateSymlink(VFSPath existingFile, VFSPath symlinkFile); | ||||
|             void GetPaths(VFSPath path, std::vector<VFSPath>& paths); | ||||
|             void CreateHardlink(VFSPath existingFile, VFSPath newName); | ||||
|             void MoveFile(VFSPath src, VFSPath dest); | ||||
|             void MoveDirectory(VFSPath src, VFSPath dest); | ||||
|             void DeleteDirectoryRecurse(VFSPath path); | ||||
|             VFSPath ReadLink(VFSPath path); | ||||
|             std::string VFSPathToSystem(VFSPath path); | ||||
|             VFSPath SystemToVFSPath(std::string path); | ||||
|             ~SubdirFilesystem(); | ||||
|     }; | ||||
| } | ||||
|  | @ -0,0 +1,50 @@ | |||
| #pragma once | ||||
| #include "../Common.hpp" | ||||
| #include "../Streams/Stream.hpp" | ||||
| namespace Tesses::Framework::Filesystem | ||||
| { | ||||
|     class VFSPath { | ||||
|         public: | ||||
|             static VFSPath RelativeCurrentDirectory(); | ||||
|             bool relative; | ||||
|             static std::vector<std::string> SplitPath(std::string path,bool native=false); | ||||
|             std::vector<std::string> path;   | ||||
|             VFSPath(); | ||||
|             VFSPath(std::vector<std::string> path); | ||||
|             VFSPath(std::string path); | ||||
|             VFSPath(VFSPath p, std::string subent); | ||||
|             VFSPath(VFSPath p, VFSPath p2); | ||||
|             VFSPath GetParent(); | ||||
|             VFSPath CollapseRelativeParents(); | ||||
|             std::string GetFileName(); | ||||
|             std::string ToString(); | ||||
|     }; | ||||
|     class VFS { | ||||
|         public: | ||||
|              | ||||
|             virtual Tesses::Framework::Streams::Stream* OpenFile(VFSPath path, std::string mode)=0; | ||||
|             virtual void CreateDirectory(VFSPath path)=0; | ||||
|             virtual void DeleteDirectory(VFSPath path)=0; | ||||
|             virtual bool RegularFileExists(VFSPath path)=0; | ||||
|             virtual bool SymlinkExists(VFSPath path); | ||||
|             virtual bool CharacterDeviceExists(VFSPath path); | ||||
|             virtual bool BlockDeviceExists(VFSPath path); | ||||
|             virtual bool SocketFileExists(VFSPath path); | ||||
|             virtual bool FIFOFileExists(VFSPath path); | ||||
|             virtual bool FileExists(VFSPath path); | ||||
|             virtual bool SpecialFileExists(VFSPath path); | ||||
|             virtual void CreateSymlink(VFSPath existingFile, VFSPath symlinkFile); | ||||
|             virtual void CreateHardlink(VFSPath existingFile, VFSPath newName); | ||||
|             virtual bool DirectoryExists(VFSPath path)=0; | ||||
|             virtual void DeleteFile(VFSPath path)=0; | ||||
|             virtual void DeleteDirectoryRecurse(VFSPath path); | ||||
|             virtual void GetPaths(VFSPath path, std::vector<VFSPath>& paths)=0; | ||||
|             virtual void MoveFile(VFSPath src, VFSPath dest)=0; | ||||
|             virtual void MoveDirectory(VFSPath src, VFSPath dest); | ||||
|             virtual VFSPath ReadLink(VFSPath path); | ||||
|             virtual std::string VFSPathToSystem(VFSPath path)=0; | ||||
|             virtual VFSPath SystemToVFSPath(std::string path)=0; | ||||
| 
 | ||||
|             virtual ~VFS(); | ||||
|     }; | ||||
| } | ||||
|  | @ -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(); | ||||
|     }; | ||||
| }    | ||||
|  | @ -0,0 +1,25 @@ | |||
| #pragma once | ||||
| #include "../Filesystem/VFS.hpp" | ||||
| #include "HttpServer.hpp" | ||||
| 
 | ||||
| namespace Tesses::Framework::Http | ||||
| { | ||||
|     class FileServer : public IHttpServer | ||||
|     { | ||||
|         Tesses::Framework::Filesystem::VFS* vfs; | ||||
|         bool ownsVFS; | ||||
|      | ||||
| 
 | ||||
|         bool SendFile(ServerContext& ctx,Tesses::Framework::Filesystem::VFSPath path); | ||||
|         public: | ||||
|             bool allowListing; | ||||
|             bool spa; | ||||
|             std::vector<std::string> defaultNames; | ||||
|             FileServer(std::filesystem::path path,bool allowListing,bool spa); | ||||
|             FileServer(std::filesystem::path path,bool allowListing, bool spa, std::vector<std::string> defaultNames); | ||||
|             FileServer(Tesses::Framework::Filesystem::VFS* fs, bool owns, bool allowListing, bool spa); | ||||
|             FileServer(Tesses::Framework::Filesystem::VFS* fs, bool owns, bool allowListing, bool spa, std::vector<std::string> defaultNames); | ||||
|             bool Handle(ServerContext& ctx); | ||||
|             ~FileServer(); | ||||
|     }; | ||||
| } | ||||
|  | @ -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(); | ||||
|     }; | ||||
| 
 | ||||
|      | ||||
| } | ||||
|  | @ -0,0 +1,70 @@ | |||
| #pragma once | ||||
| #include "../Streams/NetworkStream.hpp" | ||||
| 
 | ||||
| #include "HttpUtils.hpp" | ||||
| #include "../Threading/Thread.hpp" | ||||
| namespace Tesses::Framework::Http | ||||
| { | ||||
|      | ||||
|     class ServerContext { | ||||
|         bool sent; | ||||
|         Tesses::Framework::Streams::Stream* strm; | ||||
|         public: | ||||
|             HttpDictionary requestHeaders; | ||||
|             HttpDictionary responseHeaders; | ||||
|             HttpDictionary queryParams; | ||||
|             std::string path; | ||||
|             std::string originalPath; | ||||
|             std::string method; | ||||
|             StatusCode statusCode; | ||||
|             std::string ip; | ||||
|             uint16_t port; | ||||
|             std::string version; | ||||
|             bool encrypted; | ||||
|             ServerContext(Tesses::Framework::Streams::Stream* strm); | ||||
|             Tesses::Framework::Streams::Stream& GetStream(); | ||||
|             std::string GetOriginalPathWithQuery(); | ||||
|             std::string GetUrlWithQuery(); | ||||
|             bool Sent(); | ||||
|             bool NeedToParseFormData(); | ||||
|             void ParseFormData(std::function<Tesses::Framework::Streams::Stream*(std::string mime, std::string filename, std::string name)> cb); | ||||
|             void ReadStream(Tesses::Framework::Streams::Stream& strm); | ||||
|             void ReadStream(Tesses::Framework::Streams::Stream* strm); | ||||
|             std::string ReadString(); | ||||
|             void SendBytes(std::vector<uint8_t> buffer); | ||||
|             void SendText(std::string text); | ||||
|             void SendStream(Tesses::Framework::Streams::Stream& strm); | ||||
|             void SendStream(Tesses::Framework::Streams::Stream* strm); | ||||
|             void SendErrorPage(bool showPath); | ||||
|             void SendNotFound(); | ||||
|             void SendBadRequest(); | ||||
|             void SendException(std::exception& ex); | ||||
|             Tesses::Framework::Streams::Stream* OpenResponseStream(); | ||||
|             Tesses::Framework::Streams::Stream* OpenRequestStream(); | ||||
|             ServerContext& WithHeader(std::string key, std::string value); | ||||
|             ServerContext& WithSingleHeader(std::string key, std::string value); | ||||
|             ServerContext& WithMimeType(std::string mime); | ||||
|             ServerContext& WithContentDisposition(std::string filename, bool isInline); | ||||
|             ServerContext& WriteHeaders(); | ||||
|     }; | ||||
| 
 | ||||
|     class IHttpServer { | ||||
|         public: | ||||
|             virtual bool Handle(ServerContext& ctx)=0; | ||||
|             virtual ~IHttpServer(); | ||||
|     }; | ||||
| 
 | ||||
|     class HttpServer { | ||||
|         Tesses::Framework::Streams::TcpServer* server; | ||||
|         IHttpServer* http; | ||||
|         Tesses::Framework::Threading::Thread* thrd; | ||||
|         bool owns; | ||||
|         uint16_t port; | ||||
|         public: | ||||
|             HttpServer(uint16_t port, IHttpServer& http); | ||||
|             HttpServer(uint16_t port, IHttpServer* http, bool owns); | ||||
|             void StartAccepting(); | ||||
|             static void Process(Tesses::Framework::Streams::Stream& strm, IHttpServer& server, std::string ip, uint16_t port, bool encrypted); | ||||
|             ~HttpServer(); | ||||
|     }; | ||||
| }    | ||||
|  | @ -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(); | ||||
|     }; | ||||
| } | ||||
|  | @ -0,0 +1,144 @@ | |||
| #pragma once | ||||
| #include "../Common.hpp" | ||||
| 
 | ||||
| namespace Tesses::Framework::Http | ||||
| { | ||||
|     typedef enum StatusCode { | ||||
|     Continue=100, | ||||
|     SwitchingProtocols=101, | ||||
|     Processing=102, | ||||
|     EarlyHints=103, | ||||
|     OK=200, | ||||
|     Created=201, | ||||
|     Accepted=202, | ||||
|     NonAuthoritativeInformation=203, | ||||
|     NoContent=204, | ||||
|     ResetContent=205, | ||||
|     PartialContent=206, | ||||
|     MultiStatus=207, | ||||
|     AlreadyReported=208, | ||||
|     IMUsed=226, | ||||
|     MultipleChoices=300, | ||||
|     MovedPermanently=301, | ||||
|     Found=302, | ||||
|     SeeOther=303, | ||||
|     NotModified=304, | ||||
|     UseProxy=305, | ||||
|     TemporaryRedirect=307, | ||||
|     PermanentRedirect=308, | ||||
|     BadRequest=400, | ||||
|     Unauthorized=401, | ||||
|     PaymentRequired=402, | ||||
|     Forbidden=403, | ||||
|     NotFound=404, | ||||
|     MethodNotAllowed=405, | ||||
|     NotAcceptable=406, | ||||
|     ProxyAuthenticationRequired=407, | ||||
|     RequestTimeout=408, | ||||
|     Conflict=409, | ||||
|     Gone=410, | ||||
|     LengthRequired=411, | ||||
|     PreconditionFailed=412, | ||||
|     PayloadTooLarge=413, | ||||
|     URITooLong=414, | ||||
|     UnsupportedMediaType=415, | ||||
|     RangeNotSatisfiable=416, | ||||
|     ExpectationFailed=417, | ||||
|     ImATeapot=418, | ||||
|     MisdirectedRequest=421, | ||||
|     UnprocessableContent=422, | ||||
|     Locked=423, | ||||
|     FailedDependency=424, | ||||
|     TooEarly=425, | ||||
|     UpgradeRequired=426, | ||||
|     PreconditionRequired=428, | ||||
|     TooManyRequests=429, | ||||
|     RequestHeaderFieldsTooLarge=431, | ||||
|     UnavailableForLegalReasons=451, | ||||
|     InternalServerError=500, | ||||
|     NotImplemented=501, | ||||
|     BadGateway=502, | ||||
|     ServiceUnavailable=503, | ||||
|     GatewayTimeout=504, | ||||
|     HTTPVersionNotSupported=505, | ||||
|     VariantAlsoNegotiates=506, | ||||
|     InsufficientStorage=507, | ||||
|     LoopDetected=508, | ||||
|     NotExtended=510, | ||||
|     NetworkAuthenticationRequired=511 | ||||
| } StatusCode; | ||||
|     class HttpDictionary { | ||||
|         public: | ||||
|             std::map<std::string,std::vector<std::string>> kvp; | ||||
|             void Clear(); | ||||
|             void Clear(std::string key, bool kvpExistsAfter); | ||||
|             void SetValue(std::string key, std::string value); | ||||
|             void SetValue(std::string key, std::vector<std::string> value); | ||||
|             template<typename Itterator> | ||||
|             void SetValue(std::string key, Itterator begin, Itterator end) | ||||
|             { | ||||
|                 Clear(key,true); | ||||
|                 AddValue(key, begin, end); | ||||
|             } | ||||
|             void AddValue(std::string key, std::string value); | ||||
|             void AddValue(std::string key, std::vector<std::string> value); | ||||
|              | ||||
|             template<typename Itterator> | ||||
|             void AddValue(std::string key, Itterator begin, Itterator end) | ||||
|             { | ||||
|                 auto& ls = kvp[key]; | ||||
|                 ls.insert(ls.end(), begin, end); | ||||
|             } | ||||
| 
 | ||||
|             bool TryGetFirst(std::string key, std::string& value); | ||||
| 
 | ||||
|             bool TryGetFirstInt(std::string key, int64_t& value); | ||||
| 
 | ||||
|             bool TryGetFirstDouble(std::string key, double& value); | ||||
| 
 | ||||
|             bool GetFirstBoolean(std::string key); | ||||
|     }; | ||||
| 
 | ||||
|     class Uri { | ||||
|         public: | ||||
|             std::string GetQuery(); | ||||
|             std::string GetPathAndQuery(); | ||||
|             uint16_t GetPort(); | ||||
|             std::string HostPort(); | ||||
|             bool Relative(std::string url, Uri& uri); | ||||
|             std::string ToString(); | ||||
|             static bool TryParse(std::string url, Uri& uri); | ||||
|             std::string scheme; | ||||
|             std::string host; | ||||
|             uint16_t port; | ||||
|             std::string path; | ||||
|             HttpDictionary query; | ||||
|             std::string hash; | ||||
|     }; | ||||
| 
 | ||||
|     class HttpUtils | ||||
|     { | ||||
|         public: | ||||
|         static char NibbleToHex(uint8_t nibble); | ||||
|         static uint8_t HexToNibble(char c); | ||||
|         static std::string MimeType(std::filesystem::path p); | ||||
|         static bool Invalid(char c); | ||||
|         static std::string Sanitise(std::string text); | ||||
|         static void QueryParamsDecode(HttpDictionary& dict,std::string query); | ||||
|         static std::string Join(std::string joinStr, std::vector<std::string> ents); | ||||
|         static std::string QueryParamsEncode(HttpDictionary& dict); | ||||
|         static std::string UrlDecode(std::string v); | ||||
|         static std::string UrlEncode(std::string v); | ||||
|         static std::string UrlPathDecode(std::string v); | ||||
|         static std::string UrlPathEncode(std::string v, bool ignoreSpace=false); | ||||
|         static std::string HtmlEncode(std::string v); | ||||
|         static std::vector<std::string> SplitString(std::string text, std::string delimiter,std::size_t maxCnt = std::string::npos); | ||||
|         static std::string StatusCodeString(StatusCode code); | ||||
|      | ||||
|     }; | ||||
| 
 | ||||
|   | ||||
| 
 | ||||
|      | ||||
|      | ||||
| } | ||||
|  | @ -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(); | ||||
|     }; | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,27 @@ | |||
| #pragma once | ||||
| #include "Stream.hpp" | ||||
| #include <cstdio> | ||||
| namespace Tesses::Framework::Streams | ||||
| { | ||||
|     class FileStream : public Stream { | ||||
|         bool canRead; | ||||
|         bool canWrite; | ||||
|         bool canSeek; | ||||
|         bool owns; | ||||
|         FILE* f; | ||||
|         void SetMode(std::string mode); | ||||
|         public: | ||||
|             FileStream(std::filesystem::path p, std::string mode); | ||||
|             FileStream(FILE* f, bool owns, std::string mode , bool canSeek=true); | ||||
|             size_t Read(uint8_t* buff, size_t sz); | ||||
|             size_t Write(const uint8_t* buff, size_t sz); | ||||
|             bool CanRead(); | ||||
|             bool CanWrite(); | ||||
|             bool CanSeek(); | ||||
|             int64_t GetPosition(); | ||||
|             void Flush(); | ||||
|             void Seek(int64_t pos, SeekOrigin whence); | ||||
|             ~FileStream(); | ||||
|     | ||||
|     }; | ||||
| } | ||||
|  | @ -0,0 +1,22 @@ | |||
| #pragma once | ||||
| #include "Stream.hpp" | ||||
| namespace Tesses::Framework::Streams | ||||
| { | ||||
|     class MemoryStream : public Stream { | ||||
|         std::vector<uint8_t> buffer; | ||||
|         size_t offset; | ||||
|         bool writable; | ||||
|         public: | ||||
|             MemoryStream(bool isWritable); | ||||
|             std::vector<uint8_t>& GetBuffer(); | ||||
|             size_t Read(uint8_t* buff, size_t sz); | ||||
|             size_t Write(const uint8_t* buff, size_t sz); | ||||
|             bool CanRead(); | ||||
|             bool CanWrite(); | ||||
|             bool CanSeek(); | ||||
|             int64_t GetLength(); | ||||
|             int64_t GetPosition(); | ||||
|             void Seek(int64_t pos, SeekOrigin whence); | ||||
|          | ||||
|     }; | ||||
| } | ||||
|  | @ -0,0 +1,43 @@ | |||
| #pragma once | ||||
| #include "Stream.hpp" | ||||
| 
 | ||||
| namespace Tesses::Framework::Streams | ||||
| { | ||||
|     class NetworkStream; | ||||
|     | ||||
|     class TcpServer { | ||||
|         int32_t sock; | ||||
|         bool owns; | ||||
|         bool valid; | ||||
|         public: | ||||
|             TcpServer(int32_t sock,bool owns); | ||||
|             TcpServer(uint16_t port, int32_t backlog); | ||||
|             TcpServer(std::string ip, uint16_t port, int32_t backlog); | ||||
|             NetworkStream* GetStream(std::string& ip, uint16_t& port); | ||||
|             ~TcpServer(); | ||||
|             void Close(); | ||||
|     };   | ||||
|     class NetworkStream : public Stream { | ||||
|         int32_t sock; | ||||
|         bool owns; | ||||
|         bool success; | ||||
|         bool endOfStream; | ||||
|         public: | ||||
|         bool EndOfStream(); | ||||
|         bool CanRead(); | ||||
|         bool CanWrite(); | ||||
|         NetworkStream(bool ipV6,bool datagram); | ||||
|         NetworkStream(std::string ipOrFqdn, uint16_t port, bool datagram,bool broadcast,bool supportIPv6); | ||||
|         NetworkStream(int32_t sock, bool owns); | ||||
|         void Listen(int32_t backlog); | ||||
|         void Bind(std::string ip, uint16_t port); | ||||
|         void SetBroadcast(bool bC); | ||||
|         NetworkStream* Accept(std::string& ip, uint16_t& port); | ||||
|         size_t Read(uint8_t* buff, size_t sz); | ||||
|         size_t Write(const uint8_t* buff, size_t sz); | ||||
|         size_t ReadFrom(uint8_t* buff, size_t sz, std::string& ip, uint16_t& port); | ||||
|         size_t WriteTo(const uint8_t* buff, size_t sz, std::string ip, uint16_t port); | ||||
|         static std::vector<std::pair<std::string,std::string>> GetIPs(bool ipV6=false); | ||||
|         ~NetworkStream(); | ||||
|     }; | ||||
| } | ||||
|  | @ -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(); | ||||
|     }; | ||||
| } | ||||
|  | @ -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" | ||||
|  | @ -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(); | ||||
|     };   | ||||
| }    | ||||
|  | @ -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(); | ||||
|     }; | ||||
| } | ||||
|  | @ -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(); | ||||
|     }; | ||||
| } | ||||
|  | @ -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(); | ||||
|     }; | ||||
| } | ||||
|  | @ -0,0 +1,22 @@ | |||
| #pragma once | ||||
| #if defined(GEKKO) | ||||
| #include <ogc/mutex.h> | ||||
| #else | ||||
| #include <threads.h> | ||||
| #endif | ||||
| namespace Tesses::Framework::Threading | ||||
| { | ||||
|     class Mutex { | ||||
|         #if defined(GEKKO) | ||||
|         mutex_t mtx; | ||||
|         #else | ||||
|         mtx_t mtx; | ||||
|         #endif | ||||
|         public: | ||||
|             Mutex(); | ||||
|             void Lock(); | ||||
|             void Unlock(); | ||||
|             bool TryLock(); | ||||
|             ~Mutex(); | ||||
|     }; | ||||
| } | ||||
|  | @ -0,0 +1,27 @@ | |||
| #pragma once | ||||
| #include <functional> | ||||
| #if defined(GEKKO) | ||||
| #include <ogc/lwp.h> | ||||
| #else | ||||
| #include <threads.h> | ||||
| #endif | ||||
| #include <atomic> | ||||
| namespace Tesses::Framework::Threading | ||||
| { | ||||
|     class Thread  | ||||
|     { | ||||
|         std::atomic<bool> hasInvoked; | ||||
|         #if defined(GEKKO) | ||||
|         lwp_t thrd; | ||||
|         static void* cb(void* ptr); | ||||
|         #else | ||||
|         thrd_t thrd; | ||||
|         static int cb(void* ptr); | ||||
|         #endif | ||||
|         std::function<void()> fn; | ||||
|         public: | ||||
|         Thread(std::function<void()> fn); | ||||
|         void Join(); | ||||
|         void Detach(); | ||||
|     }; | ||||
| } | ||||
|  | @ -0,0 +1,263 @@ | |||
| #include "TessesFramework/Crypto/ClientTLSStream.hpp" | ||||
| 
 | ||||
| #if defined(TESSESFRAMEWORK_ENABLE_MBED) | ||||
| #if defined(TESSESFRAMEWORK_EMBED_CERT_BUNDLE) | ||||
| #include "CertificateChain.h" | ||||
| #else | ||||
| #include "TessesFramework/TextStreams/StreamReader.hpp" | ||||
| using StreamReader = Tesses::Framework::TextStreams::StreamReader; | ||||
| #endif | ||||
| 
 | ||||
| #include <mbedtls/entropy.h> | ||||
| #include <mbedtls/ctr_drbg.h> | ||||
| #include <mbedtls/x509.h> | ||||
| #include <mbedtls/ssl.h> | ||||
| #include <mbedtls/net_sockets.h> | ||||
| #include <mbedtls/error.h> | ||||
| #endif | ||||
| #include <cstring> | ||||
| using namespace Tesses::Framework::Streams; | ||||
| 
 | ||||
| 
 | ||||
| namespace Tesses::Framework::Crypto | ||||
| { | ||||
|     #if defined(TESSESFRAMEWORK_ENABLE_MBED) | ||||
|     class ClientTLSPrivateData { | ||||
|         public: | ||||
|             bool eos; | ||||
|             bool owns; | ||||
|             bool success; | ||||
|             Stream* strm; | ||||
|             mbedtls_entropy_context entropy; | ||||
|             mbedtls_ctr_drbg_context ctr_drbg; | ||||
|             mbedtls_ssl_context ssl; | ||||
|             mbedtls_ssl_config conf; | ||||
|             mbedtls_x509_crt cachain; | ||||
|             ~ClientTLSPrivateData() | ||||
|             { | ||||
|                 mbedtls_x509_crt_free(&cachain); | ||||
|                 mbedtls_ctr_drbg_free(&ctr_drbg); | ||||
|                 mbedtls_entropy_free(&entropy); | ||||
|                 mbedtls_ssl_config_free(&conf); | ||||
|                 mbedtls_ssl_free(&ssl); | ||||
|                 if(this->owns) delete strm; | ||||
|             } | ||||
|     }; | ||||
|     #endif | ||||
|     std::string ClientTLSStream::GetCertChain() | ||||
|     { | ||||
|         #if defined(TESSESFRAMEWORK_ENABLE_MBED) | ||||
|         #if defined(TESSESFRAMEWORK_EMBED_CERT_BUNDLE) | ||||
|         return std::string((const char*)CERTIFICATECHAIN,CERTIFICATECHAIN_SIZE); | ||||
|         #else | ||||
|         #if defined(TESSESFRAMEWORK_CERT_BUNDLE_FILE) | ||||
|         StreamReader sr(TESSESFRAMEWORK_CERT_BUNDLE_FILE); | ||||
|         return sr.ReadToEnd(); | ||||
|         #endif | ||||
|         #endif | ||||
|         #endif | ||||
|         return ""; | ||||
|     } | ||||
| 
 | ||||
|     ClientTLSStream::ClientTLSStream(Tesses::Framework::Streams::Stream* innerStream, bool owns, bool verify, std::string domain) : ClientTLSStream(innerStream,owns,verify,domain,"") | ||||
|     { | ||||
|          | ||||
|     } | ||||
| 
 | ||||
|     ClientTLSStream::ClientTLSStream(Tesses::Framework::Streams::Stream* innerStream, bool owns, bool verify, std::string domain, std::string cert) | ||||
|     { | ||||
|         #if defined(TESSESFRAMEWORK_ENABLE_MBED) | ||||
|         if(cert.empty()) | ||||
|         { | ||||
|             cert = GetCertChain(); | ||||
|         } | ||||
| 
 | ||||
|         ClientTLSPrivateData* data = new ClientTLSPrivateData(); | ||||
|         this->privateData = static_cast<void*>(data); | ||||
|         data->eos=false; | ||||
|         data->success=false; | ||||
|         data->strm = innerStream; | ||||
|         data->owns = owns; | ||||
| 
 | ||||
|          mbedtls_ssl_init(&data->ssl); | ||||
|         mbedtls_ssl_config_init(&data->conf); | ||||
|         mbedtls_x509_crt_init(&data->cachain); | ||||
|         mbedtls_ctr_drbg_init(&data->ctr_drbg); | ||||
|         mbedtls_entropy_init(&data->entropy); | ||||
|          | ||||
|         const char* pers = "TessesFramework"; | ||||
|          | ||||
|         int ret=0; | ||||
| 
 | ||||
| 
 | ||||
| /*
 | ||||
| #if defined(MBEDTLS_USE_PSA_CRYPTO) | ||||
|     psa_status_t status = psa_crypto_init(); | ||||
|     if (status != PSA_SUCCESS) { | ||||
|         mbedtls_fprintf(stderr, "Failed to initialize PSA Crypto implementation: %d\n", | ||||
|                         (int) status); | ||||
|         return; | ||||
|     } | ||||
| #endif | ||||
| */ | ||||
|     if ((ret = mbedtls_ctr_drbg_seed(&data->ctr_drbg, mbedtls_entropy_func, &data->entropy, | ||||
|                                      (const unsigned char *) pers, | ||||
|                                      strlen(pers))) != 0)  | ||||
|                                      { | ||||
|                                         printf("FAILED mbedtls_ctr_drbg_seed\n"); | ||||
|                                         return; | ||||
|                                      } | ||||
|        | ||||
|         if(ret != 0) { printf("FAILED mbedtls_x509_crt_parse cert %i\n",ret); return;} | ||||
|         ret = mbedtls_x509_crt_parse(&data->cachain, (const unsigned char *) cert.c_str(), | ||||
|                                 cert.size()+1); | ||||
| 
 | ||||
|         if(ret != 0) {printf("FAILED mbedtls_x509_crt_parse chain %i\n",ret); return;} | ||||
|         | ||||
| 
 | ||||
|          | ||||
|         if((ret = mbedtls_ssl_config_defaults(&data->conf, | ||||
|                                            MBEDTLS_SSL_IS_CLIENT, | ||||
|                                            MBEDTLS_SSL_TRANSPORT_STREAM, | ||||
|                                            MBEDTLS_SSL_PRESET_DEFAULT)) != 0) | ||||
|                                            { | ||||
|                                             char buffer[100]; | ||||
|                                             mbedtls_strerror(ret,buffer,sizeof(buffer)); | ||||
|                                             printf("FAILED mbedtls_ssl_conf_defaults %s\n",buffer);  | ||||
|                                                 return; | ||||
|                                            } | ||||
| 
 | ||||
|                                             | ||||
|                                             | ||||
|                                             | ||||
|         mbedtls_ssl_conf_rng(&data->conf, mbedtls_ctr_drbg_random, &data->ctr_drbg); | ||||
| 
 | ||||
|       /* #if defined(MBEDTLS_SSL_CACHE_C)
 | ||||
|     mbedtls_ssl_conf_session_cache(&conf, &cache, | ||||
|                                    mbedtls_ssl_cache_get, | ||||
|                                    mbedtls_ssl_cache_set); | ||||
| #endif*/ | ||||
|         mbedtls_ssl_conf_authmode(&data->conf, verify ? MBEDTLS_SSL_VERIFY_REQUIRED: MBEDTLS_SSL_VERIFY_OPTIONAL); | ||||
|         mbedtls_ssl_conf_ca_chain(&data->conf, &data->cachain, NULL); | ||||
| 
 | ||||
| 
 | ||||
|         mbedtls_ssl_set_bio(&data->ssl, static_cast<void*>(data),strm_send,strm_recv, NULL); | ||||
|         if((ret=mbedtls_ssl_setup(&data->ssl,&data->conf) != 0)) | ||||
|         { | ||||
|             printf("FAILED mbedtls_ssl_setup %i\n",ret);  | ||||
|             return; | ||||
|         } | ||||
|         if((ret=mbedtls_ssl_set_hostname(&data->ssl,domain.c_str()) != 0)) | ||||
|         { | ||||
|             printf("FAILED mbedtls_ssl_set_hostname %i\n",ret);  | ||||
|             return; | ||||
|         } | ||||
|         if((ret = mbedtls_ssl_handshake(&data->ssl)) != 0) | ||||
|         { | ||||
|             char buffer[100]; | ||||
|             mbedtls_strerror(ret,buffer,sizeof(buffer)); | ||||
|             printf("FAILED mbedtls_ssl_handshake %s\n",buffer); | ||||
|             return; | ||||
|         } | ||||
|             uint32_t flags; | ||||
|           if ((flags = mbedtls_ssl_get_verify_result(&data->ssl)) != 0) { | ||||
| #if !defined(MBEDTLS_X509_REMOVE_INFO) | ||||
|         char vrfy_buf[512]; | ||||
| #endif | ||||
| 
 | ||||
|          | ||||
| 
 | ||||
| #if !defined(MBEDTLS_X509_REMOVE_INFO) | ||||
|         mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), "  ! ", flags); | ||||
| 
 | ||||
|        | ||||
| #endif | ||||
|         if(verify) | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|         data->success=true; | ||||
| 
 | ||||
|         #endif | ||||
|     } | ||||
|     size_t ClientTLSStream::Read(uint8_t* buffer, size_t len) | ||||
|     { | ||||
|         #if defined(TESSESFRAMEWORK_ENABLE_MBED) | ||||
|         auto priv = static_cast<ClientTLSPrivateData*>(this->privateData); | ||||
|         if(!priv->success) return 0; | ||||
|         if(priv->eos) return 0; | ||||
|         int r = mbedtls_ssl_read(&priv->ssl,buffer,len); | ||||
|          | ||||
| 
 | ||||
| 
 | ||||
|         if(r == -30848)  | ||||
|         { | ||||
|             priv->eos = true; | ||||
|             return 0; | ||||
|         } | ||||
|         return (size_t)r; | ||||
|         #else | ||||
|         return (size_t)0; | ||||
|         #endif | ||||
|     } | ||||
|     size_t ClientTLSStream::Write(const uint8_t* buffer, size_t len) | ||||
|     { | ||||
|         #if defined(TESSESFRAMEWORK_ENABLE_MBED) | ||||
|         auto priv = static_cast<ClientTLSPrivateData*>(this->privateData); | ||||
|         if(!priv->success) return 0; | ||||
|         int r = mbedtls_ssl_write(&priv->ssl,buffer,len); | ||||
|         return (size_t)r; | ||||
|         #else | ||||
|         return (size_t)0; | ||||
|         #endif | ||||
|     } | ||||
|     int ClientTLSStream::strm_send(void* ctx,const unsigned char* buf,size_t len) | ||||
|     { | ||||
|         #if defined(TESSESFRAMEWORK_ENABLE_MBED) | ||||
|         auto priv = static_cast<ClientTLSPrivateData*>(ctx); | ||||
|         return (int)priv->strm->Write(buf, len); | ||||
|         #else | ||||
|         return 0; | ||||
|         #endif | ||||
|     } | ||||
|     int ClientTLSStream::strm_recv(void* ctx,unsigned char* buf,size_t len) | ||||
|     { | ||||
|         #if defined(TESSESFRAMEWORK_ENABLE_MBED) | ||||
|         auto priv = static_cast<ClientTLSPrivateData*>(ctx); | ||||
|         return (int)priv->strm->Read(buf, len); | ||||
|         #else | ||||
|         return 0; | ||||
|         #endif | ||||
|     } | ||||
|     bool ClientTLSStream::CanRead() | ||||
|     { | ||||
|         #if defined(TESSESFRAMEWORK_ENABLE_MBED) | ||||
|         return !(!static_cast<ClientTLSPrivateData*>(this->privateData)->success || static_cast<ClientTLSPrivateData*>(this->privateData)->eos); | ||||
|         #else | ||||
|         return false; | ||||
|         #endif | ||||
|     } | ||||
|     bool ClientTLSStream::CanWrite() | ||||
|     { | ||||
|          | ||||
|         #if defined(TESSESFRAMEWORK_ENABLE_MBED) | ||||
|         return !(!static_cast<ClientTLSPrivateData*>(this->privateData)->success || static_cast<ClientTLSPrivateData*>(this->privateData)->eos); | ||||
|         #else | ||||
|         return false; | ||||
|         #endif     | ||||
|     } | ||||
|     bool ClientTLSStream::EndOfStream() | ||||
|     { | ||||
|         #if defined(TESSESFRAMEWORK_ENABLE_MBED) | ||||
|         return !static_cast<ClientTLSPrivateData*>(this->privateData)->success || static_cast<ClientTLSPrivateData*>(this->privateData)->eos; | ||||
|         #else | ||||
|         return true; | ||||
|         #endif | ||||
|     } | ||||
|     ClientTLSStream::~ClientTLSStream() | ||||
|     { | ||||
|         #if defined(TESSESFRAMEWORK_ENABLE_MBED) | ||||
|         delete static_cast<ClientTLSPrivateData*>(this->privateData); | ||||
|         #endif | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,122 @@ | |||
| #include "TessesFramework/Filesystem/LocalFS.hpp" | ||||
| #include "TessesFramework/Streams/FileStream.hpp" | ||||
| #include <iostream> | ||||
| namespace Tesses::Framework::Filesystem | ||||
| { | ||||
|     VFSPath LocalFilesystem::ReadLink(VFSPath path) | ||||
|     { | ||||
|         return this->SystemToVFSPath(std::filesystem::read_symlink(this->VFSPathToSystem(path))); | ||||
|     } | ||||
|     Tesses::Framework::Streams::Stream* LocalFilesystem::OpenFile(VFSPath path, std::string mode) | ||||
|     { | ||||
|         return new Tesses::Framework::Streams::FileStream(VFSPathToSystem(path), mode); | ||||
|     } | ||||
| 
 | ||||
|     void LocalFilesystem::DeleteDirectory(VFSPath path) | ||||
|     { | ||||
|         std::filesystem::remove(VFSPathToSystem(path)); | ||||
|     } | ||||
|     void LocalFilesystem::DeleteFile(VFSPath path) | ||||
|     { | ||||
|         std::filesystem::remove(VFSPathToSystem(path)); | ||||
|     } | ||||
|     void LocalFilesystem::CreateDirectory(VFSPath path) | ||||
|     { | ||||
|         std::filesystem::create_directories(VFSPathToSystem(path)); | ||||
|     } | ||||
|     bool LocalFilesystem::DirectoryExists(VFSPath path) | ||||
|     { | ||||
|         return std::filesystem::is_directory(VFSPathToSystem(path)); | ||||
|     } | ||||
|     bool LocalFilesystem::RegularFileExists(VFSPath path) | ||||
|     { | ||||
|         return std::filesystem::is_regular_file(VFSPathToSystem(path)); | ||||
|     } | ||||
|     bool LocalFilesystem::SymlinkExists(VFSPath path) | ||||
|     { | ||||
|         return std::filesystem::is_symlink(VFSPathToSystem(path)); | ||||
|     } | ||||
|     bool LocalFilesystem::BlockDeviceExists(VFSPath path) | ||||
|     { | ||||
|         return std::filesystem::is_block_file(VFSPathToSystem(path)); | ||||
|     } | ||||
|     bool LocalFilesystem::CharacterDeviceExists(VFSPath path) | ||||
|     { | ||||
|         return std::filesystem::is_character_file(VFSPathToSystem(path)); | ||||
|     } | ||||
|     bool LocalFilesystem::SocketFileExists(VFSPath path) | ||||
|     { | ||||
|         return std::filesystem::is_socket(VFSPathToSystem(path)); | ||||
|     } | ||||
|     bool LocalFilesystem::FIFOFileExists(VFSPath path) | ||||
|     { | ||||
|         return std::filesystem::is_fifo(VFSPathToSystem(path)); | ||||
|     } | ||||
|     void LocalFilesystem::CreateSymlink(VFSPath existingFile, VFSPath symlinkFile) | ||||
|     { | ||||
|         if(std::filesystem::is_directory(VFSPathToSystem(existingFile))) | ||||
|         { | ||||
|             std::filesystem::create_directory_symlink(VFSPathToSystem(existingFile),VFSPathToSystem(symlinkFile)); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             std::filesystem::create_symlink(VFSPathToSystem(existingFile),VFSPathToSystem(symlinkFile)); | ||||
|         } | ||||
|     } | ||||
|     void LocalFilesystem::CreateHardlink(VFSPath existingFile, VFSPath newName) | ||||
|     { | ||||
|         std::filesystem::create_hard_link(VFSPathToSystem(existingFile),VFSPathToSystem(newName)); | ||||
|     } | ||||
|     void LocalFilesystem::MoveFile(VFSPath src, VFSPath dest) | ||||
|     { | ||||
|         std::filesystem::rename(VFSPathToSystem(src),VFSPathToSystem(dest)); | ||||
|     } | ||||
|     void LocalFilesystem::MoveDirectory(VFSPath src, VFSPath dest) | ||||
|     { | ||||
|         std::filesystem::rename(VFSPathToSystem(src),VFSPathToSystem(dest)); | ||||
|     } | ||||
|     std::string LocalFilesystem::VFSPathToSystem(VFSPath path) | ||||
|     { | ||||
|         #if defined(WIN32) | ||||
|         bool first=true; | ||||
|         std::string p = {}; | ||||
|         for(auto item : path.path) | ||||
|         { | ||||
|             if(!(first && !item.empty() && item.back()==':') && !(first && this->relative)) | ||||
|                 p.push_back('\\'); | ||||
|             p.append(item); | ||||
|             first=false; | ||||
|         } | ||||
|         return p; | ||||
| 
 | ||||
|         #else | ||||
|         return path.ToString(); | ||||
|         #endif | ||||
|     } | ||||
|     VFSPath LocalFilesystem::SystemToVFSPath(std::string path) | ||||
|     { | ||||
|         VFSPath p; | ||||
|         p.path = VFSPath::SplitPath(path,true); | ||||
|         p.relative=true; | ||||
|         if(!path.empty()) | ||||
|         { | ||||
|             if(path.front() == '/') p.relative=false; | ||||
|             if(!p.path.empty()) | ||||
|             { | ||||
|                 auto firstPartPath = p.path.front(); | ||||
| 
 | ||||
|                 if(!firstPartPath.empty() && firstPartPath.back() == '/') p.relative=false; | ||||
|             } | ||||
|         } | ||||
|         return p; | ||||
|     } | ||||
|     void LocalFilesystem::GetPaths(VFSPath path, std::vector<VFSPath>& paths) | ||||
|     { | ||||
|         for(auto item : std::filesystem::directory_iterator(VFSPathToSystem(path))) | ||||
|         { | ||||
|             paths.push_back(VFSPath(path, item.path().filename().string())); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // C:/Users/Jim/Joel
 | ||||
|  | @ -0,0 +1,553 @@ | |||
| #include "TessesFramework/Filesystem/MountableFilesystem.hpp" | ||||
| #include "TessesFramework/Filesystem/NullFilesystem.hpp" | ||||
| #include <iostream> | ||||
| namespace Tesses::Framework::Filesystem | ||||
| { | ||||
|     MountableFilesystem::MountableFilesystem() : MountableFilesystem(new NullFilesystem(),true) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
|     MountableFilesystem::MountableFilesystem(VFS* root, bool owns) | ||||
|     { | ||||
|         this->root = root; | ||||
|         this->owns = owns; | ||||
|     } | ||||
|     MountableDirectory::~MountableDirectory() | ||||
|     { | ||||
|         if(this->owns && this->vfs) delete this->vfs; | ||||
|         for(auto dir : this->dirs) delete dir; | ||||
|     } | ||||
| 
 | ||||
|     MountableFilesystem::~MountableFilesystem() | ||||
|     { | ||||
|         if(this->owns && this->root) delete root; | ||||
|         for(auto item : this->directories) delete item; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     void MountableFilesystem::GetFS(VFSPath srcPath, VFSPath& destRoot, VFSPath& destPath, VFS*& vfs)  | ||||
|     { | ||||
|         if(srcPath.path.empty()) return; | ||||
|         for(auto item : this->directories) | ||||
|         { | ||||
|             if(srcPath.path.front() == item->name) | ||||
|             { | ||||
|                 if(item->vfs != nullptr) | ||||
|                 { | ||||
|                     vfs = item->vfs; | ||||
|                     VFSPath srcPath1(std::vector(srcPath.path.begin()+1,srcPath.path.end())); | ||||
|                     srcPath1.relative=false; | ||||
|                     destPath = srcPath1; | ||||
|                     destRoot = VFSPath(VFSPath(),item->name); | ||||
|                      | ||||
|                      | ||||
|                 } | ||||
|                 VFSPath srcPath2(std::vector(srcPath.path.begin()+1,srcPath.path.end())); | ||||
|                 srcPath2.relative=false; | ||||
|                 item->GetFS(srcPath2,VFSPath(VFSPath(),item->name), destRoot,destPath,vfs); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|    | ||||
|      | ||||
|     void MountableDirectory::GetFS(VFSPath srcPath, VFSPath curDir, VFSPath& destRoot, VFSPath& destPath, VFS*& vfs)  | ||||
|     { | ||||
|         if(srcPath.path.empty()) return; | ||||
|         for(auto item : this->dirs) | ||||
|         { | ||||
|             if(!srcPath.path.empty() && srcPath.path.front() == item->name) | ||||
|             { | ||||
|                 if(item->vfs != nullptr) | ||||
|                 { | ||||
|                     vfs = item->vfs; | ||||
|                      | ||||
|                     VFSPath srcPath1(std::vector(srcPath.path.begin()+1,srcPath.path.end())); | ||||
| 
 | ||||
|                     srcPath1.relative=false; | ||||
|                     destPath = srcPath1; | ||||
|                     destRoot = curDir; | ||||
|                      | ||||
|                 } | ||||
|                 VFSPath srcPath2(std::vector(srcPath.path.begin()+1,srcPath.path.end())); | ||||
|                 srcPath2.relative=false; | ||||
|                 item->GetFS(srcPath2,VFSPath(curDir,item->name), destRoot,destPath,vfs); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     VFSPath MountableFilesystem::ReadLink(VFSPath path) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
|         if(vfs != nullptr) | ||||
|             return VFSPath(destRoot,vfs->ReadLink(destPath)); | ||||
|         return VFSPath(); | ||||
|     } | ||||
| 
 | ||||
|     Tesses::Framework::Streams::Stream* MountableFilesystem::OpenFile(VFSPath path, std::string mode) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|             return vfs->OpenFile(destPath,mode); | ||||
|         return nullptr; | ||||
|     } | ||||
|     void MountableFilesystem::CreateDirectory(VFSPath path) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
| 
 | ||||
|         if(destPath.path.empty()) return; | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|             vfs->CreateDirectory(destPath); | ||||
|          | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     void MountableFilesystem::DeleteDirectory(VFSPath path) | ||||
|     { | ||||
|          path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
| 
 | ||||
|         if(destPath.path.empty()) return; | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|             vfs->DeleteDirectory(destPath); | ||||
|          | ||||
|     } | ||||
| 
 | ||||
|     bool MountableFilesystem::SpecialFileExists(VFSPath path) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|             return vfs->SpecialFileExists(destPath); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bool MountableFilesystem::FileExists(VFSPath path) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|             return vfs->FileExists(destPath); | ||||
|         return false; | ||||
|     } | ||||
|     bool MountableFilesystem::RegularFileExists(VFSPath path) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|             return vfs->RegularFileExists(destPath); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bool MountableFilesystem::SymlinkExists(VFSPath path) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|             return vfs->SymlinkExists(destPath); | ||||
|         return false; | ||||
|     } | ||||
|     bool MountableFilesystem::CharacterDeviceExists(VFSPath path) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|             return vfs->CharacterDeviceExists(destPath); | ||||
|         return false; | ||||
|     } | ||||
|     bool MountableFilesystem::BlockDeviceExists(VFSPath path) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|             return vfs->BlockDeviceExists(destPath); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bool MountableFilesystem::SocketFileExists(VFSPath path) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|             return vfs->SocketFileExists(destPath); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     bool MountableFilesystem::FIFOFileExists(VFSPath path) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|             return vfs->FIFOFileExists(destPath); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     bool MountableFilesystem::DirectoryExists(VFSPath path) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
|         if(destPath.path.empty()) return true; | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|             return vfs->DirectoryExists(destPath); | ||||
|         return false; | ||||
|     } | ||||
|     void MountableFilesystem::DeleteFile(VFSPath path) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|             vfs->DeleteFile(destPath); | ||||
|         | ||||
|     } | ||||
|     void MountableFilesystem::CreateSymlink(VFSPath existingFile, VFSPath symlinkFile) | ||||
|     { | ||||
|         existingFile = existingFile.CollapseRelativeParents(); | ||||
|         symlinkFile = existingFile.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath existingDestRoot; | ||||
|         VFSPath existingDestPath = existingFile; | ||||
|         VFS* existingVFS = root; | ||||
|         VFSPath symlinkDestRoot; | ||||
|         VFSPath symlinkDestPath = symlinkFile; | ||||
|         VFS* symlinkVFS = root; | ||||
|          | ||||
|         GetFS(existingFile, existingDestRoot, existingDestPath, existingVFS); | ||||
|         GetFS(symlinkFile, symlinkDestRoot, symlinkDestPath, symlinkVFS); | ||||
|          | ||||
|         if(existingVFS != nullptr && existingVFS == symlinkVFS) | ||||
|             existingVFS->CreateSymlink(existingDestPath, symlinkDestPath); | ||||
|     } | ||||
| 
 | ||||
|     void MountableFilesystem::MoveDirectory(VFSPath src, VFSPath dest) | ||||
|     { | ||||
|         src = src.CollapseRelativeParents(); | ||||
|         dest = dest.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath srcDestRoot; | ||||
|         VFSPath srcDestPath = src; | ||||
|         VFS* srcVFS = root; | ||||
|         VFSPath destDestRoot; | ||||
|         VFSPath destDestPath = dest; | ||||
|         VFS* destVFS = root; | ||||
|          | ||||
|         GetFS(src, srcDestRoot, srcDestPath, srcVFS); | ||||
|         GetFS(dest, destDestRoot, destDestPath, destVFS); | ||||
|          | ||||
|         if(srcVFS != nullptr && srcVFS == destVFS) | ||||
|             srcVFS->MoveDirectory(srcDestPath, destDestPath); | ||||
|     } | ||||
|     void MountableFilesystem::MoveFile(VFSPath src, VFSPath dest) | ||||
|     { | ||||
|         src = src.CollapseRelativeParents(); | ||||
|         dest = dest.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath srcDestRoot; | ||||
|         VFSPath srcDestPath = src; | ||||
|         VFS* srcVFS = root; | ||||
|         VFSPath destDestRoot; | ||||
|         VFSPath destDestPath = dest; | ||||
|         VFS* destVFS = root; | ||||
|          | ||||
|         GetFS(src, srcDestRoot, srcDestPath, srcVFS); | ||||
|         GetFS(dest, destDestRoot, destDestPath, destVFS); | ||||
|          | ||||
|         if(srcVFS != nullptr && srcVFS == destVFS) | ||||
|             srcVFS->MoveFile(srcDestPath, destDestPath); | ||||
|     } | ||||
|     void MountableFilesystem::CreateHardlink(VFSPath existingFile, VFSPath newName) | ||||
|     { | ||||
|         existingFile = existingFile.CollapseRelativeParents(); | ||||
|         newName = existingFile.CollapseRelativeParents(); | ||||
|         | ||||
|         VFSPath existingDestRoot; | ||||
|         VFSPath existingDestPath = existingFile; | ||||
|         VFS* existingVFS = root; | ||||
|         VFSPath newNameRoot; | ||||
|         VFSPath newNamePath = newName; | ||||
|         VFS* newNameVFS = root; | ||||
|          | ||||
|         GetFS(existingFile, existingDestRoot, existingDestPath, existingVFS); | ||||
|         GetFS(newName, newNameRoot, newNamePath, newNameVFS); | ||||
|          | ||||
|         if(existingVFS != nullptr && existingVFS == newNameVFS) | ||||
|             existingVFS->CreateHardlink(existingDestPath, newNamePath); | ||||
|     } | ||||
|     void MountableFilesystem::GetPaths(VFSPath path, std::vector<VFSPath>& paths) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         bool mydirs = path.path.empty(); | ||||
|         std::vector<MountableDirectory*>* dirs = &this->directories; | ||||
|         if(!path.path.empty()) | ||||
|         for(auto p : path.path) | ||||
|         { | ||||
|             mydirs=true; | ||||
|             bool hasSet=false; | ||||
|          | ||||
|             for(auto itm : *dirs) | ||||
|             { | ||||
|                 if(itm->name == p) | ||||
|                 { | ||||
|                     hasSet=true; | ||||
|                     dirs = &itm->dirs; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(!hasSet) | ||||
|             { | ||||
|                 mydirs=false; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         | ||||
|         VFSPath destRoot; | ||||
|         VFSPath destPath = path; | ||||
|         VFS* vfs = root; | ||||
|          | ||||
|         GetFS(path, destRoot, destPath, vfs); | ||||
|          | ||||
| 
 | ||||
|         if(vfs != nullptr) | ||||
|         { | ||||
|             std::vector<VFSPath> paths2; | ||||
|             if(vfs->DirectoryExists(destPath) || !mydirs) | ||||
|             vfs->GetPaths(destPath,paths2); | ||||
|             for(auto item : paths2) | ||||
|             { | ||||
|                 paths.push_back(VFSPath(destPath, item.GetFileName())); | ||||
|             } | ||||
|             if(mydirs) | ||||
|             for(auto item : *dirs) | ||||
|             { | ||||
|                 bool cantAdd=false; | ||||
|                 for(auto d : paths) { | ||||
|                     if(d.GetFileName() == item->name) | ||||
|                     { | ||||
|                         cantAdd=true; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 if(!cantAdd) paths.push_back(VFSPath(destPath, item->name)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     void MountableFilesystem::Mount(VFSPath path, VFS* fs, bool owns) | ||||
|     { | ||||
|          path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         if(path.path.empty()) | ||||
|         { | ||||
|             if(owns) delete fs; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         auto* fsLs = &this->directories; | ||||
|         bool needToCreate=true; | ||||
|         for(auto index = path.path.begin(); index < path.path.end()-1; index++) | ||||
|         { | ||||
|             needToCreate=true; | ||||
|             for(auto item : *fsLs) | ||||
|             { | ||||
|                 if(item->name == *index) | ||||
|                 { | ||||
|                     needToCreate=false; | ||||
|                     fsLs = &(item->dirs); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if(needToCreate) | ||||
|             { | ||||
|                 MountableDirectory* dir = new MountableDirectory(); | ||||
|                 dir->name = *index; | ||||
|                 dir->owns=false; | ||||
|                 dir->vfs=NULL; | ||||
|                  | ||||
|                 fsLs->push_back(dir); | ||||
|                 fsLs = &(dir->dirs); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         needToCreate=true; | ||||
|         std::string lastDir = path.GetFileName(); | ||||
| 
 | ||||
|         for(auto item : *fsLs) | ||||
|         { | ||||
|             if(item->name == lastDir) | ||||
|             { | ||||
|                 needToCreate=false; | ||||
|                 if(item->owns && item->vfs) delete item->vfs; | ||||
|                 item->vfs = fs; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(needToCreate) | ||||
|         { | ||||
|              MountableDirectory* dir = new MountableDirectory(); | ||||
|                 dir->name = lastDir; | ||||
|                 dir->owns=owns; | ||||
|                 dir->vfs=fs; | ||||
|                 fsLs->push_back(dir); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     static bool myumount(MountableDirectory* dir,VFSPath path) | ||||
|     { | ||||
|         if(path.path.empty())  | ||||
|         { | ||||
|             if(dir->owns && dir->vfs) delete dir->vfs; | ||||
|             dir->vfs = nullptr; | ||||
|         } | ||||
|          | ||||
|         if(dir->dirs.empty()) | ||||
|         { | ||||
|             delete dir; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         for(auto index = dir->dirs.begin(); index < dir->dirs.end(); index++) | ||||
|         { | ||||
|             auto item = *index; | ||||
|             if(!path.path.empty() && path.path.front() == item->name) | ||||
|             { | ||||
|                 VFSPath srcPath2(std::vector(path.path.begin()+1,path.path.end())); | ||||
| 
 | ||||
|                  | ||||
|                  | ||||
|                 if(myumount(item,srcPath2)) | ||||
|                 { | ||||
|                     dir->dirs.erase(index); | ||||
|                 } | ||||
| 
 | ||||
|                 if(dir->dirs.empty()) | ||||
|                 { | ||||
|                     delete dir; | ||||
|                     return true; | ||||
|                 } | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     void MountableFilesystem::Unmount(VFSPath path) | ||||
|     { | ||||
|         path = path.CollapseRelativeParents(); | ||||
|         | ||||
|         for(auto index = this->directories.begin(); index < this->directories.end(); index++) | ||||
|         { | ||||
|             auto item = *index; | ||||
|             if(!path.path.empty() && path.path.front() == item->name) | ||||
|             { | ||||
|                  VFSPath srcPath2(std::vector(path.path.begin()+1,path.path.end())); | ||||
|                  | ||||
|                 if(myumount(item,srcPath2)) | ||||
|                 { | ||||
|                     this->directories.erase(index); | ||||
|                 } | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     std::string MountableFilesystem::VFSPathToSystem(VFSPath path) | ||||
|     { | ||||
|         return path.ToString(); | ||||
|     } | ||||
|     VFSPath MountableFilesystem::SystemToVFSPath(std::string path) | ||||
|     { | ||||
|         return VFSPath(path); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,45 @@ | |||
| #include "TessesFramework/Filesystem/NullFilesystem.hpp" | ||||
| 
 | ||||
| namespace Tesses::Framework::Filesystem | ||||
| { | ||||
|     Tesses::Framework::Streams::Stream* NullFilesystem::OpenFile(VFSPath path, std::string mode) | ||||
|     { | ||||
|         return nullptr; | ||||
|     } | ||||
|     void NullFilesystem::CreateDirectory(VFSPath path) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
|     void NullFilesystem::DeleteDirectory(VFSPath path) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
|     bool NullFilesystem::RegularFileExists(VFSPath path) | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
|     bool NullFilesystem::DirectoryExists(VFSPath path) | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
|     void NullFilesystem::DeleteFile(VFSPath path) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
|     void NullFilesystem::GetPaths(VFSPath path, std::vector<VFSPath>& paths) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
|     void NullFilesystem::MoveFile(VFSPath src, VFSPath dest) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
|     std::string NullFilesystem::VFSPathToSystem(VFSPath path) | ||||
|     { | ||||
|         return path.ToString(); | ||||
|     } | ||||
|     VFSPath NullFilesystem::SystemToVFSPath(std::string path) | ||||
|     { | ||||
|         return VFSPath(path); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,132 @@ | |||
| #include "TessesFramework/Filesystem/SubdirFilesystem.hpp" | ||||
| #include <iostream> | ||||
| namespace Tesses::Framework::Filesystem | ||||
| { | ||||
|     VFSPath SubdirFilesystem::ReadLink(VFSPath path) | ||||
|     { | ||||
|         return FromParent(this->parent->ReadLink(ToParent(path))); | ||||
|     } | ||||
|     VFSPath SubdirFilesystem::FromParent(VFSPath path) | ||||
|     { | ||||
|         // /a/b/c
 | ||||
|         // /a/b/c
 | ||||
|         VFSPath newPath; | ||||
|         newPath.relative=false; | ||||
|        | ||||
|         if(path.path.size() >= this->path.path.size()) | ||||
|         { | ||||
|             newPath.path.reserve(path.path.size()-this->path.path.size()); | ||||
|             for(size_t i = this->path.path.size(); i < path.path.size();i++) | ||||
|             { | ||||
|                 newPath.path.push_back(path.path[i]); | ||||
|             } | ||||
|         } | ||||
|         return newPath; | ||||
|     } | ||||
| 
 | ||||
|     VFSPath SubdirFilesystem::ToParent(VFSPath path) | ||||
|     { | ||||
|         return VFSPath(this->path, path.CollapseRelativeParents()); | ||||
|     } | ||||
|     SubdirFilesystem::SubdirFilesystem(VFS* parent, VFSPath path, bool owns) | ||||
|     { | ||||
|         this->parent = parent; | ||||
|         this->path = path; | ||||
|         this->owns=owns; | ||||
|     } | ||||
|     Tesses::Framework::Streams::Stream* SubdirFilesystem::OpenFile(VFSPath path, std::string mode) | ||||
|     { | ||||
|         return this->parent->OpenFile(ToParent(path),mode); | ||||
|     } | ||||
|     void SubdirFilesystem::CreateDirectory(VFSPath path) | ||||
|     { | ||||
|         this->parent->CreateDirectory(ToParent(path)); | ||||
|     } | ||||
|     void SubdirFilesystem::DeleteDirectory(VFSPath path) | ||||
|     { | ||||
|         this->parent->DeleteDirectory(ToParent(path)); | ||||
|     } | ||||
|     bool SubdirFilesystem::RegularFileExists(VFSPath path) | ||||
|     { | ||||
|         return this->parent->RegularFileExists(ToParent(path)); | ||||
|     } | ||||
|     bool SubdirFilesystem::SymlinkExists(VFSPath path) | ||||
|     { | ||||
|         return this->parent->SymlinkExists(ToParent(path)); | ||||
|     } | ||||
|     bool SubdirFilesystem::CharacterDeviceExists(VFSPath path) | ||||
|     { | ||||
|         return this->parent->CharacterDeviceExists(ToParent(path)); | ||||
|     } | ||||
|     bool SubdirFilesystem::BlockDeviceExists(VFSPath path) | ||||
|     { | ||||
|         return this->parent->BlockDeviceExists(ToParent(path)); | ||||
|     } | ||||
|     bool SubdirFilesystem::SocketFileExists(VFSPath path) | ||||
|     { | ||||
|         return this->parent->SocketFileExists(ToParent(path)); | ||||
|     } | ||||
|     bool SubdirFilesystem::FIFOFileExists(VFSPath path) | ||||
|     { | ||||
|         return this->parent->FIFOFileExists(ToParent(path)); | ||||
|     } | ||||
|     bool SubdirFilesystem::DirectoryExists(VFSPath path) | ||||
|     { | ||||
|         return this->parent->DirectoryExists(ToParent(path)); | ||||
|     } | ||||
|     void SubdirFilesystem::DeleteFile(VFSPath path) | ||||
|     { | ||||
|         this->parent->DeleteFile(ToParent(path)); | ||||
|     } | ||||
|     void SubdirFilesystem::CreateSymlink(VFSPath existingFile, VFSPath symlinkFile) | ||||
|     { | ||||
|         this->parent->CreateSymlink(ToParent(existingFile),ToParent(symlinkFile)); | ||||
|     } | ||||
|     void SubdirFilesystem::GetPaths(VFSPath path, std::vector<VFSPath>& paths) | ||||
|     { | ||||
|         std::vector<VFSPath> paths2; | ||||
|         this->parent->GetPaths(ToParent(path),paths2); | ||||
|         for(auto item : paths2) | ||||
|         { | ||||
|             paths.push_back(FromParent(item)); | ||||
|         } | ||||
|     } | ||||
|     void SubdirFilesystem::CreateHardlink(VFSPath existingFile, VFSPath newName) | ||||
|     { | ||||
|         this->parent->CreateHardlink(ToParent(existingFile),ToParent(newName)); | ||||
|     } | ||||
|     void SubdirFilesystem::MoveFile(VFSPath src, VFSPath dest) | ||||
|     { | ||||
|         this->parent->MoveFile(ToParent(src),ToParent(dest)); | ||||
|     } | ||||
|     void SubdirFilesystem::MoveDirectory(VFSPath src, VFSPath dest) | ||||
|     { | ||||
|         this->parent->MoveDirectory(ToParent(src),ToParent(dest)); | ||||
|     } | ||||
|     std::string SubdirFilesystem::VFSPathToSystem(VFSPath path) | ||||
|     { | ||||
|         return this->parent->VFSPathToSystem(ToParent(path)); | ||||
|     } | ||||
|     VFSPath SubdirFilesystem::SystemToVFSPath(std::string path) | ||||
|     {    | ||||
|         return FromParent(this->parent->SystemToVFSPath(path)); | ||||
|     } | ||||
|     void SubdirFilesystem::DeleteDirectoryRecurse(VFSPath path) | ||||
|     { | ||||
|         this->parent->DeleteDirectoryRecurse(ToParent(path)); | ||||
|     } | ||||
|     bool SubdirFilesystem::SpecialFileExists(VFSPath path) | ||||
|     { | ||||
|         return this->parent->SpecialFileExists(ToParent(path)); | ||||
|     } | ||||
|     bool SubdirFilesystem::FileExists(VFSPath path) | ||||
|     { | ||||
|         return this->parent->FileExists(ToParent(path)); | ||||
|     } | ||||
|              | ||||
|     SubdirFilesystem::~SubdirFilesystem() | ||||
|     { | ||||
|         if(this->owns) | ||||
|             delete this->parent; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,208 @@ | |||
| #include "TessesFramework/Filesystem/VFS.hpp" | ||||
| #include "TessesFramework/Http/HttpUtils.hpp" | ||||
| namespace Tesses::Framework::Filesystem | ||||
| { | ||||
|     VFSPath VFS::ReadLink(VFSPath path) | ||||
|     { | ||||
|         return VFSPath("/"); | ||||
|     } | ||||
|     VFSPath VFSPath::CollapseRelativeParents() | ||||
|     { | ||||
|         std::vector<std::string> parts; | ||||
| 
 | ||||
|         for(auto item : this->path) | ||||
|         { | ||||
|             if(item == "..") | ||||
|             { | ||||
|                 if(!parts.empty()) | ||||
|                 { | ||||
|                     parts.erase(parts.end()-1); | ||||
|                 } | ||||
|             } | ||||
|             else if(item == ".")  | ||||
|             { | ||||
|                 //do nothing but don't emit this
 | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 parts.push_back(item); | ||||
|             } | ||||
|         } | ||||
|         VFSPath newpath; | ||||
|         newpath.relative = relative; | ||||
|         newpath.path = parts; | ||||
|         return newpath; | ||||
|     } | ||||
|     VFSPath VFSPath::RelativeCurrentDirectory() | ||||
|     { | ||||
|         VFSPath path; | ||||
|         path.relative=true; | ||||
|         return path; | ||||
|     } | ||||
|      | ||||
|     std::vector<std::string> VFSPath::SplitPath(std::string path,bool nativePath) | ||||
|     { | ||||
|         std::vector<std::string> parts; | ||||
|         std::string curPath = {}; | ||||
|         for(auto c : path) | ||||
|         { | ||||
|             if(c == '/') | ||||
|             { | ||||
|                 if(!curPath.empty()) | ||||
|                 { | ||||
|                     parts.push_back(curPath); | ||||
|                     curPath = {}; | ||||
|                 } | ||||
|             } | ||||
|             #if defined(WIN32) | ||||
|             else if(c == '\\' && nativePath) | ||||
|             { | ||||
|                 if(!curPath.empty()) | ||||
|                 { | ||||
|                     parts.push_back(curPath); | ||||
|                     curPath = {}; | ||||
|                 } | ||||
|             } | ||||
|             #endif | ||||
|             else | ||||
|             { | ||||
|                 curPath.push_back(c); | ||||
|             } | ||||
|         } | ||||
|         if(!curPath.empty()) | ||||
|         { | ||||
|             parts.push_back(curPath); | ||||
|             curPath = {}; | ||||
|         } | ||||
| 
 | ||||
|         return parts; | ||||
|     } | ||||
|     VFSPath::VFSPath() | ||||
|     { | ||||
|         this->relative=false; | ||||
|     } | ||||
|     VFSPath::VFSPath(std::vector<std::string> p) | ||||
|     { | ||||
|         this->path = p; | ||||
|     } | ||||
|     VFSPath::VFSPath(std::string str) | ||||
|     { | ||||
|         this->path = SplitPath(str); | ||||
|         this->relative=true; | ||||
|         if(!str.empty()) | ||||
|         { | ||||
|             if(str.front() == '/') this->relative=false; | ||||
|             if(!this->path.empty()) | ||||
|             { | ||||
|                 auto firstPartPath = this->path.front(); | ||||
| 
 | ||||
|                 if(!firstPartPath.empty() && firstPartPath.back() == '/') this->relative=false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     VFSPath::VFSPath(VFSPath p1, VFSPath p2) | ||||
|     { | ||||
|         this->relative = p1.relative; | ||||
|         this->path.insert(this->path.end(),p1.path.begin(),p1.path.end()); | ||||
|         this->path.insert(this->path.end(),p2.path.begin(),p2.path.end()); | ||||
|     }    | ||||
|     VFSPath::VFSPath(VFSPath p1, std::string subpath) : VFSPath(p1, VFSPath(subpath)) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
|     VFSPath VFSPath::GetParent() | ||||
|     { | ||||
|         std::vector<std::string> paths; | ||||
|         if(this->path.empty()) return VFSPath(); | ||||
|         paths.insert(paths.begin(), this->path.begin(), this->path.end()-1); | ||||
|         return VFSPath(paths); | ||||
|     } | ||||
| 
 | ||||
|     std::string VFSPath::GetFileName() | ||||
|     { | ||||
|         if(this->path.empty()) return ""; | ||||
|         return this->path.back(); | ||||
|     } | ||||
| 
 | ||||
|     std::string VFSPath::ToString() | ||||
|     { | ||||
|         if(this->path.empty() && !this->relative) return "/"; | ||||
|         bool first=true; | ||||
|         std::string p = {}; | ||||
|         for(auto item : this->path) | ||||
|         { | ||||
|             if(!(first && !item.empty() && item.back()==':') && !(first && this->relative)) | ||||
|                 p.push_back('/'); | ||||
|             p.append(item); | ||||
|             first=false; | ||||
|         } | ||||
|         return p; | ||||
|     } | ||||
| 
 | ||||
|     VFS::~VFS() | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     bool VFS::FIFOFileExists(VFSPath path) {return false;} | ||||
|     bool VFS::SocketFileExists(VFSPath path) {return false;} | ||||
|     bool VFS::CharacterDeviceExists(VFSPath path) {return false;} | ||||
|     bool VFS::BlockDeviceExists(VFSPath path) {return false;} | ||||
|     bool VFS::SymlinkExists(VFSPath path) {return false;} | ||||
|     void VFS::MoveDirectory(VFSPath src, VFSPath dest) | ||||
|     { | ||||
|         std::vector<VFSPath> paths; | ||||
|         GetPaths(src, paths); | ||||
| 
 | ||||
|         for(auto item : paths) | ||||
|         { | ||||
|             if(DirectoryExists(item)) | ||||
|             { | ||||
|                 MoveDirectory(item, VFSPath(dest, item.GetFileName())); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 MoveFile(item, VFSPath(dest, item.GetFileName())); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         DeleteDirectory(src);  | ||||
|     } | ||||
|     void VFS::CreateSymlink(VFSPath existingFile, VFSPath symlinkFile) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
|     void VFS::CreateHardlink(VFSPath existingFile, VFSPath newName) | ||||
|     { | ||||
|          | ||||
|     } | ||||
|      | ||||
|     bool VFS::SpecialFileExists(VFSPath path) | ||||
|     { | ||||
|         return SymlinkExists(path) || BlockDeviceExists(path) || CharacterDeviceExists(path) || SocketFileExists(path) || FIFOFileExists(path); | ||||
|     } | ||||
|     bool VFS::FileExists(VFSPath path) | ||||
|     { | ||||
|         return RegularFileExists(path) || SpecialFileExists(path); | ||||
|     } | ||||
| 
 | ||||
|     void VFS::DeleteDirectoryRecurse(VFSPath path) | ||||
|     { | ||||
|         if(!DirectoryExists(path)) return; | ||||
|         std::vector<VFSPath> paths; | ||||
|         GetPaths(path, paths); | ||||
| 
 | ||||
|         for(auto item : paths) | ||||
|         { | ||||
|             if(DirectoryExists(item)) | ||||
|             { | ||||
|                 DeleteDirectoryRecurse(item); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 DeleteFile(item); | ||||
|             } | ||||
|         } | ||||
|         DeleteDirectory(path); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,77 @@ | |||
| #include "TessesFramework/Http/ContentDisposition.hpp" | ||||
| #include "TessesFramework/Http/HttpUtils.hpp" | ||||
| #include <iostream> | ||||
| namespace Tesses::Framework::Http | ||||
| { | ||||
|     bool ContentDisposition::TryParse(std::string str, ContentDisposition& cd) | ||||
|     { | ||||
|         auto res = HttpUtils::SplitString(str,"; "); | ||||
|          | ||||
| 
 | ||||
|         if(res.empty()) return false; | ||||
|         cd.type = res[0]; | ||||
|         bool hasFileNameStar = false; | ||||
|         for(size_t i = 1; i < res.size(); i++) | ||||
|         { | ||||
|             auto res2 = HttpUtils::SplitString(res[i],"=",2); | ||||
|             if(res2.size() == 2) | ||||
|             { | ||||
|                 if(res2[0] == "filename*") | ||||
|                 { | ||||
|                     //cd.filename = res2[1];
 | ||||
|                     //UTF-8''
 | ||||
|                     std::string p = res2[1]; | ||||
|                     if(p.find("UTF-8''") == 0) | ||||
|                     { | ||||
|                         hasFileNameStar = true; | ||||
|                         p = HttpUtils::UrlPathDecode(p.substr(7)); | ||||
|                         cd.filename = p; | ||||
|                     } | ||||
|                 } | ||||
|                 else if(res2[0] == "filename" && !hasFileNameStar) | ||||
|                 { | ||||
|                     std::string p = res2[1]; | ||||
|                     if(!p.empty() && p[0] == '\"') | ||||
|                     { | ||||
|                         p = p.substr(1, p.size()-2); | ||||
|                     } | ||||
| 
 | ||||
|                     p = HttpUtils::UrlPathDecode(p); | ||||
|                     cd.filename = p; | ||||
|                 } | ||||
|                 else if(res2[0] == "name") | ||||
|                 { | ||||
|                        std::string p = res2[1]; | ||||
|                     if(!p.empty() && p[0] == '\"') | ||||
|                     { | ||||
|                         p = p.substr(1, p.size()-2); | ||||
|                     } | ||||
| 
 | ||||
|                     cd.fieldName = HttpUtils::UrlPathDecode(p); | ||||
|                   | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     std::string ContentDisposition::ToString() | ||||
|     { | ||||
|         std::vector<std::string> parts; | ||||
| 
 | ||||
|         parts.push_back(this->type); | ||||
| 
 | ||||
|         if(!this->fieldName.empty()) | ||||
|         { | ||||
|             parts.push_back("name=\"" + HttpUtils::UrlPathEncode(this->fieldName,true) +  "\""); | ||||
|         } | ||||
| 
 | ||||
|         if(!this->filename.empty()) | ||||
|         { | ||||
|             parts.push_back("filename=\"" + HttpUtils::UrlPathEncode(this->filename,true) +  "\""); | ||||
|         } | ||||
| 
 | ||||
|         return HttpUtils::Join("; ", parts); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,131 @@ | |||
| #include "TessesFramework/Http/FileServer.hpp" | ||||
| #include "TessesFramework/Filesystem/LocalFS.hpp" | ||||
| #include "TessesFramework/Filesystem/SubdirFilesystem.hpp" | ||||
| #include <iostream> | ||||
| #include <unistd.h> | ||||
| using LocalFilesystem = Tesses::Framework::Filesystem::LocalFilesystem; | ||||
| using SubdirFilesystem = Tesses::Framework::Filesystem::SubdirFilesystem; | ||||
| using VFSPath = Tesses::Framework::Filesystem::VFSPath; | ||||
| using VFS = Tesses::Framework::Filesystem::VFS; | ||||
| namespace Tesses::Framework::Http | ||||
| { | ||||
|     FileServer::FileServer(std::filesystem::path path,bool allowListing,bool spa) : FileServer(path,allowListing,spa,{"index.html","default.html","index.htm","default.htm"}) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
|     FileServer::FileServer(std::filesystem::path path,bool allowListing, bool spa, std::vector<std::string> defaultNames) | ||||
|     { | ||||
|         LocalFilesystem* lfs=new LocalFilesystem; | ||||
|         SubdirFilesystem* sdfs=new SubdirFilesystem(lfs,lfs->SystemToVFSPath(path),true); | ||||
|         this->vfs = sdfs; | ||||
|         this->spa = spa; | ||||
| 
 | ||||
|         this->ownsVFS=true; | ||||
|         this->allowListing = allowListing; | ||||
|         this->defaultNames = defaultNames; | ||||
|     } | ||||
|     FileServer::FileServer(Tesses::Framework::Filesystem::VFS* fs, bool owns, bool allowListing,bool spa) : FileServer(fs,owns,allowListing,spa,{"index.html","default.html","index.htm","default.htm"}) | ||||
|     { | ||||
|          | ||||
|     } | ||||
|     FileServer::FileServer(Tesses::Framework::Filesystem::VFS* fs, bool owns, bool allowListing, bool spa, std::vector<std::string> defaultNames) | ||||
|     { | ||||
|         this->vfs = fs; | ||||
|         this->ownsVFS = owns; | ||||
|         this->allowListing = allowListing; | ||||
|         this->defaultNames = defaultNames; | ||||
|         this->spa = spa; | ||||
|     } | ||||
|     bool FileServer::SendFile(ServerContext& ctx,VFSPath path) | ||||
|     { | ||||
|         | ||||
|         auto strm = this->vfs->OpenFile(path,"rb"); | ||||
|         bool retVal = false; | ||||
|         if(strm != nullptr) | ||||
|         { | ||||
|             ctx.WithMimeType(HttpUtils::MimeType(path.GetFileName())).SendStream(strm); | ||||
|             retVal = true; | ||||
|             | ||||
|         } | ||||
| 
 | ||||
|         delete strm; | ||||
|         return retVal; | ||||
|     } | ||||
| 
 | ||||
|     bool FileServer::Handle(ServerContext& ctx) | ||||
|     { | ||||
|         auto path = HttpUtils::UrlPathDecode(ctx.path); | ||||
| 
 | ||||
|         if(this->vfs->DirectoryExists(path)) | ||||
|         { | ||||
|             for(auto f : defaultNames) | ||||
|             { | ||||
|                 VFSPath p(path,f); | ||||
|                  | ||||
| 
 | ||||
|                 if(this->vfs->FileExists(p)) | ||||
|                     return SendFile(ctx,p); | ||||
|             } | ||||
|             if(this->allowListing) | ||||
|             { | ||||
|                 std::string p = HttpUtils::HtmlEncode(ctx.originalPath); | ||||
|                 std::string html = "<!DOCTYPE html><html><head><title>Index of "; | ||||
|                 html.append(p); | ||||
|                 html.append("</title></head><body><h1>Index of "); | ||||
|                 html.append(p); | ||||
|                 html.append("</h1><hr><pre><a href=\"../\">../</a>\r\n"); | ||||
|                 std::vector<VFSPath> ents; | ||||
|                 vfs->GetPaths(path, ents); | ||||
| 
 | ||||
|                 for(auto item : ents) | ||||
|                 { | ||||
|                     if(vfs->DirectoryExists(item)) | ||||
|                     { | ||||
|                         //is dir
 | ||||
|                         std::string path = item.GetFileName(); | ||||
|                         html.append("<a href=\""); | ||||
|                         html.append(HttpUtils::UrlPathEncode(path) + "/"); | ||||
|                         html.append("\">"); | ||||
|                         html.append(HttpUtils::HtmlEncode(path) + "/"); | ||||
|                         html.append("</a>\r\n"); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         //is file
 | ||||
|                         std::string path = item.GetFileName(); | ||||
|                         html.append("<a href=\""); | ||||
|                         html.append(HttpUtils::UrlPathEncode(path)); | ||||
|                         html.append("\">"); | ||||
|                         html.append(HttpUtils::HtmlEncode(path)); | ||||
|                         html.append("</a>\r\n"); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 html.append("</pre><hr></body></html>"); | ||||
|                  | ||||
|                 ctx.WithMimeType("text/html").SendText(html); | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         else if(this->vfs->FileExists(path)) | ||||
|         { | ||||
|             return SendFile(ctx,path); | ||||
|         } | ||||
|         else if(this->spa) | ||||
|         { | ||||
|             for(auto f : defaultNames) | ||||
|             { | ||||
|                 VFSPath p(f); | ||||
|                 p.relative=false; | ||||
|                 if(this->vfs->FileExists(p)) | ||||
|                     return SendFile(ctx,p); | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|     FileServer::~FileServer() | ||||
|     { | ||||
|         if(this->ownsVFS) | ||||
|             delete this->vfs; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,226 @@ | |||
| #include "TessesFramework/Http/HttpClient.hpp" | ||||
| #include "TessesFramework/Crypto/ClientTLSStream.hpp" | ||||
| #include "TessesFramework/Streams/NetworkStream.hpp" | ||||
| #include "TessesFramework/TextStreams/StreamWriter.hpp" | ||||
| #include "TessesFramework/TextStreams/StreamReader.hpp" | ||||
| #include "TessesFramework/Http/HttpStream.hpp" | ||||
| #include "TessesFramework/Streams/BufferedStream.hpp" | ||||
| #include <iostream> | ||||
| using Stream = Tesses::Framework::Streams::Stream; | ||||
| using NetworkStream = Tesses::Framework::Streams::NetworkStream; | ||||
| using ClientTLSStream = Tesses::Framework::Crypto::ClientTLSStream; | ||||
| using StreamReader = Tesses::Framework::TextStreams::StreamReader; | ||||
| using StreamWriter = Tesses::Framework::TextStreams::StreamWriter; | ||||
| using BufferedStream = Tesses::Framework::Streams::BufferedStream; | ||||
| using HttpStream = Tesses::Framework::Http::HttpStream; | ||||
| namespace Tesses::Framework::Http | ||||
| { | ||||
|     HttpRequest::HttpRequest() | ||||
|     { | ||||
|         this->body=nullptr; | ||||
|         this->followRedirects=true; | ||||
|         this->ignoreSSLErrors=false; | ||||
|         this->trusted_root_cert_bundle=""; | ||||
|         this->method = "GET"; | ||||
|         this->requestHeaders.SetValue("Connection","close"); | ||||
|     } | ||||
| 
 | ||||
|     void HttpRequest::SendRequest(Tesses::Framework::Streams::Stream* strm) | ||||
|     { | ||||
|         Uri uri; | ||||
|         if(!Uri::TryParse(this->url, uri)) return; | ||||
|              | ||||
|              | ||||
|         if(body != nullptr) | ||||
|         { | ||||
|             body->HandleHeaders(requestHeaders); | ||||
|         } | ||||
| 
 | ||||
|         std::string request = method + " " + uri.GetPathAndQuery() + " HTTP/1.1\r\nHost: " + uri.HostPort() + "\r\n"; | ||||
| 
 | ||||
|         for(auto headers : requestHeaders.kvp) | ||||
|         { | ||||
|             for(auto item : headers.second) | ||||
|             { | ||||
|                 request.append(headers.first); | ||||
|                 request.append(": "); | ||||
|                 request.append(item); | ||||
|                 request.append("\r\n"); | ||||
|             } | ||||
|                  | ||||
|         } | ||||
| 
 | ||||
|         request.append("\r\n"); | ||||
| 
 | ||||
| 
 | ||||
|         StreamWriter writer(strm, false); | ||||
|         writer.Write(request); | ||||
| 
 | ||||
|         if(body != nullptr) | ||||
|         { | ||||
|             body->Write(strm); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     Stream* HttpRequest::EstablishConnection(Uri uri, bool ignoreSSLErrors, std::string trusted_root_cert_bundle) | ||||
|     { | ||||
|         if(uri.scheme == "http:") | ||||
|         { | ||||
|             return new NetworkStream(uri.host,uri.GetPort(),false,false,false); | ||||
|         } | ||||
|         else if(uri.scheme == "https:") | ||||
|         { | ||||
|             auto netStrm = new NetworkStream(uri.host,uri.GetPort(),false,false,false); | ||||
|             if(netStrm == nullptr) return netStrm; | ||||
|             return new ClientTLSStream(netStrm, true, ignoreSSLErrors,uri.host, trusted_root_cert_bundle); | ||||
|         } | ||||
|              | ||||
|         return nullptr; | ||||
|     } | ||||
|      | ||||
|     HttpResponse::~HttpResponse() | ||||
|     { | ||||
|         if(this->owns) | ||||
|             delete this->handleStrm; | ||||
|     } | ||||
| 
 | ||||
|     HttpResponse::HttpResponse(Stream* strm, bool owns) | ||||
|     { | ||||
|         this->handleStrm=nullptr; | ||||
|         this->owns=owns; | ||||
|         StreamReader reader(strm, false); | ||||
|             std::string statusLine; | ||||
|             if(!reader.ReadLine(statusLine)) return; | ||||
|             auto statusLinesPart = HttpUtils::SplitString(statusLine," ",3); | ||||
|             if(statusLinesPart.size() >= 2) | ||||
|             { | ||||
|                 this->version = statusLinesPart[0]; | ||||
|                 this->statusCode = (StatusCode)std::stoi(statusLinesPart[1]); | ||||
|             } | ||||
|             std::string line; | ||||
|             while(reader.ReadLine(line)) | ||||
|             { | ||||
|                 if(line.empty()) break; | ||||
|                 auto v = HttpUtils::SplitString(line,": ", 2); | ||||
|                 if(v.size() != 2)  | ||||
|                 { | ||||
|                     delete strm; | ||||
|                     return; | ||||
|                 } | ||||
|                 this->responseHeaders.AddValue(v[0],v[1]); | ||||
|                 line.clear(); | ||||
|             } | ||||
|         this->handleStrm = strm; | ||||
|     } | ||||
| 
 | ||||
|     HttpResponse::HttpResponse(HttpRequest& req) | ||||
|     { | ||||
|         this->owns=true; | ||||
|         this->handleStrm = nullptr; | ||||
|          | ||||
|         std::string url = req.url; | ||||
|         Uri uri; | ||||
|         while(Uri::TryParse(url, uri)) | ||||
|         { | ||||
|             auto strm = HttpRequest::EstablishConnection(uri, req.ignoreSSLErrors,req.trusted_root_cert_bundle); | ||||
|             if(strm == nullptr) return; | ||||
|             auto reqHeaders = req.requestHeaders; | ||||
|              | ||||
|             if(req.body != nullptr) | ||||
|             { | ||||
|                 req.body->HandleHeaders(reqHeaders); | ||||
|             } | ||||
| 
 | ||||
|             std::string request = req.method + " " + uri.GetPathAndQuery() + " HTTP/1.1\r\nHost: " + uri.HostPort() + "\r\n"; | ||||
| 
 | ||||
|             for(auto headers : reqHeaders.kvp) | ||||
|             { | ||||
|                 for(auto item : headers.second) | ||||
|                 { | ||||
|                     request.append(headers.first); | ||||
|                     request.append(": "); | ||||
|                     request.append(item); | ||||
|                     request.append("\r\n"); | ||||
|                 } | ||||
|                  | ||||
|             } | ||||
| 
 | ||||
|             request.append("\r\n"); | ||||
| 
 | ||||
| 
 | ||||
|             StreamWriter writer(strm, false); | ||||
|             writer.Write(request); | ||||
| 
 | ||||
|             if(req.body != nullptr) | ||||
|             { | ||||
|                 req.body->Write(strm); | ||||
|             } | ||||
| 
 | ||||
|             StreamReader reader(strm, false); | ||||
|             std::string statusLine; | ||||
|             if(!reader.ReadLine(statusLine)) break; | ||||
|             auto statusLinesPart = HttpUtils::SplitString(statusLine," ",3); | ||||
|             if(statusLinesPart.size() >= 2) | ||||
|             { | ||||
|                 this->version = statusLinesPart[0]; | ||||
|                 this->statusCode = (StatusCode)std::stoi(statusLinesPart[1]); | ||||
|             } | ||||
|             std::string line; | ||||
|             while(reader.ReadLine(line)) | ||||
|             { | ||||
|                 if(line.empty()) break; | ||||
|                 auto v = HttpUtils::SplitString(line,": ", 2); | ||||
|                 if(v.size() != 2)  | ||||
|                 { | ||||
|                     delete strm; | ||||
|                     return; | ||||
|                 } | ||||
|                 this->responseHeaders.AddValue(v[0],v[1]); | ||||
|                 line.clear(); | ||||
|             } | ||||
| 
 | ||||
|             std::string location; | ||||
|             Uri uri2; | ||||
| 
 | ||||
|              | ||||
|             if(req.followRedirects && (this->statusCode == MovedPermanently || this->statusCode == PermanentRedirect || this->statusCode == TemporaryRedirect) && this->responseHeaders.TryGetFirst("Location",location) && uri.Relative(location, uri2)) | ||||
|             { | ||||
|                 this->responseHeaders.Clear(); | ||||
|                  | ||||
|                 url = uri2.ToString(); | ||||
|                 | ||||
|                  | ||||
|                 delete strm; | ||||
|                 continue; | ||||
|             } | ||||
|             this->handleStrm = strm; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     std::string HttpResponse::ReadAsString() | ||||
|     { | ||||
|         auto strm = ReadAsStream(); | ||||
|         if(strm == nullptr) return {}; | ||||
|         StreamReader r(strm, true); | ||||
|         return r.ReadToEnd(); | ||||
|     } | ||||
|     void HttpResponse::CopyToStream(Stream* strm) | ||||
|     { | ||||
|         if(strm == nullptr) return; | ||||
|         auto src = ReadAsStream(); | ||||
|         if(src == nullptr) return; | ||||
|         src->CopyTo(*strm); | ||||
|         delete src; | ||||
|     } | ||||
|     Stream* HttpResponse::ReadAsStream() | ||||
|     { | ||||
|         if(this->handleStrm == nullptr) return nullptr; | ||||
|         int64_t length = -1; | ||||
|         if(!this->responseHeaders.TryGetFirstInt("Content-Length",length)) | ||||
|         { | ||||
|             length = -1; | ||||
|         } | ||||
| 
 | ||||
|         return new HttpStream(this->handleStrm,false,length,true,version=="HTTP/1.1"); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,651 @@ | |||
| #include "TessesFramework/Http/HttpServer.hpp" | ||||
| #include "TessesFramework/Streams/FileStream.hpp" | ||||
| #include "TessesFramework/Streams/MemoryStream.hpp" | ||||
| #include "TessesFramework/TextStreams/StreamWriter.hpp" | ||||
| #include "TessesFramework/TextStreams/StreamReader.hpp" | ||||
| #include "TessesFramework/Http/ContentDisposition.hpp" | ||||
| #include "TessesFramework/Streams/BufferedStream.hpp" | ||||
| #include "TessesFramework/Http/HttpStream.hpp" | ||||
| 
 | ||||
| #include <iostream> | ||||
| using FileStream = Tesses::Framework::Streams::FileStream; | ||||
| using Stream = Tesses::Framework::Streams::Stream; | ||||
| using SeekOrigin = Tesses::Framework::Streams::SeekOrigin; | ||||
| using MemoryStream = Tesses::Framework::Streams::MemoryStream; | ||||
| using StreamReader = Tesses::Framework::TextStreams::StreamReader; | ||||
| using StreamWriter = Tesses::Framework::TextStreams::StreamWriter; | ||||
| using TcpServer = Tesses::Framework::Streams::TcpServer; | ||||
| using NetworkStream = Tesses::Framework::Streams::NetworkStream; | ||||
| using BufferedStream = Tesses::Framework::Streams::BufferedStream; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace Tesses::Framework::Http | ||||
| { | ||||
|      | ||||
|     /*
 | ||||
|     static int _header_field(multipart_parser* p, const char *at, size_t length) | ||||
|     { | ||||
|         UploadData* data = static_cast<UploadData*>(multipart_parser_get_data(p)); | ||||
|         data->currentHeaderKey = std::string(at,length); | ||||
|         return 0; | ||||
|     } | ||||
|     static int _header_value(multipart_parser* p, const char *at, size_t length) | ||||
|     { | ||||
|         UploadData* data = static_cast<UploadData*>(multipart_parser_get_data(p)); | ||||
|         data->currentHeaders.AddValue(data->currentHeaderKey, std::string(at,length)); | ||||
|         std::cout << data->currentHeaderKey << ": " <<  std::string(at,length) << std::endl; | ||||
|         return 0; | ||||
|     } | ||||
|     static int _part_begin(multipart_parser* p) | ||||
|     { | ||||
|         UploadData* data = static_cast<UploadData*>(multipart_parser_get_data(p)); | ||||
|         std::string cd0; | ||||
|         ContentDisposition cd1; | ||||
|         std::string ct; | ||||
|         if(!data->currentHeaders.TryGetFirst("Content-Type",ct)) | ||||
|             ct = "application/octet-stream"; | ||||
|         if(data->currentHeaders.TryGetFirst("Content-Disposition", cd0) && ContentDisposition::TryParse(cd0,cd1)) | ||||
|         { | ||||
|             if(cd1.filename.empty()) | ||||
|             { | ||||
|                 data->isFile=false; | ||||
|                 data->key = cd1.fieldName; | ||||
|                 data->currentBody = new MemoryStream(true); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 data->isFile = true; | ||||
|                 data->currentBody = data->cb(ct, cd1.filename, cd1.fieldName); | ||||
|             } | ||||
|         } | ||||
|         return 0; | ||||
|     } | ||||
|     static int _part_end(multipart_parser* p) | ||||
|     { | ||||
|         UploadData* data = static_cast<UploadData*>(multipart_parser_get_data(p)); | ||||
|         if(data->currentBody == nullptr) return 0; | ||||
|         if(data->isFile) | ||||
|         { | ||||
|             delete data->currentBody; | ||||
|             data->currentBody = nullptr; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             MemoryStream* ms = dynamic_cast<MemoryStream*>(data->currentBody); | ||||
|             if(ms != nullptr) | ||||
|             { | ||||
|                 ms->Seek(0, SeekOrigin::Begin); | ||||
|                 auto& buff = ms->GetBuffer(); | ||||
|                 data->ctx->queryParams.AddValue(data->key, std::string(buff.begin(),buff.end())); | ||||
|             } | ||||
|             delete data->currentBody; | ||||
|             data->currentBody=nullptr; | ||||
|         } | ||||
|         data->currentHeaders.Clear(); | ||||
|         return 0; | ||||
|     }*/ | ||||
|     bool ServerContext::NeedToParseFormData() | ||||
|     { | ||||
|         std::string ct; | ||||
|         if(this->requestHeaders.TryGetFirst("Content-Type",ct)) | ||||
|         { | ||||
|             if(ct.find("multipart/form-data") == 0) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|     static bool parseUntillBoundaryEnd(Tesses::Framework::Streams::Stream* src, Tesses::Framework::Streams::Stream* dest, std::string boundary) | ||||
|     { | ||||
|         bool hasMore=true; | ||||
|         uint8_t checkBuffer[boundary.size()]; | ||||
|         int b; | ||||
|         size_t i = 0; | ||||
|         size_t i2 = 0; | ||||
| 
 | ||||
|         uint8_t buffer[1024]; | ||||
|         size_t offsetInMem = 0; | ||||
| 
 | ||||
|         while((b = src->ReadByte()) != -1) | ||||
|         { | ||||
|             if(i == boundary.size()) | ||||
|             { | ||||
|                 if (b == '-') | ||||
|                 { | ||||
|                     i2++; | ||||
|                     if(i2 == 2) hasMore=false; | ||||
|                 } | ||||
|                 if (b == '\n') break; | ||||
|                 else if(b != '-') | ||||
|                     i2 = 0; | ||||
|                 continue; | ||||
|             } | ||||
|             i2=0; | ||||
| 
 | ||||
|             if (b == boundary[i]) //start filling the check buffer
 | ||||
|             { | ||||
|                 checkBuffer[i] = (uint8_t)b; | ||||
|                 i++; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 size_t idx = 0; | ||||
|                 while (idx < i) //write the buffer data to stream
 | ||||
|                 { | ||||
| 
 | ||||
|                 if(offsetInMem >= sizeof(buffer)) | ||||
|                 { | ||||
|                     dest->Write(buffer, sizeof(buffer)); | ||||
|                     offsetInMem=0; | ||||
|                      | ||||
|                 } | ||||
| 
 | ||||
|                     buffer[offsetInMem++] = checkBuffer[idx]; | ||||
|                          | ||||
|                         idx++; | ||||
|                 } | ||||
| 
 | ||||
|                 i = 0; | ||||
|                  | ||||
|                 if(offsetInMem >= sizeof(buffer)) | ||||
|                 { | ||||
|                     dest->Write(buffer, sizeof(buffer)); | ||||
|                     offsetInMem=0; | ||||
|                      | ||||
|                 } | ||||
| 
 | ||||
|                 buffer[offsetInMem++] = (uint8_t)b; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(offsetInMem > 0) | ||||
|         { | ||||
|             dest->Write(buffer,offsetInMem); | ||||
|         } | ||||
|         return hasMore; | ||||
|     } | ||||
| 
 | ||||
|     static bool parseSection(ServerContext* ctx, std::string boundary, std::function<Tesses::Framework::Streams::Stream*(std::string mime, std::string filename, std::string name)> cb) | ||||
|     { | ||||
|         HttpDictionary req; | ||||
|         StreamReader reader(ctx->GetStream()); | ||||
|         std::string line; | ||||
|         while(reader.ReadLine(line)) | ||||
|         { | ||||
|             auto v = HttpUtils::SplitString(line,": ", 2);     | ||||
|             if(v.size() == 2) | ||||
|                 req.AddValue(v[0],v[1]); | ||||
|             line.clear(); | ||||
|         } | ||||
| 
 | ||||
|         std::string cd0; | ||||
|         ContentDisposition cd1; | ||||
|         std::string ct; | ||||
|         | ||||
|         if(!req.TryGetFirst("Content-Type",ct)) | ||||
|             ct = "application/octet-stream"; | ||||
|         if(req.TryGetFirst("Content-Disposition", cd0) && ContentDisposition::TryParse(cd0,cd1)) | ||||
|         { | ||||
|             if(cd1.filename.empty()) | ||||
|             { | ||||
|                 MemoryStream ms(true); | ||||
| 
 | ||||
|                 bool retVal = parseUntillBoundaryEnd(&ctx->GetStream(),&ms,boundary); | ||||
|                 auto& buff = ms.GetBuffer(); | ||||
| 
 | ||||
|                 ctx->queryParams.AddValue(cd1.fieldName, std::string(buff.begin(),buff.end())); | ||||
| 
 | ||||
|                 return retVal; | ||||
| 
 | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 auto strm = cb(ct, cd1.filename, cd1.fieldName);  | ||||
|                 bool retVal = parseUntillBoundaryEnd(&ctx->GetStream(),strm,boundary); | ||||
|                 delete strm; | ||||
|                 return retVal; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     void ServerContext::ParseFormData(std::function<Tesses::Framework::Streams::Stream*(std::string mime, std::string filename, std::string name)> cb) | ||||
|     { | ||||
|         std::string ct; | ||||
|         if(this->requestHeaders.TryGetFirst("Content-Type",ct)) | ||||
|         { | ||||
|             if(ct.find("multipart/form-data") != 0) | ||||
|             { | ||||
|                 std::cout << "Not form data" << std::endl; | ||||
|                 return; | ||||
|             } | ||||
|             auto res = ct.find("boundary="); | ||||
|             if(res == std::string::npos) return; | ||||
|             ct = "--" + ct.substr(res+9); | ||||
|         } | ||||
|         Stream nullStrm; | ||||
|         parseUntillBoundaryEnd(this->strm,&nullStrm, ct); | ||||
| 
 | ||||
|         while(parseSection(this, ct, cb)); | ||||
|     }    | ||||
|              | ||||
|     HttpServer::HttpServer(uint16_t port, IHttpServer* http, bool owns) | ||||
|     { | ||||
|         this->server = new TcpServer(port, 10); | ||||
|         this->http = http; | ||||
|         this->owns = owns; | ||||
|         this->thrd=nullptr; | ||||
|         this->port = port; | ||||
|     } | ||||
|     HttpServer::HttpServer(uint16_t port, IHttpServer& http) : HttpServer(port,&http,false) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
|     Stream* ServerContext::OpenResponseStream() | ||||
|     { | ||||
|         if(sent) return nullptr; | ||||
|         int64_t length = -1; | ||||
|         if(!this->responseHeaders.TryGetFirstInt("Content-Length",length)) | ||||
|             length = -1; | ||||
|          | ||||
|         if(this->version == "HTTP/1.1" && length == -1) | ||||
|             this->responseHeaders.SetValue("Transfer-Encoding","chunked"); | ||||
| 
 | ||||
|         this->WriteHeaders(); | ||||
|         return new HttpStream(this->strm,false,length,false,version == "HTTP/1.1"); | ||||
|     } | ||||
|     Stream* ServerContext::OpenRequestStream() | ||||
|     { | ||||
|         int64_t length = -1; | ||||
|         if(!this->requestHeaders.TryGetFirstInt("Content-Length",length)) | ||||
|             length = -1; | ||||
|         return new HttpStream(this->strm,false,length,true,version == "HTTP/1.1"); | ||||
|     } | ||||
|     void HttpServer::StartAccepting() | ||||
|     { | ||||
|         fflush(stdout); | ||||
|         if(http == nullptr) return; | ||||
|         auto svr=this->server; | ||||
|         auto http = this->http; | ||||
|         thrd = new Threading::Thread([svr,http]()->void { | ||||
|             while(TF_IsRunning()) | ||||
|             { | ||||
|                 std::string ip; | ||||
|                 uint16_t port; | ||||
|                 auto sock =svr->GetStream(ip,port); | ||||
|                 if(sock == nullptr) return; | ||||
|                 Threading::Thread thrd2([sock,http,ip,port]()->void { | ||||
|                     HttpServer::Process(*sock,*http,ip,port,false); | ||||
|                 }); | ||||
|                 thrd2.Detach(); | ||||
|             } | ||||
|         }); | ||||
|         std::cout << "\e[34mInterfaces:\n"; | ||||
|         for(auto _ip : NetworkStream::GetIPs()) | ||||
|         { | ||||
|             std::cout << "\e[32m"; | ||||
|             std::cout << _ip.first << ": "; | ||||
|             std::cout << "\e[35mhttp://"; | ||||
|             std::cout << _ip.second << ":" << std::to_string(this->port) << "/\n"; | ||||
|         } | ||||
|         std::cout << "\e[31mAlmost Ready to Listen\e[39m\n"; | ||||
|     } | ||||
|     HttpServer::~HttpServer() | ||||
|     { | ||||
|         this->server->Close(); | ||||
|         TF_ConnectToSelf(this->port); | ||||
|         if(this->thrd != nullptr) | ||||
|         { | ||||
|             this->thrd->Join(); | ||||
|             delete this->thrd; | ||||
|         } | ||||
|         if(this->owns) | ||||
|             delete http; | ||||
|         delete this->server; | ||||
|     } | ||||
|     IHttpServer::~IHttpServer() | ||||
|     { | ||||
|      | ||||
|     } | ||||
|     ServerContext::ServerContext(Stream* strm) | ||||
|     { | ||||
|         this->statusCode = OK; | ||||
|         this->strm = strm; | ||||
|         this->sent = false; | ||||
|         this->responseHeaders.AddValue("Server","TessesFrameworkWebServer"); | ||||
|     } | ||||
|     Stream& ServerContext::GetStream() | ||||
|     { | ||||
|         return *this->strm; | ||||
|     } | ||||
|     void ServerContext::SendBytes(std::vector<uint8_t> buff) | ||||
|     { | ||||
|         MemoryStream strm(false); | ||||
|         strm.GetBuffer() = buff; | ||||
|         SendStream(strm); | ||||
|     } | ||||
|     void ServerContext::SendText(std::string text) | ||||
|     { | ||||
|         MemoryStream strm(false); | ||||
|         auto& buff= strm.GetBuffer(); | ||||
|         buff.insert(buff.end(),text.begin(),text.end()); | ||||
|         SendStream(strm); | ||||
|     } | ||||
|     void ServerContext::SendErrorPage(bool showPath) | ||||
|     { | ||||
|         if(sent) return; | ||||
|         std::string errorHtml = showPath ? ("<html><head><title>File " + HttpUtils::HtmlEncode(this->originalPath) + " " + HttpUtils::StatusCodeString(this->statusCode) + "</title></head><body><h1>" + std::to_string((int)this->statusCode) + " " + HttpUtils::StatusCodeString(this->statusCode) + "</h1><h4>" + HttpUtils::HtmlEncode(this->originalPath) + "</h4></body></html>") : ""; | ||||
| 
 | ||||
|         WithMimeType("text/html").SendText(errorHtml); | ||||
|     } | ||||
|     void ServerContext::SendStream(Stream* strm) | ||||
|     { | ||||
|         if(strm == nullptr) return; | ||||
|         SendStream(*strm); | ||||
|     } | ||||
|     void ServerContext::SendStream(Stream& strm) | ||||
|     { | ||||
|         if(sent) return; | ||||
|         if(strm.CanSeek()) | ||||
|         { | ||||
|             int64_t len=strm.GetLength(); | ||||
|             std::string range={}; | ||||
|             if(this->requestHeaders.TryGetFirst("Range",range)) | ||||
|             { | ||||
|                 auto res = HttpUtils::SplitString(range,"=",2); | ||||
|                 if(res.size() == 2) | ||||
|                 { | ||||
|                     if(res[0] != "bytes") | ||||
|                     { | ||||
|                         this->statusCode = BadRequest; | ||||
|                         this->WriteHeaders(); | ||||
|                         return; | ||||
|                     } | ||||
|                     res = HttpUtils::SplitString(res[1],", ",2); | ||||
|                     if(res.size() != 1)  | ||||
|                     { | ||||
|                         this->statusCode = BadRequest; | ||||
|                         this->WriteHeaders(); | ||||
|                         return; | ||||
|                     } | ||||
|                     auto dash=HttpUtils::SplitString(res[0],"-",2); | ||||
|                     int64_t begin = 0; | ||||
|                     int64_t end = -1; | ||||
| 
 | ||||
|                     if(dash.size() == 1 && res[0].find_first_of('-') != std::string::npos) | ||||
|                     { | ||||
|                         //NUMBER-
 | ||||
|                         begin = std::stoll(dash[0]); | ||||
|                     } | ||||
|                     else if(dash.size() == 2) | ||||
|                     { | ||||
|                         //NUMBER-NUMBER
 | ||||
|                         //or
 | ||||
|                         //-NUMBER
 | ||||
| 
 | ||||
|                         if(dash[0].size() > 0) | ||||
|                         { | ||||
|                             //NUMBER-NUMBER
 | ||||
|                             begin = std::stoll(dash[0]); | ||||
|                             end = std::stoll(dash[1]); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             //-NUMBER
 | ||||
|                             end = std::stoll(dash[1]); | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         this->statusCode = BadRequest; | ||||
|                         this->WriteHeaders(); | ||||
|                         return; | ||||
|                     } | ||||
| 
 | ||||
|                     if(end == -1) | ||||
|                     { | ||||
|                         end = len-1;  | ||||
|                     } | ||||
| 
 | ||||
|                     if(end > len-1) | ||||
|                     { | ||||
|                         this->statusCode = RangeNotSatisfiable; | ||||
|                         this->WithSingleHeader("Content-Range","bytes */" + std::to_string(len)); | ||||
|                         this->WriteHeaders(); | ||||
|                         return; | ||||
|                     } | ||||
| 
 | ||||
|                     if(begin >= end) { | ||||
|                         this->statusCode = RangeNotSatisfiable; | ||||
|                         this->WithSingleHeader("Content-Range","bytes */" + std::to_string(len)); | ||||
|                         this->WriteHeaders(); | ||||
|                         return; | ||||
|                     } | ||||
| 
 | ||||
|                     int64_t myLen = (end - begin)+1; | ||||
| 
 | ||||
|                     this->WithSingleHeader("Accept-Ranges","bytes"); | ||||
|                     this->WithSingleHeader("Content-Length",std::to_string(myLen)); | ||||
|                     this->WithSingleHeader("Content-Range","bytes " + std::to_string(begin) + "-" + std::to_string(end) + "/" + std::to_string(len)); | ||||
|                     this->statusCode = PartialContent; | ||||
|                     this->WriteHeaders(); | ||||
|                     strm.Seek(begin,SeekOrigin::Begin); | ||||
| 
 | ||||
|                     uint8_t buffer[1024]; | ||||
| 
 | ||||
|                     size_t read=0; | ||||
|                     do { | ||||
|                         read = sizeof(buffer); | ||||
|                         myLen = (end - begin)+1; | ||||
|                         if(myLen < read) read = (size_t)myLen; | ||||
|                         if(read == 0) break; | ||||
|                         read = strm.Read(buffer,read); | ||||
|                         if(read == 0) break; | ||||
|                         this->strm->WriteBlock(buffer,read); | ||||
| 
 | ||||
|                         begin += read; | ||||
|                     } while(read > 0); | ||||
| 
 | ||||
|                      | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     this->statusCode = BadRequest; | ||||
|                     this->SendErrorPage(true); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if(len > -1) | ||||
|                 { | ||||
|                     this->WithSingleHeader("Accept-Range","bytes"); | ||||
|                     this->WithSingleHeader("Content-Length",std::to_string(len)); | ||||
|                     this->WriteHeaders(); | ||||
|                     strm.CopyTo(*this->strm); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             auto chunkedStream = this->OpenResponseStream(); | ||||
|             this->strm->CopyTo(chunkedStream); | ||||
|             delete chunkedStream; | ||||
|              | ||||
|         } | ||||
|     } | ||||
|     ServerContext& ServerContext::WithHeader(std::string key, std::string value) | ||||
|     { | ||||
|         this->responseHeaders.AddValue(key, value); | ||||
|         return *this;     | ||||
|     } | ||||
|     ServerContext& ServerContext::WithSingleHeader(std::string key, std::string value) | ||||
|     { | ||||
|         this->responseHeaders.SetValue(key, value); | ||||
|         return *this; | ||||
|     } | ||||
|     ServerContext& ServerContext::WithMimeType(std::string mime) | ||||
|     { | ||||
|         this->responseHeaders.SetValue("Content-Type",mime); | ||||
|         return *this; | ||||
|     } | ||||
|     ServerContext& ServerContext::WithContentDisposition(std::string filename, bool isInline) | ||||
|     { | ||||
|         ContentDisposition cd; | ||||
|         cd.type = isInline ? "inline" : "attachment"; | ||||
|         cd.filename = filename; | ||||
| 
 | ||||
|         //std::string cd;
 | ||||
|         //cd = (isInline ? "inline; filename*=UTF-8''" : "attachment; filename*=UTF-8''") + HttpUtils::UrlPathEncode(filename);
 | ||||
|         this->responseHeaders.SetValue("Content-Disposition",cd.ToString()); | ||||
|         return *this; | ||||
|     } | ||||
|     void ServerContext::SendNotFound() | ||||
|     { | ||||
|         if(sent) return; | ||||
|         statusCode = StatusCode::NotFound; | ||||
|         SendErrorPage(true); | ||||
|     } | ||||
|     void ServerContext::SendBadRequest() | ||||
|     { | ||||
|         if(sent) return; | ||||
|         statusCode = StatusCode::BadRequest; | ||||
|         SendErrorPage(false); | ||||
|     } | ||||
|     void ServerContext::SendException(std::exception& ex) | ||||
|     { | ||||
|         /*<!DOCTYPE html>
 | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <title>Internal Server Error at /</title> | ||||
| </head> | ||||
| <body> | ||||
|     <h1>Internal Server Error at /</h1> | ||||
|     <p>what():  std::exception</p> | ||||
| </body> | ||||
| </html>*/ | ||||
|        this->WithMimeType("text/html").SendText("<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Internal Server Error at " + HttpUtils::HtmlEncode(this->originalPath) + "</title></head><body><h1>Internal Server Error at " + HttpUtils::HtmlEncode(this->originalPath) + "</h1><p>what(): " + HttpUtils::HtmlEncode(ex.what()) + "</p></body></html>"); | ||||
|     } | ||||
|     ServerContext& ServerContext::WriteHeaders() | ||||
|     { | ||||
|         if(this->sent) return *this; | ||||
|         this->sent = true; | ||||
| 
 | ||||
|         StreamWriter writer(this->strm,false); | ||||
|         writer.newline = "\r\n"; | ||||
|         writer.WriteLine("HTTP/1.1 " + std::to_string((int)statusCode) + " " + HttpUtils::StatusCodeString(statusCode)); | ||||
|         for(auto& hdr : responseHeaders.kvp) | ||||
|         { | ||||
|             auto& key = hdr.first; | ||||
|             for(auto& val : hdr.second) | ||||
|             { | ||||
|                 writer.WriteLine(key + ": " + val); | ||||
|             } | ||||
|         } | ||||
|         writer.WriteLine(); | ||||
|          | ||||
|         return *this; | ||||
|     } | ||||
|     void HttpServer::Process(Stream& strm, IHttpServer& server, std::string ip, uint16_t port, bool encrypted) | ||||
|     { | ||||
|          | ||||
|         while(true) | ||||
|         { | ||||
|             BufferedStream bStrm(strm); | ||||
|             StreamReader reader(bStrm); | ||||
|             ServerContext ctx(&bStrm); | ||||
|             ctx.ip = ip; | ||||
|             ctx.port = port; | ||||
|             ctx.encrypted = encrypted; | ||||
|             try{ | ||||
|             bool firstLine = true; | ||||
|             std::string line; | ||||
|             while(reader.ReadLine(line)) | ||||
|             { | ||||
|                  if(firstLine) | ||||
|                 { | ||||
|                     auto v = HttpUtils::SplitString(line, " ", 3); | ||||
|                     if(v.size() != 3) { | ||||
|                         ctx.statusCode = BadRequest; | ||||
|                         ctx.WithMimeType("text/plain").SendText("First line is not 3 elements"); | ||||
|                         return; | ||||
|                     } | ||||
|                     ctx.method = v[0]; | ||||
|                     auto pp = HttpUtils::SplitString(v[1],"?", 2); | ||||
|                     pp.resize(2); | ||||
| 
 | ||||
|                     ctx.originalPath = pp[0]; | ||||
|                     ctx.path = ctx.originalPath; | ||||
| 
 | ||||
|                     auto queryPart = pp[1]; | ||||
|                     if(!queryPart.empty()) | ||||
|                     { | ||||
|                         HttpUtils::QueryParamsDecode(ctx.queryParams, queryPart); | ||||
|                     } | ||||
| 
 | ||||
|                     ctx.version = v[2]; | ||||
| 
 | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     auto v = HttpUtils::SplitString(line,": ", 2); | ||||
|                     if(v.size() != 2) { | ||||
|                         ctx.statusCode = BadRequest; | ||||
|                         ctx.WithMimeType("text/plain").SendText("Header line is not 2 elements"); | ||||
|                         return; | ||||
|                     } | ||||
|                    | ||||
|                     ctx.requestHeaders.AddValue(v[0],v[1]); | ||||
|                      | ||||
|                 } | ||||
|                 line.clear(); | ||||
|                 firstLine=false; | ||||
|             } | ||||
| 
 | ||||
|             if(firstLine) return; | ||||
| 
 | ||||
|             std::string type; | ||||
|             int64_t length; | ||||
| 
 | ||||
|             if(ctx.requestHeaders.TryGetFirst("Content-Type",type) && type == "application/x-www-form-urlencoded" && ctx.requestHeaders.TryGetFirstInt("Content-Length",length)) | ||||
|             { | ||||
|                 size_t len = (size_t)length; | ||||
|                 uint8_t* buffer = new uint8_t[len]; | ||||
|                 len = bStrm.ReadBlock(buffer,len); | ||||
|                 std::string query((const char*)buffer,len); | ||||
|                 delete buffer; | ||||
|                 HttpUtils::QueryParamsDecode(ctx.queryParams, query); | ||||
|             } | ||||
|              | ||||
|                 if(!server.Handle(ctx))  | ||||
|                 { | ||||
|                     ctx.SendNotFound(); | ||||
|                 } | ||||
|             }  | ||||
|             catch(std::exception& ex) | ||||
|             { | ||||
|                 ctx.SendException(ex); | ||||
|             } | ||||
| 
 | ||||
|             if(ctx.version != "HTTP/1.1" ) return; | ||||
| 
 | ||||
|             std::string connection; | ||||
|             if(ctx.requestHeaders.TryGetFirst("Connection", connection)) | ||||
|             { | ||||
|                 if(connection != "keep-alive") return; | ||||
|             } | ||||
| 
 | ||||
|             if(ctx.responseHeaders.TryGetFirst("Connection", connection)) | ||||
|             { | ||||
|                 if(connection != "keep-alive") return; | ||||
|             } | ||||
|              | ||||
|             if(bStrm.EndOfStream()) return; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,166 @@ | |||
| #include "TessesFramework/Http/HttpStream.hpp" | ||||
| #include "TessesFramework/TextStreams/StreamWriter.hpp" | ||||
| #include "TessesFramework/TextStreams/StreamReader.hpp" | ||||
| #include <sstream> | ||||
| #include <iostream> | ||||
| using StreamWriter = Tesses::Framework::TextStreams::StreamWriter; | ||||
| using StreamReader = Tesses::Framework::TextStreams::StreamReader; | ||||
| namespace Tesses::Framework::Http | ||||
| { | ||||
|     HttpStream::HttpStream(Tesses::Framework::Streams::Stream* strm, bool owns, int64_t length, bool recv, bool http1_1) | ||||
|     { | ||||
|         this->strm = strm; | ||||
|         this->owns = owns; | ||||
|         this->length = length; | ||||
|         this->recv = recv; | ||||
|         this->http1_1 = http1_1; | ||||
|         this->offset = 0; | ||||
|         this->read = 0; | ||||
|         this->position = 0; | ||||
|         this->done=false; | ||||
|     } | ||||
|     HttpStream::HttpStream(Tesses::Framework::Streams::Stream& strm, int64_t length, bool recv,bool http1_1) : HttpStream(&strm,false,length,recv,http1_1) | ||||
|     { | ||||
|          | ||||
|     } | ||||
|     bool HttpStream::CanRead() | ||||
|     { | ||||
|         if(this->done) return false; | ||||
|         if(!this->recv) return false; | ||||
|         if(this->offset < this->read) return true; | ||||
|         return this->strm->CanRead(); | ||||
|     } | ||||
|     bool HttpStream::CanWrite() | ||||
|     { | ||||
|         if(this->recv) return false; | ||||
|         return this->strm->CanWrite(); | ||||
|     } | ||||
|     bool HttpStream::EndOfStream() | ||||
|     { | ||||
|         if(this->done) return true; | ||||
|         if(!this->recv) return true; | ||||
|         if(this->offset < this->read) return false; | ||||
|         return this->strm->EndOfStream(); | ||||
|     } | ||||
|     int64_t HttpStream::GetLength() | ||||
|     { | ||||
|         return this->length; | ||||
|     } | ||||
|     int64_t HttpStream::GetPosition() | ||||
|     { | ||||
|         return this->position; | ||||
|     } | ||||
|     size_t HttpStream::Read(uint8_t* buff, size_t len) | ||||
|     { | ||||
|         if(this->done) return 0; | ||||
|         if(!this->recv) return 0; | ||||
|         if(this->length == 0) return 0; | ||||
|         if(this->length > 0) | ||||
|         { | ||||
|             len = std::min((size_t)(this->length - this->position), len); | ||||
|             if(len > 0) | ||||
|                 len = this->strm->Read(buff,len); | ||||
|             this->position += len; | ||||
|             return len; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             if(this->http1_1) | ||||
|             { | ||||
|                 if(this->offset < this->read) | ||||
|                 { | ||||
|                     len = std::min((size_t)(this->read - this->offset), len); | ||||
|                     if(len > 0) | ||||
|                         len = this->strm->Read(buff,len); | ||||
|                     this->offset += len; | ||||
|                     this->position += len; | ||||
|                     if(this->offset >= this->read) | ||||
|                     { | ||||
|                         StreamReader reader(this->strm, false); | ||||
|                         reader.ReadLine(); | ||||
|                     } | ||||
|                     return len; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     StreamReader reader(this->strm, false); | ||||
|                     std::string line = reader.ReadLine(); | ||||
|                     if(!line.empty()) | ||||
|                     { | ||||
|                         this->read = std::stoull(line, NULL, 16); | ||||
| 
 | ||||
|                      | ||||
|                         if(this->read == 0) | ||||
|                         { | ||||
|                             reader.ReadLine(); | ||||
|                             this->done=true; | ||||
|                             return 0; | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             this->offset=0; | ||||
| 
 | ||||
|                             len = std::min((size_t)(this->read - this->offset), len); | ||||
|                             if(len > 0) | ||||
|                                 len = this->strm->Read(buff,len); | ||||
|                             this->offset += len; | ||||
|                             this->position += len; | ||||
|                             return len; | ||||
|                         } | ||||
|                     } | ||||
|                     return 0; | ||||
|                 } | ||||
|                  | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return this->strm->Read(buff,len); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     size_t HttpStream::Write(const uint8_t* buff, size_t len) | ||||
|     { | ||||
|         if(this->recv) return 0; | ||||
|         if(this->length == 0) return 0; | ||||
|         if(this->length > 0) | ||||
|         { | ||||
|             len = std::min((size_t)(this->length - this->position), len); | ||||
|             if(len > 0) | ||||
|                 len = this->strm->Write(buff,len); | ||||
|             this->position += len; | ||||
|             return len; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             if(this->http1_1) | ||||
|             { | ||||
|                 std::stringstream strm; | ||||
|                 strm << std::hex << len; | ||||
|                  | ||||
|                 StreamWriter writer(this->strm,false); | ||||
|                 writer.newline = "\r\n"; | ||||
|                 writer.WriteLine(strm.str()); | ||||
|                  | ||||
|                 this->strm->WriteBlock(buff, len); | ||||
| 
 | ||||
|                 writer.WriteLine(); | ||||
|                 return len; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return this->strm->Write(buff,len); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     HttpStream::~HttpStream() | ||||
|     { | ||||
|         if(this->length == -1 && this->http1_1)  | ||||
|         { | ||||
|             StreamWriter writer(this->strm,false); | ||||
|             writer.newline = "\r\n"; | ||||
|             writer.WriteLine("0"); | ||||
|             writer.WriteLine(); | ||||
|         } | ||||
|         if(this->owns) delete this->strm; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,732 @@ | |||
| #include "TessesFramework/Http/HttpUtils.hpp" | ||||
| #include "TessesFramework/Filesystem/VFS.hpp" | ||||
| using VFSPath = Tesses::Framework::Filesystem::VFSPath; | ||||
| namespace Tesses::Framework::Http { | ||||
|     bool Uri::Relative(std::string url, Uri& uri) | ||||
|     { | ||||
|         auto index = url.find_first_of("//"); | ||||
|         if(index != std::string::npos) | ||||
|         { | ||||
|             if(Uri::TryParse(url,uri)) | ||||
|             { | ||||
|                 if(index == 0) | ||||
|                     uri.scheme = this->scheme; | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         else if(!url.empty()) | ||||
|         { | ||||
|             if(url[0] == '/') | ||||
|             { | ||||
|                  | ||||
|                 auto thirdPart = HttpUtils::SplitString(url,"#",2); | ||||
|                 if(thirdPart.empty()) return false; | ||||
|                 if(thirdPart.size() == 2) | ||||
|                 { | ||||
|                     uri.hash=thirdPart[1]; | ||||
|                 } | ||||
| 
 | ||||
| 
 | ||||
|                 auto fourthPart = HttpUtils::SplitString(thirdPart[1],"?",2); | ||||
|                  | ||||
|                 VFSPath p = fourthPart[0]; | ||||
|                 uri.path = p.CollapseRelativeParents().ToString(); //this should be safe
 | ||||
|                 if(fourthPart.size() == 2) | ||||
|                 { | ||||
|                     HttpUtils::QueryParamsDecode(uri.query, fourthPart[1]);    | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 auto thirdPart = HttpUtils::SplitString(url,"#",2); | ||||
|                 if(thirdPart.empty()) return false; | ||||
|                 if(thirdPart.size() == 2) | ||||
|                 { | ||||
|                     uri.hash=thirdPart[1]; | ||||
|                 } | ||||
| 
 | ||||
| 
 | ||||
|                 auto fourthPart = HttpUtils::SplitString(thirdPart[1],"?",2); | ||||
|                  | ||||
|                 VFSPath p = VFSPath(this->path,fourthPart[0]); | ||||
|                 uri.path = p.CollapseRelativeParents().ToString(); //this should be safe
 | ||||
|                 if(fourthPart.size() == 2) | ||||
|                 { | ||||
|                     HttpUtils::QueryParamsDecode(uri.query, fourthPart[1]);    | ||||
|                 } | ||||
|             } | ||||
|             uri.scheme = this->scheme; | ||||
|             uri.host = this->host; | ||||
|             uri.port = this->port; | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|     std::string Uri::HostPort() | ||||
|     { | ||||
|         if(this->port != 0) return this->host + ":" + std::to_string(this->port); | ||||
|         return this->host; | ||||
|     } | ||||
|     uint16_t Uri::GetPort() | ||||
|     { | ||||
|         if(this->port != 0) return this->port; | ||||
| 
 | ||||
|         if(this->scheme == "http:") | ||||
|             return 80; | ||||
|         if(this->scheme == "https:") | ||||
|             return 443; | ||||
|         if(this->scheme == "sftp:") | ||||
|             return 22; | ||||
|         if(this->scheme == "ftp:") | ||||
|             return 21; | ||||
|         if(this->scheme == "tftp:") | ||||
|             return 69; | ||||
|         return 0; | ||||
|     } | ||||
|     bool Uri::TryParse(std::string url, Uri& uri) | ||||
|     { | ||||
|         uri.scheme = ""; | ||||
|         uri.port=0; | ||||
|         auto firstPart = HttpUtils::SplitString(url,"//",2); | ||||
|         if(firstPart.size() == 2) | ||||
|         uri.scheme=firstPart[0]; | ||||
|         else if(firstPart.empty()) | ||||
|         return false; | ||||
| 
 | ||||
|             auto secondPart = HttpUtils::SplitString(firstPart.size() == 2 ? firstPart[1] : firstPart[0] ,"/",2); | ||||
| 
 | ||||
|             if(secondPart.size() == 1) | ||||
|             { | ||||
|                 uri.path = "/"; | ||||
|             } | ||||
|             else if(secondPart.size() == 2) | ||||
|             { | ||||
|                 auto thirdPart = HttpUtils::SplitString(secondPart[1],"#",2); | ||||
|                 if(thirdPart.empty()) return false; | ||||
|                 if(thirdPart.size() == 2) | ||||
|                 { | ||||
|                     uri.hash=thirdPart[1]; | ||||
|                 } | ||||
| 
 | ||||
| 
 | ||||
|                 auto fourthPart = HttpUtils::SplitString(thirdPart[0],"?",2); | ||||
|                 uri.path = "/" + fourthPart[0]; //this should be safe
 | ||||
|                 if(fourthPart.size() == 2) | ||||
|                 { | ||||
|                     HttpUtils::QueryParamsDecode(uri.query, fourthPart[1]);    | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|              | ||||
|             auto hostPortPart = HttpUtils::SplitString(secondPart[0],":",2); | ||||
| 
 | ||||
|             if(hostPortPart.empty()) return false; | ||||
|             if(hostPortPart.size() == 2) | ||||
|             { | ||||
|                 uri.port = (uint16_t)std::stoul(hostPortPart[1]); | ||||
|             } | ||||
|             uri.host = hostPortPart[0]; | ||||
|          | ||||
|         return true; | ||||
|     } | ||||
|     std::string Uri::GetPathAndQuery() | ||||
|     { | ||||
|         return this->path + this->GetQuery(); | ||||
|     } | ||||
|     std::string Uri::GetQuery() | ||||
|     { | ||||
|         if(this->query.kvp.empty()) return ""; | ||||
|         std::string queryStr = "?"; | ||||
|         queryStr.append(HttpUtils::QueryParamsEncode(query)); | ||||
|         return queryStr; | ||||
|     } | ||||
| 
 | ||||
|     std::string Uri::ToString() | ||||
|     { | ||||
|         std::string uri = this->scheme; | ||||
|         uri.append("//"); | ||||
|         uri.append(this->host); | ||||
|         if(this->port > 0)  | ||||
|         { | ||||
|             uri.push_back(':'); | ||||
|             uri.append(std::to_string(this->port)); | ||||
|         } | ||||
|         uri.append(this->GetPathAndQuery()); | ||||
|         return uri; | ||||
|     } | ||||
|     char HttpUtils::NibbleToHex(uint8_t b) | ||||
|     { | ||||
|         b %= 16; | ||||
|         if(b >= 0 && b <= 9) | ||||
|             return b + '0'; | ||||
|         if(b >= 10 && b <= 15) | ||||
|             return b + ('a' - 10); | ||||
|         return 0; | ||||
|     } | ||||
|     uint8_t HttpUtils::HexToNibble(char c) | ||||
|     { | ||||
|         if(c >= '0' && c <= '9') | ||||
|             return (uint8_t)(c - '0'); | ||||
|          | ||||
|         if(c >= 'A' && c <= 'F') | ||||
|             return (uint8_t)(c - 55); | ||||
|          | ||||
|         if(c >= 'a' && c <= 'f') | ||||
|             return (uint8_t)(c - 87); | ||||
| 
 | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     std::string HttpUtils::MimeType(std::filesystem::path p) | ||||
|     { | ||||
|         std::string ext = p.extension().string(); | ||||
|         if(ext == ".html" || ext == ".htm") | ||||
|         { | ||||
|             return "text/html"; | ||||
|         } | ||||
|         if(ext == ".txt" || ext == ".log" || ext == ".twss") | ||||
|         { | ||||
|             return "text/plain"; | ||||
|         } | ||||
|         if(ext == ".woff") | ||||
|         { | ||||
|             return "application/x-font-woff"; | ||||
|         } | ||||
|         if(ext == ".vtt") | ||||
|         { | ||||
|             return "text/vtt"; | ||||
|         } | ||||
|         if(ext == ".svg") | ||||
|         { | ||||
|             return "image/svg+xml"; | ||||
|         } | ||||
|         if(ext == ".webp") | ||||
|         { | ||||
|             return "image/webp"; | ||||
|         } | ||||
|         if(ext == ".vcf") | ||||
|         { | ||||
|             return "text/v-card"; | ||||
|         } | ||||
|         if(ext == ".rss" || ext == ".xml" || ext == ".atom" || ext == ".rdf") | ||||
|         { | ||||
|             return "application/xml"; | ||||
|         } | ||||
|         if(ext == ".js") | ||||
|         { | ||||
|             return "text/javascript"; | ||||
|         } | ||||
|         if(ext == ".json") | ||||
|         { | ||||
|             return "application/json"; | ||||
|         } | ||||
|         if(ext == ".wasm") | ||||
|         { | ||||
|             return "application/wasm"; | ||||
|         } | ||||
|         if(ext == ".png") | ||||
|         { | ||||
|             return "image/png"; | ||||
|         } | ||||
|         if(ext == ".jpg" || ext == ".jpeg") | ||||
|         { | ||||
|             return "image/jpeg"; | ||||
|         } | ||||
|         if(ext == ".css") | ||||
|         { | ||||
|             return "text/css"; | ||||
|         } | ||||
|         if(ext == ".gif") | ||||
|         { | ||||
|             return "image/gif"; | ||||
|         } | ||||
|         if(ext == ".mp4") | ||||
|         { | ||||
|             return "video/mp4"; | ||||
|         } | ||||
|         if(ext == ".mov") | ||||
|         { | ||||
|             return "video/quicktime"; | ||||
|         } | ||||
|         if(ext == ".m4a") | ||||
|         { | ||||
|             return "audio/mp4"; | ||||
|         } | ||||
|         if(ext == ".webm") | ||||
|         { | ||||
|             return "video/webm"; | ||||
|         } | ||||
|         if(ext == ".webmanifest") | ||||
|         { | ||||
|             return "application/manifest+json"; | ||||
|         } | ||||
|         if(ext == ".ico") | ||||
|         { | ||||
|             return "image/x-icon"; | ||||
|         } | ||||
| 
 | ||||
|         return "application/octet-stream"; | ||||
|     } | ||||
|     bool HttpUtils::Invalid(char c) | ||||
|     { | ||||
|         //just do windows because it is the strictist when it comes to windows, mac and linux
 | ||||
|         if(c >= 0 && c < 32) return true; | ||||
|         if(c == 127) return true; | ||||
|         if(c == '\\') return true; | ||||
|         if(c == '*') return true; | ||||
|         if(c == '/') return true; | ||||
|         if(c == '|') return true; | ||||
|         if(c == ':') return true; | ||||
|         if(c == '<') return true; | ||||
|         if(c == '>') return true; | ||||
|         if(c == '\"') return true; | ||||
|         if(c == '?') return true; | ||||
|         return false; | ||||
|     } | ||||
|     std::string HttpUtils::Sanitise(std::string text) | ||||
|     { | ||||
|         std::string myStr={}; | ||||
|         for(auto item : text) | ||||
|         { | ||||
|             if(Invalid(item)) continue; | ||||
|             myStr.push_back(item); | ||||
|         } | ||||
|         return myStr; | ||||
|     } | ||||
| 
 | ||||
|     void HttpUtils::QueryParamsDecode(HttpDictionary& dict,std::string query) | ||||
|     { | ||||
|         for(auto item : SplitString(query,"&")) | ||||
|         { | ||||
|             std::vector<std::string> ss=SplitString(item,"=",2); | ||||
|             if(ss.size() >= 1) | ||||
|             { | ||||
|                 std::string value = {}; | ||||
|                 if(ss.size() == 2) | ||||
|                 { | ||||
|                     value = UrlDecode(ss[1]); | ||||
|                 } | ||||
|                 dict.AddValue(UrlDecode(ss[0]),value); | ||||
|             } | ||||
|         } | ||||
|     }     | ||||
|     std::string HttpUtils::Join(std::string joinStr, std::vector<std::string> ents) | ||||
|     { | ||||
|         std::string str={}; | ||||
|         bool first=true; | ||||
| 
 | ||||
|         for(auto item : ents) | ||||
|         { | ||||
|             if(!first) str.append(joinStr); | ||||
|             str.append(item); | ||||
|             first=false; | ||||
|         } | ||||
|         return str; | ||||
|     } | ||||
|     std::string HttpUtils::QueryParamsEncode(HttpDictionary& dict) | ||||
|     { | ||||
|         std::string s={}; | ||||
|         bool first = true; | ||||
|         for(auto item : dict.kvp) | ||||
|         { | ||||
|             for(auto item2 : item.second) | ||||
|             { | ||||
|                 if(!first) | ||||
|                 { | ||||
|                     s.push_back('&'); | ||||
|                 } | ||||
|                 s.insert(s.size(),UrlEncode(item.first)); | ||||
|                 s.push_back('='); | ||||
|                 s.insert(s.size(),UrlEncode(item2)); | ||||
|                 first=false; | ||||
|             } | ||||
|         } | ||||
|         return s; | ||||
|     } | ||||
| 
 | ||||
|     std::string HttpUtils::UrlDecode(std::string v) | ||||
|     {    | ||||
|         std::string s = {}; | ||||
| 
 | ||||
|         for(size_t i = 0;i<v.size();i++) | ||||
|         { | ||||
|             if(v[i] == '+') | ||||
|                 s.push_back(' '); | ||||
|             else if(v[i] == '%') | ||||
|             { | ||||
|                 i++; | ||||
|                 uint8_t n = HexToNibble(v[i])<<4; | ||||
|                 i++; | ||||
|                 n |= HexToNibble(v[i]); | ||||
|                 s.push_back((char)n); | ||||
|             } | ||||
|             else | ||||
|                 s.push_back(v[i]); | ||||
| 
 | ||||
|         } | ||||
|         return s; | ||||
|     } | ||||
|     std::string HttpUtils::UrlPathEncode(std::string v,bool ignoreSpace) | ||||
|     { | ||||
|         std::string s = {}; | ||||
| 
 | ||||
|         for(auto item : v) | ||||
|         { | ||||
|             if(item >= 'A' && item <= 'Z') | ||||
|                 s.push_back(item); | ||||
|             else if(item >= 'a' && item <= 'z') | ||||
|                 s.push_back(item); | ||||
|             else if(item >= '0' && item <= '9') | ||||
|                 s.push_back(item); | ||||
|             else if(item == '-' || item == '_' || item == '.' || item == '~') | ||||
|                 s.push_back(item); | ||||
|             else | ||||
|             { | ||||
|                 if(item != ' ' || !ignoreSpace) | ||||
|                 { | ||||
|                     s.push_back('%'); | ||||
|                     s.push_back(NibbleToHex((item >> 4) & 0xF)); | ||||
|                     s.push_back(NibbleToHex((item) & 0xF)); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     s.push_back(' '); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return s; | ||||
|     } | ||||
|     std::string HttpUtils::UrlPathDecode(std::string v) | ||||
|     { | ||||
|         std::string s = {}; | ||||
| 
 | ||||
|         for(size_t i = 0;i<v.size();i++) | ||||
|         { | ||||
|             if(v[i] == '%') | ||||
|             { | ||||
|                 i++; | ||||
|                 uint8_t n = HexToNibble(v[i])<<4; | ||||
|                 i++; | ||||
|                 n |= HexToNibble(v[i]); | ||||
|                 s.push_back((char)n); | ||||
|             } | ||||
|             else | ||||
|                 s.push_back(v[i]); | ||||
| 
 | ||||
|         } | ||||
|         return s; | ||||
|     } | ||||
| 
 | ||||
|     std::string HttpUtils::UrlEncode(std::string v) | ||||
|     { | ||||
|         std::string s = {}; | ||||
| 
 | ||||
|         for(auto item : v) | ||||
|         { | ||||
|             if(item == ' ') | ||||
|                 s.push_back('+'); | ||||
|             else if(item >= 'A' && item <= 'Z') | ||||
|                 s.push_back(item); | ||||
|             else if(item >= 'a' && item <= 'z') | ||||
|                 s.push_back(item); | ||||
|             else if(item >= '0' && item <= '9') | ||||
|                 s.push_back(item); | ||||
|             else if(item == '-' || item == '_' || item == '.' || item == '~') | ||||
|                 s.push_back(item); | ||||
|             else | ||||
|             { | ||||
|                 s.push_back('%'); | ||||
|                 s.push_back(NibbleToHex((item >> 4) & 0xF)); | ||||
|                 s.push_back(NibbleToHex((item) & 0xF)); | ||||
|             } | ||||
|         } | ||||
|         return s; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<std::string> HttpUtils::SplitString(std::string text, std::string delimiter,std::size_t maxCnt) | ||||
|     { | ||||
|         std::vector<std::string> strs; | ||||
|         std::size_t i = 1; | ||||
|         while(text.length() > 0) | ||||
|         { | ||||
|             if(i == maxCnt) | ||||
|            { | ||||
|                 strs.push_back(text); | ||||
|                 break;     | ||||
|            } | ||||
|            std::size_t index= text.find(delimiter); | ||||
| 
 | ||||
|             | ||||
| 
 | ||||
|            if(index == std::string::npos) | ||||
|            { | ||||
|                 strs.push_back(text); | ||||
|                 break; | ||||
|            } | ||||
|            else | ||||
|            { | ||||
|                 std::string left = text.substr(0,index); | ||||
| 
 | ||||
|                 text = text.substr(index+delimiter.size()); | ||||
| 
 | ||||
|                 strs.push_back(left); | ||||
| 
 | ||||
|            } | ||||
|            i++; | ||||
|         } | ||||
|         return strs; | ||||
|     } | ||||
|     std::string HttpUtils::HtmlEncode(std::string html) | ||||
|     { | ||||
|         std::string myHtml = {}; | ||||
|         for(auto item : html) | ||||
|         { | ||||
|             if(item == '\"') | ||||
|             { | ||||
|                 myHtml.append("""); | ||||
|             } | ||||
|             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<std::string> value) | ||||
|     { | ||||
|         kvp[key] = value; | ||||
|     } | ||||
|     void HttpDictionary::AddValue(std::string key, std::string value) | ||||
|     { | ||||
|         kvp[key].push_back(value); | ||||
|     } | ||||
|     void HttpDictionary::AddValue(std::string key, std::vector<std::string> value) | ||||
|     { | ||||
|         auto& ls = kvp[key]; | ||||
|         ls.insert(ls.end(), value.begin(), value.end()); | ||||
|     } | ||||
|     bool HttpDictionary::TryGetFirst(std::string key, std::string& value) | ||||
|     { | ||||
|         if(kvp.count(key) == 0) return false; | ||||
|         auto& ls = kvp[key]; | ||||
|         if(ls.empty()) return false; | ||||
|         value = ls.front(); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     bool HttpDictionary::TryGetFirstInt(std::string key, int64_t& value) | ||||
|     { | ||||
|         std::string val; | ||||
|         if(!TryGetFirst(key,val)) return false; | ||||
|         try{ | ||||
|             size_t off = 0; | ||||
|             auto v = std::stoll(val,&off); | ||||
|             if(off != val.size()) return false; | ||||
|             value = v; | ||||
|         }  | ||||
|         catch(std::exception& ex) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     bool HttpDictionary::TryGetFirstDouble(std::string key, double& value) | ||||
|     { | ||||
|         std::string val; | ||||
|         if(!TryGetFirst(key,val)) return false; | ||||
|         try{ | ||||
|             size_t off = 0; | ||||
|             auto v = std::stod(val,&off); | ||||
|             if(off != val.size()) return false; | ||||
|             value = v; | ||||
|         }  | ||||
|         catch(std::exception& ex) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     bool HttpDictionary::GetFirstBoolean(std::string key) | ||||
|     { | ||||
|         std::string val; | ||||
|         if(!TryGetFirst(key,val)) return false; | ||||
|         return val == "true" || val == "on"; | ||||
|     } | ||||
|      | ||||
| } | ||||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,68 @@ | |||
| #include "TessesFramework/Streams/MemoryStream.hpp" | ||||
| 
 | ||||
| namespace Tesses::Framework::Streams | ||||
| { | ||||
|     MemoryStream::MemoryStream(bool isWritable) | ||||
|     { | ||||
|         this->offset=0; | ||||
|         this->writable = isWritable; | ||||
|     } | ||||
|     std::vector<uint8_t>& MemoryStream::GetBuffer() | ||||
|     { | ||||
|         return this->buffer; | ||||
|     } | ||||
|     size_t MemoryStream::Read(uint8_t* buff, size_t sz) | ||||
|     { | ||||
|         if(this->offset >= this->buffer.size()) return 0; | ||||
|         size_t toRead = std::min(sz, this->buffer.size()-this->offset); | ||||
|         memcpy(buff, this->buffer.data() + this->offset, sz); | ||||
|         this->offset += toRead; | ||||
|         return toRead; | ||||
|     } | ||||
|     size_t MemoryStream::Write(const uint8_t* buff, size_t sz) | ||||
|     { | ||||
|         if(!this->writable) return 0; | ||||
|         if(this->offset > this->buffer.size()) | ||||
|         { | ||||
|             this->buffer.resize(this->offset+sz); | ||||
|         } | ||||
|         this->buffer.insert(this->buffer.begin()+this->offset, buff, buff+sz); | ||||
|         this->offset+=sz; | ||||
|         return sz; | ||||
|     } | ||||
|     bool MemoryStream::CanRead() | ||||
|     { | ||||
|         return true; | ||||
|     } | ||||
|     bool MemoryStream::CanWrite() | ||||
|     { | ||||
|         return this->writable; | ||||
|     } | ||||
|     bool MemoryStream::CanSeek() | ||||
|     { | ||||
|         return true; | ||||
|     } | ||||
|     int64_t MemoryStream::GetLength() | ||||
|     { | ||||
|         return this->buffer.size(); | ||||
|     } | ||||
|     int64_t MemoryStream::GetPosition() | ||||
|     { | ||||
|         return (int64_t)this->offset; | ||||
|     } | ||||
|     void MemoryStream::Seek(int64_t pos, SeekOrigin whence) | ||||
|     { | ||||
|         switch(whence) | ||||
|         { | ||||
|             case SeekOrigin::Begin: | ||||
|                 this->offset = (size_t)pos; | ||||
|                 break; | ||||
|             case SeekOrigin::Current: | ||||
|                 this->offset += (size_t)pos; | ||||
|                 break; | ||||
|             case SeekOrigin::End: | ||||
|                 this->offset = (size_t)(this->buffer.size() + pos); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,528 @@ | |||
| #include "TessesFramework/Streams/NetworkStream.hpp" | ||||
| #include "TessesFramework/Http/HttpUtils.hpp" | ||||
| 
 | ||||
| using HttpUtils = Tesses::Framework::Http::HttpUtils; | ||||
| #if defined(GEKKO) | ||||
| 
 | ||||
| #define ss_family sin_family | ||||
| #endif | ||||
| #if defined(GEKKO) && !(defined(TESSESFRAMEWORK_USE_WII_SOCKET) && defined(HW_RVL))  | ||||
| #include <network.h> | ||||
| #define NETWORK_RECV net_recv | ||||
| #define sockaddr_storage sockaddr_in | ||||
| #error "Not supported yet" | ||||
| #else | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| extern "C" { | ||||
| #include <netinet/in.h> | ||||
| #include <sys/socket.h> | ||||
| #include <arpa/inet.h> | ||||
| #include <netdb.h> | ||||
| #include <fcntl.h> | ||||
| #include <unistd.h> | ||||
| } | ||||
| #if defined(GEKKO) | ||||
| 
 | ||||
| extern "C" uint32_t if_config( char *local_ip, char *netmask, char *gateway,bool use_dhcp, int max_retries); | ||||
| #else | ||||
| #include <ifaddrs.h> | ||||
| #endif | ||||
| 
 | ||||
| #define NETWORK_SEND send | ||||
| #define NETWORK_RECV recv | ||||
| #define NETWORK_SENDTO sendto | ||||
| #define NETWORK_RECVFROM recvfrom | ||||
| #define NETWORK_SOCKET socket | ||||
| #define NETWORK_SETSOCKOPT setsockopt | ||||
| #define NETWORK_CONNECT connect | ||||
| #define NETWORK_BIND bind | ||||
| #define NETWORK_LISTEN listen | ||||
| #define NETWORK_ACCEPT accept | ||||
| #define NETWORK_GETADDRINFO getaddrinfo | ||||
| #define NETWORK_FREEADDRINFO freeaddrinfo | ||||
| #define NETWORK_CLOSE close | ||||
| #endif | ||||
| 
 | ||||
| #undef AF_INET6 | ||||
| 
 | ||||
| 
 | ||||
| namespace Tesses::Framework::Streams { | ||||
|     std::string StringifyIP(struct sockaddr* addr); | ||||
|     std::vector<std::pair<std::string,std::string>> NetworkStream::GetIPs(bool ipV6) | ||||
|     { | ||||
|         std::vector<std::pair<std::string, std::string>> ipConfig; | ||||
|          | ||||
|         #if defined(GEKKO) | ||||
|         //if_config( char *local_ip, char *netmask, char *gateway,bool use_dhcp, int max_retries);
 | ||||
|         char localIp[16]; | ||||
|         char netmask[16]; | ||||
|         char gateway[16]; | ||||
|         if_config(localIp,netmask, gateway, true, 1); | ||||
|         ipConfig.push_back(std::pair<std::string,std::string>("net",localIp)); | ||||
|         #else | ||||
|         struct ifaddrs *ifAddrStruct = NULL; | ||||
|         getifaddrs(&ifAddrStruct); | ||||
| 
 | ||||
|         for (struct ifaddrs *ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) { | ||||
|             if (ifa->ifa_addr->sa_family == AF_INET) { // IPv4
 | ||||
|                  | ||||
|                 ipConfig.push_back(std::pair<std::string,std::string>(ifa->ifa_name, StringifyIP(ifa->ifa_addr))); | ||||
|                  | ||||
|             } | ||||
|             #if defined(AF_INET6) | ||||
|             if (ifa->ifa_addr->sa_family == AF_INET6 && ipV6) { // IPv6
 | ||||
|                  | ||||
|                 ipConfig.push_back(std::pair<std::string,std::string>(ifa->ifa_name, StringifyIP(ifa->ifa_addr))); | ||||
|                  | ||||
|             } | ||||
|             #endif | ||||
|         } | ||||
| 
 | ||||
|         freeifaddrs(ifAddrStruct); | ||||
|         #endif | ||||
|         return ipConfig; | ||||
|          | ||||
|     } | ||||
|     void SetPort(struct sockaddr* addr, uint16_t port) | ||||
|     { | ||||
|         if(addr->sa_family == AF_INET) | ||||
|         { | ||||
|             struct sockaddr_in* a = (struct sockaddr_in*)addr;\ | ||||
|             a->sin_port = htons(port); | ||||
|         } | ||||
|         #if defined(AF_INET6) | ||||
|         if(addr->sa_family == AF_INET6) | ||||
|         { | ||||
|             struct sockaddr_in6* a = (struct sockaddr_in6*)addr;\ | ||||
|             a->sin6_port = htons(port); | ||||
|              | ||||
|         } | ||||
|         #endif | ||||
|          | ||||
|     } | ||||
|     uint16_t GetPort(struct sockaddr* addr) | ||||
|     { | ||||
|         if(addr->sa_family == AF_INET) | ||||
|         { | ||||
|             struct sockaddr_in* a = (struct sockaddr_in*)addr;\ | ||||
|             return ntohs(a->sin_port); | ||||
|         } | ||||
|         #if defined(AF_INET6) | ||||
|         if(addr->sa_family == AF_INET6) | ||||
|         { | ||||
|             struct sockaddr_in6* a = (struct sockaddr_in6*)addr;\ | ||||
|             return ntohs(a->sin6_port); | ||||
|              | ||||
|         } | ||||
|         #endif | ||||
|         return 0; | ||||
|     } | ||||
|     bool IPParse(std::string str,struct sockaddr_storage* addr) | ||||
|     {     | ||||
|         memset(addr,0,sizeof(struct sockaddr_storage)); | ||||
|         uint8_t ip[16]; | ||||
|         if(inet_pton(AF_INET,str.c_str(),ip)==1) | ||||
|         { | ||||
|             struct sockaddr_in* inAddr = (struct sockaddr_in*)addr; | ||||
|             inAddr->sin_family = AF_INET; | ||||
|             memcpy(&inAddr->sin_addr,ip,4); | ||||
|             return true; | ||||
|         } | ||||
|         #if defined(AF_INET6) | ||||
|         if(inet_pton(AF_INET6,str.c_str(),ip)==1) | ||||
|         { | ||||
| 
 | ||||
|             struct sockaddr_in6* inAddr = (struct sockaddr_in6*)addr; | ||||
|              | ||||
|             inAddr->sin6_family = AF_INET6; | ||||
|             memcpy(&inAddr->sin6_addr,ip,16); | ||||
|             return 6; | ||||
|         } | ||||
|         #endif | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     std::string StringifyIP(struct sockaddr* addr) | ||||
|     { | ||||
|         if(addr->sa_family == AF_INET) | ||||
|         { | ||||
|             uint8_t* ip = (uint8_t*)&(((struct sockaddr_in*)addr)->sin_addr); | ||||
|             return std::to_string((uint32_t)ip[0]) + "." + std::to_string((uint32_t)ip[1]) +  "." + std::to_string((uint32_t)ip[2]) + "." + std::to_string((uint32_t)ip[3]); | ||||
|         } | ||||
|         #if defined(AF_INET6) | ||||
|         if(addr->sa_family == AF_INET6) | ||||
|         { | ||||
|             uint8_t* ip = (uint8_t*)&(((struct sockaddr_in6*)addr)->sin6_addr); | ||||
| 
 | ||||
|             return std::string({ | ||||
|             HttpUtils::NibbleToHex((ip[0] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[0] & 0x0F), | ||||
|             HttpUtils::NibbleToHex((ip[1] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[1] & 0x0F), | ||||
|             ':', | ||||
|             HttpUtils::NibbleToHex((ip[2] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[2] & 0x0F), | ||||
|             HttpUtils::NibbleToHex((ip[3] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[3] & 0x0F), | ||||
|             ':', | ||||
|             HttpUtils::NibbleToHex((ip[4] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[4] & 0x0F), | ||||
|             HttpUtils::NibbleToHex((ip[5] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[5] & 0x0F), | ||||
|             ':', | ||||
|             HttpUtils::NibbleToHex((ip[6] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[6] & 0x0F), | ||||
|             HttpUtils::NibbleToHex((ip[7] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[7] & 0x0F), | ||||
|             ':', | ||||
|             HttpUtils::NibbleToHex((ip[8] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[8] & 0x0F), | ||||
|             HttpUtils::NibbleToHex((ip[9] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[9] & 0x0F), | ||||
|             ':', | ||||
|             HttpUtils::NibbleToHex((ip[10] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[10] & 0x0F), | ||||
|             HttpUtils::NibbleToHex((ip[11] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[11] & 0x0F), | ||||
|             ':', | ||||
|             HttpUtils::NibbleToHex((ip[12] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[12] & 0x0F), | ||||
|             HttpUtils::NibbleToHex((ip[13] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[13] & 0x0F), | ||||
|             ':', | ||||
|             HttpUtils::NibbleToHex((ip[14] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[14] & 0x0F), | ||||
|             HttpUtils::NibbleToHex((ip[15] >> 4) & 0x0F), | ||||
|             HttpUtils::NibbleToHex(ip[15] & 0x0F), | ||||
|             }); | ||||
| 
 | ||||
|              | ||||
|              | ||||
|              | ||||
|         } | ||||
|         #endif | ||||
|         return ""; | ||||
|     } | ||||
| 
 | ||||
|     TcpServer::TcpServer(uint16_t port, int32_t backlog) | ||||
|     { | ||||
|         this->owns=true; | ||||
|         this->sock = NETWORK_SOCKET(AF_INET, SOCK_STREAM, 0);     | ||||
|         if(this->sock < 0)  | ||||
|         { | ||||
|             this->valid=false; | ||||
|             return; | ||||
|         }     | ||||
| 
 | ||||
|         struct sockaddr_in addr; | ||||
|         memset(&addr, 0, sizeof(addr)); | ||||
|         addr.sin_family = AF_INET; | ||||
|         addr.sin_port = htons(port); | ||||
|         if(NETWORK_BIND(this->sock, (const sockaddr*)&addr, (socklen_t)sizeof(addr)) != 0) | ||||
|         { | ||||
|             this->valid = false; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if(NETWORK_LISTEN(this->sock, backlog) != 0)  | ||||
|         { | ||||
|             this->valid = false; | ||||
|             return; | ||||
|         } | ||||
|         this->valid = true; | ||||
|     } | ||||
|     TcpServer::TcpServer(std::string ip, uint16_t port, int32_t backlog) | ||||
|     { | ||||
|         this->owns=true; | ||||
|         struct sockaddr_storage addr; | ||||
|         memset(&addr, 0, sizeof(addr)); | ||||
| 
 | ||||
|         uint8_t ipBytes[16]; | ||||
|         bool success = IPParse(ip, &addr); | ||||
|         if(!success)  | ||||
|         { | ||||
|             this->valid=false; | ||||
|             return; | ||||
|         } | ||||
|         | ||||
|         SetPort((struct sockaddr*)&addr, port); | ||||
| 
 | ||||
|         this->sock = NETWORK_SOCKET((int)addr.ss_family, SOCK_STREAM, 0);     | ||||
|         if(this->sock < 0)  | ||||
|         { | ||||
|             this->valid=false; | ||||
|             return; | ||||
|         }     | ||||
|          | ||||
|          | ||||
|      | ||||
|          | ||||
|         if(NETWORK_BIND(this->sock, (const sockaddr*)&addr, (socklen_t)sizeof(addr)) != 0) | ||||
|         { | ||||
|             this->valid = false; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if(NETWORK_LISTEN(this->sock, backlog) != 0)  | ||||
|         { | ||||
|             this->valid = false; | ||||
|             return; | ||||
|         } | ||||
|         this->valid = true; | ||||
|     } | ||||
|     TcpServer::TcpServer(int32_t sock, bool owns) | ||||
|     { | ||||
|         this->valid = sock >= 0; | ||||
|         this->sock = sock; | ||||
|         this->owns = owns; | ||||
|     } | ||||
|     TcpServer::~TcpServer() | ||||
|     { | ||||
|         if(this->valid && this->owns) | ||||
|             Close(); | ||||
|     } | ||||
|     void TcpServer::Close() | ||||
|     { | ||||
|         if(this->valid) | ||||
|             NETWORK_CLOSE(this->sock); | ||||
|         this->valid=false; | ||||
|     } | ||||
|     NetworkStream* TcpServer::GetStream(std::string& ip, uint16_t& port) | ||||
|     { | ||||
|         struct sockaddr_storage storage; | ||||
|         memset(&storage,0, sizeof(storage)); | ||||
|         socklen_t addrlen=(socklen_t)sizeof(storage); | ||||
|          | ||||
|         int s = NETWORK_ACCEPT(this->sock, (struct sockaddr*)&storage, &addrlen); | ||||
|         if(s < 0) | ||||
|         { | ||||
|             return nullptr; | ||||
|         } | ||||
| 
 | ||||
|         ip = StringifyIP((struct sockaddr*)&storage); | ||||
|         port = GetPort((struct sockaddr*)&storage); | ||||
|          | ||||
|         return new NetworkStream(s,true); | ||||
|     } | ||||
|     bool NetworkStream::CanRead() | ||||
|     { | ||||
|         return this->success; | ||||
|     } | ||||
|     bool NetworkStream::CanWrite() | ||||
|     { | ||||
|         return this->success; | ||||
|     } | ||||
|     NetworkStream::NetworkStream(bool ipV6,bool datagram) | ||||
|     { | ||||
|         this->endOfStream=false; | ||||
|         this->owns = true; | ||||
|         this->success=false; | ||||
|         if(ipV6) | ||||
|         { | ||||
|             #if defined(AF_INET6) | ||||
|             this->sock = socket(AF_INET6, datagram ? SOCK_DGRAM : SOCK_STREAM, 0); | ||||
|             if(this->sock >= 0) this->success=true; | ||||
|             #endif | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             #if defined(AF_INET) | ||||
|             this->sock = socket(AF_INET, datagram ? SOCK_DGRAM : SOCK_STREAM, 0); | ||||
|             if(this->sock >= 0) this->success=true; | ||||
|             #endif | ||||
|         } | ||||
|     } | ||||
|     NetworkStream::NetworkStream(std::string ipOrFqdn, uint16_t port, bool datagram, bool broadcast, bool supportIPv6) | ||||
|     { | ||||
|         this->endOfStream = false; | ||||
|         this->owns=true; | ||||
|         this->success=false; | ||||
|         std::string portStr = std::to_string((uint32_t)port); | ||||
|         struct addrinfo hint; | ||||
|         memset(&hint, 0, sizeof(hint)); | ||||
|         #if defined(AF_INET6) | ||||
|         hint.ai_family = supportIPv6 ? AF_UNSPEC : AF_INET; | ||||
|         #else | ||||
|         hint.ai_family = AF_INET; | ||||
|         #endif | ||||
|         hint.ai_socktype = datagram ? SOCK_DGRAM : SOCK_STREAM; | ||||
| 
 | ||||
|         struct addrinfo* result;    | ||||
| 
 | ||||
| 
 | ||||
|         int status = NETWORK_GETADDRINFO(ipOrFqdn.c_str(),portStr.c_str(), &hint, &result); | ||||
|         if(status != 0) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         struct addrinfo* tmp=result; | ||||
| 
 | ||||
| 
 | ||||
|         while(tmp != nullptr) | ||||
|         { | ||||
|             sock = NETWORK_SOCKET(tmp->ai_family,tmp->ai_socktype,tmp->ai_protocol); | ||||
|             if(broadcast) | ||||
|             { | ||||
|                 int broadcast = 1; | ||||
|                 if (NETWORK_SETSOCKOPT(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)) != 0) { | ||||
|                     this->success=false; | ||||
|                     NETWORK_CLOSE(this->sock); | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(sock != -1) | ||||
|             { | ||||
|                 this->success=true; | ||||
|                 if(NETWORK_CONNECT(this->sock,(const sockaddr*)tmp->ai_addr,tmp->ai_addrlen) == 0) break; | ||||
|                 this->success=false; | ||||
|                 NETWORK_CLOSE(this->sock); | ||||
|             } | ||||
|             tmp = tmp->ai_next; | ||||
|         } | ||||
|         NETWORK_FREEADDRINFO(result); | ||||
| 
 | ||||
|     } | ||||
|     NetworkStream::NetworkStream(int32_t sock, bool owns) | ||||
|     { | ||||
|         this->endOfStream = false; | ||||
|         this->success = sock >= 0; | ||||
|         this->sock = sock; | ||||
|         this->owns = owns; | ||||
|     } | ||||
|     bool NetworkStream::EndOfStream() | ||||
|     { | ||||
|         return this->endOfStream; | ||||
|     } | ||||
|     void NetworkStream::Listen(int32_t backlog) | ||||
|     {    | ||||
|         if(this->success) | ||||
|             NETWORK_LISTEN(this->sock, backlog); | ||||
|     } | ||||
|      | ||||
|     void NetworkStream::Bind(std::string ip, uint16_t port) | ||||
|     { | ||||
|         if(!this->success) return; | ||||
|         struct sockaddr_storage addr; | ||||
|         memset(&addr, 0, sizeof(addr)); | ||||
| 
 | ||||
|         uint8_t ipBytes[16]; | ||||
|         bool success = IPParse(ip, &addr); | ||||
|         if(!success)  | ||||
|         { | ||||
|             this->success=false; | ||||
|             if(this->owns) | ||||
|                 NETWORK_CLOSE(this->sock); | ||||
|             return; | ||||
|         } | ||||
|         | ||||
|         SetPort((struct sockaddr*)&addr, port); | ||||
| 
 | ||||
|         int r = NETWORK_BIND(this->sock, (struct sockaddr*)&addr, sizeof(addr)); | ||||
|         if(r != 0) | ||||
|         { | ||||
|             this->success=false; | ||||
|             if(this->owns) | ||||
|                 NETWORK_CLOSE(this->sock); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|     void NetworkStream::SetBroadcast(bool bC) | ||||
|     { | ||||
|         if(!this->success) return; | ||||
|         int broadcast = 1; | ||||
|         if (NETWORK_SETSOCKOPT(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)) != 0)  | ||||
|         { | ||||
|             this->success=false; | ||||
|             if(this->owns) | ||||
|                 NETWORK_CLOSE(this->sock); | ||||
|                      | ||||
|         } | ||||
|     } | ||||
|     NetworkStream* NetworkStream::Accept(std::string& ip, uint16_t& port) | ||||
|     { | ||||
|         if(!this->success) return nullptr; | ||||
|         struct sockaddr_storage storage; | ||||
|         socklen_t addrlen=(socklen_t)sizeof(storage); | ||||
|         int s = NETWORK_ACCEPT(this->sock, (struct sockaddr*)&storage, (socklen_t*)&addrlen); | ||||
|         if(s < 0) | ||||
|         { | ||||
|             return nullptr; | ||||
|         } | ||||
| 
 | ||||
|         ip = StringifyIP((struct sockaddr*)&storage); | ||||
|         port = GetPort((struct sockaddr*)&storage); | ||||
|          | ||||
|         return new NetworkStream(s,true); | ||||
|     } | ||||
|     size_t NetworkStream::Read(uint8_t* buff, size_t sz) | ||||
|     { | ||||
|          | ||||
|         if(!this->success) return 0; | ||||
|         ssize_t r = NETWORK_RECV(this->sock,buff,sz,0); | ||||
| 
 | ||||
|         if(r <= 0) | ||||
|         { | ||||
|             this->endOfStream=true; | ||||
|             return 0; | ||||
|         } | ||||
|          | ||||
|         return (size_t)r; | ||||
|     } | ||||
|     size_t NetworkStream::Write(const uint8_t* buff, size_t sz) | ||||
|     { | ||||
| 
 | ||||
|         if(!this->success) return 0; | ||||
|         | ||||
|         ssize_t sz2 = NETWORK_SEND(this->sock,buff,sz, 0); | ||||
|         if(sz2 < 0) return 0; | ||||
|          | ||||
|         return (size_t)sz; | ||||
|     } | ||||
|     size_t NetworkStream::ReadFrom(uint8_t* buff, size_t sz, std::string& ip, uint16_t& port) | ||||
|     { | ||||
|         if(!this->success) return 0; | ||||
|         struct sockaddr_storage storage; | ||||
|         socklen_t addrlen=(socklen_t)sizeof(storage); | ||||
|         ssize_t r = NETWORK_RECVFROM(this->sock,buff,sz,0, (struct sockaddr*)&storage, (socklen_t*)&addrlen); | ||||
|         ip = StringifyIP((struct sockaddr*)&storage); | ||||
|         port = GetPort((struct sockaddr*)&storage); | ||||
|         if(r < 0) return 0; | ||||
|         return (size_t)r; | ||||
| 
 | ||||
|          | ||||
| 
 | ||||
|          | ||||
|          | ||||
|     } | ||||
|     size_t NetworkStream::WriteTo(const uint8_t* buff, size_t sz, std::string ip, uint16_t port) | ||||
|     { | ||||
| 
 | ||||
|         if(!this->success) return 0; | ||||
|         struct sockaddr_storage addr; | ||||
|         memset(&addr, 0, sizeof(addr)); | ||||
| 
 | ||||
|         uint8_t ipBytes[16]; | ||||
|         bool success = IPParse(ip, &addr); | ||||
|         if(!success)  | ||||
|         { | ||||
|             this->success=false; | ||||
|             if(this->owns) | ||||
|                 NETWORK_CLOSE(this->sock); | ||||
|             return 0; | ||||
|         } | ||||
|         | ||||
|         SetPort((struct sockaddr*)&addr, port); | ||||
|         ssize_t sz2 = NETWORK_SENDTO(this->sock,buff,sz, 0, (const sockaddr*)&addr, (socklen_t)sizeof(addr)); | ||||
|         if(sz2 < 0) return 0; | ||||
|         return (size_t)sz2; | ||||
|     } | ||||
|     NetworkStream::~NetworkStream() | ||||
|     { | ||||
|         if(this->owns && this->success) | ||||
|             NETWORK_CLOSE(this->sock); | ||||
|     } | ||||
| } | ||||
|  | @ -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() | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,114 @@ | |||
| #include "TessesFramework/Common.hpp" | ||||
| #include "TessesFramework/Streams/NetworkStream.hpp" | ||||
| #include <atomic> | ||||
| #include <csignal> | ||||
| #if defined(GEKKO) | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <gccore.h> | ||||
| #include <fat.h> | ||||
| #include <ogc/pad.h> | ||||
| 
 | ||||
| 
 | ||||
| #if defined(HW_RVL) | ||||
| #if defined(TESSESFRAMEWORK_USE_WII_SOCKET) | ||||
| #include <wiisocket.h> | ||||
| #endif | ||||
| #include <wiiuse/wpad.h> | ||||
| #endif | ||||
| 
 | ||||
| static void *xfb = NULL; | ||||
| static GXRModeObj *rmode = NULL; | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| namespace Tesses::Framework | ||||
| { | ||||
|     volatile static bool isRunningSig=true; | ||||
|     volatile static std::atomic<bool> isRunning; | ||||
|     void TF_ConnectToSelf(uint16_t port) | ||||
|     { | ||||
|         Tesses::Framework::Streams::NetworkStream ns("127.0.0.1",port,false,false,false); | ||||
|          | ||||
|     } | ||||
|     bool TF_IsRunning() | ||||
|     { | ||||
|         return isRunning; | ||||
|     } | ||||
|     static void _sigInt(int c) | ||||
|     { | ||||
|         isRunningSig=false; | ||||
|     } | ||||
|     void TF_RunEventLoop() | ||||
|     { | ||||
|         while(isRunning) | ||||
|         { | ||||
|             TF_RunEventLoopItteration(); | ||||
|         } | ||||
|     } | ||||
|     void TF_RunEventLoopItteration() | ||||
|     { | ||||
|      | ||||
|         if(!isRunningSig) isRunning=false; | ||||
|         #if defined(GEKKO) | ||||
|         PAD_ScanPads(); | ||||
|         if(PAD_ButtonsDown(0) & PAD_BUTTON_START) isRunning=false; | ||||
|         #endif | ||||
|     } | ||||
|      | ||||
|     void TF_Quit() | ||||
|     { | ||||
|         isRunning=false; | ||||
|     } | ||||
|      | ||||
|     void TF_Init() | ||||
|     { | ||||
|      | ||||
|          | ||||
|         isRunning=true; | ||||
|         #if defined(GEKKO) | ||||
|         fatInitDefault(); | ||||
|         VIDEO_Init(); | ||||
|         PAD_Init(); | ||||
|         #if defined(HW_RVL) | ||||
|         #if defined(TESSESFRAMEWORK_USE_WII_SOCKET) | ||||
|         wiisocket_init(); | ||||
|         #endif | ||||
|         WPAD_Init(); | ||||
|         #endif | ||||
|         rmode = VIDEO_GetPreferredMode(NULL); | ||||
| 
 | ||||
|         // Allocate memory for the display in the uncached region
 | ||||
|         xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); | ||||
| 
 | ||||
|         // Initialise the console, required for printf
 | ||||
|         console_init(xfb,20,20,rmode->fbWidth,rmode->xfbHeight,rmode->fbWidth*VI_DISPLAY_PIX_SZ); | ||||
|         //SYS_STDIO_Report(true);
 | ||||
| 
 | ||||
|         // Set up the video registers with the chosen mode
 | ||||
|         VIDEO_Configure(rmode); | ||||
| 
 | ||||
|         // Tell the video hardware where our display memory is
 | ||||
|         VIDEO_SetNextFramebuffer(xfb); | ||||
| 
 | ||||
|         // Make the display visible
 | ||||
|         VIDEO_SetBlack(false); | ||||
|         // Flush the video register changes to the hardware
 | ||||
|         VIDEO_Flush(); | ||||
| 
 | ||||
|         // Wait for Video setup to complete
 | ||||
|         VIDEO_WaitVSync(); | ||||
|         if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync(); | ||||
| 
 | ||||
| 
 | ||||
|         // The console understands VT terminal escape codes
 | ||||
|         // This positions the cursor on row 2, column 0
 | ||||
|         // we can use variables for this with format codes too
 | ||||
|         // e.g. printf ("\x1b[%d;%dH", row, column );
 | ||||
|         printf("\x1b[2;0H"); | ||||
|         #else | ||||
|         signal(SIGPIPE,SIG_IGN); | ||||
|         signal(SIGINT,_sigInt); | ||||
|         #endif | ||||
|     } | ||||
| } | ||||
|  | @ -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; | ||||
|     } | ||||
| }; | ||||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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() | ||||
|     { | ||||
|          | ||||
|     } | ||||
| }    | ||||
|  | @ -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() | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,49 @@ | |||
| #include "TessesFramework/Threading/Mutex.hpp" | ||||
| #include <cstring> | ||||
| namespace Tesses::Framework::Threading | ||||
| { | ||||
|     Mutex::Mutex() | ||||
|     { | ||||
|         #if defined(GEKKO) | ||||
|         mtx = LWP_MUTEX_NULL; | ||||
|         LWP_MutexInit(&mtx, true); | ||||
|         #else | ||||
|         memset(&mtx, 0, sizeof(mtx_t)); | ||||
|         mtx_init(&mtx, mtx_recursive); | ||||
| 
 | ||||
|         #endif | ||||
|     } | ||||
|     void Mutex::Lock() | ||||
|     { | ||||
|         #if defined(GEKKO) | ||||
|         LWP_MutexLock(mtx); | ||||
|         #else | ||||
|         mtx_lock(&mtx); | ||||
|         #endif | ||||
|     } | ||||
|     void Mutex::Unlock() | ||||
|     { | ||||
|         #if defined(GEKKO) | ||||
|         LWP_MutexUnlock(mtx); | ||||
|         #else | ||||
|         mtx_unlock(&mtx); | ||||
|         #endif | ||||
|     } | ||||
|     bool Mutex::TryLock() | ||||
|     { | ||||
|         #if defined(GEKKO) | ||||
|         return LWP_MutexTryLock(mtx) == 0; | ||||
|         #else | ||||
|         return mtx_trylock(&mtx) == thrd_success; | ||||
|         #endif | ||||
|     } | ||||
|     Mutex::~Mutex() | ||||
|     { | ||||
|         #if defined(GEKKO) | ||||
|         LWP_MutexDestroy(mtx); | ||||
|         #else | ||||
|         mtx_destroy(&mtx); | ||||
|         #endif | ||||
| 
 | ||||
|     } | ||||
| }; | ||||
|  | @ -0,0 +1,53 @@ | |||
| #include "TessesFramework/Threading/Thread.hpp" | ||||
| #include <iostream> | ||||
| namespace Tesses::Framework::Threading | ||||
| { | ||||
|     #if defined(GEKKO) | ||||
|     void* Thread::cb(void* data) | ||||
|     #else | ||||
|     int Thread::cb(void* data) | ||||
|     #endif | ||||
|     { | ||||
|         auto thrd = static_cast<Thread*>(data); | ||||
|          | ||||
|         auto fn = thrd->fn; | ||||
|         thrd->hasInvoked=true; | ||||
|         fn(); | ||||
|         #if defined(GEKKO) | ||||
|             return NULL; | ||||
|         #else | ||||
|             return 0; | ||||
|         #endif | ||||
|     } | ||||
|     Thread::Thread(std::function<void()> fn) | ||||
|     { | ||||
|         this->hasInvoked=false; | ||||
|         this->fn = fn; | ||||
|              | ||||
|         #if defined(GEKKO) | ||||
|             thrd = LWP_THREAD_NULL; | ||||
|             LWP_CreateThread(&thrd, cb, static_cast<void*>(this), NULL, 12000, LWP_PRIO_HIGHEST); | ||||
|         #else | ||||
|             thrd_create(&thrd, cb, static_cast<void*>(this)); | ||||
|         #endif | ||||
|         while(!this->hasInvoked); | ||||
|     } | ||||
|     void Thread::Detach() | ||||
|     { | ||||
|         #if !defined(GEKKO) | ||||
|             thrd_detach(thrd); | ||||
|         #endif | ||||
|     } | ||||
| 
 | ||||
|     void Thread::Join() | ||||
|     { | ||||
|         #if defined(GEKKO) | ||||
|         void* res; | ||||
|         LWP_JoinThread(thrd,&res); | ||||
|         #else | ||||
|         int res; | ||||
|         thrd_join(thrd,&res); | ||||
|         #endif | ||||
|          | ||||
|     } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue