├── .gitignore ├── CMakeLists.txt ├── FileServer.sln ├── FileServer ├── CMakeLists.txt ├── FileServer.cpp ├── FileServer.h ├── FileServer.vcxproj ├── FileServer.vcxproj.filters ├── Main.cpp ├── pch.cpp └── pch.h ├── HttpLib ├── CMakeLists.txt ├── ContentReader.cpp ├── ContentReader.h ├── FileUploadHandler.cpp ├── FileUploadHandler.h ├── FormData.h ├── FormDataHandler.h ├── FormDataParser.cpp ├── FormDataParser.h ├── HttpLib.vcxproj ├── HttpLib.vcxproj.filters ├── HttpRequest.cpp ├── HttpRequest.h ├── MultipartFormDataParser.cpp ├── MultipartFormDataParser.h ├── Property.h ├── RequestHandler.h ├── Socket.h ├── Stream.h ├── StreamReader.cpp ├── StreamReader.h ├── StreamWriter.cpp ├── StreamWriter.h ├── StringUtils.cpp ├── StringUtils.h ├── UploadHandler.h ├── UploadedFile.h ├── UrlEncodedFormDataParser.cpp ├── UrlEncodedFormDataParser.h ├── WebServer.cpp ├── WebServer.h ├── linux │ └── Socket.cpp ├── pch.cpp ├── pch.h └── win32 │ └── Socket.cpp ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.obj 2 | *.tlog 3 | *.pdb 4 | *.pch 5 | *.FileListAbsolute.txt 6 | *.idb 7 | *.VC.db 8 | *.suo 9 | *.exe 10 | *.lib 11 | *.tli 12 | *.tlh 13 | *.log 14 | Debug/ 15 | Release/ 16 | obj/ 17 | .vs/ 18 | *.vcxproj.user 19 | *.vcxproj.vspscc 20 | *.csproj.user 21 | *.csproj.vspscc 22 | *adhoc.cpp.copied 23 | *.js.map 24 | *_i.c 25 | *_p.c 26 | *_i.h 27 | *.aps 28 | */packages/* 29 | dotnet-tools.json 30 | CMakeFiles/ 31 | Makefile 32 | cmake_install.cmake 33 | CMakeCache.txt 34 | *.a 35 | build/ 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.9) 2 | project(LocalFileServer) 3 | 4 | add_subdirectory (HttpLib) 5 | add_subdirectory (FileServer) -------------------------------------------------------------------------------- /FileServer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30804.86 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileServer", "FileServer\FileServer.vcxproj", "{32BC1CFD-14D4-4899-8967-AD9E00FFA112}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HttpLib", "HttpLib\HttpLib.vcxproj", "{568640C8-7688-4473-B071-4018D291E0B4}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Debug|x86 = Debug|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {32BC1CFD-14D4-4899-8967-AD9E00FFA112}.Debug|x64.ActiveCfg = Debug|x64 19 | {32BC1CFD-14D4-4899-8967-AD9E00FFA112}.Debug|x64.Build.0 = Debug|x64 20 | {32BC1CFD-14D4-4899-8967-AD9E00FFA112}.Debug|x86.ActiveCfg = Debug|Win32 21 | {32BC1CFD-14D4-4899-8967-AD9E00FFA112}.Debug|x86.Build.0 = Debug|Win32 22 | {32BC1CFD-14D4-4899-8967-AD9E00FFA112}.Release|x64.ActiveCfg = Release|x64 23 | {32BC1CFD-14D4-4899-8967-AD9E00FFA112}.Release|x64.Build.0 = Release|x64 24 | {32BC1CFD-14D4-4899-8967-AD9E00FFA112}.Release|x86.ActiveCfg = Release|Win32 25 | {32BC1CFD-14D4-4899-8967-AD9E00FFA112}.Release|x86.Build.0 = Release|Win32 26 | {568640C8-7688-4473-B071-4018D291E0B4}.Debug|x64.ActiveCfg = Debug|x64 27 | {568640C8-7688-4473-B071-4018D291E0B4}.Debug|x64.Build.0 = Debug|x64 28 | {568640C8-7688-4473-B071-4018D291E0B4}.Debug|x86.ActiveCfg = Debug|Win32 29 | {568640C8-7688-4473-B071-4018D291E0B4}.Debug|x86.Build.0 = Debug|Win32 30 | {568640C8-7688-4473-B071-4018D291E0B4}.Release|x64.ActiveCfg = Release|x64 31 | {568640C8-7688-4473-B071-4018D291E0B4}.Release|x64.Build.0 = Release|x64 32 | {568640C8-7688-4473-B071-4018D291E0B4}.Release|x86.ActiveCfg = Release|Win32 33 | {568640C8-7688-4473-B071-4018D291E0B4}.Release|x86.Build.0 = Release|Win32 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {897D5C43-6319-430A-91F8-DB2302638F06} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /FileServer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.9) 2 | project(FileServer) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | #Can manually add the sources using the set command as follows: 7 | #set(SOURCES src/mainapp.cpp src/Student.cpp) 8 | 9 | include_directories("../HttpLib") 10 | 11 | file(GLOB SOURCES "*.cpp") 12 | 13 | add_executable(FileServer ${SOURCES}) 14 | 15 | target_precompile_headers(FileServer PRIVATE pch.h) 16 | 17 | if (NOT MSVC) 18 | target_link_libraries(${PROJECT_NAME} HttpLib stdc++fs pthread) 19 | endif() 20 | 21 | -------------------------------------------------------------------------------- /FileServer/FileServer.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include "FileServer.h" 4 | #include "HttpRequest.h" 5 | #include "StreamWriter.h" 6 | #include "StringUtils.h" 7 | #include "FileUploadHandler.h" 8 | #include 9 | #include 10 | #include 11 | 12 | net::FileServer::FileServer(const std::filesystem::path& rootFolder) 13 | { 14 | _rootFolder = rootFolder; 15 | } 16 | 17 | net::FileServer::~FileServer() 18 | { 19 | // Empty destructor prevents error about deletion of incomplete type. 20 | } 21 | 22 | void net::FileServer::handleRequest(const HttpRequest& request, bool* keepAlive) 23 | { 24 | _keepAlive = keepAlive; 25 | if (request.getHeader("connection") == "keep-alive") 26 | *_keepAlive = true; 27 | 28 | _streamWriter = std::make_unique(request.stream()); 29 | 30 | if (request.hasFormData()) 31 | { 32 | auto formData = request.readFormData(); 33 | for (const auto& [id, file] : formData.files) 34 | { 35 | auto path = formData.values["path"]; 36 | std::string dest = formData.values["path"] + file.fileName; 37 | std::filesystem::rename(file.tempName, std::filesystem::u8path(dest)); 38 | serveString("302 Found", "", { "Location: " + formData.values["path"] }); 39 | return; 40 | } 41 | } 42 | 43 | // Remove forward slash from HTTP request path. 44 | std::string resourceName = request.uri(); 45 | if (resourceName.length() > 0 && resourceName[0] == '/') 46 | resourceName = resourceName.substr(1); 47 | 48 | auto path = _rootFolder / std::filesystem::u8path(resourceName); 49 | if (std::filesystem::is_directory(path)) 50 | { 51 | //if (request.getHeader("authorization") == "") 52 | // return serve401Unauthorized(); 53 | serveDirectory(path); 54 | } 55 | else 56 | { 57 | serveFile(path); 58 | } 59 | } 60 | 61 | std::string net::FileServer::mimeType(const std::filesystem::path& path) 62 | { 63 | auto ext = StringUtils::toLower(path.extension().u8string()); 64 | if (ext == ".htm") return "text/html"; 65 | if (ext == ".html") return "text/html"; 66 | if (ext == ".php") return "text/html"; 67 | if (ext == ".css") return "text/css"; 68 | if (ext == ".txt") return "text/plain"; 69 | if (ext == ".pdf") return "application/pdf"; 70 | if (ext == ".ts") return "application/octet-stream"; 71 | if (ext == ".js") return "application/javascript"; 72 | if (ext == ".js.map") return "application/octet-stream"; 73 | if (ext == ".json") return "application/json"; 74 | if (ext == ".xml") return "application/xml"; 75 | if (ext == ".swf") return "application/x-shockwave-flash"; 76 | if (ext == ".flv") return "video/x-flv"; 77 | if (ext == ".png") return "image/png"; 78 | if (ext == ".jpe") return "image/jpeg"; 79 | if (ext == ".jpeg") return "image/jpeg"; 80 | if (ext == ".jpg") return "image/jpeg"; 81 | if (ext == ".gif") return "image/gif"; 82 | if (ext == ".bmp") return "image/bmp"; 83 | if (ext == ".ico") return "image/vnd.microsoft.icon"; 84 | if (ext == ".tiff") return "image/tiff"; 85 | if (ext == ".tif") return "image/tiff"; 86 | if (ext == ".svg") return "image/svg+xml"; 87 | if (ext == ".svgz") return "image/svg+xml"; 88 | if (ext == ".mp4") return "video/mp4"; 89 | if (ext == ".mkv") return "video/x-matroska"; 90 | return "application/text"; 91 | } 92 | 93 | void net::FileServer::serveString(std::string_view status, std::string_view body) 94 | { 95 | return serveString(status, body, {}); 96 | } 97 | 98 | void net::FileServer::serveString(std::string_view status, std::string_view body, const std::list& extraHeaders) 99 | { 100 | std::stringstream ss; 101 | ss << "HTTP/1.1 " << status << "\r\n"; 102 | ss << "Connection: " << connectionStr() << "\r\n"; 103 | ss << "Content-Type: text/html; charset=utf-8\r\n"; 104 | ss << "Content-Length: " << body.length() << "\r\n"; 105 | ss << "Server: " << "DemoServer\r\n"; 106 | for (const auto& header : extraHeaders) 107 | ss << header << "\r\n"; 108 | ss << "\r\n"; 109 | std::string headers = ss.str(); 110 | _streamWriter->write(headers); 111 | _streamWriter->write(body); 112 | } 113 | 114 | void net::FileServer::serve404NotFound() 115 | { 116 | serveString("404 Not Found", "

404 File not found.

\n"); 117 | } 118 | 119 | void net::FileServer::serve401Unauthorized() 120 | { 121 | serveString("401 Unauthorized", "", {"WWW-Authenticate: Basic realm=\"File Server\", charset=\"UTF-8\""}); 122 | } 123 | 124 | void net::FileServer::serveFile(const std::filesystem::path& path) 125 | { 126 | std::string mediaType = mimeType(path); 127 | std::ifstream fs; 128 | fs.open(path, std::ios_base::binary | std::ios::ate); 129 | if (fs) 130 | { 131 | long sz = (long)fs.tellg(); 132 | fs.seekg(0); 133 | 134 | std::stringstream ss; 135 | ss << "HTTP/1.1 200 OK\r\n"; 136 | ss << "Connection: " << connectionStr() << "\r\n"; 137 | ss << "Content-Type: " << mediaType << "; charset=utf-8\r\n"; 138 | ss << "Content-Length: " << sz << "\r\n"; 139 | ss << "Server: " << "DemoServer\r\n"; 140 | ss << "\r\n"; 141 | std::string headers = ss.str(); 142 | _streamWriter->write(headers); 143 | 144 | char buff[4096]; 145 | while (!fs.eof()) 146 | { 147 | fs.read(buff, sizeof(buff)); 148 | std::streamsize bytesRead = fs.gcount(); 149 | _streamWriter->write(buff, (int)bytesRead); 150 | } 151 | fs.close(); 152 | } 153 | else 154 | { 155 | serve404NotFound(); 156 | } 157 | } 158 | 159 | void net::FileServer::serveDirectory(const std::filesystem::path& path) 160 | { 161 | std::stringstream body; 162 | body << "Directory listing\n"; 163 | body << "\n"; 164 | 165 | for (const auto& entry : std::filesystem::directory_iterator(path)) 166 | { 167 | std::string displayName; 168 | std::string href; 169 | auto name = entry.path().filename().u8string(); 170 | if (entry.is_directory()) 171 | { 172 | displayName = std::string("[") + name + "]"; 173 | href = name + "/"; 174 | } 175 | else 176 | { 177 | displayName = name; 178 | href = name; 179 | } 180 | body << "" << displayName << "
\n"; 181 | } 182 | body << "
"; 183 | body << " "; 184 | body << "
File:
"; 185 | body << " "; 186 | body << "
"; 187 | body << "\n"; 188 | serveString("200 OK", body.str()); 189 | } 190 | 191 | std::string net::FileServer::connectionStr() const 192 | { 193 | return *_keepAlive ? "keep-alive" : "close"; 194 | } -------------------------------------------------------------------------------- /FileServer/FileServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "RequestHandler.h" 4 | #include "FormDataHandler.h" 5 | #include "UploadedFile.h" 6 | #include 7 | #include 8 | #include 9 | 10 | namespace net 11 | { 12 | class StreamWriter; 13 | class UploadHandler; 14 | 15 | class FileServer 16 | { 17 | public: 18 | FileServer(const std::filesystem::path& rootFolder); 19 | virtual ~FileServer(); 20 | 21 | void handleRequest(const HttpRequest& request, bool* keepAlive); 22 | 23 | private: 24 | static std::string mimeType(const std::filesystem::path& path); 25 | void serveString(std::string_view status, std::string_view body, const std::list& extraHeaders); 26 | void serveString(std::string_view status, std::string_view body); 27 | void serve404NotFound(); 28 | void serve401Unauthorized(); 29 | void serveFile(const std::filesystem::path& path); 30 | void serveDirectory(const std::filesystem::path& path); 31 | std::string connectionStr() const; 32 | 33 | private: 34 | std::unique_ptr _streamWriter; 35 | bool* _keepAlive; 36 | std::filesystem::path _rootFolder; 37 | }; 38 | } 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /FileServer/FileServer.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {32bc1cfd-14d4-4899-8967-ad9e00ffa112} 25 | FileServer 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v142 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v142 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v142 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v142 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | false 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | 85 | 86 | 87 | Level3 88 | true 89 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 90 | true 91 | stdcpp17 92 | Use 93 | pch.h 94 | ..\HttpLib;%(AdditionalIncludeDirectories) 95 | 96 | 97 | Console 98 | true 99 | ws2_32.lib;%(AdditionalDependencies) 100 | 101 | 102 | 103 | 104 | Level3 105 | true 106 | true 107 | true 108 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 109 | true 110 | stdcpp17 111 | Use 112 | pch.h 113 | MultiThreaded 114 | ..\HttpLib;%(AdditionalIncludeDirectories) 115 | 116 | 117 | Console 118 | true 119 | true 120 | true 121 | ws2_32.lib;%(AdditionalDependencies) 122 | 123 | 124 | 125 | 126 | Level3 127 | true 128 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 129 | true 130 | stdcpp17 131 | Use 132 | pch.h 133 | ..\HttpLib;%(AdditionalIncludeDirectories) 134 | 135 | 136 | Console 137 | true 138 | ws2_32.lib;%(AdditionalDependencies) 139 | 140 | 141 | 142 | 143 | Level3 144 | true 145 | true 146 | true 147 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 148 | true 149 | stdcpp17 150 | Use 151 | pch.h 152 | ..\HttpLib;%(AdditionalIncludeDirectories) 153 | 154 | 155 | Console 156 | true 157 | true 158 | true 159 | ws2_32.lib;%(AdditionalDependencies) 160 | 161 | 162 | 163 | 164 | 165 | 166 | Create 167 | Create 168 | Create 169 | Create 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | {568640c8-7688-4473-b071-4018d291e0b4} 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /FileServer/FileServer.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | 29 | 30 | Header Files 31 | 32 | 33 | Header Files 34 | 35 | 36 | -------------------------------------------------------------------------------- /FileServer/Main.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Socket.h" 3 | #include "FileServer.h" 4 | #include "RequestHandler.h" 5 | #include "WebServer.h" 6 | #include 7 | #include 8 | 9 | namespace net 10 | { 11 | class FileRequestHandler : public RequestHandler 12 | { 13 | public: 14 | FileRequestHandler(const std::filesystem::path& rootFolder) 15 | { 16 | _rootFolder = rootFolder; 17 | } 18 | 19 | void handleRequest(const HttpRequest& request, bool* keepAlive) override 20 | { 21 | FileServer fileServer(_rootFolder); 22 | fileServer.handleRequest(request, keepAlive); 23 | } 24 | 25 | private: 26 | std::filesystem::path _rootFolder; 27 | }; 28 | } 29 | 30 | void showUsage() 31 | { 32 | std::cout << std::endl; 33 | std::cout << "Usage: " << std::endl; 34 | std::cout << std::endl; 35 | std::cout << " FileServer [-p ] [-a ] [-r ]" << std::endl; 36 | std::cout << std::endl; 37 | std::cout << "Defaults:" << std::endl; 38 | std::cout << std::endl; 39 | std::cout << " PORT = 8080" << std::endl; 40 | std::cout << " IP_ADDRESS = 127.0.0.1" << std::endl; 41 | std::cout << " ROOT_FOLDER = '.' (Current working directory)" << std::endl; 42 | std::cout << std::endl; 43 | } 44 | 45 | int main(int argc, char* argv[]) 46 | { 47 | int port = 8080; 48 | std::string ip = "127.0.0.1"; 49 | std::filesystem::path rootFolder = "."; 50 | 51 | if (argc % 2 == 0) 52 | { 53 | showUsage(); 54 | return 0; 55 | } 56 | 57 | for (int i = 1; i < argc; i += 2) 58 | { 59 | if (strcmp(argv[i], "-p") == 0) 60 | { 61 | port = atoi(argv[i + 1]); 62 | } 63 | else if (strcmp(argv[i], "-a") == 0) 64 | { 65 | ip = argv[i + 1]; 66 | } 67 | else if (strcmp(argv[i], "-r") == 0) 68 | { 69 | rootFolder = argv[i + 1]; 70 | } 71 | else 72 | { 73 | showUsage(); 74 | return 0; 75 | } 76 | } 77 | 78 | net::Socket::startUp(); 79 | net::FileRequestHandler handler(rootFolder); 80 | net::WebServer webServer(handler); 81 | webServer.start(ip, port); 82 | net::Socket::shutDown(); 83 | return 0; 84 | } 85 | -------------------------------------------------------------------------------- /FileServer/pch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" -------------------------------------------------------------------------------- /FileServer/pch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | -------------------------------------------------------------------------------- /HttpLib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.9) 2 | project(HttpLib) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | #Can manually add the sources using the set command as follows: 7 | #set(SOURCES src/mainapp.cpp src/Student.cpp) 8 | 9 | include_directories(".") 10 | 11 | file(GLOB SOURCES "*.cpp" "linux/*.cpp") 12 | 13 | add_library(HttpLib ${SOURCES}) 14 | 15 | target_precompile_headers(HttpLib PRIVATE pch.h) 16 | 17 | if (NOT MSVC) 18 | target_link_libraries(${PROJECT_NAME} stdc++fs pthread) 19 | endif() 20 | 21 | -------------------------------------------------------------------------------- /HttpLib/ContentReader.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include "ContentReader.h" 4 | #include "Stream.h" 5 | #include 6 | #include 7 | 8 | net::ContentChunk::operator bool() const 9 | { 10 | return size > 0; 11 | } 12 | 13 | ////////////////////////////////////////////////////////////////////////////////////////////////// 14 | 15 | net::ContentReader::ContentReader(InputStream* stream, int contentLength, bool contentLengthValid) 16 | { 17 | auto maxContentLength = std::numeric_limits::max(); 18 | _stream = stream; 19 | _remaining = contentLengthValid ? contentLength : maxContentLength; 20 | _buffSize = (int)sizeof(_buff); 21 | } 22 | 23 | net::ContentChunk net::ContentReader::readChunk() 24 | { 25 | if (_remaining > 0) 26 | { 27 | int n = _stream->receive(_buff, std::min(_buffSize, _remaining)); 28 | if (n > 0) 29 | { 30 | _remaining -= n; 31 | return { _buff, n }; 32 | } 33 | else 34 | { 35 | // Client connection has closed, indicating the end of the content. 36 | _remaining = 0; 37 | } 38 | } 39 | // Return an empty chunk to indicate that there are no more chunks to follow. 40 | return { _buff, 0 }; 41 | } 42 | -------------------------------------------------------------------------------- /HttpLib/ContentReader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace net 4 | { 5 | class InputStream; 6 | 7 | class ContentChunk 8 | { 9 | public: 10 | char* data; 11 | int size; 12 | 13 | explicit operator bool() const; 14 | }; 15 | 16 | class ContentReader 17 | { 18 | public: 19 | ContentReader(InputStream* stream, int contentLength, bool contentLengthValid); 20 | 21 | ContentChunk readChunk(); 22 | 23 | private: 24 | InputStream* _stream; 25 | char _buff[4096]; 26 | int _buffSize; 27 | int _remaining; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /HttpLib/FileUploadHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include "FileUploadHandler.h" 4 | #include "StringUtils.h" 5 | 6 | net::FileUploadHandler::FileUploadHandler(std::string_view id, std::string_view fileName) 7 | { 8 | _id = id; 9 | _fileName = fileName; 10 | 11 | std::ostringstream tempName; 12 | tempName << "upload_" << StringUtils::randomString(8) << "_" << fileName; 13 | _tempName = std::filesystem::temp_directory_path() / std::filesystem::u8path(tempName.str()); 14 | _fs.open(_tempName, std::ios_base::binary); 15 | } 16 | 17 | void net::FileUploadHandler::write(char* data, int size) 18 | { 19 | _fs.write(data, static_cast(size)); 20 | } 21 | 22 | void net::FileUploadHandler::endOfStream() 23 | { 24 | _fs.close(); 25 | } 26 | 27 | std::filesystem::path net::FileUploadHandler::tempName() const 28 | { 29 | return _tempName; 30 | } 31 | -------------------------------------------------------------------------------- /HttpLib/FileUploadHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "UploadHandler.h" 4 | #include 5 | #include 6 | 7 | namespace net 8 | { 9 | class FileUploadHandler : public UploadHandler 10 | { 11 | public: 12 | FileUploadHandler(std::string_view id, std::string_view fileName); 13 | 14 | void write(char* data, int size) override; 15 | void endOfStream() override; 16 | std::filesystem::path tempName() const; 17 | 18 | private: 19 | std::string _id; 20 | std::string _fileName; 21 | std::filesystem::path _tempName; 22 | std::ofstream _fs; 23 | }; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /HttpLib/FormData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "UploadedFile.h" 4 | #include 5 | #include 6 | 7 | namespace net 8 | { 9 | class FormData 10 | { 11 | public: 12 | std::map values; 13 | std::map files; 14 | }; 15 | } -------------------------------------------------------------------------------- /HttpLib/FormDataHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace net 6 | { 7 | class UploadHandler; 8 | 9 | class FormDataHandler 10 | { 11 | public: 12 | virtual void addDataPair(const std::string& name, const std::string& value) = 0; 13 | virtual std::unique_ptr createUploadHandler(const std::string& id, const std::string& fname) = 0; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /HttpLib/FormDataParser.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "FormDataParser.h" 3 | 4 | net::FormDataParser::FormDataParser(FormDataHandler* handler) 5 | { 6 | _handler = handler; 7 | } 8 | -------------------------------------------------------------------------------- /HttpLib/FormDataParser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace net 6 | { 7 | class FormDataHandler; 8 | 9 | class FormDataParser 10 | { 11 | public: 12 | FormDataParser(FormDataHandler* handler); 13 | virtual void processChars(char* data, int count) = 0; 14 | virtual void endOfStream() = 0; 15 | 16 | protected: 17 | FormDataHandler* _handler; 18 | std::ostringstream _stream; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /HttpLib/HttpLib.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {568640c8-7688-4473-b071-4018d291e0b4} 25 | HttpLib 26 | 10.0 27 | 28 | 29 | 30 | StaticLibrary 31 | true 32 | v142 33 | Unicode 34 | 35 | 36 | StaticLibrary 37 | false 38 | v142 39 | true 40 | Unicode 41 | 42 | 43 | StaticLibrary 44 | true 45 | v142 46 | Unicode 47 | 48 | 49 | StaticLibrary 50 | false 51 | v142 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | false 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | 85 | 86 | 87 | Level3 88 | true 89 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 90 | true 91 | Use 92 | pch.h 93 | stdcpp17 94 | 95 | 96 | 97 | 98 | true 99 | 100 | 101 | 102 | 103 | Level3 104 | true 105 | true 106 | true 107 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 108 | true 109 | Use 110 | pch.h 111 | stdcpp17 112 | 113 | 114 | 115 | 116 | true 117 | true 118 | true 119 | 120 | 121 | 122 | 123 | Level3 124 | true 125 | _DEBUG;_LIB;%(PreprocessorDefinitions) 126 | true 127 | Use 128 | pch.h 129 | stdcpp17 130 | 131 | 132 | 133 | 134 | true 135 | 136 | 137 | 138 | 139 | Level3 140 | true 141 | true 142 | true 143 | NDEBUG;_LIB;%(PreprocessorDefinitions) 144 | true 145 | Use 146 | pch.h 147 | stdcpp17 148 | 149 | 150 | 151 | 152 | true 153 | true 154 | true 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | Create 186 | Create 187 | Create 188 | Create 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /HttpLib/HttpLib.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files 47 | 48 | 49 | Header Files 50 | 51 | 52 | Header Files 53 | 54 | 55 | Header Files 56 | 57 | 58 | Header Files 59 | 60 | 61 | Header Files 62 | 63 | 64 | Header Files 65 | 66 | 67 | Header Files 68 | 69 | 70 | Header Files 71 | 72 | 73 | Header Files 74 | 75 | 76 | 77 | 78 | Source Files 79 | 80 | 81 | Source Files 82 | 83 | 84 | Source Files 85 | 86 | 87 | Source Files 88 | 89 | 90 | Source Files 91 | 92 | 93 | Source Files 94 | 95 | 96 | Source Files 97 | 98 | 99 | Source Files 100 | 101 | 102 | Source Files 103 | 104 | 105 | Source Files 106 | 107 | 108 | Source Files 109 | 110 | 111 | Source Files 112 | 113 | 114 | -------------------------------------------------------------------------------- /HttpLib/HttpRequest.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "HttpRequest.h" 3 | #include "StringUtils.h" 4 | #include "StreamReader.h" 5 | #include "MultipartFormDataParser.h" 6 | #include "UrlEncodedFormDataParser.h" 7 | #include "ContentReader.h" 8 | 9 | #include "FormDataHandler.h" 10 | #include "FormData.h" 11 | #include "FileUploadHandler.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace net 19 | { 20 | class FormDataManager : public FormDataHandler 21 | { 22 | public: 23 | void addDataPair(const std::string& name, const std::string& value) override 24 | { 25 | formData.values[std::string(name)] = value; 26 | } 27 | 28 | std::unique_ptr createUploadHandler(const std::string& id, const std::string& fname) override 29 | { 30 | auto handler = std::make_unique(id, fname); 31 | UploadedFile uploadedFile{ handler->tempName(), std::string(fname) }; 32 | formData.files[id] = uploadedFile; 33 | return handler; 34 | } 35 | 36 | public: 37 | FormData formData; 38 | }; 39 | } 40 | 41 | net::HttpRequest::HttpRequest(Stream* stream, std::string_view clientIP) 42 | { 43 | _isValid = true; 44 | _hasCredentials = false; 45 | _clientIP = clientIP; 46 | _stream = stream; 47 | _contentLength = 0; 48 | _contentLengthValid = false; 49 | StreamReader sr(stream); 50 | 51 | std::string str; 52 | bool rc = sr.readLine(str); 53 | if (!rc) 54 | { 55 | _isValid = false; 56 | return; 57 | } 58 | std::vector tokens = StringUtils::split(str); 59 | 60 | if (tokens.size() >= 2) 61 | { 62 | _method = tokens[0]; 63 | _uri = StringUtils::urlDecode(tokens[1]); 64 | } 65 | 66 | while (sr.readLine(str)) 67 | { 68 | if (str.length() > 0) 69 | addHeader(str); 70 | else 71 | break; 72 | } 73 | 74 | parseCredentials(); 75 | parseContentTypeHeader(); 76 | parseContentLengthHeader(); 77 | } 78 | 79 | void net::HttpRequest::addHeader(std::string_view header) 80 | { 81 | auto [name, value] = StringUtils::splitAtFirstOf(header, ':'); 82 | if (!value.empty()) 83 | { 84 | auto nameTrimmed = StringUtils::toLower(StringUtils::trim(name)); 85 | headers[nameTrimmed] = StringUtils::trim(value); 86 | } 87 | } 88 | 89 | net::HttpRequest::~HttpRequest() 90 | { 91 | } 92 | 93 | std::string net::HttpRequest::method() const 94 | { 95 | return _method; 96 | } 97 | 98 | std::string net::HttpRequest::uri() const 99 | { 100 | return _uri; 101 | } 102 | 103 | std::string net::HttpRequest::clientIP() const 104 | { 105 | return _clientIP; 106 | } 107 | 108 | bool net::HttpRequest::isValid() const 109 | { 110 | return _isValid; 111 | } 112 | 113 | std::string net::HttpRequest::getHeader(const char* attr, const char* defaultValue) const 114 | { 115 | if (headers.count(attr) > 0) 116 | return headers.at(attr); 117 | else 118 | return defaultValue; 119 | } 120 | 121 | net::Stream* net::HttpRequest::stream() const 122 | { 123 | return _stream; 124 | } 125 | 126 | void net::HttpRequest::parseCredentials() 127 | { 128 | std::string auth = getHeader("authorization"); 129 | if (auth != "") 130 | { 131 | auto [authType, credentials] = StringUtils::splitAtFirstOf(auth, ' '); 132 | if (authType == "Basic" && !credentials.empty()) 133 | { 134 | auto decoded = StringUtils::base64_decode(credentials); 135 | auto [username, password] = StringUtils::splitAtFirstOf(decoded, ':'); 136 | if (!password.empty()) 137 | { 138 | _username = username; 139 | _password = password; 140 | _hasCredentials = true; 141 | } 142 | } 143 | } 144 | } 145 | 146 | bool net::HttpRequest::hasCredentials() const 147 | { 148 | return _hasCredentials; 149 | } 150 | 151 | std::string net::HttpRequest::username() const 152 | { 153 | return _username; 154 | } 155 | 156 | std::string net::HttpRequest::password() const 157 | { 158 | return _password; 159 | } 160 | 161 | bool net::HttpRequest::decodeFormData(FormDataHandler* handler) const 162 | { 163 | std::unique_ptr parser; 164 | if (_contentType == "multipart/form-data" && _boundary != "") 165 | parser = std::make_unique(handler, _boundary); 166 | else if (_contentType == "application/x-www-form-urlencoded") 167 | parser = std::make_unique(handler); 168 | else 169 | return false; 170 | 171 | ContentReader reader(_stream, _contentLength, _contentLengthValid); 172 | while (auto chunk = reader.readChunk()) 173 | parser->processChars(chunk.data, chunk.size); 174 | 175 | parser->endOfStream(); 176 | return true; 177 | } 178 | 179 | std::string net::HttpRequest::readPostData() const 180 | { 181 | std::ostringstream ss; 182 | ContentReader reader(_stream, _contentLength, _contentLengthValid); 183 | while (auto chunk = reader.readChunk()) 184 | ss.write(chunk.data, (size_t)chunk.size); 185 | 186 | return ss.str(); 187 | } 188 | 189 | net::FormData net::HttpRequest::readFormData() const 190 | { 191 | FormDataManager formDataManager; 192 | decodeFormData(&formDataManager); 193 | return formDataManager.formData; 194 | } 195 | 196 | void net::HttpRequest::parseContentTypeHeader() 197 | { 198 | auto contentLengthHeader = getHeader("content-length"); 199 | if (contentLengthHeader != "") 200 | { 201 | _contentLengthValid = true; 202 | _contentLength = atoi(contentLengthHeader.c_str()); 203 | } 204 | } 205 | 206 | void net::HttpRequest::parseContentLengthHeader() 207 | { 208 | std::list props; 209 | auto contentTypeHeader = getHeader("content-type"); 210 | StringUtils::parseNameValuePairs(props, contentTypeHeader, ';'); 211 | if (!props.empty()) 212 | { 213 | _contentType = props.front().name; 214 | for (const auto& prop : props) 215 | { 216 | if (prop.name == "boundary") 217 | _boundary = prop.value; 218 | } 219 | } 220 | } 221 | 222 | int net::HttpRequest::contentLength() const 223 | { 224 | return _contentLength; 225 | } 226 | 227 | bool net::HttpRequest::contentLengthValid() const 228 | { 229 | return _contentLengthValid; 230 | } 231 | 232 | std::string net::HttpRequest::boundary() const 233 | { 234 | return _boundary; 235 | } 236 | 237 | bool net::HttpRequest::hasFormData() const 238 | { 239 | if (_method != "POST") 240 | return false; 241 | 242 | return (_contentType == "multipart/form-data" && _boundary != "") 243 | || (_contentType == "application/x-www-form-urlencoded"); 244 | } 245 | -------------------------------------------------------------------------------- /HttpLib/HttpRequest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Stream.h" 4 | #include "FormData.h" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace net 10 | { 11 | class FormDataHandler; 12 | 13 | class HttpRequest 14 | { 15 | public: 16 | HttpRequest(Stream* stream, std::string_view clientIP); 17 | virtual ~HttpRequest(); 18 | 19 | std::string method() const; 20 | std::string uri() const; 21 | bool isValid() const; 22 | std::string clientIP() const; 23 | Stream* stream() const; 24 | std::string getHeader(const char* attr, const char* defaultValue = "") const; 25 | bool hasCredentials() const; 26 | std::string username() const; 27 | std::string password() const; 28 | bool decodeFormData(FormDataHandler* handler) const; 29 | std::string readPostData() const; 30 | FormData readFormData() const; 31 | int contentLength() const; 32 | bool contentLengthValid() const; 33 | std::string boundary() const; 34 | bool hasFormData() const; 35 | 36 | private: 37 | void addHeader(std::string_view header); 38 | void parseCredentials(); 39 | void parseContentTypeHeader(); 40 | void parseContentLengthHeader(); 41 | 42 | public: 43 | std::map headers; 44 | 45 | private: 46 | std::string _method; 47 | std::string _uri; 48 | std::string _clientIP; 49 | Stream* _stream; 50 | bool _isValid; 51 | bool _hasCredentials; 52 | std::string _username; 53 | std::string _password; 54 | std::string _boundary; 55 | int _contentLength; 56 | std::string _contentType; 57 | bool _contentLengthValid; 58 | }; 59 | } -------------------------------------------------------------------------------- /HttpLib/MultipartFormDataParser.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "MultipartFormDataParser.h" 3 | #include "Property.h" 4 | #include "StringUtils.h" 5 | #include "FormDataHandler.h" 6 | #include "UploadHandler.h" 7 | #include 8 | #include 9 | #include 10 | 11 | net::MultipartFormDataParser::MultipartFormDataParser(FormDataHandler* handler, std::string_view boundary) 12 | : FormDataParser(handler) 13 | { 14 | _handler = handler; 15 | _boundary = boundary; 16 | _index = 0; 17 | _state = 0; 18 | _isFile = false; 19 | _chunk[0] = 0; 20 | _chunkSize = sizeof(_chunk); 21 | _numWrites = 0; 22 | } 23 | 24 | net::MultipartFormDataParser::~MultipartFormDataParser() 25 | { 26 | endOfStream(); 27 | } 28 | 29 | void net::MultipartFormDataParser::processChars(char* data, int count) 30 | { 31 | for (int i = 0; i < count; i++) 32 | processChar(data[i]); 33 | } 34 | 35 | void net::MultipartFormDataParser::processChar(char ch) 36 | { 37 | if (ch == 13 || _index >= _chunkSize) 38 | { 39 | processChunk(_chunk, _index); 40 | _index = 0; 41 | } 42 | _chunk[_index++] = ch; 43 | } 44 | 45 | void net::MultipartFormDataParser::closeStream() 46 | { 47 | if (_fs) 48 | _fs->endOfStream(); 49 | _fs.reset(); 50 | } 51 | 52 | void net::MultipartFormDataParser::endOfStream() 53 | { 54 | closeStream(); 55 | } 56 | 57 | bool net::MultipartFormDataParser::isBoundary(char* chunk, int size) 58 | { 59 | auto boundaryLength = _boundary.length(); 60 | if (size == boundaryLength + 2) 61 | { 62 | if (strncmp(&chunk[2], _boundary.c_str(), size - 2) == 0) 63 | return true; 64 | } 65 | else if (size == boundaryLength + 4) 66 | { 67 | if (strncmp(&chunk[4], _boundary.c_str(), size - 4) == 0) 68 | return true; 69 | } 70 | else if (size == boundaryLength + 6) 71 | { 72 | if (strncmp(&chunk[4], _boundary.c_str(), size - 6) == 0) 73 | return true; 74 | } 75 | return false; 76 | } 77 | 78 | void net::MultipartFormDataParser::processChunk(char* chunk, int size) 79 | { 80 | if (_state == 0) // Waiting for boundary 81 | { 82 | if (isBoundary(chunk, size)) 83 | { 84 | _isFile = false; 85 | _state = 1; 86 | } 87 | } 88 | else if (_state == 1) // Expecting header line 89 | { 90 | if (size == 2) 91 | { 92 | _state = 2; 93 | } 94 | else 95 | { 96 | std::string header(chunk + 2, size - 2); 97 | std::list props; 98 | StringUtils::parseNameValuePairs(props, header, ';'); 99 | for (const auto& prop : props) 100 | { 101 | if (prop.name == "name") 102 | { 103 | _name = prop.value; 104 | } 105 | else if (prop.name == "filename") 106 | { 107 | _isFile = true; 108 | _numWrites = 0; 109 | _fs = _handler->createUploadHandler(_name, prop.value); 110 | } 111 | } 112 | } 113 | } 114 | else if (_state == 2) // Expecting data 115 | { 116 | if (isBoundary(chunk, size)) 117 | { 118 | _isFile = false; 119 | closeStream(); 120 | _state = 1; 121 | } 122 | else 123 | { 124 | if (_isFile) 125 | { 126 | if (_fs) 127 | { 128 | if (_numWrites++ == 0) 129 | _fs->write(chunk + 2, size - 2); 130 | else 131 | _fs->write(chunk, size); 132 | } 133 | } 134 | else if (size > 2) 135 | { 136 | std::string value(chunk + 2, size - 2); 137 | _handler->addDataPair(_name, value); 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /HttpLib/MultipartFormDataParser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FormDataParser.h" 4 | #include "UploadedFile.h" 5 | #include 6 | #include 7 | 8 | namespace net 9 | { 10 | class FormDataHandler; 11 | class UploadHandler; 12 | 13 | class MultipartFormDataParser : public FormDataParser 14 | { 15 | public: 16 | MultipartFormDataParser(FormDataHandler* handler, std::string_view boundary); 17 | MultipartFormDataParser(const MultipartFormDataParser& other) = delete; 18 | MultipartFormDataParser& operator=(const MultipartFormDataParser& rhs) = delete; 19 | virtual ~MultipartFormDataParser(); 20 | 21 | void processChars(char* data, int count) override; 22 | void endOfStream() override; 23 | 24 | private: 25 | void processChunk(char* chunk, int size); 26 | bool isBoundary(char* chunk, int size); 27 | void closeStream(); 28 | void processChar(char ch); 29 | 30 | private: 31 | char _chunk[256]; 32 | int _chunkSize; 33 | int _index; 34 | int _state; 35 | std::string _boundary; 36 | bool _isFile; 37 | std::string _name; 38 | std::unique_ptr _fs; 39 | int _numWrites; 40 | }; 41 | } -------------------------------------------------------------------------------- /HttpLib/Property.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace net 6 | { 7 | class Property 8 | { 9 | public: 10 | Property(const std::string& name_, const std::string& value_) 11 | { 12 | name = name_; 13 | value = value_; 14 | } 15 | 16 | public: 17 | std::string name; 18 | std::string value; 19 | }; 20 | } -------------------------------------------------------------------------------- /HttpLib/RequestHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace net 4 | { 5 | class HttpRequest; 6 | 7 | class RequestHandler 8 | { 9 | public: 10 | virtual void handleRequest(const HttpRequest& request, bool* keepAlive) = 0; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /HttpLib/Socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Stream.h" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace net 9 | { 10 | class EndPointImpl; 11 | 12 | class EndPoint 13 | { 14 | public: 15 | friend class Socket; 16 | 17 | public: 18 | EndPoint(); 19 | EndPoint(const char* ip, int port); 20 | EndPoint(const EndPoint& other); 21 | virtual ~EndPoint(); 22 | 23 | std::string ipAddress() const; 24 | int port() const; 25 | 26 | private: 27 | std::unique_ptr pImpl; 28 | }; 29 | 30 | class SocketImpl; 31 | 32 | enum class Protocol 33 | { 34 | None, 35 | TCP, 36 | UDP 37 | }; 38 | 39 | class Socket : public net::Stream 40 | { 41 | public: 42 | Socket(Protocol proto); 43 | 44 | static bool startUp(); 45 | static void shutDown(); 46 | static std::vector getHostByName(const char* hostName); 47 | bool valid() const; 48 | bool bind(const EndPoint& endPoint); 49 | bool listen(); 50 | bool close(); 51 | Socket accept(EndPoint* clientIP = nullptr); 52 | int send(const char* buf, int len) override; 53 | int receive(char* buf, int len) override; 54 | int sendTo(const char* buf, int len, const EndPoint& endPoint); 55 | int receiveFrom(char* buf, int len, EndPoint* sourceAddress); 56 | EndPoint endPoint() const; 57 | bool connect(const EndPoint& endPoint); 58 | 59 | private: 60 | std::shared_ptr pImpl; 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /HttpLib/Stream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | namespace net 3 | { 4 | class InputStream 5 | { 6 | public: 7 | virtual int receive(char* buf, int len) = 0; 8 | }; 9 | 10 | class OutputStream 11 | { 12 | public: 13 | virtual int send(const char* buf, int len) = 0; 14 | }; 15 | 16 | class Stream : public InputStream, public OutputStream 17 | { 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /HttpLib/StreamReader.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "StreamReader.h" 3 | #include "Stream.h" 4 | 5 | net::StreamReader::StreamReader(InputStream* stream) 6 | { 7 | _stream = stream; 8 | } 9 | 10 | bool net::StreamReader::readLine(std::string& str) 11 | { 12 | int n; 13 | int total = 0; 14 | char buf[1]; 15 | 16 | str.clear(); 17 | while (1) 18 | { 19 | n = _stream->receive(&buf[0], sizeof(buf)); 20 | total += n; 21 | if (n == 1) 22 | { 23 | char ch = buf[0]; 24 | if (ch == 10) 25 | return true; 26 | else if ((ch >= ' ') || (ch == '\t')) 27 | str += buf[0]; 28 | } 29 | else 30 | { 31 | return total > 0; 32 | } 33 | } 34 | return true; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /HttpLib/StreamReader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace net 6 | { 7 | class InputStream; 8 | 9 | class StreamReader 10 | { 11 | public: 12 | StreamReader(InputStream* socket); 13 | 14 | bool readLine(std::string& str); 15 | 16 | private: 17 | InputStream* _stream; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /HttpLib/StreamWriter.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "StreamWriter.h" 3 | #include "Stream.h" 4 | 5 | net::StreamWriter::StreamWriter(OutputStream* stream) 6 | { 7 | _stream = stream; 8 | } 9 | 10 | bool net::StreamWriter::write(const char* buff, int length) 11 | { 12 | int n, total = 0; 13 | if (length == 0) 14 | return true; 15 | 16 | do 17 | { 18 | n = _stream->send(buff, length - total); 19 | buff += n; 20 | total += n; 21 | } while (n > 0 && length > total); 22 | return total == length; 23 | } 24 | 25 | bool net::StreamWriter::write(std::string_view str) 26 | { 27 | return write(str.data(), (int) str.length()); 28 | } 29 | -------------------------------------------------------------------------------- /HttpLib/StreamWriter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace net 6 | { 7 | class OutputStream; 8 | 9 | class StreamWriter 10 | { 11 | public: 12 | StreamWriter(OutputStream* socket); 13 | 14 | bool write(const char* buff, int length); 15 | bool write(std::string_view str); 16 | 17 | private: 18 | OutputStream* _stream; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /HttpLib/StringUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "StringUtils.h" 3 | #include 4 | #include 5 | #include 6 | 7 | std::string net::StringUtils::toLower(std::string_view stringView) 8 | { 9 | std::string str(stringView); 10 | for (auto& ch : str) 11 | ch = tolower(ch); 12 | 13 | return std::move(str); 14 | } 15 | 16 | void net::StringUtils::parseNameValuePairs(std::list& props, std::string_view str, char delim) 17 | { 18 | props.clear(); 19 | std::ostringstream name; 20 | std::ostringstream value; 21 | int state = 0; 22 | for (auto ch : str) 23 | { 24 | if (state == 0) 25 | { 26 | if (ch != ' ') 27 | { 28 | name << ch; 29 | state = 1; 30 | } 31 | } 32 | else if (state == 1) 33 | { 34 | if (ch == '=') 35 | { 36 | state = 2; 37 | } 38 | else if (ch == delim) 39 | { 40 | props.push_back(Property(name.str(), value.str())); 41 | name.str(""); 42 | value.str(""); 43 | state = 0; 44 | } 45 | else 46 | { 47 | name << ch; 48 | } 49 | } 50 | else if (state == 2) 51 | { 52 | if (ch == delim) 53 | { 54 | props.push_back(Property(name.str(), value.str())); 55 | name.str(""); 56 | value.str(""); 57 | state = 0; 58 | } 59 | else 60 | { 61 | if (ch != '\"') 62 | value << ch; 63 | } 64 | } 65 | } 66 | if (name.str() != "") 67 | { 68 | props.push_back(Property(name.str(), value.str())); 69 | } 70 | } 71 | 72 | static int hex2dec(char ch) 73 | { 74 | if (ch >= '0' && ch <= '9') 75 | return ch - '0'; 76 | else if (ch >= 'A' && ch <= 'F') 77 | return 10 + ch - 'A'; 78 | else if (ch >= 'a' && ch <= 'f') 79 | return 10 + ch - 'a'; 80 | return 0; 81 | } 82 | 83 | std::string net::StringUtils::urlDecode(std::string_view url) 84 | { 85 | size_t n = url.size(); 86 | std::ostringstream result; 87 | 88 | for (size_t i = 0; i < n; i++) 89 | { 90 | char ch = url[i]; 91 | if ((ch == '%') && (i < n - 2)) 92 | { 93 | int hiNibble = hex2dec(url[++i]); 94 | int loNibble = hex2dec(url[++i]); 95 | result << (char)(16 * hiNibble + loNibble); 96 | } 97 | else 98 | { 99 | result << ch; 100 | } 101 | } 102 | return result.str(); 103 | } 104 | 105 | void net::StringUtils::decodeFormData(std::string_view url, std::map& properties) 106 | { 107 | int state = 0; 108 | std::ostringstream key; 109 | std::ostringstream value; 110 | for (auto ch : url) 111 | { 112 | if (ch == '+') 113 | ch = ' '; 114 | if (state == 0) 115 | { 116 | if (ch == '=') 117 | state = 1; 118 | else 119 | key << ch; 120 | } 121 | else if (state == 1) 122 | { 123 | if (ch == '&') 124 | { 125 | properties[key.str()] = urlDecode(value.str()); 126 | state = 0; 127 | key.str(""); 128 | value.str(""); 129 | } 130 | else 131 | { 132 | value << ch; 133 | } 134 | } 135 | } 136 | if (key.str().length() > 0) 137 | properties[key.str()] = urlDecode(value.str()); 138 | } 139 | 140 | std::string_view net::StringUtils::ltrim(std::string_view str) 141 | { 142 | size_t i, n = str.length(); 143 | for (i = 0; i < n && std::isspace(str[i]); i++); 144 | 145 | str.remove_prefix(i); 146 | return str; 147 | } 148 | 149 | std::string_view net::StringUtils::rtrim(std::string_view str) 150 | { 151 | size_t i, n = str.length(); 152 | for (i = 0; i < n && std::isspace(str[n-i-1]); i++); 153 | 154 | str.remove_suffix(i); 155 | return str; 156 | } 157 | 158 | std::string_view net::StringUtils::trim(std::string_view str) 159 | { 160 | return rtrim(ltrim(str)); 161 | } 162 | 163 | class BitStream 164 | { 165 | public: 166 | std::stringstream bytes; 167 | 168 | private: 169 | uint16_t accumulator; 170 | int bits = 0; 171 | 172 | public: 173 | void addBits(uint8_t value, int n) 174 | { 175 | accumulator = (accumulator << n) + value; 176 | bits += n; 177 | if (bits >= 8) 178 | { 179 | bytes << (char)(accumulator >> (bits - 8)); 180 | accumulator &= 0xff; 181 | bits -= 8; 182 | } 183 | } 184 | }; 185 | 186 | 187 | std::string net::StringUtils::base64_decode(std::string_view msg) 188 | { 189 | const char* alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 190 | BitStream stream; 191 | for (auto ch : msg) 192 | { 193 | auto pos = strchr(alphabet, ch); 194 | if (pos != nullptr) 195 | stream.addBits((uint8_t)(pos - alphabet), 6); 196 | } 197 | return stream.bytes.str(); 198 | } 199 | 200 | std::vector net::StringUtils::split(std::string_view str) 201 | { 202 | std::vector tokens; 203 | size_t start = 0, n = str.length(); 204 | for (size_t i = 0; i < n; i++) 205 | { 206 | if (str[i] == ' ') 207 | { 208 | tokens.push_back(str.substr(start, i - start)); 209 | start = i + 1; 210 | } 211 | } 212 | tokens.push_back(str.substr(start, n)); 213 | return tokens; 214 | } 215 | 216 | std::pair net::StringUtils::splitAtFirstOf(std::string_view str, char delimiter) 217 | { 218 | auto n = str.length(); 219 | auto pos = str.find_first_of(delimiter); 220 | if (pos != std::string::npos) 221 | return std::make_pair(str.substr(0, pos), str.substr(pos + 1, n - 1)); 222 | else 223 | return std::make_pair(str.substr(0, pos), str.substr(0, 0)); 224 | } 225 | 226 | std::string net::StringUtils::randomString(int length) 227 | { 228 | const std::string alphabet = "0123456789ABCDEF"; 229 | std::random_device random_device; 230 | std::mt19937 generator(random_device()); 231 | std::uniform_int_distribution<> distribution(0, alphabet.size() - 1); 232 | std::string result(length, ' '); 233 | for (auto& ch : result) 234 | ch = alphabet[distribution(generator)]; 235 | 236 | return result; 237 | } 238 | -------------------------------------------------------------------------------- /HttpLib/StringUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Property.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace net::StringUtils 10 | { 11 | std::string toLower(std::string_view str); 12 | void parseNameValuePairs(std::list& props, std::string_view str, char delim); 13 | std::string urlDecode(std::string_view url); 14 | void decodeFormData(std::string_view url, std::map& properties); 15 | std::string_view ltrim(std::string_view str); 16 | std::string_view rtrim(std::string_view str); 17 | std::string_view trim(std::string_view str); 18 | std::string base64_decode(std::string_view encoded); 19 | std::vector split(std::string_view str); 20 | std::pair splitAtFirstOf(std::string_view str, char delimiter); 21 | std::string randomString(int length); 22 | } 23 | -------------------------------------------------------------------------------- /HttpLib/UploadHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace net 4 | { 5 | class UploadHandler 6 | { 7 | public: 8 | virtual void write(char* data, int size) = 0; 9 | virtual void endOfStream() = 0; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /HttpLib/UploadedFile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace net 6 | { 7 | class UploadedFile 8 | { 9 | public: 10 | std::filesystem::path tempName; 11 | std::string fileName; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /HttpLib/UrlEncodedFormDataParser.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "UrlEncodedFormDataParser.h" 3 | #include "StringUtils.h" 4 | #include "FormDataHandler.h" 5 | #include 6 | 7 | net::UrlEncodedFormDataParser::UrlEncodedFormDataParser(FormDataHandler* handler) 8 | : FormDataParser(handler) 9 | { 10 | } 11 | 12 | void net::UrlEncodedFormDataParser::processChars(char* data, int count) 13 | { 14 | _stream.write(data, (size_t)count); 15 | } 16 | 17 | void net::UrlEncodedFormDataParser::endOfStream() 18 | { 19 | std::map props; 20 | StringUtils::decodeFormData(_stream.str(), props); 21 | for (auto const& [key, value] : props) 22 | _handler->addDataPair(key, value); 23 | } 24 | -------------------------------------------------------------------------------- /HttpLib/UrlEncodedFormDataParser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FormDataParser.h" 4 | 5 | namespace net 6 | { 7 | class FormDataHandler; 8 | 9 | class UrlEncodedFormDataParser : public FormDataParser 10 | { 11 | public: 12 | UrlEncodedFormDataParser(FormDataHandler* handler); 13 | 14 | void processChars(char* data, int count) override; 15 | void endOfStream() override; 16 | }; 17 | } -------------------------------------------------------------------------------- /HttpLib/WebServer.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include "WebServer.h" 4 | #include "Socket.h" 5 | #include "StreamWriter.h" 6 | #include "HttpRequest.h" 7 | #include "RequestHandler.h" 8 | #include 9 | #include 10 | 11 | static void clientThreadFn(net::Socket socket, const std::string& clientIP, net::RequestHandler& handler) 12 | { 13 | bool keepAlive; 14 | do 15 | { 16 | keepAlive = false; 17 | net::HttpRequest request(&socket, clientIP); 18 | handler.handleRequest(request, &keepAlive); 19 | } while (keepAlive); 20 | 21 | // Force the client to close the connection 22 | //char dummy[256]; 23 | //int nb = socket.receive(dummy, sizeof(dummy)); 24 | 25 | socket.close(); 26 | } 27 | 28 | net::WebServer::WebServer(RequestHandler& handler) : _handler(handler) 29 | { 30 | } 31 | 32 | bool net::WebServer::start(const std::string& ip, int port) 33 | { 34 | Socket socket(Protocol::TCP); 35 | net::EndPoint endPoint(ip.c_str(), port); 36 | socket.bind(endPoint); 37 | bool rc = socket.listen(); 38 | 39 | while (true) 40 | { 41 | EndPoint clientEndPoint; 42 | auto client = socket.accept(&clientEndPoint); 43 | auto clientIP = clientEndPoint.ipAddress(); 44 | if (!client.valid()) 45 | { 46 | perror("accept() error"); 47 | } 48 | 49 | std::thread clientThread(clientThreadFn, client, std::ref(clientIP), std::ref(_handler)); 50 | clientThread.detach(); 51 | } 52 | 53 | std::cout << "Program complete." << std::endl; 54 | socket.close(); 55 | } 56 | 57 | -------------------------------------------------------------------------------- /HttpLib/WebServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace net 6 | { 7 | class RequestHandler; 8 | 9 | class WebServer 10 | { 11 | public: 12 | WebServer(RequestHandler& handler); 13 | bool start(const std::string& ip, int port); 14 | 15 | private: 16 | RequestHandler& _handler; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /HttpLib/linux/Socket.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "../Socket.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class net::EndPointImpl 11 | { 12 | public: 13 | struct sockaddr_in sa; 14 | }; 15 | 16 | net::EndPoint::EndPoint() 17 | : EndPoint("0.0.0.0", 0) 18 | { 19 | } 20 | 21 | net::EndPoint::EndPoint(const char* ip, int port) 22 | : pImpl(std::make_unique()) 23 | { 24 | pImpl->sa.sin_family = AF_INET; 25 | pImpl->sa.sin_addr.s_addr = inet_addr(ip); 26 | pImpl->sa.sin_port = htons(port); 27 | } 28 | 29 | net::EndPoint::EndPoint(const EndPoint& other) 30 | : pImpl(std::make_unique()) 31 | { 32 | pImpl->sa = other.pImpl->sa; 33 | } 34 | 35 | net::EndPoint::~EndPoint() 36 | { 37 | } 38 | 39 | std::string net::EndPoint::ipAddress() const 40 | { 41 | char* strAddress = inet_ntoa(pImpl->sa.sin_addr); 42 | return strAddress; 43 | } 44 | 45 | int net::EndPoint::port() const 46 | { 47 | return ntohs(pImpl->sa.sin_port); 48 | } 49 | 50 | ////////// 51 | 52 | class net::SocketImpl 53 | { 54 | public: 55 | int s; 56 | }; 57 | 58 | net::Socket::Socket(Protocol proto) : pImpl(std::make_shared()) 59 | { 60 | if (proto == Protocol::TCP) 61 | pImpl->s = socket(AF_INET, SOCK_STREAM, 0); 62 | else if (proto == Protocol::UDP) 63 | pImpl->s = socket(AF_INET, SOCK_DGRAM, 0); 64 | else 65 | pImpl->s = -1; 66 | } 67 | 68 | bool net::Socket::valid() const 69 | { 70 | return pImpl->s != -1; 71 | } 72 | 73 | bool net::Socket::startUp() 74 | { 75 | return true; 76 | } 77 | 78 | void net::Socket::shutDown() 79 | { 80 | } 81 | 82 | bool net::Socket::bind(const EndPoint& endPoint) 83 | { 84 | // Ensure port is released to OS as soon as application closes 85 | int one = 1; 86 | setsockopt(pImpl->s, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)); 87 | 88 | auto result = ::bind(pImpl->s, (struct sockaddr*)&endPoint.pImpl->sa, sizeof(endPoint.pImpl->sa)); 89 | return result != -1; 90 | } 91 | 92 | bool net::Socket::listen() 93 | { 94 | auto result = ::listen(pImpl->s, SOMAXCONN); 95 | return result != -1; 96 | } 97 | 98 | net::Socket net::Socket::accept(EndPoint* clientIP) 99 | { 100 | struct sockaddr_in clientaddr; 101 | socklen_t clientaddr_size = sizeof(clientaddr); 102 | Socket client(Protocol::None); 103 | client.pImpl->s = ::accept(pImpl->s, (struct sockaddr *)&clientaddr, &clientaddr_size); 104 | if (valid() && clientIP != nullptr) 105 | { 106 | clientIP->pImpl->sa = clientaddr; 107 | } 108 | return client; 109 | } 110 | 111 | bool net::Socket::close() 112 | { 113 | auto result = ::close(pImpl->s); 114 | return result != -1; 115 | } 116 | 117 | int net::Socket::send(const char* buf, int len) 118 | { 119 | int flags = 0; 120 | return ::send(pImpl->s, buf, len, flags); 121 | } 122 | 123 | int net::Socket::receive(char* buf, int len) 124 | { 125 | int flags = 0; 126 | return ::recv(pImpl->s, buf, len, flags); 127 | } 128 | 129 | int net::Socket::sendTo(const char* buf, int len, const EndPoint& endPoint) 130 | { 131 | int flags = 0; 132 | return ::sendto(pImpl->s, buf, len, flags, (struct sockaddr*)&endPoint.pImpl->sa, sizeof(endPoint.pImpl->sa)); 133 | } 134 | 135 | int net::Socket::receiveFrom(char* buf, int len, EndPoint* sourceAddress) 136 | { 137 | int flags = 0; 138 | socklen_t fromLen = (socklen_t)sizeof(sourceAddress->pImpl->sa); 139 | return ::recvfrom(pImpl->s, buf, len, flags, (struct sockaddr*)&sourceAddress->pImpl->sa, &fromLen); 140 | } 141 | 142 | net::EndPoint net::Socket::endPoint() const 143 | { 144 | net::EndPoint endPoint; 145 | socklen_t nameLen = (int)sizeof(endPoint.pImpl->sa); 146 | //int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 147 | ::getsockname(pImpl->s, (struct sockaddr*)&endPoint.pImpl->sa, &nameLen); 148 | return endPoint; 149 | } 150 | 151 | std::vector net::Socket::getHostByName(const char* hostName) 152 | { 153 | std::vector hosts; 154 | struct hostent* remoteHost = ::gethostbyname(hostName); 155 | 156 | int i = 0; 157 | struct in_addr addr; 158 | if (remoteHost->h_addrtype == AF_INET) 159 | { 160 | while (remoteHost->h_addr_list[i] != 0) 161 | { 162 | addr.s_addr = *(u_long *) remoteHost->h_addr_list[i++]; 163 | hosts.push_back(inet_ntoa(addr)); 164 | } 165 | } 166 | 167 | return hosts; 168 | } 169 | 170 | bool net::Socket::connect(const EndPoint& endPoint) 171 | { 172 | int rc = ::connect(pImpl->s, (struct sockaddr*)&endPoint.pImpl->sa, sizeof(endPoint.pImpl->sa)); 173 | return (rc == 0); 174 | } 175 | -------------------------------------------------------------------------------- /HttpLib/pch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | -------------------------------------------------------------------------------- /HttpLib/pch.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | -------------------------------------------------------------------------------- /HttpLib/win32/Socket.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include "../Socket.h" 4 | #include 5 | #include 6 | 7 | static std::vector wideString(const char* utf8String) 8 | { 9 | int cc = MultiByteToWideChar(CP_UTF8, 0, utf8String, -1, NULL, 0); 10 | std::vector data(cc); 11 | MultiByteToWideChar(CP_UTF8, 0, utf8String, -1, &data[0], cc); 12 | return data; 13 | } 14 | 15 | class net::EndPointImpl 16 | { 17 | public: 18 | struct sockaddr_in sa; 19 | }; 20 | 21 | net::EndPoint::EndPoint() 22 | : EndPoint("0.0.0.0", 0) 23 | { 24 | } 25 | 26 | net::EndPoint::EndPoint(const char* ip, int port) 27 | : pImpl(std::make_unique()) 28 | { 29 | pImpl->sa.sin_family = AF_INET; 30 | //sa.sin_addr.s_addr = inet_addr(ip); 31 | InetPtonA(AF_INET, ip, &pImpl->sa.sin_addr.s_addr); 32 | pImpl->sa.sin_port = htons(port); 33 | } 34 | 35 | net::EndPoint::EndPoint(const EndPoint& other) 36 | : pImpl(std::make_unique()) 37 | { 38 | pImpl->sa = other.pImpl->sa; 39 | } 40 | 41 | net::EndPoint::~EndPoint() 42 | { 43 | } 44 | 45 | std::string net::EndPoint::ipAddress() const 46 | { 47 | char strAddress[64]; 48 | InetNtopA(pImpl->sa.sin_family, &pImpl->sa.sin_addr, strAddress, sizeof(strAddress)); 49 | return strAddress; 50 | } 51 | 52 | int net::EndPoint::port() const 53 | { 54 | return ntohs(pImpl->sa.sin_port); 55 | } 56 | 57 | //////////////////////////////////// 58 | 59 | class net::SocketImpl 60 | { 61 | public: 62 | SOCKET s; 63 | }; 64 | 65 | net::Socket::Socket(Protocol proto) : pImpl(std::make_shared()) 66 | { 67 | if (proto == Protocol::TCP) 68 | pImpl->s = socket(AF_INET, SOCK_STREAM, 0); 69 | else if (proto == Protocol::UDP) 70 | pImpl->s = socket(AF_INET, SOCK_DGRAM, 0); 71 | else 72 | pImpl->s = INVALID_SOCKET; 73 | } 74 | 75 | bool net::Socket::valid() const 76 | { 77 | return pImpl->s != INVALID_SOCKET; 78 | } 79 | 80 | bool net::Socket::startUp() 81 | { 82 | WSADATA wsa; 83 | if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) 84 | return false; 85 | else 86 | return true; 87 | } 88 | 89 | void net::Socket::shutDown() 90 | { 91 | WSACleanup(); 92 | } 93 | 94 | bool net::Socket::bind(const EndPoint& endPoint) 95 | { 96 | // Ensure port is released to OS as soon as application closes 97 | int one = 1; 98 | setsockopt(pImpl->s, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)); 99 | 100 | auto result = ::bind(pImpl->s, (struct sockaddr*)&endPoint.pImpl->sa, sizeof(endPoint.pImpl->sa)); 101 | return result != SOCKET_ERROR; 102 | } 103 | 104 | bool net::Socket::listen() 105 | { 106 | auto result = ::listen(pImpl->s, SOMAXCONN); 107 | return result != SOCKET_ERROR; 108 | } 109 | 110 | net::Socket net::Socket::accept(EndPoint* clientIP) 111 | { 112 | SOCKADDR_IN addr; 113 | int addrlen = sizeof(addr); 114 | Socket client(Protocol::None); 115 | client.pImpl->s = ::accept(pImpl->s, (SOCKADDR*)&addr, &addrlen); 116 | if (valid() && clientIP != nullptr) 117 | { 118 | clientIP->pImpl->sa = addr; 119 | } 120 | return client; 121 | } 122 | 123 | bool net::Socket::close() 124 | { 125 | auto result = ::closesocket(pImpl->s); 126 | return result != SOCKET_ERROR; 127 | } 128 | 129 | int net::Socket::send(const char* buf, int len) 130 | { 131 | int flags = 0; 132 | return ::send(pImpl->s, buf, len, flags); 133 | } 134 | 135 | int net::Socket::receive(char* buf, int len) 136 | { 137 | int flags = 0; 138 | return ::recv(pImpl->s, buf, len, flags); 139 | } 140 | 141 | int net::Socket::sendTo(const char* buf, int len, const EndPoint& endPoint) 142 | { 143 | int flags = 0; 144 | return ::sendto(pImpl->s, buf, len, flags, (SOCKADDR*)&endPoint.pImpl->sa, sizeof(endPoint.pImpl->sa)); 145 | } 146 | 147 | int net::Socket::receiveFrom(char* buf, int len, EndPoint* sourceAddress) 148 | { 149 | int flags = 0; 150 | int fromLen = sizeof(sourceAddress->pImpl->sa); 151 | return ::recvfrom(pImpl->s, buf, len, flags, (SOCKADDR*)&sourceAddress->pImpl->sa, &fromLen); 152 | } 153 | 154 | net::EndPoint net::Socket::endPoint() const 155 | { 156 | net::EndPoint endPoint; 157 | int nameLen = (int)sizeof(endPoint.pImpl->sa); 158 | ::getsockname(pImpl->s, (SOCKADDR*)&endPoint.pImpl->sa, &nameLen); 159 | return endPoint; 160 | } 161 | 162 | std::vector net::Socket::getHostByName(const char* hostName) 163 | { 164 | std::vector hosts; 165 | 166 | DWORD dwRetval; 167 | struct addrinfo* result = NULL; 168 | struct addrinfo hints; 169 | struct addrinfo* ptr = nullptr; 170 | struct sockaddr_in* sockaddr_ipv4; 171 | 172 | ZeroMemory(&hints, sizeof(hints)); 173 | hints.ai_family = AF_UNSPEC; 174 | hints.ai_socktype = SOCK_STREAM; 175 | hints.ai_protocol = IPPROTO_TCP; 176 | 177 | dwRetval = ::getaddrinfo(hostName, "http", &hints, &result); 178 | if (dwRetval != 0) 179 | return hosts; 180 | 181 | for (ptr = result; ptr != nullptr; ptr = ptr->ai_next) 182 | { 183 | if (ptr->ai_family == AF_INET) 184 | { 185 | sockaddr_ipv4 = (struct sockaddr_in*)ptr->ai_addr; 186 | char strAddress[64]; 187 | InetNtopA(AF_INET, &sockaddr_ipv4->sin_addr, strAddress, sizeof(strAddress)); 188 | hosts.push_back(strAddress); 189 | } 190 | } 191 | 192 | return hosts; 193 | } 194 | 195 | bool net::Socket::connect(const EndPoint& endPoint) 196 | { 197 | int rc = ::connect(pImpl->s, (struct sockaddr*)&endPoint.pImpl->sa, sizeof(endPoint.pImpl->sa)); 198 | return (rc == 0); 199 | } 200 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # local-file-server 2 | 3 | A simple HTTP File server written in C++17. 4 | 5 | Designed for sharing files across a local network (or with VMs). 6 | 7 | * Cross-platform (Windows / linux). 8 | * Directory listings 9 | 10 | 11 | ## Building 12 | 13 | ### Building on Windows with Visual Studio 14 | 15 | There is a solution file (.sln) in the top level of the repo. You will need Visual Studio 2017 or later. 16 | 17 | ### Building on Linux with CMake 18 | 19 | Open a console at the top level of the repo. Then type: 20 | 21 | ``` 22 | mkdir build 23 | cd build 24 | cmake -DCMAKE_BUILD_TYPE=Release .. 25 | make 26 | ``` 27 | --------------------------------------------------------------------------------