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