├── .circleci └── config.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── UNLICENSE ├── azure-pipelines.yml ├── example ├── Makefile ├── example.sln ├── example.vcxproj ├── example.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── main.cpp ├── include └── HTTPRequest.hpp ├── sonar-project.properties └── tests ├── CMakeLists.txt ├── Makefile ├── encoding.cpp ├── main.cpp ├── parsing.cpp ├── tests.sln ├── tests.vcxproj └── tests.xcodeproj ├── project.pbxproj └── project.xcworkspace └── contents.xcworkspacedata /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | test: 5 | docker: 6 | - image: gcc:8.2 7 | steps: 8 | - checkout 9 | - run: git submodule update --init 10 | - run: 11 | name: Download SonarCloud 12 | command: | 13 | mkdir -p $HOME/.sonar 14 | wget https://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip -P $HOME/.sonar 15 | unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/ 16 | - run: 17 | name: Build and run 18 | command: | 19 | export PATH=$HOME/.sonar/build-wrapper-linux-x86:$PATH 20 | build-wrapper-linux-x86-64 --out-dir bw-output make -C tests/ 21 | tests/tests 22 | - run: 23 | name: Generate coverage 24 | command: | 25 | cd tests 26 | gcov encoding.cpp main.cpp parsing.cpp 27 | - sonarcloud/scan 28 | 29 | orbs: 30 | sonarcloud: sonarsource/sonarcloud@1.0.3 31 | 32 | workflows: 33 | main: 34 | jobs: 35 | - test: 36 | context: SonarCloud -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | example/example.xcodeproj/project.xcworkspace/xcshareddata 2 | example/example.xcodeproj/project.xcworkspace/xcuserdata 3 | example/example.xcodeproj/xcshareddata 4 | example/example.xcodeproj/xcuserdata 5 | example/.vs 6 | example/Win32 7 | example/x64/ 8 | example/example.opensdf 9 | example/example.sdf 10 | example/example.VC.db 11 | example/example.VC.VC.opendb 12 | example/example 13 | example/example.exe 14 | *.o 15 | *.d 16 | *.user 17 | tests/tests.xcodeproj/project.xcworkspace/xcshareddata 18 | tests/tests.xcodeproj/project.xcworkspace/xcuserdata 19 | tests/tests.xcodeproj/xcshareddata 20 | tests/tests.xcodeproj/xcuserdata 21 | tests/.vs 22 | tests/Win32 23 | tests/x64/ 24 | tests/tests.opensdf 25 | tests/tests.sdf 26 | tests/tests.VC.db 27 | tests/tests.VC.VC.opendb 28 | tests/tests 29 | tests/tests.exe 30 | *.gcov 31 | *.gcda 32 | *.gcno 33 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/Catch2"] 2 | path = external/Catch2 3 | url = https://github.com/catchorg/Catch2.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | project(HTTPRequest CXX) 3 | 4 | # Header only library will be interface. All properties get passed to targets that 'link' to it 5 | add_library(HTTPRequest INTERFACE) 6 | target_compile_features(HTTPRequest INTERFACE cxx_std_17) 7 | 8 | target_include_directories(HTTPRequest 9 | INTERFACE 10 | ${CMAKE_CURRENT_SOURCE_DIR}/include 11 | ) 12 | 13 | # Optionally build unit tests 14 | option(BUILD_TESTING "Build Unit Tests" OFF) 15 | if (BUILD_TESTING) 16 | enable_testing() 17 | add_subdirectory(tests) 18 | endif() 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTTPRequest 2 | 3 | HTTPRequest is a single-header C++ library for making HTTP requests. You can just include it in your project and use it. HTTPRequest was tested on macOS, Windows, Haiku, BSD, and GNU/Linux, but it should work on most of the Linux-based platforms. Supports IPv4 and IPv6. HTTPRequest requires C++17 or newer. 4 | 5 | ## Usage 6 | 7 | To use the library simply include `HTTPRequest.hpp` using `#include "HTTPRequest.hpp"`. 8 | 9 | ### Example of a GET request 10 | ```cpp 11 | try 12 | { 13 | // you can pass http::InternetProtocol::V6 to Request to make an IPv6 request 14 | http::Request request{"http://test.com/test"}; 15 | 16 | // send a get request 17 | const auto response = request.send("GET"); 18 | std::cout << std::string{response.body.begin(), response.body.end()} << '\n'; // print the result 19 | } 20 | catch (const std::exception& e) 21 | { 22 | std::cerr << "Request failed, error: " << e.what() << '\n'; 23 | } 24 | ``` 25 | 26 | ### Example of a POST request with form data 27 | ```cpp 28 | try 29 | { 30 | http::Request request{"http://test.com/test"}; 31 | const string body = "foo=1&bar=baz"; 32 | const auto response = request.send("POST", body, { 33 | {"Content-Type", "application/x-www-form-urlencoded"} 34 | }); 35 | std::cout << std::string{response.body.begin(), response.body.end()} << '\n'; // print the result 36 | } 37 | catch (const std::exception& e) 38 | { 39 | std::cerr << "Request failed, error: " << e.what() << '\n'; 40 | } 41 | ``` 42 | 43 | ### Example of a POST request with a JSON body 44 | ```cpp 45 | try 46 | { 47 | http::Request request{"http://test.com/test"}; 48 | const std::string body = "{\"foo\": 1, \"bar\": \"baz\"}"; 49 | const auto response = request.send("POST", body, { 50 | {"Content-Type", "application/json"} 51 | }); 52 | std::cout << std::string{response.body.begin(), response.body.end()} << '\n'; // print the result 53 | } 54 | catch (const std::exception& e) 55 | { 56 | std::cerr << "Request failed, error: " << e.what() << '\n'; 57 | } 58 | ``` 59 | 60 | ### Example of a GET request using Basic authorization 61 | ```cpp 62 | try 63 | { 64 | http::Request request{"http://user:password@test.com/test"}; 65 | const auto response = request.send("GET"); 66 | std::cout << std::string{response.body.begin(), response.body.end()} << '\n'; // print the result 67 | } 68 | catch (const std::exception& e) 69 | { 70 | std::cerr << "Request failed, error: " << e.what() << '\n'; 71 | } 72 | ``` 73 | 74 | To set a timeout for HTTP requests, pass `std::chrono::duration` as a last parameter to `send()`. A negative duration (default) passed to `send()` disables timeout. 75 | 76 | ## License 77 | 78 | HTTPRequest is released to the Public Domain. 79 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | 4 | jobs: 5 | - job: Tests_Linux 6 | displayName: Tests on Linux 7 | pool: 8 | vmImage: 'Ubuntu-latest' 9 | steps: 10 | - checkout: self 11 | submodules: true 12 | - script: | 13 | cd tests 14 | make -j2 15 | displayName: 'make' 16 | - script: | 17 | cd tests 18 | ./tests 19 | displayName: 'run' 20 | 21 | - job: Tests_Windows 22 | displayName: Tests on Windows 23 | pool: 24 | vmImage: 'vs2017-win2016' 25 | steps: 26 | - checkout: self 27 | submodules: true 28 | - task: MSBuild@1 29 | inputs: 30 | solution: tests/tests.vcxproj 31 | configuration: Release 32 | - script: | 33 | cd tests/Win32/Release 34 | tests.exe 35 | displayName: 'run' 36 | 37 | - job: Tests_macOS 38 | displayName: Tests on macOS 39 | pool: 40 | vmImage: 'macOS-10.15' 41 | steps: 42 | - checkout: self 43 | submodules: true 44 | - script: | 45 | cd tests 46 | make -j2 47 | displayName: 'make' 48 | - script: | 49 | cd tests 50 | ./tests 51 | displayName: 'run' 52 | 53 | - job: Example_Linux 54 | displayName: Example on Linux 55 | pool: 56 | vmImage: 'Ubuntu-latest' 57 | steps: 58 | - script: | 59 | cd example 60 | make -j2 61 | displayName: 'make' 62 | 63 | - job: Example_Windows 64 | displayName: Example on Windows 65 | pool: 66 | vmImage: 'vs2017-win2016' 67 | steps: 68 | - task: MSBuild@1 69 | inputs: 70 | solution: example/example.vcxproj 71 | configuration: Release 72 | 73 | - job: Example_macOS 74 | displayName: Example on macOS 75 | pool: 76 | vmImage: 'macOS-10.15' 77 | steps: 78 | - script: | 79 | cd example 80 | make -j2 81 | displayName: 'make' 82 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | DEBUG=0 2 | ifeq ($(OS),Windows_NT) 3 | platform=windows 4 | else ifeq ($(shell uname -s),Linux) 5 | platform=linux 6 | endif 7 | ifeq ($(shell uname -s),Darwin) 8 | platform=macos 9 | else ifeq ($(shell uname -s),Haiku) 10 | platform=haiku 11 | endif 12 | 13 | CXXFLAGS=-std=c++11 -Wall -Wshadow -O2 -I../include 14 | LDFLAGS=-O2 15 | ifeq ($(platform),windows) 16 | LDFLAGS+=-lws2_32 17 | else ifeq ($(platform),haiku) 18 | LDFLAGS+=-lnetwork 19 | endif 20 | SOURCES=main.cpp 21 | BASE_NAMES=$(basename $(SOURCES)) 22 | OBJECTS=$(BASE_NAMES:=.o) 23 | DEPENDENCIES=$(OBJECTS:.o=.d) 24 | EXECUTABLE=example 25 | 26 | all: $(EXECUTABLE) 27 | ifeq ($(DEBUG),1) 28 | all: CXXFLAGS+=-DDEBUG -g 29 | endif 30 | 31 | $(EXECUTABLE): $(OBJECTS) 32 | $(CXX) $(OBJECTS) $(LDFLAGS) -o $@ 33 | 34 | -include $(DEPENDENCIES) 35 | 36 | %.o: %.cpp 37 | $(CXX) -c $(CXXFLAGS) -MMD -MP $< -o $@ 38 | 39 | .PHONY: clean 40 | clean: 41 | $(RM) $(EXECUTABLE) $(OBJECTS) $(DEPENDENCIES) $(EXECUTABLE).exe -------------------------------------------------------------------------------- /example/example.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example", "example.vcxproj", "{614C7EC0-3262-40DF-B884-224B959A01F9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Debug|x64 = Debug|x64 12 | Release|Win32 = Release|Win32 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Debug|Win32.ActiveCfg = Debug|Win32 17 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Debug|Win32.Build.0 = Debug|Win32 18 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Debug|x64.ActiveCfg = Debug|x64 19 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Debug|x64.Build.0 = Debug|x64 20 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Release|Win32.ActiveCfg = Release|Win32 21 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Release|Win32.Build.0 = Release|Win32 22 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Release|x64.ActiveCfg = Release|x64 23 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /example/example.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 | 23 | 24 | 25 | 26 | 27 | 28 | {614C7EC0-3262-40DF-B884-224B959A01F9} 29 | Win32Proj 30 | rtmp_relay 31 | 10.0 32 | 33 | 34 | 35 | Application 36 | true 37 | v140 38 | Unicode 39 | 40 | 41 | Application 42 | false 43 | v140 44 | true 45 | Unicode 46 | 47 | 48 | Application 49 | true 50 | v143 51 | Unicode 52 | 53 | 54 | Application 55 | false 56 | v143 57 | true 58 | Unicode 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | true 80 | ../include;$(IncludePath) 81 | $(SolutionDir)$(Platform)\$(Configuration)\ 82 | $(Platform)\$(Configuration)\ 83 | 84 | 85 | true 86 | ../include;$(IncludePath) 87 | 88 | 89 | false 90 | ../include;$(IncludePath) 91 | $(SolutionDir)$(Platform)\$(Configuration)\ 92 | $(Platform)\$(Configuration)\ 93 | 94 | 95 | false 96 | ../include;$(IncludePath) 97 | 98 | 99 | 100 | Level3 101 | Disabled 102 | _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 103 | true 104 | 105 | 106 | Console 107 | true 108 | ws2_32.lib;%(AdditionalDependencies) 109 | 110 | 111 | 112 | 113 | Level4 114 | Disabled 115 | _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 116 | true 117 | TurnOffAllWarnings 118 | stdcpp17 119 | 120 | 121 | Console 122 | true 123 | ws2_32.lib;%(AdditionalDependencies) 124 | 125 | 126 | 127 | 128 | Level3 129 | MaxSpeed 130 | true 131 | true 132 | _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 133 | true 134 | 135 | 136 | Console 137 | true 138 | true 139 | true 140 | ws2_32.lib;%(AdditionalDependencies) 141 | 142 | 143 | 144 | 145 | Level4 146 | MaxSpeed 147 | true 148 | true 149 | _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 150 | true 151 | TurnOffAllWarnings 152 | stdcpp17 153 | 154 | 155 | Console 156 | true 157 | true 158 | true 159 | ws2_32.lib;%(AdditionalDependencies) 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /example/example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 30DAD98B1ECA11AC00E9F3D7 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 30DAD98A1ECA11AC00E9F3D7 /* main.cpp */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXFileReference section */ 14 | 30DAD9801ECA117100E9F3D7 /* example */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = example; sourceTree = BUILT_PRODUCTS_DIR; }; 15 | 30DAD98A1ECA11AC00E9F3D7 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; 16 | 30DAD98E1ECA180B00E9F3D7 /* HTTPRequest.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = HTTPRequest.hpp; sourceTree = ""; }; 17 | /* End PBXFileReference section */ 18 | 19 | /* Begin PBXGroup section */ 20 | 30DAD9771ECA117100E9F3D7 = { 21 | isa = PBXGroup; 22 | children = ( 23 | 30DAD98D1ECA11BC00E9F3D7 /* include */, 24 | 30DAD98C1ECA11B600E9F3D7 /* example */, 25 | 30DAD9811ECA117100E9F3D7 /* Products */, 26 | ); 27 | sourceTree = ""; 28 | }; 29 | 30DAD9811ECA117100E9F3D7 /* Products */ = { 30 | isa = PBXGroup; 31 | children = ( 32 | 30DAD9801ECA117100E9F3D7 /* example */, 33 | ); 34 | name = Products; 35 | sourceTree = ""; 36 | }; 37 | 30DAD98C1ECA11B600E9F3D7 /* example */ = { 38 | isa = PBXGroup; 39 | children = ( 40 | 30DAD98A1ECA11AC00E9F3D7 /* main.cpp */, 41 | ); 42 | name = example; 43 | sourceTree = ""; 44 | }; 45 | 30DAD98D1ECA11BC00E9F3D7 /* include */ = { 46 | isa = PBXGroup; 47 | children = ( 48 | 30DAD98E1ECA180B00E9F3D7 /* HTTPRequest.hpp */, 49 | ); 50 | name = include; 51 | path = ../include; 52 | sourceTree = ""; 53 | }; 54 | /* End PBXGroup section */ 55 | 56 | /* Begin PBXNativeTarget section */ 57 | 30DAD97F1ECA117100E9F3D7 /* example */ = { 58 | isa = PBXNativeTarget; 59 | buildConfigurationList = 30DAD9871ECA117100E9F3D7 /* Build configuration list for PBXNativeTarget "example" */; 60 | buildPhases = ( 61 | 30DAD97C1ECA117100E9F3D7 /* Sources */, 62 | ); 63 | buildRules = ( 64 | ); 65 | dependencies = ( 66 | ); 67 | name = example; 68 | productName = example; 69 | productReference = 30DAD9801ECA117100E9F3D7 /* example */; 70 | productType = "com.apple.product-type.tool"; 71 | }; 72 | /* End PBXNativeTarget section */ 73 | 74 | /* Begin PBXProject section */ 75 | 30DAD9781ECA117100E9F3D7 /* Project object */ = { 76 | isa = PBXProject; 77 | attributes = { 78 | LastUpgradeCheck = 0830; 79 | ORGANIZATIONNAME = "Elviss Strazdins"; 80 | TargetAttributes = { 81 | 30DAD97F1ECA117100E9F3D7 = { 82 | CreatedOnToolsVersion = 8.3.2; 83 | ProvisioningStyle = Automatic; 84 | }; 85 | }; 86 | }; 87 | buildConfigurationList = 30DAD97B1ECA117100E9F3D7 /* Build configuration list for PBXProject "example" */; 88 | compatibilityVersion = "Xcode 3.2"; 89 | developmentRegion = English; 90 | hasScannedForEncodings = 0; 91 | knownRegions = ( 92 | English, 93 | en, 94 | ); 95 | mainGroup = 30DAD9771ECA117100E9F3D7; 96 | productRefGroup = 30DAD9811ECA117100E9F3D7 /* Products */; 97 | projectDirPath = ""; 98 | projectRoot = ""; 99 | targets = ( 100 | 30DAD97F1ECA117100E9F3D7 /* example */, 101 | ); 102 | }; 103 | /* End PBXProject section */ 104 | 105 | /* Begin PBXSourcesBuildPhase section */ 106 | 30DAD97C1ECA117100E9F3D7 /* Sources */ = { 107 | isa = PBXSourcesBuildPhase; 108 | buildActionMask = 2147483647; 109 | files = ( 110 | 30DAD98B1ECA11AC00E9F3D7 /* main.cpp in Sources */, 111 | ); 112 | runOnlyForDeploymentPostprocessing = 0; 113 | }; 114 | /* End PBXSourcesBuildPhase section */ 115 | 116 | /* Begin XCBuildConfiguration section */ 117 | 30DAD9851ECA117100E9F3D7 /* Debug */ = { 118 | isa = XCBuildConfiguration; 119 | buildSettings = { 120 | ALWAYS_SEARCH_USER_PATHS = NO; 121 | CLANG_ANALYZER_NONNULL = YES; 122 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 123 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; 124 | CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; 125 | CLANG_CXX_LIBRARY = "libc++"; 126 | CLANG_ENABLE_MODULES = YES; 127 | CLANG_ENABLE_OBJC_ARC = YES; 128 | CLANG_WARN_ASSIGN_ENUM = YES; 129 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 130 | CLANG_WARN_BOOL_CONVERSION = YES; 131 | CLANG_WARN_COMMA = YES; 132 | CLANG_WARN_CONSTANT_CONVERSION = YES; 133 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; 134 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 135 | CLANG_WARN_EMPTY_BODY = YES; 136 | CLANG_WARN_ENUM_CONVERSION = YES; 137 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; 138 | CLANG_WARN_INFINITE_RECURSION = YES; 139 | CLANG_WARN_INT_CONVERSION = YES; 140 | CLANG_WARN_OBJC_ROOT_CLASS = YES; 141 | CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; 142 | CLANG_WARN_STRICT_PROTOTYPES = YES; 143 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; 144 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 145 | CLANG_WARN_UNREACHABLE_CODE = YES; 146 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 147 | CODE_SIGN_IDENTITY = "-"; 148 | COPY_PHASE_STRIP = NO; 149 | DEBUG_INFORMATION_FORMAT = dwarf; 150 | ENABLE_STRICT_OBJC_MSGSEND = YES; 151 | ENABLE_TESTABILITY = YES; 152 | GCC_DYNAMIC_NO_PIC = NO; 153 | GCC_NO_COMMON_BLOCKS = YES; 154 | GCC_OPTIMIZATION_LEVEL = 0; 155 | GCC_PREPROCESSOR_DEFINITIONS = ( 156 | "DEBUG=1", 157 | "$(inherited)", 158 | ); 159 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 160 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 161 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES; 162 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 163 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 164 | GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; 165 | GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; 166 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 167 | GCC_WARN_PEDANTIC = YES; 168 | GCC_WARN_SHADOW = YES; 169 | GCC_WARN_SIGN_COMPARE = YES; 170 | GCC_WARN_UNDECLARED_SELECTOR = YES; 171 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 172 | GCC_WARN_UNKNOWN_PRAGMAS = YES; 173 | GCC_WARN_UNUSED_FUNCTION = YES; 174 | GCC_WARN_UNUSED_LABEL = YES; 175 | GCC_WARN_UNUSED_PARAMETER = YES; 176 | GCC_WARN_UNUSED_VARIABLE = YES; 177 | HEADER_SEARCH_PATHS = ../include; 178 | MACOSX_DEPLOYMENT_TARGET = 10.12; 179 | MTL_ENABLE_DEBUG_INFO = YES; 180 | ONLY_ACTIVE_ARCH = YES; 181 | SDKROOT = macosx; 182 | }; 183 | name = Debug; 184 | }; 185 | 30DAD9861ECA117100E9F3D7 /* Release */ = { 186 | isa = XCBuildConfiguration; 187 | buildSettings = { 188 | ALWAYS_SEARCH_USER_PATHS = NO; 189 | CLANG_ANALYZER_NONNULL = YES; 190 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 191 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; 192 | CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; 193 | CLANG_CXX_LIBRARY = "libc++"; 194 | CLANG_ENABLE_MODULES = YES; 195 | CLANG_ENABLE_OBJC_ARC = YES; 196 | CLANG_WARN_ASSIGN_ENUM = YES; 197 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 198 | CLANG_WARN_BOOL_CONVERSION = YES; 199 | CLANG_WARN_COMMA = YES; 200 | CLANG_WARN_CONSTANT_CONVERSION = YES; 201 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; 202 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 203 | CLANG_WARN_EMPTY_BODY = YES; 204 | CLANG_WARN_ENUM_CONVERSION = YES; 205 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; 206 | CLANG_WARN_INFINITE_RECURSION = YES; 207 | CLANG_WARN_INT_CONVERSION = YES; 208 | CLANG_WARN_OBJC_ROOT_CLASS = YES; 209 | CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; 210 | CLANG_WARN_STRICT_PROTOTYPES = YES; 211 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; 212 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 213 | CLANG_WARN_UNREACHABLE_CODE = YES; 214 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 215 | CODE_SIGN_IDENTITY = "-"; 216 | COPY_PHASE_STRIP = NO; 217 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 218 | ENABLE_NS_ASSERTIONS = NO; 219 | ENABLE_STRICT_OBJC_MSGSEND = YES; 220 | GCC_NO_COMMON_BLOCKS = YES; 221 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 222 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 223 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES; 224 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 225 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 226 | GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; 227 | GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; 228 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 229 | GCC_WARN_PEDANTIC = YES; 230 | GCC_WARN_SHADOW = YES; 231 | GCC_WARN_SIGN_COMPARE = YES; 232 | GCC_WARN_UNDECLARED_SELECTOR = YES; 233 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 234 | GCC_WARN_UNKNOWN_PRAGMAS = YES; 235 | GCC_WARN_UNUSED_FUNCTION = YES; 236 | GCC_WARN_UNUSED_LABEL = YES; 237 | GCC_WARN_UNUSED_PARAMETER = YES; 238 | GCC_WARN_UNUSED_VARIABLE = YES; 239 | HEADER_SEARCH_PATHS = ../include; 240 | MACOSX_DEPLOYMENT_TARGET = 10.12; 241 | MTL_ENABLE_DEBUG_INFO = NO; 242 | SDKROOT = macosx; 243 | }; 244 | name = Release; 245 | }; 246 | 30DAD9881ECA117100E9F3D7 /* Debug */ = { 247 | isa = XCBuildConfiguration; 248 | buildSettings = { 249 | PRODUCT_NAME = "$(TARGET_NAME)"; 250 | }; 251 | name = Debug; 252 | }; 253 | 30DAD9891ECA117100E9F3D7 /* Release */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | PRODUCT_NAME = "$(TARGET_NAME)"; 257 | }; 258 | name = Release; 259 | }; 260 | /* End XCBuildConfiguration section */ 261 | 262 | /* Begin XCConfigurationList section */ 263 | 30DAD97B1ECA117100E9F3D7 /* Build configuration list for PBXProject "example" */ = { 264 | isa = XCConfigurationList; 265 | buildConfigurations = ( 266 | 30DAD9851ECA117100E9F3D7 /* Debug */, 267 | 30DAD9861ECA117100E9F3D7 /* Release */, 268 | ); 269 | defaultConfigurationIsVisible = 0; 270 | defaultConfigurationName = Release; 271 | }; 272 | 30DAD9871ECA117100E9F3D7 /* Build configuration list for PBXNativeTarget "example" */ = { 273 | isa = XCConfigurationList; 274 | buildConfigurations = ( 275 | 30DAD9881ECA117100E9F3D7 /* Debug */, 276 | 30DAD9891ECA117100E9F3D7 /* Release */, 277 | ); 278 | defaultConfigurationIsVisible = 0; 279 | defaultConfigurationName = Release; 280 | }; 281 | /* End XCConfigurationList section */ 282 | }; 283 | rootObject = 30DAD9781ECA117100E9F3D7 /* Project object */; 284 | } 285 | -------------------------------------------------------------------------------- /example/example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPRequest 3 | // 4 | 5 | #include 6 | #include 7 | #include "HTTPRequest.hpp" 8 | 9 | int main(int argc, const char* argv[]) 10 | { 11 | try 12 | { 13 | std::string uri; 14 | std::string method = "GET"; 15 | std::string arguments; 16 | std::string output; 17 | auto protocol = http::InternetProtocol::v4; 18 | 19 | for (int i = 1; i < argc; ++i) 20 | { 21 | const auto arg = std::string{argv[i]}; 22 | 23 | if (arg == "--help") 24 | { 25 | std::cout << "example --url [--protocol ] [--method ] [--arguments ] [--output ]\n"; 26 | return EXIT_SUCCESS; 27 | } 28 | else if (arg == "--uri") 29 | { 30 | if (++i < argc) uri = argv[i]; 31 | else throw std::runtime_error("Missing argument for --url"); 32 | } 33 | else if (arg == "--protocol") 34 | { 35 | if (++i < argc) 36 | { 37 | if (std::string{argv[i]} == "ipv4") 38 | protocol = http::InternetProtocol::v4; 39 | else if (std::string{argv[i]} == "ipv6") 40 | protocol = http::InternetProtocol::v6; 41 | else 42 | throw std::runtime_error{"Invalid protocol"}; 43 | } 44 | else throw std::runtime_error{"Missing argument for --protocol"}; 45 | } 46 | else if (arg == "--method") 47 | { 48 | if (++i < argc) method = argv[i]; 49 | else throw std::runtime_error{"Missing argument for --method"}; 50 | } 51 | else if (arg == "--arguments") 52 | { 53 | if (++i < argc) arguments = argv[i]; 54 | else throw std::runtime_error{"Missing argument for --arguments"}; 55 | } 56 | else if (arg == "--output") 57 | { 58 | if (++i < argc) output = argv[i]; 59 | else throw std::runtime_error{"Missing argument for --output"}; 60 | } 61 | else 62 | throw std::runtime_error{"Invalid flag: " + arg}; 63 | } 64 | 65 | http::Request request{uri, protocol}; 66 | 67 | const auto response = request.send(method, arguments, { 68 | {"Content-Type", "application/x-www-form-urlencoded"}, 69 | {"User-Agent", "runscope/0.1"}, 70 | {"Accept", "*/*"} 71 | }, std::chrono::seconds(2)); 72 | 73 | std::cout << response.status.reason << '\n'; 74 | 75 | if (response.status.code == http::Status::Ok) 76 | { 77 | if (!output.empty()) 78 | { 79 | std::ofstream outfile{output, std::ofstream::binary}; 80 | outfile.write(reinterpret_cast(response.body.data()), 81 | static_cast(response.body.size())); 82 | } 83 | else 84 | std::cout << std::string{response.body.begin(), response.body.end()} << '\n'; 85 | } 86 | } 87 | catch (const http::RequestError& e) 88 | { 89 | std::cerr << "Request error: " << e.what() << '\n'; 90 | return EXIT_FAILURE; 91 | } 92 | catch (const http::ResponseError& e) 93 | { 94 | std::cerr << "Response error: " << e.what() << '\n'; 95 | return EXIT_FAILURE; 96 | } 97 | catch (const std::exception& e) 98 | { 99 | std::cerr << "Error: " << e.what() << '\n'; 100 | return EXIT_FAILURE; 101 | } 102 | 103 | return EXIT_SUCCESS; 104 | } 105 | -------------------------------------------------------------------------------- /include/HTTPRequest.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPRequest 3 | // 4 | 5 | #ifndef HTTPREQUEST_HPP 6 | #define HTTPREQUEST_HPP 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #if defined(_WIN32) || defined(__CYGWIN__) 26 | # pragma push_macro("WIN32_LEAN_AND_MEAN") 27 | # pragma push_macro("NOMINMAX") 28 | # ifndef WIN32_LEAN_AND_MEAN 29 | # define WIN32_LEAN_AND_MEAN 30 | # endif // WIN32_LEAN_AND_MEAN 31 | # ifndef NOMINMAX 32 | # define NOMINMAX 33 | # endif // NOMINMAX 34 | # include 35 | # if _WIN32_WINNT < _WIN32_WINNT_WINXP 36 | extern "C" char *_strdup(const char *strSource); 37 | # define strdup _strdup 38 | # include 39 | # endif // _WIN32_WINNT < _WIN32_WINNT_WINXP 40 | # include 41 | # pragma pop_macro("WIN32_LEAN_AND_MEAN") 42 | # pragma pop_macro("NOMINMAX") 43 | #else 44 | # include 45 | # include 46 | # include 47 | # include 48 | # include 49 | # include 50 | # include 51 | # include 52 | #endif // defined(_WIN32) || defined(__CYGWIN__) 53 | 54 | namespace http 55 | { 56 | class RequestError final: public std::logic_error 57 | { 58 | public: 59 | using logic_error::logic_error; 60 | using logic_error::operator=; 61 | }; 62 | 63 | class ResponseError final: public std::runtime_error 64 | { 65 | public: 66 | using runtime_error::runtime_error; 67 | using runtime_error::operator=; 68 | }; 69 | 70 | enum class InternetProtocol: std::uint8_t 71 | { 72 | v4, 73 | v6 74 | }; 75 | 76 | struct Uri final 77 | { 78 | std::string scheme; 79 | std::string user; 80 | std::string password; 81 | std::string host; 82 | std::string port; 83 | std::string path; 84 | std::string query; 85 | std::string fragment; 86 | }; 87 | 88 | struct Version final 89 | { 90 | uint16_t major; 91 | uint16_t minor; 92 | }; 93 | 94 | struct Status final 95 | { 96 | // RFC 7231, 6. Response Status Codes 97 | enum Code: std::uint16_t 98 | { 99 | Continue = 100, 100 | SwitchingProtocol = 101, 101 | Processing = 102, 102 | EarlyHints = 103, 103 | 104 | Ok = 200, 105 | Created = 201, 106 | Accepted = 202, 107 | NonAuthoritativeInformation = 203, 108 | NoContent = 204, 109 | ResetContent = 205, 110 | PartialContent = 206, 111 | MultiStatus = 207, 112 | AlreadyReported = 208, 113 | ImUsed = 226, 114 | 115 | MultipleChoice = 300, 116 | MovedPermanently = 301, 117 | Found = 302, 118 | SeeOther = 303, 119 | NotModified = 304, 120 | UseProxy = 305, 121 | TemporaryRedirect = 307, 122 | PermanentRedirect = 308, 123 | 124 | BadRequest = 400, 125 | Unauthorized = 401, 126 | PaymentRequired = 402, 127 | Forbidden = 403, 128 | NotFound = 404, 129 | MethodNotAllowed = 405, 130 | NotAcceptable = 406, 131 | ProxyAuthenticationRequired = 407, 132 | RequestTimeout = 408, 133 | Conflict = 409, 134 | Gone = 410, 135 | LengthRequired = 411, 136 | PreconditionFailed = 412, 137 | PayloadTooLarge = 413, 138 | UriTooLong = 414, 139 | UnsupportedMediaType = 415, 140 | RangeNotSatisfiable = 416, 141 | ExpectationFailed = 417, 142 | MisdirectedRequest = 421, 143 | UnprocessableEntity = 422, 144 | Locked = 423, 145 | FailedDependency = 424, 146 | TooEarly = 425, 147 | UpgradeRequired = 426, 148 | PreconditionRequired = 428, 149 | TooManyRequests = 429, 150 | RequestHeaderFieldsTooLarge = 431, 151 | UnavailableForLegalReasons = 451, 152 | 153 | InternalServerError = 500, 154 | NotImplemented = 501, 155 | BadGateway = 502, 156 | ServiceUnavailable = 503, 157 | GatewayTimeout = 504, 158 | HttpVersionNotSupported = 505, 159 | VariantAlsoNegotiates = 506, 160 | InsufficientStorage = 507, 161 | LoopDetected = 508, 162 | NotExtended = 510, 163 | NetworkAuthenticationRequired = 511 164 | }; 165 | 166 | Version version; 167 | std::uint16_t code; 168 | std::string reason; 169 | }; 170 | 171 | using HeaderField = std::pair; 172 | using HeaderFields = std::vector; 173 | 174 | struct Response final 175 | { 176 | Status status; 177 | HeaderFields headerFields; 178 | std::vector body; 179 | }; 180 | 181 | inline namespace detail 182 | { 183 | #if defined(_WIN32) || defined(__CYGWIN__) 184 | namespace winsock 185 | { 186 | class ErrorCategory final: public std::error_category 187 | { 188 | public: 189 | const char* name() const noexcept override 190 | { 191 | return "Windows Sockets API"; 192 | } 193 | 194 | std::string message(const int condition) const override 195 | { 196 | switch (condition) 197 | { 198 | case WSA_INVALID_HANDLE: return "Specified event object handle is invalid"; 199 | case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available"; 200 | case WSA_INVALID_PARAMETER: return "One or more parameters are invalid"; 201 | case WSA_OPERATION_ABORTED: return "Overlapped operation aborted"; 202 | case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state"; 203 | case WSA_IO_PENDING: return "Overlapped operations will complete later"; 204 | case WSAEINTR: return "Interrupted function call"; 205 | case WSAEBADF: return "File handle is not valid"; 206 | case WSAEACCES: return "Permission denied"; 207 | case WSAEFAULT: return "Bad address"; 208 | case WSAEINVAL: return "Invalid argument"; 209 | case WSAEMFILE: return "Too many open files"; 210 | case WSAEWOULDBLOCK: return "Resource temporarily unavailable"; 211 | case WSAEINPROGRESS: return "Operation now in progress"; 212 | case WSAEALREADY: return "Operation already in progress"; 213 | case WSAENOTSOCK: return "Socket operation on nonsocket"; 214 | case WSAEDESTADDRREQ: return "Destination address required"; 215 | case WSAEMSGSIZE: return "Message too long"; 216 | case WSAEPROTOTYPE: return "Protocol wrong type for socket"; 217 | case WSAENOPROTOOPT: return "Bad protocol option"; 218 | case WSAEPROTONOSUPPORT: return "Protocol not supported"; 219 | case WSAESOCKTNOSUPPORT: return "Socket type not supported"; 220 | case WSAEOPNOTSUPP: return "Operation not supported"; 221 | case WSAEPFNOSUPPORT: return "Protocol family not supported"; 222 | case WSAEAFNOSUPPORT: return "Address family not supported by protocol family"; 223 | case WSAEADDRINUSE: return "Address already in use"; 224 | case WSAEADDRNOTAVAIL: return "Cannot assign requested address"; 225 | case WSAENETDOWN: return "Network is down"; 226 | case WSAENETUNREACH: return "Network is unreachable"; 227 | case WSAENETRESET: return "Network dropped connection on reset"; 228 | case WSAECONNABORTED: return "Software caused connection abort"; 229 | case WSAECONNRESET: return "Connection reset by peer"; 230 | case WSAENOBUFS: return "No buffer space available"; 231 | case WSAEISCONN: return "Socket is already connected"; 232 | case WSAENOTCONN: return "Socket is not connected"; 233 | case WSAESHUTDOWN: return "Cannot send after socket shutdown"; 234 | case WSAETOOMANYREFS: return "Too many references"; 235 | case WSAETIMEDOUT: return "Connection timed out"; 236 | case WSAECONNREFUSED: return "Connection refused"; 237 | case WSAELOOP: return "Cannot translate name"; 238 | case WSAENAMETOOLONG: return "Name too long"; 239 | case WSAEHOSTDOWN: return "Host is down"; 240 | case WSAEHOSTUNREACH: return "No route to host"; 241 | case WSAENOTEMPTY: return "Directory not empty"; 242 | case WSAEPROCLIM: return "Too many processes"; 243 | case WSAEUSERS: return "User quota exceeded"; 244 | case WSAEDQUOT: return "Disk quota exceeded"; 245 | case WSAESTALE: return "Stale file handle reference"; 246 | case WSAEREMOTE: return "Item is remote"; 247 | case WSASYSNOTREADY: return "Network subsystem is unavailable"; 248 | case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range"; 249 | case WSANOTINITIALISED: return "Successful WSAStartup not yet performed"; 250 | case WSAEDISCON: return "Graceful shutdown in progress"; 251 | case WSAENOMORE: return "No more results"; 252 | case WSAECANCELLED: return "Call has been canceled"; 253 | case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid"; 254 | case WSAEINVALIDPROVIDER: return "Service provider is invalid"; 255 | case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize"; 256 | case WSASYSCALLFAILURE: return "System call failure"; 257 | case WSASERVICE_NOT_FOUND: return "Service not found"; 258 | case WSATYPE_NOT_FOUND: return "Class type not found"; 259 | case WSA_E_NO_MORE: return "No more results"; 260 | case WSA_E_CANCELLED: return "Call was canceled"; 261 | case WSAEREFUSED: return "Database query was refused"; 262 | case WSAHOST_NOT_FOUND: return "Host not found"; 263 | case WSATRY_AGAIN: return "Nonauthoritative host not found"; 264 | case WSANO_RECOVERY: return "This is a nonrecoverable error"; 265 | case WSANO_DATA: return "Valid name, no data record of requested type"; 266 | case WSA_QOS_RECEIVERS: return "QoS receivers"; 267 | case WSA_QOS_SENDERS: return "QoS senders"; 268 | case WSA_QOS_NO_SENDERS: return "No QoS senders"; 269 | case WSA_QOS_NO_RECEIVERS: return "QoS no receivers"; 270 | case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed"; 271 | case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error"; 272 | case WSA_QOS_POLICY_FAILURE: return "QoS policy failure"; 273 | case WSA_QOS_BAD_STYLE: return "QoS bad style"; 274 | case WSA_QOS_BAD_OBJECT: return "QoS bad object"; 275 | case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error"; 276 | case WSA_QOS_GENERIC_ERROR: return "QoS generic error"; 277 | case WSA_QOS_ESERVICETYPE: return "QoS service type error"; 278 | case WSA_QOS_EFLOWSPEC: return "QoS flowspec error"; 279 | case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer"; 280 | case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style"; 281 | case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type"; 282 | case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count"; 283 | case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length"; 284 | case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count"; 285 | case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object"; 286 | case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object"; 287 | case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor"; 288 | case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec"; 289 | case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec"; 290 | case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object"; 291 | case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object"; 292 | case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type"; 293 | default: return "Unknown error (" + std::to_string(condition) + ")"; 294 | } 295 | } 296 | }; 297 | 298 | inline const ErrorCategory errorCategory; 299 | 300 | class Api final 301 | { 302 | public: 303 | Api() 304 | { 305 | WSADATA wsaData; 306 | const auto error = WSAStartup(MAKEWORD(2, 2), &wsaData); 307 | if (error != 0) 308 | throw std::system_error{error, errorCategory, "WSAStartup failed"}; 309 | 310 | if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) 311 | { 312 | WSACleanup(); 313 | throw std::runtime_error{"Invalid WinSock version"}; 314 | } 315 | 316 | started = true; 317 | } 318 | 319 | ~Api() 320 | { 321 | if (started) WSACleanup(); 322 | } 323 | 324 | Api(Api&& other) noexcept: 325 | started{std::exchange(other.started, false)} 326 | { 327 | } 328 | 329 | Api& operator=(Api&& other) noexcept 330 | { 331 | if (&other == this) return *this; 332 | if (started) WSACleanup(); 333 | started = std::exchange(other.started, false); 334 | return *this; 335 | } 336 | 337 | private: 338 | bool started = false; 339 | }; 340 | } 341 | #endif // defined(_WIN32) || defined(__CYGWIN__) 342 | 343 | constexpr int getAddressFamily(const InternetProtocol internetProtocol) 344 | { 345 | return (internetProtocol == InternetProtocol::v4) ? AF_INET : 346 | (internetProtocol == InternetProtocol::v6) ? AF_INET6 : 347 | throw RequestError{"Unsupported protocol"}; 348 | } 349 | 350 | class Socket final 351 | { 352 | public: 353 | #if defined(_WIN32) || defined(__CYGWIN__) 354 | using Type = SOCKET; 355 | static constexpr Type invalid = INVALID_SOCKET; 356 | #else 357 | using Type = int; 358 | static constexpr Type invalid = -1; 359 | #endif // defined(_WIN32) || defined(__CYGWIN__) 360 | 361 | explicit Socket(const InternetProtocol internetProtocol): 362 | endpoint{socket(getAddressFamily(internetProtocol), SOCK_STREAM, IPPROTO_TCP)} 363 | { 364 | if (endpoint == invalid) 365 | #if defined(_WIN32) || defined(__CYGWIN__) 366 | throw std::system_error{WSAGetLastError(), winsock::errorCategory, "Failed to create socket"}; 367 | #else 368 | throw std::system_error{errno, std::system_category(), "Failed to create socket"}; 369 | #endif // defined(_WIN32) || defined(__CYGWIN__) 370 | 371 | #if defined(_WIN32) || defined(__CYGWIN__) 372 | ULONG mode = 1; 373 | if (ioctlsocket(endpoint, FIONBIO, &mode) == SOCKET_ERROR) 374 | { 375 | close(); 376 | throw std::system_error{WSAGetLastError(), winsock::errorCategory, "Failed to get socket flags"}; 377 | } 378 | #else 379 | const auto flags = fcntl(endpoint, F_GETFL); 380 | if (flags == -1) 381 | { 382 | close(); 383 | throw std::system_error{errno, std::system_category(), "Failed to get socket flags"}; 384 | } 385 | 386 | if (fcntl(endpoint, F_SETFL, flags | O_NONBLOCK) == -1) 387 | { 388 | close(); 389 | throw std::system_error{errno, std::system_category(), "Failed to set socket flags"}; 390 | } 391 | #endif // defined(_WIN32) || defined(__CYGWIN__) 392 | 393 | #ifdef __APPLE__ 394 | const int value = 1; 395 | if (setsockopt(endpoint, SOL_SOCKET, SO_NOSIGPIPE, &value, sizeof(value)) == -1) 396 | { 397 | close(); 398 | throw std::system_error{errno, std::system_category(), "Failed to set socket option"}; 399 | } 400 | #endif // __APPLE__ 401 | } 402 | 403 | ~Socket() 404 | { 405 | if (endpoint != invalid) close(); 406 | } 407 | 408 | Socket(Socket&& other) noexcept: 409 | endpoint{std::exchange(other.endpoint, invalid)} 410 | { 411 | } 412 | 413 | Socket& operator=(Socket&& other) noexcept 414 | { 415 | if (&other == this) return *this; 416 | if (endpoint != invalid) close(); 417 | endpoint = std::exchange(other.endpoint, invalid); 418 | return *this; 419 | } 420 | 421 | void connect(const struct sockaddr* address, const socklen_t addressSize, const std::int64_t timeout) 422 | { 423 | #if defined(_WIN32) || defined(__CYGWIN__) 424 | auto result = ::connect(endpoint, address, addressSize); 425 | while (result == -1 && WSAGetLastError() == WSAEINTR) 426 | result = ::connect(endpoint, address, addressSize); 427 | 428 | if (result == -1) 429 | { 430 | if (WSAGetLastError() == WSAEWOULDBLOCK) 431 | { 432 | select(SelectType::write, timeout); 433 | 434 | char socketErrorPointer[sizeof(int)]; 435 | socklen_t optionLength = sizeof(socketErrorPointer); 436 | if (getsockopt(endpoint, SOL_SOCKET, SO_ERROR, socketErrorPointer, &optionLength) == SOCKET_ERROR) 437 | throw std::system_error{WSAGetLastError(), winsock::errorCategory, "Failed to get socket option"}; 438 | 439 | int socketError; 440 | std::memcpy(&socketError, socketErrorPointer, sizeof(socketErrorPointer)); 441 | 442 | if (socketError != 0) 443 | throw std::system_error{socketError, winsock::errorCategory, "Failed to connect"}; 444 | } 445 | else 446 | throw std::system_error{WSAGetLastError(), winsock::errorCategory, "Failed to connect"}; 447 | } 448 | #else 449 | auto result = ::connect(endpoint, address, addressSize); 450 | while (result == -1 && errno == EINTR) 451 | result = ::connect(endpoint, address, addressSize); 452 | 453 | if (result == -1) 454 | { 455 | if (errno == EINPROGRESS) 456 | { 457 | select(SelectType::write, timeout); 458 | 459 | int socketError; 460 | socklen_t optionLength = sizeof(socketError); 461 | if (getsockopt(endpoint, SOL_SOCKET, SO_ERROR, &socketError, &optionLength) == -1) 462 | throw std::system_error{errno, std::system_category(), "Failed to get socket option"}; 463 | 464 | if (socketError != 0) 465 | throw std::system_error{socketError, std::system_category(), "Failed to connect"}; 466 | } 467 | else 468 | throw std::system_error{errno, std::system_category(), "Failed to connect"}; 469 | } 470 | #endif // defined(_WIN32) || defined(__CYGWIN__) 471 | } 472 | 473 | std::size_t send(const void* buffer, const std::size_t length, const std::int64_t timeout) 474 | { 475 | select(SelectType::write, timeout); 476 | #if defined(_WIN32) || defined(__CYGWIN__) 477 | auto result = ::send(endpoint, reinterpret_cast(buffer), 478 | static_cast(length), 0); 479 | 480 | while (result == SOCKET_ERROR && WSAGetLastError() == WSAEINTR) 481 | result = ::send(endpoint, reinterpret_cast(buffer), 482 | static_cast(length), 0); 483 | 484 | if (result == SOCKET_ERROR) 485 | throw std::system_error{WSAGetLastError(), winsock::errorCategory, "Failed to send data"}; 486 | #else 487 | auto result = ::send(endpoint, reinterpret_cast(buffer), 488 | length, noSignal); 489 | 490 | while (result == -1 && errno == EINTR) 491 | result = ::send(endpoint, reinterpret_cast(buffer), 492 | length, noSignal); 493 | 494 | if (result == -1) 495 | throw std::system_error{errno, std::system_category(), "Failed to send data"}; 496 | #endif // defined(_WIN32) || defined(__CYGWIN__) 497 | return static_cast(result); 498 | } 499 | 500 | std::size_t recv(void* buffer, const std::size_t length, const std::int64_t timeout) 501 | { 502 | select(SelectType::read, timeout); 503 | #if defined(_WIN32) || defined(__CYGWIN__) 504 | auto result = ::recv(endpoint, reinterpret_cast(buffer), 505 | static_cast(length), 0); 506 | 507 | while (result == SOCKET_ERROR && WSAGetLastError() == WSAEINTR) 508 | result = ::recv(endpoint, reinterpret_cast(buffer), 509 | static_cast(length), 0); 510 | 511 | if (result == SOCKET_ERROR) 512 | throw std::system_error{WSAGetLastError(), winsock::errorCategory, "Failed to read data"}; 513 | #else 514 | auto result = ::recv(endpoint, reinterpret_cast(buffer), 515 | length, noSignal); 516 | 517 | while (result == -1 && errno == EINTR) 518 | result = ::recv(endpoint, reinterpret_cast(buffer), 519 | length, noSignal); 520 | 521 | if (result == -1) 522 | throw std::system_error{errno, std::system_category(), "Failed to read data"}; 523 | #endif // defined(_WIN32) || defined(__CYGWIN__) 524 | return static_cast(result); 525 | } 526 | 527 | private: 528 | enum class SelectType 529 | { 530 | read, 531 | write 532 | }; 533 | 534 | void select(const SelectType type, const std::int64_t timeout) 535 | { 536 | fd_set descriptorSet; 537 | FD_ZERO(&descriptorSet); 538 | FD_SET(endpoint, &descriptorSet); 539 | 540 | #if defined(_WIN32) || defined(__CYGWIN__) 541 | TIMEVAL selectTimeout{ 542 | static_cast(timeout / 1000), 543 | static_cast((timeout % 1000) * 1000) 544 | }; 545 | auto count = ::select(0, 546 | (type == SelectType::read) ? &descriptorSet : nullptr, 547 | (type == SelectType::write) ? &descriptorSet : nullptr, 548 | nullptr, 549 | (timeout >= 0) ? &selectTimeout : nullptr); 550 | 551 | while (count == SOCKET_ERROR && WSAGetLastError() == WSAEINTR) 552 | count = ::select(0, 553 | (type == SelectType::read) ? &descriptorSet : nullptr, 554 | (type == SelectType::write) ? &descriptorSet : nullptr, 555 | nullptr, 556 | (timeout >= 0) ? &selectTimeout : nullptr); 557 | 558 | if (count == SOCKET_ERROR) 559 | throw std::system_error{WSAGetLastError(), winsock::errorCategory, "Failed to select socket"}; 560 | else if (count == 0) 561 | throw ResponseError{"Request timed out"}; 562 | #else 563 | timeval selectTimeout{ 564 | static_cast(timeout / 1000), 565 | static_cast((timeout % 1000) * 1000) 566 | }; 567 | auto count = ::select(endpoint + 1, 568 | (type == SelectType::read) ? &descriptorSet : nullptr, 569 | (type == SelectType::write) ? &descriptorSet : nullptr, 570 | nullptr, 571 | (timeout >= 0) ? &selectTimeout : nullptr); 572 | 573 | while (count == -1 && errno == EINTR) 574 | count = ::select(endpoint + 1, 575 | (type == SelectType::read) ? &descriptorSet : nullptr, 576 | (type == SelectType::write) ? &descriptorSet : nullptr, 577 | nullptr, 578 | (timeout >= 0) ? &selectTimeout : nullptr); 579 | 580 | if (count == -1) 581 | throw std::system_error{errno, std::system_category(), "Failed to select socket"}; 582 | else if (count == 0) 583 | throw ResponseError{"Request timed out"}; 584 | #endif // defined(_WIN32) || defined(__CYGWIN__) 585 | } 586 | 587 | void close() noexcept 588 | { 589 | #if defined(_WIN32) || defined(__CYGWIN__) 590 | closesocket(endpoint); 591 | #else 592 | ::close(endpoint); 593 | #endif // defined(_WIN32) || defined(__CYGWIN__) 594 | } 595 | 596 | #if defined(__unix__) && !defined(__APPLE__) && !defined(__CYGWIN__) 597 | static constexpr int noSignal = MSG_NOSIGNAL; 598 | #else 599 | static constexpr int noSignal = 0; 600 | #endif // defined(__unix__) && !defined(__APPLE__) 601 | 602 | Type endpoint = invalid; 603 | }; 604 | 605 | inline char toLower(const char c) noexcept 606 | { 607 | return (c >= 'A' && c <= 'Z') ? c - ('A' - 'a') : c; 608 | } 609 | 610 | template 611 | T toLower(const T& s) 612 | { 613 | T result = s; 614 | for (auto& c : result) c = toLower(c); 615 | return result; 616 | } 617 | 618 | // RFC 7230, 3.2.3. WhiteSpace 619 | template 620 | constexpr bool isWhiteSpaceChar(const C c) noexcept 621 | { 622 | return c == 0x20 || c == 0x09; // space or tab 623 | }; 624 | 625 | // RFC 5234, Appendix B.1. Core Rules 626 | template 627 | constexpr bool isDigitChar(const C c) noexcept 628 | { 629 | return c >= 0x30 && c <= 0x39; // 0 - 9 630 | } 631 | 632 | // RFC 5234, Appendix B.1. Core Rules 633 | template 634 | constexpr bool isAlphaChar(const C c) noexcept 635 | { 636 | return 637 | (c >= 0x61 && c <= 0x7A) || // a - z 638 | (c >= 0x41 && c <= 0x5A); // A - Z 639 | } 640 | 641 | // RFC 7230, 3.2.6. Field Value Components 642 | template 643 | constexpr bool isTokenChar(const C c) noexcept 644 | { 645 | return c == 0x21 || // ! 646 | c == 0x23 || // # 647 | c == 0x24 || // $ 648 | c == 0x25 || // % 649 | c == 0x26 || // & 650 | c == 0x27 || // ' 651 | c == 0x2A || // * 652 | c == 0x2B || // + 653 | c == 0x2D || // - 654 | c == 0x2E || // . 655 | c == 0x5E || // ^ 656 | c == 0x5F || // _ 657 | c == 0x60 || // ` 658 | c == 0x7C || // | 659 | c == 0x7E || // ~ 660 | isDigitChar(c) || 661 | isAlphaChar(c); 662 | }; 663 | 664 | // RFC 5234, Appendix B.1. Core Rules 665 | template 666 | constexpr bool isVisibleChar(const C c) noexcept 667 | { 668 | return c >= 0x21 && c <= 0x7E; 669 | } 670 | 671 | // RFC 7230, Appendix B. Collected ABNF 672 | template 673 | constexpr bool isObsoleteTextChar(const C c) noexcept 674 | { 675 | return static_cast(c) >= 0x80 && 676 | static_cast(c) <= 0xFF; 677 | } 678 | 679 | template 680 | Iterator skipWhiteSpaces(const Iterator begin, const Iterator end) 681 | { 682 | auto i = begin; 683 | for (i = begin; i != end; ++i) 684 | if (!isWhiteSpaceChar(*i)) 685 | break; 686 | 687 | return i; 688 | } 689 | 690 | // RFC 5234, Appendix B.1. Core Rules 691 | template ::value>::type* = nullptr> 692 | constexpr T digitToUint(const C c) 693 | { 694 | // DIGIT 695 | return (c >= 0x30 && c <= 0x39) ? static_cast(c - 0x30) : // 0 - 9 696 | throw ResponseError{"Invalid digit"}; 697 | } 698 | 699 | // RFC 5234, Appendix B.1. Core Rules 700 | template ::value>::type* = nullptr> 701 | constexpr T hexDigitToUint(const C c) 702 | { 703 | // HEXDIG 704 | return (c >= 0x30 && c <= 0x39) ? static_cast(c - 0x30) : // 0 - 9 705 | (c >= 0x41 && c <= 0x46) ? static_cast(c - 0x41) + T(10) : // A - Z 706 | (c >= 0x61 && c <= 0x66) ? static_cast(c - 0x61) + T(10) : // a - z, some services send lower-case hex digits 707 | throw ResponseError{"Invalid hex digit"}; 708 | } 709 | 710 | // RFC 3986, 3. Syntax Components 711 | template 712 | Uri parseUri(const Iterator begin, const Iterator end) 713 | { 714 | Uri result; 715 | 716 | // RFC 3986, 3.1. Scheme 717 | auto i = begin; 718 | if (i == end || !isAlphaChar(*begin)) 719 | throw RequestError{"Invalid scheme"}; 720 | 721 | result.scheme.push_back(*i++); 722 | 723 | for (; i != end && (isAlphaChar(*i) || isDigitChar(*i) || *i == '+' || *i == '-' || *i == '.'); ++i) 724 | result.scheme.push_back(*i); 725 | 726 | if (i == end || *i++ != ':') 727 | throw RequestError{"Invalid scheme"}; 728 | if (i == end || *i++ != '/') 729 | throw RequestError{"Invalid scheme"}; 730 | if (i == end || *i++ != '/') 731 | throw RequestError{"Invalid scheme"}; 732 | 733 | // RFC 3986, 3.2. Authority 734 | std::string authority = std::string(i, end); 735 | 736 | // RFC 3986, 3.5. Fragment 737 | const auto fragmentPosition = authority.find('#'); 738 | if (fragmentPosition != std::string::npos) 739 | { 740 | result.fragment = authority.substr(fragmentPosition + 1); 741 | authority.resize(fragmentPosition); // remove the fragment part 742 | } 743 | 744 | // RFC 3986, 3.4. Query 745 | const auto queryPosition = authority.find('?'); 746 | if (queryPosition != std::string::npos) 747 | { 748 | result.query = authority.substr(queryPosition + 1); 749 | authority.resize(queryPosition); // remove the query part 750 | } 751 | 752 | // RFC 3986, 3.3. Path 753 | const auto pathPosition = authority.find('/'); 754 | if (pathPosition != std::string::npos) 755 | { 756 | // RFC 3986, 3.3. Path 757 | result.path = authority.substr(pathPosition); 758 | authority.resize(pathPosition); 759 | } 760 | else 761 | result.path = "/"; 762 | 763 | // RFC 3986, 3.2.1. User Information 764 | std::string userinfo; 765 | const auto hostPosition = authority.find('@'); 766 | if (hostPosition != std::string::npos) 767 | { 768 | userinfo = authority.substr(0, hostPosition); 769 | 770 | const auto passwordPosition = userinfo.find(':'); 771 | if (passwordPosition != std::string::npos) 772 | { 773 | result.user = userinfo.substr(0, passwordPosition); 774 | result.password = userinfo.substr(passwordPosition + 1); 775 | } 776 | else 777 | result.user = userinfo; 778 | 779 | result.host = authority.substr(hostPosition + 1); 780 | } 781 | else 782 | result.host = authority; 783 | 784 | // RFC 3986, 3.2.2. Host 785 | const auto portPosition = result.host.find(':'); 786 | if (portPosition != std::string::npos) 787 | { 788 | // RFC 3986, 3.2.3. Port 789 | result.port = result.host.substr(portPosition + 1); 790 | result.host.resize(portPosition); 791 | } 792 | 793 | return result; 794 | } 795 | 796 | // RFC 7230, 2.6. Protocol Versioning 797 | template 798 | std::pair parseVersion(const Iterator begin, const Iterator end) 799 | { 800 | auto i = begin; 801 | 802 | if (i == end || *i++ != 'H') 803 | throw ResponseError{"Invalid HTTP version"}; 804 | if (i == end || *i++ != 'T') 805 | throw ResponseError{"Invalid HTTP version"}; 806 | if (i == end || *i++ != 'T') 807 | throw ResponseError{"Invalid HTTP version"}; 808 | if (i == end || *i++ != 'P') 809 | throw ResponseError{"Invalid HTTP version"}; 810 | if (i == end || *i++ != '/') 811 | throw ResponseError{"Invalid HTTP version"}; 812 | 813 | if (i == end) 814 | throw ResponseError{"Invalid HTTP version"}; 815 | 816 | const auto majorVersion = digitToUint(*i++); 817 | 818 | if (i == end || *i++ != '.') 819 | throw ResponseError{"Invalid HTTP version"}; 820 | 821 | if (i == end) 822 | throw ResponseError{"Invalid HTTP version"}; 823 | 824 | const auto minorVersion = digitToUint(*i++); 825 | 826 | return {i, Version{majorVersion, minorVersion}}; 827 | } 828 | 829 | // RFC 7230, 3.1.2. Status Line 830 | template 831 | std::pair parseStatusCode(const Iterator begin, const Iterator end) 832 | { 833 | std::uint16_t result = 0; 834 | 835 | auto i = begin; 836 | while (i != end && isDigitChar(*i)) 837 | result = static_cast(result * 10U) + digitToUint(*i++); 838 | 839 | if (std::distance(begin, i) != 3) 840 | throw ResponseError{"Invalid status code"}; 841 | 842 | return {i, result}; 843 | } 844 | 845 | // RFC 7230, 3.1.2. Status Line 846 | template 847 | std::pair parseReasonPhrase(const Iterator begin, const Iterator end) 848 | { 849 | std::string result; 850 | 851 | auto i = begin; 852 | for (; i != end && (isWhiteSpaceChar(*i) || isVisibleChar(*i) || isObsoleteTextChar(*i)); ++i) 853 | result.push_back(static_cast(*i)); 854 | 855 | return {i, std::move(result)}; 856 | } 857 | 858 | // RFC 7230, 3.2.6. Field Value Components 859 | template 860 | std::pair parseToken(const Iterator begin, const Iterator end) 861 | { 862 | std::string result; 863 | 864 | auto i = begin; 865 | for (; i != end && isTokenChar(*i); ++i) 866 | result.push_back(static_cast(*i)); 867 | 868 | if (result.empty()) 869 | throw ResponseError{"Invalid token"}; 870 | 871 | return {i, std::move(result)}; 872 | } 873 | 874 | // RFC 7230, 3.2. Header Fields 875 | template 876 | std::pair parseFieldValue(const Iterator begin, const Iterator end) 877 | { 878 | std::string result; 879 | 880 | auto i = begin; 881 | for (; i != end && (isWhiteSpaceChar(*i) || isVisibleChar(*i) || isObsoleteTextChar(*i)); ++i) 882 | result.push_back(static_cast(*i)); 883 | 884 | // trim white spaces 885 | result.erase(std::find_if(result.rbegin(), result.rend(), [](const char c) noexcept { 886 | return !isWhiteSpaceChar(c); 887 | }).base(), result.end()); 888 | 889 | return {i, std::move(result)}; 890 | } 891 | 892 | // RFC 7230, 3.2. Header Fields 893 | template 894 | std::pair parseFieldContent(const Iterator begin, const Iterator end) 895 | { 896 | std::string result; 897 | 898 | auto i = begin; 899 | 900 | for (;;) 901 | { 902 | const auto fieldValueResult = parseFieldValue(i, end); 903 | i = fieldValueResult.first; 904 | result += fieldValueResult.second; 905 | 906 | // Handle obsolete fold as per RFC 7230, 3.2.4. Field Parsing 907 | // Obsolete folding is known as linear white space (LWS) in RFC 2616, 2.2 Basic Rules 908 | auto obsoleteFoldIterator = i; 909 | if (obsoleteFoldIterator == end || *obsoleteFoldIterator++ != '\r') 910 | break; 911 | 912 | if (obsoleteFoldIterator == end || *obsoleteFoldIterator++ != '\n') 913 | break; 914 | 915 | if (obsoleteFoldIterator == end || !isWhiteSpaceChar(*obsoleteFoldIterator++)) 916 | break; 917 | 918 | result.push_back(' '); 919 | i = obsoleteFoldIterator; 920 | } 921 | 922 | return {i, std::move(result)}; 923 | } 924 | 925 | // RFC 7230, 3.2. Header Fields 926 | template 927 | std::pair parseHeaderField(const Iterator begin, const Iterator end) 928 | { 929 | auto tokenResult = parseToken(begin, end); 930 | auto i = tokenResult.first; 931 | auto fieldName = toLower(tokenResult.second); 932 | 933 | if (i == end || *i++ != ':') 934 | throw ResponseError{"Invalid header"}; 935 | 936 | i = skipWhiteSpaces(i, end); 937 | 938 | auto valueResult = parseFieldContent(i, end); 939 | i = valueResult.first; 940 | auto fieldValue = std::move(valueResult.second); 941 | 942 | if (i == end || *i++ != '\r') 943 | throw ResponseError{"Invalid header"}; 944 | 945 | if (i == end || *i++ != '\n') 946 | throw ResponseError{"Invalid header"}; 947 | 948 | return {i, {std::move(fieldName), std::move(fieldValue)}}; 949 | } 950 | 951 | // RFC 7230, 3.1.2. Status Line 952 | template 953 | std::pair parseStatusLine(const Iterator begin, const Iterator end) 954 | { 955 | const auto versionResult = parseVersion(begin, end); 956 | auto i = versionResult.first; 957 | 958 | if (i == end || *i++ != ' ') 959 | throw ResponseError{"Invalid status line"}; 960 | 961 | const auto statusCodeResult = parseStatusCode(i, end); 962 | i = statusCodeResult.first; 963 | 964 | if (i == end || *i++ != ' ') 965 | throw ResponseError{"Invalid status line"}; 966 | 967 | auto reasonPhraseResult = parseReasonPhrase(i, end); 968 | i = reasonPhraseResult.first; 969 | 970 | if (i == end || *i++ != '\r') 971 | throw ResponseError{"Invalid status line"}; 972 | 973 | if (i == end || *i++ != '\n') 974 | throw ResponseError{"Invalid status line"}; 975 | 976 | return {i, Status{ 977 | versionResult.second, 978 | statusCodeResult.second, 979 | std::move(reasonPhraseResult.second) 980 | }}; 981 | } 982 | 983 | // RFC 7230, 4.1. Chunked Transfer Coding 984 | template ::value>::type* = nullptr> 985 | T stringToUint(const Iterator begin, const Iterator end) 986 | { 987 | T result = 0; 988 | for (auto i = begin; i != end; ++i) 989 | result = T(10U) * result + digitToUint(*i); 990 | 991 | return result; 992 | } 993 | 994 | template ::value>::type* = nullptr> 995 | T hexStringToUint(const Iterator begin, const Iterator end) 996 | { 997 | T result = 0; 998 | for (auto i = begin; i != end; ++i) 999 | result = T(16U) * result + hexDigitToUint(*i); 1000 | 1001 | return result; 1002 | } 1003 | 1004 | // RFC 7230, 3.1.1. Request Line 1005 | inline std::string encodeRequestLine(const std::string& method, const std::string& target) 1006 | { 1007 | return method + " " + target + " HTTP/1.1\r\n"; 1008 | } 1009 | 1010 | // RFC 7230, 3.2. Header Fields 1011 | inline std::string encodeHeaderFields(const HeaderFields& headerFields) 1012 | { 1013 | std::string result; 1014 | for (const auto& headerField : headerFields) 1015 | { 1016 | if (headerField.first.empty()) 1017 | throw RequestError{"Invalid header field name"}; 1018 | 1019 | for (const auto c : headerField.first) 1020 | if (!isTokenChar(c)) 1021 | throw RequestError{"Invalid header field name"}; 1022 | 1023 | for (const auto c : headerField.second) 1024 | if (!isWhiteSpaceChar(c) && !isVisibleChar(c) && !isObsoleteTextChar(c)) 1025 | throw RequestError{"Invalid header field value"}; 1026 | 1027 | result += headerField.first + ": " + headerField.second + "\r\n"; 1028 | } 1029 | 1030 | return result; 1031 | } 1032 | 1033 | // RFC 4648, 4. Base 64 Encoding 1034 | template 1035 | std::string encodeBase64(const Iterator begin, const Iterator end) 1036 | { 1037 | constexpr std::array chars{ 1038 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 1039 | 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 1040 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 1041 | 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 1042 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' 1043 | }; 1044 | 1045 | std::string result; 1046 | std::size_t c = 0; 1047 | std::array charArray; 1048 | 1049 | for (auto i = begin; i != end; ++i) 1050 | { 1051 | charArray[c++] = static_cast(*i); 1052 | if (c == 3) 1053 | { 1054 | result += chars[static_cast((charArray[0] & 0xFC) >> 2)]; 1055 | result += chars[static_cast(((charArray[0] & 0x03) << 4) + ((charArray[1] & 0xF0) >> 4))]; 1056 | result += chars[static_cast(((charArray[1] & 0x0F) << 2) + ((charArray[2] & 0xC0) >> 6))]; 1057 | result += chars[static_cast(charArray[2] & 0x3f)]; 1058 | c = 0; 1059 | } 1060 | } 1061 | 1062 | if (c) 1063 | { 1064 | result += chars[static_cast((charArray[0] & 0xFC) >> 2)]; 1065 | 1066 | if (c == 1) 1067 | result += chars[static_cast((charArray[0] & 0x03) << 4)]; 1068 | else // c == 2 1069 | { 1070 | result += chars[static_cast(((charArray[0] & 0x03) << 4) + ((charArray[1] & 0xF0) >> 4))]; 1071 | result += chars[static_cast((charArray[1] & 0x0F) << 2)]; 1072 | } 1073 | 1074 | while (++c < 4) result += '='; // padding 1075 | } 1076 | 1077 | return result; 1078 | } 1079 | 1080 | inline std::vector encodeHtml(const Uri& uri, 1081 | const std::string& method, 1082 | const std::vector& body, 1083 | HeaderFields headerFields) 1084 | { 1085 | if (uri.scheme != "http") 1086 | throw RequestError{"Only HTTP scheme is supported"}; 1087 | 1088 | // RFC 7230, 5.3. Request Target 1089 | const std::string requestTarget = uri.path + (uri.query.empty() ? "" : '?' + uri.query); 1090 | 1091 | // RFC 7230, 5.4. Host 1092 | headerFields.push_back({"Host", uri.host}); 1093 | 1094 | // RFC 7230, 3.3.2. Content-Length 1095 | headerFields.push_back({"Content-Length", std::to_string(body.size())}); 1096 | 1097 | // RFC 7617, 2. The 'Basic' Authentication Scheme 1098 | if (!uri.user.empty() || !uri.password.empty()) 1099 | { 1100 | std::string userinfo = uri.user + ':' + uri.password; 1101 | headerFields.push_back({"Authorization", "Basic " + encodeBase64(userinfo.begin(), userinfo.end())}); 1102 | } 1103 | 1104 | const auto headerData = encodeRequestLine(method, requestTarget) + 1105 | encodeHeaderFields(headerFields) + 1106 | "\r\n"; 1107 | 1108 | std::vector result(headerData.begin(), headerData.end()); 1109 | result.insert(result.end(), body.begin(), body.end()); 1110 | 1111 | return result; 1112 | } 1113 | } 1114 | 1115 | class Request final 1116 | { 1117 | public: 1118 | explicit Request(const std::string& uriString, 1119 | const InternetProtocol protocol = InternetProtocol::v4): 1120 | internetProtocol{protocol}, 1121 | uri{parseUri(uriString.begin(), uriString.end())} 1122 | { 1123 | } 1124 | 1125 | Response send(const std::string& method = "GET", 1126 | const std::string& body = "", 1127 | const HeaderFields& headerFields = {}, 1128 | const std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) 1129 | { 1130 | return send(method, 1131 | std::vector(body.begin(), body.end()), 1132 | headerFields, 1133 | timeout); 1134 | } 1135 | 1136 | Response send(const std::string& method, 1137 | const std::vector& body, 1138 | const HeaderFields& headerFields = {}, 1139 | const std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) 1140 | { 1141 | const auto stopTime = std::chrono::steady_clock::now() + timeout; 1142 | 1143 | if (uri.scheme != "http") 1144 | throw RequestError{"Only HTTP scheme is supported"}; 1145 | 1146 | addrinfo hints = {}; 1147 | hints.ai_family = getAddressFamily(internetProtocol); 1148 | hints.ai_socktype = SOCK_STREAM; 1149 | 1150 | const char* port = uri.port.empty() ? "80" : uri.port.c_str(); 1151 | 1152 | addrinfo* info; 1153 | if (getaddrinfo(uri.host.c_str(), port, &hints, &info) != 0) 1154 | #if defined(_WIN32) || defined(__CYGWIN__) 1155 | throw std::system_error{WSAGetLastError(), winsock::errorCategory, "Failed to get address info of " + uri.host}; 1156 | #else 1157 | throw std::system_error{errno, std::system_category(), "Failed to get address info of " + uri.host}; 1158 | #endif // defined(_WIN32) || defined(__CYGWIN__) 1159 | 1160 | const std::unique_ptr addressInfo{info, freeaddrinfo}; 1161 | 1162 | const auto requestData = encodeHtml(uri, method, body, headerFields); 1163 | 1164 | Socket socket{internetProtocol}; 1165 | 1166 | const auto getRemainingMilliseconds = [](const std::chrono::steady_clock::time_point time) noexcept -> std::int64_t { 1167 | const auto now = std::chrono::steady_clock::now(); 1168 | const auto remainingTime = std::chrono::duration_cast(time - now); 1169 | return (remainingTime.count() > 0) ? remainingTime.count() : 0; 1170 | }; 1171 | 1172 | // take the first address from the list 1173 | socket.connect(addressInfo->ai_addr, static_cast(addressInfo->ai_addrlen), 1174 | (timeout.count() >= 0) ? getRemainingMilliseconds(stopTime) : -1); 1175 | 1176 | auto remaining = requestData.size(); 1177 | auto sendData = requestData.data(); 1178 | 1179 | // send the request 1180 | while (remaining > 0) 1181 | { 1182 | const auto size = socket.send(sendData, remaining, 1183 | (timeout.count() >= 0) ? getRemainingMilliseconds(stopTime) : -1); 1184 | remaining -= size; 1185 | sendData += size; 1186 | } 1187 | 1188 | std::array tempBuffer; 1189 | constexpr std::array crlf = {'\r', '\n'}; 1190 | constexpr std::array headerEnd = {'\r', '\n', '\r', '\n'}; 1191 | Response response; 1192 | std::vector responseData; 1193 | bool parsingBody = false; 1194 | bool contentLengthReceived = false; 1195 | std::size_t contentLength = 0U; 1196 | bool chunkedResponse = false; 1197 | std::size_t expectedChunkSize = 0U; 1198 | bool removeCrlfAfterChunk = false; 1199 | 1200 | // read the response 1201 | for (;;) 1202 | { 1203 | const auto size = socket.recv(tempBuffer.data(), tempBuffer.size(), 1204 | (timeout.count() >= 0) ? getRemainingMilliseconds(stopTime) : -1); 1205 | if (size == 0) // disconnected 1206 | return response; 1207 | 1208 | responseData.insert(responseData.end(), tempBuffer.begin(), tempBuffer.begin() + size); 1209 | 1210 | if (!parsingBody) 1211 | { 1212 | // RFC 7230, 3. Message Format 1213 | // Empty line indicates the end of the header section (RFC 7230, 2.1. Client/Server Messaging) 1214 | const auto endIterator = std::search(responseData.cbegin(), responseData.cend(), 1215 | headerEnd.cbegin(), headerEnd.cend()); 1216 | if (endIterator == responseData.cend()) break; // two consecutive CRLFs not found 1217 | 1218 | const auto headerBeginIterator = responseData.cbegin(); 1219 | const auto headerEndIterator = endIterator + 2; 1220 | 1221 | auto statusLineResult = parseStatusLine(headerBeginIterator, headerEndIterator); 1222 | auto i = statusLineResult.first; 1223 | 1224 | response.status = std::move(statusLineResult.second); 1225 | 1226 | for (;;) 1227 | { 1228 | auto headerFieldResult = parseHeaderField(i, headerEndIterator); 1229 | i = headerFieldResult.first; 1230 | 1231 | auto fieldName = std::move(headerFieldResult.second.first); 1232 | auto fieldValue = std::move(headerFieldResult.second.second); 1233 | 1234 | if (fieldName == "transfer-encoding") 1235 | { 1236 | // RFC 7230, 3.3.1. Transfer-Encoding 1237 | if (fieldValue == "chunked") 1238 | chunkedResponse = true; 1239 | else 1240 | throw ResponseError{"Unsupported transfer encoding: " + fieldValue}; 1241 | } 1242 | else if (fieldName == "content-length") 1243 | { 1244 | // RFC 7230, 3.3.2. Content-Length 1245 | contentLength = stringToUint(fieldValue.cbegin(), fieldValue.cend()); 1246 | contentLengthReceived = true; 1247 | response.body.reserve(contentLength); 1248 | } 1249 | 1250 | response.headerFields.push_back({std::move(fieldName), std::move(fieldValue)}); 1251 | 1252 | if (i == headerEndIterator) 1253 | break; 1254 | } 1255 | 1256 | responseData.erase(responseData.cbegin(), headerEndIterator + 2); 1257 | parsingBody = true; 1258 | } 1259 | 1260 | if (parsingBody) 1261 | { 1262 | // Content-Length must be ignored if Transfer-Encoding is received (RFC 7230, 3.2. Content-Length) 1263 | if (chunkedResponse) 1264 | { 1265 | // RFC 7230, 4.1. Chunked Transfer Coding 1266 | for (;;) 1267 | { 1268 | if (expectedChunkSize > 0) 1269 | { 1270 | const auto toWrite = (std::min)(expectedChunkSize, responseData.size()); 1271 | response.body.insert(response.body.end(), responseData.begin(), 1272 | responseData.begin() + static_cast(toWrite)); 1273 | responseData.erase(responseData.begin(), 1274 | responseData.begin() + static_cast(toWrite)); 1275 | expectedChunkSize -= toWrite; 1276 | 1277 | if (expectedChunkSize == 0) removeCrlfAfterChunk = true; 1278 | if (responseData.empty()) break; 1279 | } 1280 | else 1281 | { 1282 | if (removeCrlfAfterChunk) 1283 | { 1284 | if (responseData.size() < 2) break; 1285 | 1286 | if (!std::equal(crlf.begin(), crlf.end(), responseData.begin())) 1287 | throw ResponseError{"Invalid chunk"}; 1288 | 1289 | removeCrlfAfterChunk = false; 1290 | responseData.erase(responseData.begin(), responseData.begin() + 2); 1291 | } 1292 | 1293 | const auto i = std::search(responseData.begin(), responseData.end(), 1294 | crlf.begin(), crlf.end()); 1295 | 1296 | if (i == responseData.end()) break; 1297 | 1298 | expectedChunkSize = detail::hexStringToUint(responseData.begin(), i); 1299 | responseData.erase(responseData.begin(), i + 2); 1300 | 1301 | if (expectedChunkSize == 0) 1302 | return response; 1303 | } 1304 | } 1305 | } 1306 | else 1307 | { 1308 | response.body.insert(response.body.end(), responseData.begin(), responseData.end()); 1309 | responseData.clear(); 1310 | 1311 | // got the whole content 1312 | if (contentLengthReceived && response.body.size() >= contentLength) 1313 | return response; 1314 | } 1315 | } 1316 | } 1317 | 1318 | return response; 1319 | } 1320 | 1321 | private: 1322 | #if defined(_WIN32) || defined(__CYGWIN__) 1323 | winsock::Api winSock; 1324 | #endif // defined(_WIN32) || defined(__CYGWIN__) 1325 | InternetProtocol internetProtocol; 1326 | Uri uri; 1327 | }; 1328 | } 1329 | 1330 | #endif // HTTPREQUEST_HPP 1331 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=elnormous_HTTPRequest 2 | sonar.organization=elnormous-github 3 | 4 | # This is the name and version displayed in the SonarCloud UI. 5 | #sonar.projectName=HTTPRequest 6 | #sonar.projectVersion=1.0 7 | 8 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 9 | sonar.sources=include,tests 10 | 11 | # Encoding of the source code. Default is default system encoding 12 | #sonar.sourceEncoding=UTF-8 13 | sonar.cfamily.build-wrapper-output=bw-output 14 | sonar.cfamily.gcov.reportsPath=tests -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(HTTPRequest_tests 2 | main.cpp 3 | encoding.cpp 4 | parsing.cpp 5 | ) 6 | 7 | target_link_libraries(HTTPRequest_tests 8 | PRIVATE 9 | HTTPRequest 10 | ) 11 | 12 | target_include_directories(HTTPRequest_tests 13 | PRIVATE 14 | ${PROJECT_SOURCE_DIR}/external/Catch2/single_include 15 | ) 16 | 17 | # Enable use of 'make test' 18 | add_test(test HTTPRequest_tests) 19 | add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} 20 | DEPENDS HTTPRequest_tests) 21 | 22 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | DEBUG=0 2 | CXXFLAGS=-std=c++17 -Wall -Wextra -Wshadow -I../external/Catch2/single_include -I../include 3 | SOURCES=encoding.cpp main.cpp parsing.cpp 4 | BASE_NAMES=$(basename $(SOURCES)) 5 | OBJECTS=$(BASE_NAMES:=.o) 6 | DEPENDENCIES=$(OBJECTS:.o=.d) 7 | EXECUTABLE=tests 8 | 9 | .PHONY: all 10 | all: $(EXECUTABLE) 11 | ifeq ($(DEBUG),1) 12 | all: CXXFLAGS+=-DDEBUG -g 13 | else 14 | all: CXXFLAGS+=-O3 15 | all: LDFLAGS+=-O3 16 | endif 17 | 18 | $(EXECUTABLE): $(OBJECTS) 19 | $(CXX) $(OBJECTS) $(LDFLAGS) -o $@ -fprofile-arcs -ftest-coverage 20 | 21 | -include $(DEPENDENCIES) 22 | 23 | %.o: %.cpp 24 | $(CXX) -c $(CXXFLAGS) -MMD -MP $< -o $@ -fprofile-arcs -ftest-coverage 25 | 26 | .PHONY: clean 27 | clean: 28 | $(RM) $(EXECUTABLE) $(OBJECTS) $(DEPENDENCIES) $(EXECUTABLE).exe *.gcda *.gcno -------------------------------------------------------------------------------- /tests/encoding.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "catch2/catch.hpp" 3 | #include "HTTPRequest.hpp" 4 | 5 | TEST_CASE("Encode status line", "[serialization]") 6 | { 7 | const auto result = http::encodeRequestLine("GET", "/"); 8 | REQUIRE(result == "GET / HTTP/1.1\r\n"); 9 | } 10 | 11 | TEST_CASE("Encode header", "[serialization]") 12 | { 13 | const auto result = http::encodeHeaderFields({ 14 | {"a", "b"} 15 | }); 16 | REQUIRE(result == "a: b\r\n"); 17 | } 18 | 19 | TEST_CASE("Encode header without value", "[serialization]") 20 | { 21 | const auto result = http::encodeHeaderFields({ 22 | {"a", ""} 23 | }); 24 | REQUIRE(result == "a: \r\n"); 25 | } 26 | 27 | TEST_CASE("Encode headers", "[serialization]") 28 | { 29 | const auto result = http::encodeHeaderFields({ 30 | {"a", "b"}, 31 | {"c", "d"} 32 | }); 33 | REQUIRE(result == "a: b\r\nc: d\r\n"); 34 | } 35 | 36 | TEST_CASE("Encode header with an empty name", "[serialization]") 37 | { 38 | REQUIRE_THROWS_AS(http::encodeHeaderFields({ 39 | {"", "b"} 40 | }), http::RequestError); 41 | } 42 | 43 | TEST_CASE("Encode header with a new-line in name", "[serialization]") 44 | { 45 | REQUIRE_THROWS_AS(http::encodeHeaderFields({ 46 | {"a\n", ""} 47 | }), http::RequestError); 48 | } 49 | 50 | TEST_CASE("Encode header with a new-line in value", "[serialization]") 51 | { 52 | REQUIRE_THROWS_AS(http::encodeHeaderFields({ 53 | {"a", "\n"} 54 | }), http::RequestError); 55 | } 56 | 57 | TEST_CASE("Encode Base64", "[serialization]") 58 | { 59 | const std::string str = "test:test"; 60 | const auto result = http::encodeBase64(str.begin(), str.end()); 61 | REQUIRE(result == "dGVzdDp0ZXN0"); 62 | } 63 | 64 | TEST_CASE("Encode HTML", "[serialization]") 65 | { 66 | http::Uri uri; 67 | uri.scheme = "http"; 68 | uri.path = "/"; 69 | uri.host = "test.com"; 70 | 71 | const auto result = http::detail::encodeHtml(uri, "GET", {}, {}); 72 | const std::string check = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Length: 0\r\n\r\n"; 73 | 74 | REQUIRE(check.size() == result.size()); 75 | 76 | for (std::size_t i = 0; i < check.size(); ++i) 77 | REQUIRE(static_cast(check[i]) == result[i]); 78 | } 79 | 80 | TEST_CASE("Encode HTML with data", "[serialization]") 81 | { 82 | http::Uri uri; 83 | uri.scheme = "http"; 84 | uri.path = "/"; 85 | uri.host = "test.com"; 86 | const std::vector body = {'1'}; 87 | 88 | const auto result = http::detail::encodeHtml(uri, "GET", body, {}); 89 | const std::string check = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Length: 1\r\n\r\n1"; 90 | 91 | REQUIRE(check.size() == result.size()); 92 | 93 | for (std::size_t i = 0; i < check.size(); ++i) 94 | REQUIRE(static_cast(check[i]) == result[i]); 95 | } 96 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch2/catch.hpp" 3 | -------------------------------------------------------------------------------- /tests/parsing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "catch2/catch.hpp" 3 | #include "HTTPRequest.hpp" 4 | 5 | TEST_CASE("To lower", "[parsing]") 6 | { 7 | REQUIRE(http::toLower('x') == 'x'); 8 | REQUIRE(http::toLower('Y') == 'y'); 9 | 10 | std::string empty = ""; 11 | REQUIRE(http::toLower(empty) == ""); 12 | 13 | std::string lower = "abc"; 14 | REQUIRE(http::toLower(lower) == "abc"); 15 | 16 | std::string upper = "DEF"; 17 | REQUIRE(http::toLower(upper) == "def"); 18 | 19 | std::string mixed = "GhI"; 20 | REQUIRE(http::toLower(mixed) == "ghi"); 21 | } 22 | 23 | TEST_CASE("White space", "[parsing]") 24 | { 25 | for (int c = 0; c < 256; ++c) 26 | REQUIRE(http::isWhiteSpaceChar(static_cast(c)) == (c == ' ' || c == '\t')); 27 | } 28 | 29 | TEST_CASE("Digit", "[parsing]") 30 | { 31 | for (int c = 0; c < 256; ++c) 32 | REQUIRE(http::isDigitChar(static_cast(c)) == (c >= '0' && c <= '9')); 33 | } 34 | 35 | TEST_CASE("Alpha", "[parsing]") 36 | { 37 | for (int c = 0; c < 256; ++c) 38 | REQUIRE(http::isAlphaChar(static_cast(c)) == ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))); 39 | } 40 | 41 | TEST_CASE("Token char", "[parsing]") 42 | { 43 | for (int c = 0; c < 256; ++c) 44 | REQUIRE(http::isTokenChar(static_cast(c)) == 45 | (c >= 0x21 && c <= 0x7E && 46 | c != '"' && c != '(' && c != ')' && c != ',' && c != '/' && 47 | c != ':' && c != ';' && c != '<' && c != '=' && c != '>' && 48 | c != '?' && c != '@' && c != '[' && c != '\\' && c != ']' && 49 | c != '{' && c != '}')); 50 | } 51 | 52 | TEST_CASE("Visible char", "[parsing]") 53 | { 54 | for (int c = 0; c < 256; ++c) 55 | REQUIRE(http::isVisibleChar(static_cast(c)) == (c >= 0x21 && c <= 0x7E)); 56 | } 57 | 58 | TEST_CASE("OBS text char", "[parsing]") 59 | { 60 | for (int c = 0; c < 256; ++c) 61 | REQUIRE(http::isObsoleteTextChar(static_cast(c)) == (c >= 0x80 && c <= 0xFF)); 62 | } 63 | 64 | TEST_CASE("Skip empty whites paces", "[parsing]") 65 | { 66 | const std::string str = ""; 67 | const auto i = http::skipWhiteSpaces(str.begin(), str.end()); 68 | REQUIRE(i == str.begin()); 69 | REQUIRE(i == str.end()); 70 | } 71 | 72 | TEST_CASE("Skip one white space", "[parsing]") 73 | { 74 | const std::string str = " "; 75 | const auto i = http::skipWhiteSpaces(str.begin(), str.end()); 76 | REQUIRE(i == str.end()); 77 | } 78 | 79 | TEST_CASE("Skip one white space at the beggining", "[parsing]") 80 | { 81 | const std::string str = " a"; 82 | const auto i = http::skipWhiteSpaces(str.begin(), str.end()); 83 | REQUIRE(i == str.begin() + 1); 84 | } 85 | 86 | TEST_CASE("Don't skip white spaces", "[parsing]") 87 | { 88 | const std::string str = "a "; 89 | const auto i = http::skipWhiteSpaces(str.begin(), str.end()); 90 | REQUIRE(i == str.begin()); 91 | } 92 | 93 | TEST_CASE("Parse token", "[parsing]") 94 | { 95 | const std::string str = "token"; 96 | const auto result = http::parseToken(str.begin(), str.end()); 97 | REQUIRE(result.first == str.end()); 98 | REQUIRE(result.second == "token"); 99 | } 100 | 101 | TEST_CASE("Parse HTTP version", "[parsing]") 102 | { 103 | const std::string str = "HTTP/1.1"; 104 | const auto result = http::parseVersion(str.begin(), str.end()); 105 | REQUIRE(result.first == str.end()); 106 | REQUIRE(result.second.major == 1U); 107 | REQUIRE(result.second.minor == 1U); 108 | } 109 | 110 | TEST_CASE("Invalid HTTP in version", "[parsing]") 111 | { 112 | const std::string str = "TTP/1.1"; 113 | REQUIRE_THROWS_AS(http::parseVersion(str.begin(), str.end()), http::ResponseError); 114 | } 115 | 116 | TEST_CASE("No slash in HTTP version", "[parsing]") 117 | { 118 | const std::string str = "HTTP1.1"; 119 | REQUIRE_THROWS_AS(http::parseVersion(str.begin(), str.end()), http::ResponseError); 120 | } 121 | 122 | TEST_CASE("No minor version in HTTP version", "[parsing]") 123 | { 124 | const std::string str = "HTTP/1."; 125 | REQUIRE_THROWS_AS(http::parseVersion(str.begin(), str.end()), http::ResponseError); 126 | } 127 | 128 | TEST_CASE("Parse status code", "[parsing]") 129 | { 130 | const std::string str = "333"; 131 | const auto result = http::parseStatusCode(str.begin(), str.end()); 132 | REQUIRE(result.first == str.end()); 133 | REQUIRE(result.second == 333); 134 | } 135 | 136 | TEST_CASE("Too short status code", "[parsing]") 137 | { 138 | const std::string str = "33"; 139 | REQUIRE_THROWS_AS(http::parseStatusCode(str.begin(), str.end()), http::ResponseError); 140 | } 141 | 142 | TEST_CASE("Too long status code", "[parsing]") 143 | { 144 | const std::string str = "3333"; 145 | REQUIRE_THROWS_AS(http::parseStatusCode(str.begin(), str.end()), http::ResponseError); 146 | } 147 | 148 | TEST_CASE("Invalid status code", "[parsing]") 149 | { 150 | const std::string str = "33a"; 151 | REQUIRE_THROWS_AS(http::parseStatusCode(str.begin(), str.end()), http::ResponseError); 152 | } 153 | 154 | TEST_CASE("Parse reason phrase", "[parsing]") 155 | { 156 | const std::string str = "reason"; 157 | const auto result = http::parseReasonPhrase(str.begin(), str.end()); 158 | REQUIRE(result.first == str.end()); 159 | REQUIRE(result.second == "reason"); 160 | } 161 | 162 | TEST_CASE("Parse reason phrase with space", "[parsing]") 163 | { 164 | const std::string str = "reason s"; 165 | const auto result = http::parseReasonPhrase(str.begin(), str.end()); 166 | REQUIRE(result.first == str.end()); 167 | REQUIRE(result.second == "reason s"); 168 | } 169 | 170 | TEST_CASE("Parse status", "[parsing]") 171 | { 172 | const std::string str = "HTTP/1.1 123 test\r\n"; 173 | const auto result = http::parseStatusLine(str.begin(), str.end()); 174 | REQUIRE(result.first == str.end()); 175 | REQUIRE(result.second.version.major == 1); 176 | REQUIRE(result.second.version.minor == 1); 177 | REQUIRE(result.second.code == 123); 178 | REQUIRE(result.second.reason == "test"); 179 | } 180 | 181 | TEST_CASE("Parse field value", "[parsing]") 182 | { 183 | const std::string str = "value"; 184 | const auto result = http::parseFieldValue(str.begin(), str.end()); 185 | REQUIRE(result.first == str.end()); 186 | REQUIRE(result.second == "value"); 187 | } 188 | 189 | TEST_CASE("Parse field value with a space", "[parsing]") 190 | { 191 | const std::string str = "value s"; 192 | const auto result = http::parseFieldValue(str.begin(), str.end()); 193 | REQUIRE(result.first == str.end()); 194 | REQUIRE(result.second == "value s"); 195 | } 196 | 197 | TEST_CASE("Parse field value with trailing white spaces", "[parsing]") 198 | { 199 | const std::string str = "value \t"; 200 | const auto result = http::parseFieldValue(str.begin(), str.end()); 201 | REQUIRE(result.first == str.end()); 202 | REQUIRE(result.second == "value"); 203 | } 204 | 205 | TEST_CASE("Parse field content", "[parsing]") 206 | { 207 | const std::string str = "content"; 208 | const auto result = http::parseFieldContent(str.begin(), str.end()); 209 | REQUIRE(result.first == str.end()); 210 | REQUIRE(result.second == "content"); 211 | } 212 | 213 | TEST_CASE("Parse field content with obsolete folding", "[parsing]") 214 | { 215 | const std::string str = "content\r\n t"; 216 | const auto result = http::parseFieldContent(str.begin(), str.end()); 217 | REQUIRE(result.first == str.end()); 218 | REQUIRE(result.second == "content t"); 219 | } 220 | 221 | TEST_CASE("Parse field content with obsolete folding and white space", "[parsing]") 222 | { 223 | const std::string str = "content\r\n t"; 224 | const auto result = http::parseFieldContent(str.begin(), str.end()); 225 | REQUIRE(result.first == str.end()); 226 | REQUIRE(result.second == "content t"); 227 | } 228 | 229 | TEST_CASE("Parse field content with obsolete folding with empty first line", "[parsing]") 230 | { 231 | const std::string str = "\r\n t"; 232 | const auto result = http::parseFieldContent(str.begin(), str.end()); 233 | REQUIRE(result.first == str.end()); 234 | REQUIRE(result.second == " t"); 235 | } 236 | 237 | TEST_CASE("Parse header field", "[parsing]") 238 | { 239 | const std::string str = "field:value\r\n"; 240 | const auto result = http::parseHeaderField(str.begin(), str.end()); 241 | REQUIRE(result.first == str.end()); 242 | REQUIRE(result.second.first == "field"); 243 | REQUIRE(result.second.second == "value"); 244 | } 245 | 246 | TEST_CASE("Parse header field upper case", "[parsing]") 247 | { 248 | const std::string str = "Field:Value\r\n"; 249 | const auto result = http::parseHeaderField(str.begin(), str.end()); 250 | REQUIRE(result.first == str.end()); 251 | REQUIRE(result.second.first == "field"); 252 | REQUIRE(result.second.second == "Value"); 253 | } 254 | 255 | TEST_CASE("Parse header field with spaces", "[parsing]") 256 | { 257 | const std::string str = "field:value s\r\n"; 258 | const auto result = http::parseHeaderField(str.begin(), str.end()); 259 | REQUIRE(result.first == str.end()); 260 | REQUIRE(result.second.first == "field"); 261 | REQUIRE(result.second.second == "value s"); 262 | } 263 | 264 | TEST_CASE("Parse header field with spaces after colon", "[parsing]") 265 | { 266 | const std::string str = "field: \tvalue\r\n"; 267 | const auto result = http::parseHeaderField(str.begin(), str.end()); 268 | REQUIRE(result.first == str.end()); 269 | REQUIRE(result.second.first == "field"); 270 | REQUIRE(result.second.second == "value"); 271 | } 272 | 273 | TEST_CASE("Parse header field with no value", "[parsing]") 274 | { 275 | const std::string str = "field:\r\n"; 276 | auto result = http::parseHeaderField(str.begin(), str.end()); 277 | REQUIRE(result.first == str.end()); 278 | REQUIRE(result.second.first == "field"); 279 | REQUIRE(result.second.second == ""); 280 | } 281 | 282 | TEST_CASE("Parse header field with trailing white space", "[parsing]") 283 | { 284 | const std::string str = "field:value \r\n"; 285 | auto result = http::parseHeaderField(str.begin(), str.end()); 286 | REQUIRE(result.first == str.end()); 287 | REQUIRE(result.second.first == "field"); 288 | REQUIRE(result.second.second == "value"); 289 | } 290 | 291 | TEST_CASE("Parse header field with no colon", "[parsing]") 292 | { 293 | const std::string str = "field\r\n"; 294 | REQUIRE_THROWS_AS(http::parseHeaderField(str.begin(), str.end()), http::ResponseError); 295 | } 296 | 297 | TEST_CASE("Parse header with missing line feed", "[parsing]") 298 | { 299 | const std::string str = "a:b\rc:d\r\n"; 300 | REQUIRE_THROWS_AS(http::parseHeaderField(str.begin(), str.end()), http::ResponseError); 301 | } 302 | 303 | TEST_CASE("Parse header with missing carriage return", "[parsing]") 304 | { 305 | const std::string str = "a:b\nc:d\r\n"; 306 | REQUIRE_THROWS_AS(http::parseHeaderField(str.begin(), str.end()), http::ResponseError); 307 | } 308 | 309 | TEST_CASE("Parse header field without CRLF", "[parsing]") 310 | { 311 | const std::string str = "field:value"; 312 | REQUIRE_THROWS_AS(http::parseHeaderField(str.begin(), str.end()), http::ResponseError); 313 | } 314 | 315 | TEST_CASE("Parse header field with obsolete fold", "[parsing]") 316 | { 317 | const std::string str = "field:value1\r\n value2\r\n"; 318 | const auto result = http::parseHeaderField(str.begin(), str.end()); 319 | REQUIRE(result.first == str.end()); 320 | REQUIRE(result.second.first == "field"); 321 | REQUIRE(result.second.second == "value1 value2"); 322 | } 323 | 324 | TEST_CASE("Digit to unsigned int", "[parsing]") 325 | { 326 | const char c = '1'; 327 | REQUIRE(http::digitToUint(c) == 1U); 328 | } 329 | 330 | TEST_CASE("Invalid digit", "[parsing]") 331 | { 332 | const char c = 'a'; 333 | REQUIRE_THROWS_AS(http::digitToUint(c), http::ResponseError); 334 | } 335 | 336 | TEST_CASE("Digits to unsigned int", "[parsing]") 337 | { 338 | const std::string str = "11"; 339 | REQUIRE(http::stringToUint(str.begin(), str.end()) == 11U); 340 | } 341 | 342 | TEST_CASE("Invalid digit string", "[parsing]") 343 | { 344 | const std::string str = "1x"; 345 | REQUIRE_THROWS_AS(http::stringToUint(str.begin(), str.end()), http::ResponseError); 346 | } 347 | 348 | TEST_CASE("Hex digit and letter to unsigned int", "[parsing]") 349 | { 350 | const std::string str = "1A"; 351 | REQUIRE(http::hexStringToUint(str.begin(), str.end()) == 26U); 352 | } 353 | 354 | TEST_CASE("Hex digit to unsigned int", "[parsing]") 355 | { 356 | const char c = '1'; 357 | REQUIRE(http::hexDigitToUint(c) == 1U); 358 | } 359 | 360 | TEST_CASE("Hex lowercase letter to unsigned int", "[parsing]") 361 | { 362 | const char c = 'a'; 363 | REQUIRE(http::hexDigitToUint(c) == 10U); 364 | } 365 | 366 | TEST_CASE("Hex uppercase letter to unsigned int", "[parsing]") 367 | { 368 | const char c = 'A'; 369 | REQUIRE(http::hexDigitToUint(c) == 10U); 370 | } 371 | 372 | TEST_CASE("Invalid hex", "[parsing]") 373 | { 374 | const char c = 'x'; 375 | REQUIRE_THROWS_AS(http::hexDigitToUint(c), http::ResponseError); 376 | } 377 | 378 | TEST_CASE("Hex digits with a letter last to unsigned int", "[parsing]") 379 | { 380 | const std::string str = "1A"; 381 | REQUIRE(http::hexStringToUint(str.begin(), str.end()) == 26U); 382 | } 383 | 384 | TEST_CASE("Hex digits with a letter first to unsigned int", "[parsing]") 385 | { 386 | const std::string str = "A1"; 387 | REQUIRE(http::hexStringToUint(str.begin(), str.end()) == 161U); 388 | } 389 | 390 | TEST_CASE("Invalid hex string", "[parsing]") 391 | { 392 | const std::string str = "ax"; 393 | REQUIRE_THROWS_AS(http::hexStringToUint(str.begin(), str.end()), http::ResponseError); 394 | } 395 | 396 | TEST_CASE("Parse URL", "[parsing]") 397 | { 398 | const std::string str = "tt://www.test.com:80/path"; 399 | const http::Uri uri = http::parseUri(str.begin(), str.end()); 400 | REQUIRE(uri.scheme == "tt"); 401 | REQUIRE(uri.user == ""); 402 | REQUIRE(uri.password == ""); 403 | REQUIRE(uri.host == "www.test.com"); 404 | REQUIRE(uri.port == "80"); 405 | REQUIRE(uri.path == "/path"); 406 | REQUIRE(uri.query == ""); 407 | REQUIRE(uri.fragment == ""); 408 | } 409 | 410 | TEST_CASE("Parse URL with non-alpha non-digit characters in scheme", "[parsing]") 411 | { 412 | const std::string str = "t.t+-://foo"; 413 | const http::Uri uri = http::parseUri(str.begin(), str.end()); 414 | REQUIRE(uri.scheme == "t.t+-"); 415 | REQUIRE(uri.host == "foo"); 416 | } 417 | 418 | TEST_CASE("Parse URL with invalid character in scheme", "[parsing]") 419 | { 420 | const std::string str = "tt!://foo"; 421 | REQUIRE_THROWS_AS(http::parseUri(str.begin(), str.end()), http::RequestError); 422 | } 423 | 424 | TEST_CASE("Parse URL with fragment", "[parsing]") 425 | { 426 | const std::string str = "tt://www.test.com/path#fragment"; 427 | const http::Uri uri = http::parseUri(str.begin(), str.end()); 428 | REQUIRE(uri.scheme == "tt"); 429 | REQUIRE(uri.user == ""); 430 | REQUIRE(uri.password == ""); 431 | REQUIRE(uri.host == "www.test.com"); 432 | REQUIRE(uri.port == ""); 433 | REQUIRE(uri.path == "/path"); 434 | REQUIRE(uri.query == ""); 435 | REQUIRE(uri.fragment == "fragment"); 436 | } 437 | 438 | TEST_CASE("Parse URL with query and fragment", "[parsing]") 439 | { 440 | const std::string str = "tt://www.test.com/path?query=1#fragment"; 441 | const http::Uri uri = http::parseUri(str.begin(), str.end()); 442 | REQUIRE(uri.scheme == "tt"); 443 | REQUIRE(uri.user == ""); 444 | REQUIRE(uri.password == ""); 445 | REQUIRE(uri.host == "www.test.com"); 446 | REQUIRE(uri.port == ""); 447 | REQUIRE(uri.path == "/path"); 448 | REQUIRE(uri.query == "query=1"); 449 | REQUIRE(uri.fragment == "fragment"); 450 | } 451 | 452 | TEST_CASE("Parse URL without scheme", "[parsing]") 453 | { 454 | const std::string str = "www.test.com/path?query=1#fragment"; 455 | REQUIRE_THROWS_AS(http::parseUri(str.begin(), str.end()), http::RequestError); 456 | } 457 | 458 | TEST_CASE("Parse URL with user", "[parsing]") 459 | { 460 | const std::string str = "tt://test@test.com/"; 461 | const http::Uri uri = http::parseUri(str.begin(), str.end()); 462 | REQUIRE(uri.scheme == "tt"); 463 | REQUIRE(uri.user == "test"); 464 | REQUIRE(uri.password == ""); 465 | REQUIRE(uri.host == "test.com"); 466 | REQUIRE(uri.port == ""); 467 | REQUIRE(uri.path == "/"); 468 | REQUIRE(uri.query == ""); 469 | REQUIRE(uri.fragment == ""); 470 | } 471 | 472 | TEST_CASE("Parse URL with user and password", "[parsing]") 473 | { 474 | const std::string str = "tt://test:test@test.com/"; 475 | const http::Uri uri = http::parseUri(str.begin(), str.end()); 476 | REQUIRE(uri.scheme == "tt"); 477 | REQUIRE(uri.user == "test"); 478 | REQUIRE(uri.password == "test"); 479 | REQUIRE(uri.host == "test.com"); 480 | REQUIRE(uri.port == ""); 481 | REQUIRE(uri.path == "/"); 482 | REQUIRE(uri.query == ""); 483 | REQUIRE(uri.fragment == ""); 484 | } 485 | -------------------------------------------------------------------------------- /tests/tests.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tests", "tests.vcxproj", "{614C7EC0-3262-40DF-B884-224B959A01F9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Debug|x64 = Debug|x64 12 | Release|Win32 = Release|Win32 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Debug|Win32.ActiveCfg = Debug|Win32 17 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Debug|Win32.Build.0 = Debug|Win32 18 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Debug|x64.ActiveCfg = Debug|x64 19 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Debug|x64.Build.0 = Debug|x64 20 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Release|Win32.ActiveCfg = Release|Win32 21 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Release|Win32.Build.0 = Release|Win32 22 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Release|x64.ActiveCfg = Release|x64 23 | {614C7EC0-3262-40DF-B884-224B959A01F9}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /tests/tests.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 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {614C7EC0-3262-40DF-B884-224B959A01F9} 31 | Win32Proj 32 | rtmp_relay 33 | 10.0 34 | 35 | 36 | 37 | Application 38 | true 39 | v143 40 | Unicode 41 | 42 | 43 | Application 44 | false 45 | v143 46 | true 47 | Unicode 48 | 49 | 50 | Application 51 | true 52 | v143 53 | Unicode 54 | 55 | 56 | Application 57 | false 58 | v143 59 | true 60 | Unicode 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | true 82 | ..\external\Catch2\single_include;..\include;$(IncludePath) 83 | $(SolutionDir)$(Platform)\$(Configuration)\ 84 | $(Platform)\$(Configuration)\ 85 | 86 | 87 | true 88 | ..\external\Catch2\single_include;..\include;$(IncludePath) 89 | 90 | 91 | false 92 | ..\external\Catch2\single_include;..\include;$(IncludePath) 93 | $(SolutionDir)$(Platform)\$(Configuration)\ 94 | $(Platform)\$(Configuration)\ 95 | 96 | 97 | false 98 | ..\external\Catch2\single_include;..\include;$(IncludePath) 99 | 100 | 101 | 102 | Level3 103 | Disabled 104 | _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | true 106 | stdcpp17 107 | 108 | 109 | Console 110 | true 111 | ws2_32.lib;%(AdditionalDependencies) 112 | 113 | 114 | 115 | 116 | Level4 117 | Disabled 118 | _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 119 | true 120 | stdcpp17 121 | 122 | 123 | Console 124 | true 125 | ws2_32.lib;%(AdditionalDependencies) 126 | 127 | 128 | 129 | 130 | Level3 131 | MaxSpeed 132 | true 133 | true 134 | _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 135 | true 136 | stdcpp17 137 | 138 | 139 | Console 140 | true 141 | true 142 | true 143 | ws2_32.lib;%(AdditionalDependencies) 144 | 145 | 146 | 147 | 148 | Level4 149 | MaxSpeed 150 | true 151 | true 152 | _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 153 | true 154 | stdcpp17 155 | 156 | 157 | Console 158 | true 159 | true 160 | true 161 | ws2_32.lib;%(AdditionalDependencies) 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /tests/tests.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 303E879D251EE589008B7E24 /* parsing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 303E879C251EE589008B7E24 /* parsing.cpp */; }; 11 | 307E6ED527CB02AC00D665C8 /* encoding.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 307E6ED427CB02AC00D665C8 /* encoding.cpp */; }; 12 | C6C90FD721A5A24D00B5FCB7 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C6C90FD621A5A24D00B5FCB7 /* main.cpp */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXCopyFilesBuildPhase section */ 16 | C6C90FD121A5A24D00B5FCB7 /* CopyFiles */ = { 17 | isa = PBXCopyFilesBuildPhase; 18 | buildActionMask = 2147483647; 19 | dstPath = /usr/share/man/man1/; 20 | dstSubfolderSpec = 0; 21 | files = ( 22 | ); 23 | runOnlyForDeploymentPostprocessing = 1; 24 | }; 25 | /* End PBXCopyFilesBuildPhase section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 303E879C251EE589008B7E24 /* parsing.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = parsing.cpp; sourceTree = ""; }; 29 | 307E6ED427CB02AC00D665C8 /* encoding.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = encoding.cpp; sourceTree = ""; }; 30 | 30977F4B27B496BA00D89E07 /* HTTPRequest.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = HTTPRequest.hpp; path = ../include/HTTPRequest.hpp; sourceTree = ""; }; 31 | C6C90FD321A5A24D00B5FCB7 /* tests */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = tests; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | C6C90FD621A5A24D00B5FCB7 /* main.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | C6C90FD021A5A24D00B5FCB7 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | C6C90FCA21A5A24D00B5FCB7 = { 47 | isa = PBXGroup; 48 | children = ( 49 | 307E6ED427CB02AC00D665C8 /* encoding.cpp */, 50 | 30977F4B27B496BA00D89E07 /* HTTPRequest.hpp */, 51 | C6C90FD621A5A24D00B5FCB7 /* main.cpp */, 52 | 303E879C251EE589008B7E24 /* parsing.cpp */, 53 | C6C90FD421A5A24D00B5FCB7 /* Products */, 54 | ); 55 | sourceTree = ""; 56 | }; 57 | C6C90FD421A5A24D00B5FCB7 /* Products */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | C6C90FD321A5A24D00B5FCB7 /* tests */, 61 | ); 62 | name = Products; 63 | sourceTree = ""; 64 | }; 65 | /* End PBXGroup section */ 66 | 67 | /* Begin PBXNativeTarget section */ 68 | C6C90FD221A5A24D00B5FCB7 /* tests */ = { 69 | isa = PBXNativeTarget; 70 | buildConfigurationList = C6C90FDA21A5A24D00B5FCB7 /* Build configuration list for PBXNativeTarget "tests" */; 71 | buildPhases = ( 72 | C6C90FCF21A5A24D00B5FCB7 /* Sources */, 73 | C6C90FD021A5A24D00B5FCB7 /* Frameworks */, 74 | C6C90FD121A5A24D00B5FCB7 /* CopyFiles */, 75 | ); 76 | buildRules = ( 77 | ); 78 | dependencies = ( 79 | ); 80 | name = tests; 81 | productName = WebsocketTest; 82 | productReference = C6C90FD321A5A24D00B5FCB7 /* tests */; 83 | productType = "com.apple.product-type.tool"; 84 | }; 85 | /* End PBXNativeTarget section */ 86 | 87 | /* Begin PBXProject section */ 88 | C6C90FCB21A5A24D00B5FCB7 /* Project object */ = { 89 | isa = PBXProject; 90 | attributes = { 91 | LastUpgradeCheck = 1010; 92 | ORGANIZATIONNAME = "Elviss Strazdins"; 93 | TargetAttributes = { 94 | C6C90FD221A5A24D00B5FCB7 = { 95 | CreatedOnToolsVersion = 10.1; 96 | }; 97 | }; 98 | }; 99 | buildConfigurationList = C6C90FCE21A5A24D00B5FCB7 /* Build configuration list for PBXProject "tests" */; 100 | compatibilityVersion = "Xcode 9.3"; 101 | developmentRegion = en; 102 | hasScannedForEncodings = 0; 103 | knownRegions = ( 104 | en, 105 | ); 106 | mainGroup = C6C90FCA21A5A24D00B5FCB7; 107 | productRefGroup = C6C90FD421A5A24D00B5FCB7 /* Products */; 108 | projectDirPath = ""; 109 | projectRoot = ""; 110 | targets = ( 111 | C6C90FD221A5A24D00B5FCB7 /* tests */, 112 | ); 113 | }; 114 | /* End PBXProject section */ 115 | 116 | /* Begin PBXSourcesBuildPhase section */ 117 | C6C90FCF21A5A24D00B5FCB7 /* Sources */ = { 118 | isa = PBXSourcesBuildPhase; 119 | buildActionMask = 2147483647; 120 | files = ( 121 | C6C90FD721A5A24D00B5FCB7 /* main.cpp in Sources */, 122 | 303E879D251EE589008B7E24 /* parsing.cpp in Sources */, 123 | 307E6ED527CB02AC00D665C8 /* encoding.cpp in Sources */, 124 | ); 125 | runOnlyForDeploymentPostprocessing = 0; 126 | }; 127 | /* End PBXSourcesBuildPhase section */ 128 | 129 | /* Begin XCBuildConfiguration section */ 130 | C6C90FD821A5A24D00B5FCB7 /* Debug */ = { 131 | isa = XCBuildConfiguration; 132 | buildSettings = { 133 | ALWAYS_SEARCH_USER_PATHS = NO; 134 | CLANG_ANALYZER_NONNULL = YES; 135 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 136 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; 137 | CLANG_CXX_LANGUAGE_STANDARD = "c++17"; 138 | CLANG_CXX_LIBRARY = "libc++"; 139 | CLANG_ENABLE_MODULES = YES; 140 | CLANG_ENABLE_OBJC_ARC = YES; 141 | CLANG_ENABLE_OBJC_WEAK = YES; 142 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_INTEGER = YES; 143 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES; 144 | CLANG_WARN_ASSIGN_ENUM = YES; 145 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 146 | CLANG_WARN_BOOL_CONVERSION = YES; 147 | CLANG_WARN_COMMA = YES; 148 | CLANG_WARN_CONSTANT_CONVERSION = YES; 149 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 150 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 151 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 152 | CLANG_WARN_EMPTY_BODY = YES; 153 | CLANG_WARN_ENUM_CONVERSION = YES; 154 | CLANG_WARN_FLOAT_CONVERSION = YES; 155 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; 156 | CLANG_WARN_INFINITE_RECURSION = YES; 157 | CLANG_WARN_INT_CONVERSION = YES; 158 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 159 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 160 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 161 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 162 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 163 | CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; 164 | CLANG_WARN_STRICT_PROTOTYPES = YES; 165 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; 166 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 167 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 168 | CLANG_WARN_UNREACHABLE_CODE = YES; 169 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 170 | DEBUG_INFORMATION_FORMAT = dwarf; 171 | ENABLE_STRICT_OBJC_MSGSEND = YES; 172 | ENABLE_TESTABILITY = YES; 173 | GCC_NO_COMMON_BLOCKS = YES; 174 | GCC_OPTIMIZATION_LEVEL = 0; 175 | GCC_PREPROCESSOR_DEFINITIONS = ( 176 | "DEBUG=1", 177 | "$(inherited)", 178 | ); 179 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 180 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 181 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES; 182 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 183 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 184 | GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; 185 | GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; 186 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 187 | GCC_WARN_PEDANTIC = YES; 188 | GCC_WARN_SHADOW = YES; 189 | GCC_WARN_SIGN_COMPARE = YES; 190 | GCC_WARN_UNDECLARED_SELECTOR = YES; 191 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 192 | GCC_WARN_UNKNOWN_PRAGMAS = YES; 193 | GCC_WARN_UNUSED_FUNCTION = YES; 194 | GCC_WARN_UNUSED_LABEL = YES; 195 | GCC_WARN_UNUSED_PARAMETER = YES; 196 | GCC_WARN_UNUSED_VARIABLE = YES; 197 | MACOSX_DEPLOYMENT_TARGET = 10.12; 198 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 199 | MTL_FAST_MATH = YES; 200 | ONLY_ACTIVE_ARCH = YES; 201 | SDKROOT = macosx; 202 | }; 203 | name = Debug; 204 | }; 205 | C6C90FD921A5A24D00B5FCB7 /* Release */ = { 206 | isa = XCBuildConfiguration; 207 | buildSettings = { 208 | ALWAYS_SEARCH_USER_PATHS = NO; 209 | CLANG_ANALYZER_NONNULL = YES; 210 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 211 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; 212 | CLANG_CXX_LANGUAGE_STANDARD = "c++17"; 213 | CLANG_CXX_LIBRARY = "libc++"; 214 | CLANG_ENABLE_MODULES = YES; 215 | CLANG_ENABLE_OBJC_ARC = YES; 216 | CLANG_ENABLE_OBJC_WEAK = YES; 217 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_INTEGER = YES; 218 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES; 219 | CLANG_WARN_ASSIGN_ENUM = YES; 220 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 221 | CLANG_WARN_BOOL_CONVERSION = YES; 222 | CLANG_WARN_COMMA = YES; 223 | CLANG_WARN_CONSTANT_CONVERSION = YES; 224 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 225 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 226 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 227 | CLANG_WARN_EMPTY_BODY = YES; 228 | CLANG_WARN_ENUM_CONVERSION = YES; 229 | CLANG_WARN_FLOAT_CONVERSION = YES; 230 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; 231 | CLANG_WARN_INFINITE_RECURSION = YES; 232 | CLANG_WARN_INT_CONVERSION = YES; 233 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 234 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 235 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 236 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 237 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 238 | CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; 239 | CLANG_WARN_STRICT_PROTOTYPES = YES; 240 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; 241 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 242 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 243 | CLANG_WARN_UNREACHABLE_CODE = YES; 244 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 245 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 246 | ENABLE_NS_ASSERTIONS = NO; 247 | ENABLE_STRICT_OBJC_MSGSEND = YES; 248 | GCC_NO_COMMON_BLOCKS = YES; 249 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 250 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 251 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES; 252 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 253 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 254 | GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; 255 | GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; 256 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 257 | GCC_WARN_PEDANTIC = YES; 258 | GCC_WARN_SHADOW = YES; 259 | GCC_WARN_SIGN_COMPARE = YES; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 262 | GCC_WARN_UNKNOWN_PRAGMAS = YES; 263 | GCC_WARN_UNUSED_FUNCTION = YES; 264 | GCC_WARN_UNUSED_LABEL = YES; 265 | GCC_WARN_UNUSED_PARAMETER = YES; 266 | GCC_WARN_UNUSED_VARIABLE = YES; 267 | MACOSX_DEPLOYMENT_TARGET = 10.12; 268 | MTL_ENABLE_DEBUG_INFO = NO; 269 | MTL_FAST_MATH = YES; 270 | SDKROOT = macosx; 271 | }; 272 | name = Release; 273 | }; 274 | C6C90FDB21A5A24D00B5FCB7 /* Debug */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | HEADER_SEARCH_PATHS = ( 278 | ../external/Catch2/single_include, 279 | ../include, 280 | ); 281 | PRODUCT_NAME = "$(TARGET_NAME)"; 282 | }; 283 | name = Debug; 284 | }; 285 | C6C90FDC21A5A24D00B5FCB7 /* Release */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | HEADER_SEARCH_PATHS = ( 289 | ../external/Catch2/single_include, 290 | ../include, 291 | ); 292 | PRODUCT_NAME = "$(TARGET_NAME)"; 293 | }; 294 | name = Release; 295 | }; 296 | /* End XCBuildConfiguration section */ 297 | 298 | /* Begin XCConfigurationList section */ 299 | C6C90FCE21A5A24D00B5FCB7 /* Build configuration list for PBXProject "tests" */ = { 300 | isa = XCConfigurationList; 301 | buildConfigurations = ( 302 | C6C90FD821A5A24D00B5FCB7 /* Debug */, 303 | C6C90FD921A5A24D00B5FCB7 /* Release */, 304 | ); 305 | defaultConfigurationIsVisible = 0; 306 | defaultConfigurationName = Release; 307 | }; 308 | C6C90FDA21A5A24D00B5FCB7 /* Build configuration list for PBXNativeTarget "tests" */ = { 309 | isa = XCConfigurationList; 310 | buildConfigurations = ( 311 | C6C90FDB21A5A24D00B5FCB7 /* Debug */, 312 | C6C90FDC21A5A24D00B5FCB7 /* Release */, 313 | ); 314 | defaultConfigurationIsVisible = 0; 315 | defaultConfigurationName = Release; 316 | }; 317 | /* End XCConfigurationList section */ 318 | }; 319 | rootObject = C6C90FCB21A5A24D00B5FCB7 /* Project object */; 320 | } 321 | -------------------------------------------------------------------------------- /tests/tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | --------------------------------------------------------------------------------