From 23613a23a21a5a16e52bf654b6b817aae0d01c45 Mon Sep 17 00:00:00 2001 From: blinker Date: Mon, 3 Aug 2026 15:49:03 +0000 Subject: [PATCH] FEATURE/hw15-api-call: copy drone_simulation folder and create drone_simulation_api_call, implement ApiClient class as a wrapper for httplib client, make api call to save simulation.json file --- .githooks/pre-commit | 3 +- CMakeLists.txt | 1 + drone_simulation_api_call/CMakeLists.txt | 34 + drone_simulation_api_call/data/ammo.json | 32 + .../data/ballistic_table.txt | 3306 ++ drone_simulation_api_call/data/config.json | 23 + .../data/simulation_example.json | 215 + drone_simulation_api_call/data/targets.json | 2426 ++ .../include/api/api_client.hpp | 18 + .../include/api/network.hpp | 6 + .../include/config_loaders/factory.hpp | 15 + .../config_loaders/file_config_loader.hpp | 18 + .../include/drone_physics.hpp | 33 + .../drone_states/state_accelerating.hpp | 10 + .../drone_states/state_decelerating.hpp | 10 + .../include/drone_states/state_moving.hpp | 10 + .../include/drone_states/state_stopped.hpp | 10 + .../include/drone_states/state_turning.hpp | 10 + drone_simulation_api_call/include/httplib.h | 23633 ++++++++++++++ .../include/interfaces/ballistic_solver.hpp | 13 + .../include/interfaces/config_loader.hpp | 10 + .../include/interfaces/drone_state.hpp | 16 + .../include/interfaces/target_provider.hpp | 9 + drone_simulation_api_call/include/json.hpp | 25830 ++++++++++++++++ .../include/mission_processor.hpp | 48 + .../providers/json_target_provider.hpp | 16 + .../providers/thread_safe_target_provider.hpp | 32 + .../include/simulation.hpp | 165 + .../include/solvers/analytical_solver.hpp | 12 + .../include/solvers/ballistic_table.hpp | 158 + .../include/solvers/table_solver.hpp | 14 + .../include/thread_safe_queue.hpp | 28 + .../src/api/api_client.cpp | 21 + drone_simulation_api_call/src/api/network.cpp | 20 + .../src/config_loaders/factory.cpp | 43 + .../src/config_loaders/file_config_loader.cpp | 30 + .../src/drone_physics.cpp | 109 + .../src/drone_states/state_accelerating.cpp | 20 + .../src/drone_states/state_decelerating.cpp | 25 + .../src/drone_states/state_moving.cpp | 20 + .../src/drone_states/state_stopped.cpp | 21 + .../src/drone_states/state_turning.cpp | 20 + drone_simulation_api_call/src/main.cpp | 134 + .../src/mission_processor.cpp | 226 + .../src/providers/json_target_provider.cpp | 20 + .../providers/thread_safe_target_provider.cpp | 86 + drone_simulation_api_call/src/simulation.cpp | 281 + .../src/solvers/analytical_solver.cpp | 14 + .../src/solvers/table_solver.cpp | 14 + 49 files changed, 57267 insertions(+), 1 deletion(-) create mode 100644 drone_simulation_api_call/CMakeLists.txt create mode 100644 drone_simulation_api_call/data/ammo.json create mode 100644 drone_simulation_api_call/data/ballistic_table.txt create mode 100644 drone_simulation_api_call/data/config.json create mode 100644 drone_simulation_api_call/data/simulation_example.json create mode 100644 drone_simulation_api_call/data/targets.json create mode 100644 drone_simulation_api_call/include/api/api_client.hpp create mode 100644 drone_simulation_api_call/include/api/network.hpp create mode 100644 drone_simulation_api_call/include/config_loaders/factory.hpp create mode 100644 drone_simulation_api_call/include/config_loaders/file_config_loader.hpp create mode 100644 drone_simulation_api_call/include/drone_physics.hpp create mode 100644 drone_simulation_api_call/include/drone_states/state_accelerating.hpp create mode 100644 drone_simulation_api_call/include/drone_states/state_decelerating.hpp create mode 100644 drone_simulation_api_call/include/drone_states/state_moving.hpp create mode 100644 drone_simulation_api_call/include/drone_states/state_stopped.hpp create mode 100644 drone_simulation_api_call/include/drone_states/state_turning.hpp create mode 100644 drone_simulation_api_call/include/httplib.h create mode 100644 drone_simulation_api_call/include/interfaces/ballistic_solver.hpp create mode 100644 drone_simulation_api_call/include/interfaces/config_loader.hpp create mode 100644 drone_simulation_api_call/include/interfaces/drone_state.hpp create mode 100644 drone_simulation_api_call/include/interfaces/target_provider.hpp create mode 100644 drone_simulation_api_call/include/json.hpp create mode 100644 drone_simulation_api_call/include/mission_processor.hpp create mode 100644 drone_simulation_api_call/include/providers/json_target_provider.hpp create mode 100644 drone_simulation_api_call/include/providers/thread_safe_target_provider.hpp create mode 100644 drone_simulation_api_call/include/simulation.hpp create mode 100644 drone_simulation_api_call/include/solvers/analytical_solver.hpp create mode 100644 drone_simulation_api_call/include/solvers/ballistic_table.hpp create mode 100644 drone_simulation_api_call/include/solvers/table_solver.hpp create mode 100644 drone_simulation_api_call/include/thread_safe_queue.hpp create mode 100644 drone_simulation_api_call/src/api/api_client.cpp create mode 100644 drone_simulation_api_call/src/api/network.cpp create mode 100644 drone_simulation_api_call/src/config_loaders/factory.cpp create mode 100644 drone_simulation_api_call/src/config_loaders/file_config_loader.cpp create mode 100644 drone_simulation_api_call/src/drone_physics.cpp create mode 100644 drone_simulation_api_call/src/drone_states/state_accelerating.cpp create mode 100644 drone_simulation_api_call/src/drone_states/state_decelerating.cpp create mode 100644 drone_simulation_api_call/src/drone_states/state_moving.cpp create mode 100644 drone_simulation_api_call/src/drone_states/state_stopped.cpp create mode 100644 drone_simulation_api_call/src/drone_states/state_turning.cpp create mode 100644 drone_simulation_api_call/src/main.cpp create mode 100644 drone_simulation_api_call/src/mission_processor.cpp create mode 100644 drone_simulation_api_call/src/providers/json_target_provider.cpp create mode 100644 drone_simulation_api_call/src/providers/thread_safe_target_provider.cpp create mode 100644 drone_simulation_api_call/src/simulation.cpp create mode 100644 drone_simulation_api_call/src/solvers/analytical_solver.cpp create mode 100644 drone_simulation_api_call/src/solvers/table_solver.cpp diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 2763cb0..7a91c92 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -9,7 +9,8 @@ if ! command -v clang-format >/dev/null 2>&1; then fi staged=$(git diff --cached --name-only --diff-filter=ACMR \ - | grep -E '\.(cpp|cc|cxx|hpp|hh|hxx|h)$' || true) + | grep -E '\.(cpp|cc|cxx|hpp|hh|hxx|h)$' \ + | grep -vE '(^|/)(json|httplib)\.h(pp)?$' || true) if [ -z "$staged" ]; then exit 0 diff --git a/CMakeLists.txt b/CMakeLists.txt index ab7bb68..ab362f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ enable_testing() # Домашні роботи. По мірі появи додаємо кожну окремим add_subdirectory. add_subdirectory(drone_simulation) +add_subdirectory(drone_simulation_api_call) add_subdirectory(homework_04) add_subdirectory(homework_05) add_subdirectory(homework_06) diff --git a/drone_simulation_api_call/CMakeLists.txt b/drone_simulation_api_call/CMakeLists.txt new file mode 100644 index 0000000..c92935c --- /dev/null +++ b/drone_simulation_api_call/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.20) +project(drone_simulation_api_call CXX) + +add_library(drone_simulation_api_lib + src/simulation.cpp + src/api/network.cpp + src/api/api_client.cpp + src/solvers/analytical_solver.cpp + src/solvers/table_solver.cpp + src/providers/json_target_provider.cpp + src/providers/thread_safe_target_provider.cpp + src/config_loaders/file_config_loader.cpp + src/config_loaders/factory.cpp + src/mission_processor.cpp + src/drone_states/state_stopped.cpp + src/drone_states/state_accelerating.cpp + src/drone_states/state_decelerating.cpp + src/drone_states/state_turning.cpp + src/drone_states/state_moving.cpp + src/drone_physics.cpp +) + +find_package(Threads REQUIRED) +target_link_libraries(drone_simulation_api_lib PUBLIC Threads::Threads) + +target_include_directories(drone_simulation_api_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_compile_options(drone_simulation_api_lib PRIVATE -Wall -Wextra -pedantic) + +add_executable(drone_simulation_api_check src/main.cpp) +target_link_libraries(drone_simulation_api_check PRIVATE drone_simulation_api_lib) +target_compile_options(drone_simulation_api_check PRIVATE -Wall -Wextra -pedantic) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/drone_simulation_api_call/data/ammo.json b/drone_simulation_api_call/data/ammo.json new file mode 100644 index 0000000..a1c485b --- /dev/null +++ b/drone_simulation_api_call/data/ammo.json @@ -0,0 +1,32 @@ +[ + { + "name": "VOG-17", + "mass": 0.35, + "drag": 0.004, + "lift": 0 + }, + { + "name": "M67", + "mass": 0.6, + "drag": 0.005, + "lift": 0 + }, + { + "name": "RKG-3", + "mass": 1.2, + "drag": 0.007, + "lift": 0 + }, + { + "name": "GLIDING-VOG", + "mass": 0.45, + "drag": 0.005, + "lift": 0.005 + }, + { + "name": "GLIDING-RKG", + "mass": 1.4, + "drag": 0.007, + "lift": 0.005 + } +] \ No newline at end of file diff --git a/drone_simulation_api_call/data/ballistic_table.txt b/drone_simulation_api_call/data/ballistic_table.txt new file mode 100644 index 0000000..dd6f8a1 --- /dev/null +++ b/drone_simulation_api_call/data/ballistic_table.txt @@ -0,0 +1,3306 @@ +11 10 5 3 2 +50. 60. 70. 80. 90. 100. 110. 120. 130. 140. 150. +1. 2. 3. 4. 5. 6. 7. 8. 9. 10. +0.35 0.45 0.6 1.2 1.4 +0.004 0.005 0.007 +0. 0.005 +3.5045080212366058 2.8938155146905893 +3.6201084533913095 14.596626942484109 +3.5838661492081623 2.8231231700671886 +3.699868100734687 14.211657158007464 +3.7431679856267204 2.6873984271417486 +3.8586643789492037 13.466257312622519 +3.434299419039064 2.958124778467509 +3.5057865423231194 12.054688393980792 +3.4957122792461197 2.901780310727317 +3.567722186976936 11.823864656286617 +3.6192238760294444 2.792294557180691 +3.691746897656515 11.369252603859444 +3.373217574322246 3.015461112081073 +3.4158069139739107 9.849975552679414 +3.418994857073471 2.9723683150270346 +3.461922419838276 9.715163696317177 +3.5111080873957388 2.887856088525701 +3.5545292376780524 9.447386390106308 +3.2823890485031724 3.103183050768454 +3.29621753747683 6.545379353297966 +3.30499485751573 3.081069618549535 +3.31889350510648 6.502233114091517 +3.350413352446452 3.0372049441187245 +3.3644377383844164 6.416124020523711 +3.2695046818165725 3.1158714772391387 +3.2805028894888673 6.07086025714968 +3.288840417769944 3.096852954496167 +3.299886876792528 6.036553354701744 +3.327671035659814 3.059075667594949 +3.338805489799236 5.96807204860553 +3.5067468293498383 5.765119833790019 +3.653123867982994 17.76421385861027 +3.586569569938885 5.61906406734092 +3.732742337036709 17.268540638041436 +3.746693699828885 5.339306831394281 +3.890940663856774 16.320514058289717 +3.4360937077508926 5.898199667368929 +3.5304393612441345 15.209419498191588 +3.4978971628219844 5.781590873807161 +3.5925651422181604 14.899721243720753 +3.6221220594329635 5.555444578941792 +3.7167670023557733 14.29564316715318 +3.374598078828289 6.017022994352121 +3.4336947181038253 13.000544633644015 +3.4206878814769124 5.927702459199835 +3.4800772460516227 12.810378478128392 +3.513387033888586 5.752797882590855 +3.5731149185388724 12.435418498191614 +3.283105188510465 6.199140404412416 +3.3047769621150334 9.705326236146131 +3.3058819798238988 6.153193962710674 +3.3276326232411995 9.636559059493788 +3.351632673545713 6.062128125438335 +3.373518130528189 9.499795858087904 +3.270121653562058 6.22551536609908 +3.2877982247306563 9.233920429146137 +3.289605745891789 6.185985393143057 +3.3073387590228083 9.17784006080078 +3.328725892580271 6.107520794217627 +3.346558071602352 9.06622021575093 +3.510215374434465 8.602953291674265 +3.690408196440236 20.989123238576802 +3.5907386689712655 8.374805884507674 +3.7698247503260056 20.363895988801147 +3.752081234680552 7.939293651994592 +3.9272871430727956 19.182761795843444 +3.438885002325029 8.81130807406083 +3.5582772525050306 18.411650560707738 +3.5012839003712237 8.628716144956165 +3.6206559178553186 18.010081236681586 +3.626582353375272 8.275596517151532 +3.7451199878875734 17.23398182252289 +3.376753364514925 8.997733267378202 +3.4538756402504416 16.188624500958046 +3.423324006429023 8.857561162642932 +3.500629639883226 15.934427379107984 +3.5169164064616396 8.583684709654335 +3.5942766635544654 15.436473237422963 +3.284229201897865 9.284190746255323 +3.3144080032046093 12.885548146724743 +3.307272536639881 9.211836383874243 +3.337527858403358 12.787584300450991 +3.3535388623825826 9.06859772635649 +3.3839153090577834 12.593285340195392 +3.271090747562447 9.325750609274928 +3.296002720264084 12.41456231822149 +3.2908065076399233 9.26346901180043 +3.3157754345344017 12.33373375217687 +3.330377174853081 9.13996868350277 +3.355440618926201 12.173214136414801 +3.5147251286397743 11.400914259625143 +3.7322073975497823 24.263710644091898 +3.596131535415094 11.08295500018713 +3.811216064816327 23.487156479350194 +3.758980468109193 10.478555366927889 +3.9675558734774237 22.03895382726952 +3.442530875536972 11.692114989184134 +3.589521098200983 21.65594557765714 +3.5056898083124834 11.436877921714666 +3.6521264274385623 21.147285317496895 +3.632338913860988 10.944981871227755 +3.7767700391095365 20.173408054678525 +3.379579895141287 11.953346126741296 +3.4765282702095446 19.41033754798965 +3.426770670035081 11.756867518821256 +3.5237058237476777 19.081851789969924 +3.52150328179288 11.374024161454425 +3.6180362984671737 18.442523252714498 +3.2857122240435808 12.356017937748097 +3.3252060229880938 16.08413781190225 +3.309104465733921 12.254163403156914 +3.3486570988416053 15.95269345631721 +3.356042502975262 12.052815972345435 +3.395671172429519 15.692671139931674 +3.272370472734567 12.414567506076052 +3.3051986583433357 15.611145115724675 +3.2923901082063045 12.326837420338821 +3.3252650905560643 15.501996840586935 +3.3325492979805196 12.153092156043025 +3.3654922945669443 15.285700076845876 +3.520110876563541 14.15506612986139 +3.778698182165511 27.583363725423176 +3.602537178285896 13.73915888734922 +3.8569473173437814 26.63115808652755 +3.7670895848548795 12.952390538378907 +4.011547048316258 24.879483369703127 +3.446906251564902 14.537218597785548 +3.6243381316403775 24.939507197240044 +3.510954734474191 14.202197496083798 +3.687054823867036 24.306573986836625 +3.639160297620886 13.559118298044751 +3.811635499634857 23.106674849454304 +3.3829865966661576 14.88107057394149 +3.501788823033367 22.663963993308453 +3.4309114261029583 14.622359510526586 +3.549390486933331 22.249561593371197 +3.526978640424285 14.119837806970278 +3.644378724011017 21.448425000355716 +3.2875113289101634 15.41303423141529 +3.337241951412992 19.30040194270388 +3.3113232250110465 15.278252387519347 +3.3610749456584323 19.130621189027732 +3.359064939071429 15.012259153786507 +3.408806873994907 18.795667010976715 +3.2739243978569994 15.490580405265744 +3.315446862854939 18.823084833167123 +3.2943103344163474 15.374404769127848 +3.335855499080458 18.68157031413461 +3.3351757854250255 15.14465656527266 +3.376734092809495 18.401754468924086 +3.5262290623025736 16.863029345628213 +3.8300156981287996 30.945280935412768 +3.6097734800378687 16.34097458900805 +3.9070068519614694 29.79076306668333 +3.776152600891512 15.358632851082998 +4.059036182770933 27.697493309725907 +3.451901847277813 17.344442174093786 +3.6628661285193154 28.26122476946717 +3.516939419066485 16.922312577925833 +3.7254885855586974 27.48498576983798 +3.6468475179153597 16.11558305615513 +3.849611095374843 26.028772263765195 +3.386893706576454 17.77904195706544 +3.5297705542316007 25.949221452633687 +3.43564442611278 17.451925728814526 +3.5777454196850815 25.435991419407312 +3.5331957047485356 16.818734566265448 +3.67326956913794 24.45084502372131 +3.289588748448124 18.454114159271715 +3.3505725793875154 22.53448116287608 +3.3138808516154015 18.282759160056422 +3.3748227132754796 22.321006871514196 +3.362537212802299 17.94521514056876 +3.4233323402751052 21.901049987870618 +3.2757204768034254 18.552799966986658 +3.3267955747199993 22.05052590461981 +3.296526531177881 18.404980687873135 +3.3475826558120008 21.87218869490093 +3.338198311721946 18.113123356043182 +3.3891770634133884 21.520389050712545 +3.5329556249676157 19.52347321510996 +3.8862662520406728 34.34771575310235 +3.6176845655775702 18.887231619763345 +3.961353023777712 32.96204431114094 +3.785954779654295 17.696757082965547 +4.109787510611806 30.487913219676322 +3.457422548825947 20.112440992440174 +3.705225841364755 31.621095169218002 +3.5235233998142363 19.595883509716966 +3.767456056970196 30.68069993432647 +3.655230970479857 18.61331674374025 +3.8905788627159343 28.936130728685363 +3.391231384109772 20.646025326073012 +3.5605742634314064 29.26683331864365 +3.440880893040886 20.244237365062475 +3.608819299798506 28.640598656395575 +3.5400277207417203 19.469391720234437 +3.7046641106403255 27.44763430110869 +3.2919112980135177 21.47845014312187 +3.3652462485731958 25.787130065530352 +3.316735249796701 21.266736908225766 +3.389933912156009 25.524137642873676 +3.3663988449095075 20.85052400739873 +3.4392513700393836 25.008331826818445 +3.277730474510072 21.600507693363284 +3.339285351362415 25.294150782006476 +3.2990029880792675 21.417715441943738 +3.3604755172025587 25.074158501014416 +3.3415656377060907 21.057427868063524 +3.4028267413797075 24.64126742291323 +3.5401838140516992 22.135796283961923 +3.9475326034491984 37.78944893432588 +3.626137655493897 21.377629622715578 +4.019920275468374 36.14175474360307 +3.7963172788490613 19.967317165996324 +4.163561095087207 33.2468629860779 +3.4633858049120705 22.840455884301548 +3.7515277022325475 35.01982670502658 +3.53060290922092 22.222282802014146 +3.812972460130008 33.892599403649776 +3.664166877873724 21.05218518944945 +3.93441385744752 31.8261171788374 +3.3959385867751823 23.481233378618228 +3.594294557395917 32.618241269618515 +3.446543660655215 22.99851759739426 +3.642653276273755 31.86353183844659 +3.5473657109141166 22.071228412470557 +3.7385120562034166 30.437424200866122 +3.294449692457209 24.485459985069692 +3.381306408438773 29.05957164983131 +3.3198493552687784 24.22952048546074 +3.406437596821611 28.740779005035712 +3.370596839371626 23.727419517989897 +3.4565645994349232 28.117544195753574 +3.2799295409675446 24.633180340262122 +3.352952134227205 28.555053709212434 +3.3017082206334667 24.41200086560262 +3.3745589018540696 28.288211517252822 +3.3452327650164313 23.976844190872683 +3.417685744958581 27.764519641186478 +3.547821974077613 24.699912963805367 +4.013874989245117 41.26939579383717 +3.635020016548729 23.812470613943702 +4.082621533387855 39.32696402075077 +3.8070920085148012 22.171576814957557 +4.220116661748274 35.97126815458086 +3.4697200706357934 25.528148354047335 +3.801875173921213 38.45854419245037 +3.5380887432869033 24.801387833075367 +3.8620427430908046 37.1199629517378 +3.673533828918087 23.432687948651694 +3.9809870914563383 34.696700066496064 +3.40096203998483 26.28420457406495 +3.6310237207851688 36.00539918098016 +3.4525656989879425 25.714387642262825 +3.6792842073992205 35.10539571640187 +3.5551161946497833 24.624187727489105 +3.7747601332841327 33.41935352892785 +3.2971781360213805 27.47472883893429 +3.398793997093393 32.35339705753316 +3.323190536045166 27.17065282500284 +3.4243605439567384 31.972057390052054 +3.375084836486246 26.575423075174726 +3.4752713250549263 31.229091122785825 +3.282295708457743 27.650436879576738 +3.36782930552637 31.83465437296379 +3.3046144616875304 27.387407586262924 +3.389855396364403 31.51540398500446 +3.349160239438487 26.87089478386639 +3.4337553935375524 30.890614752183936 +3.5557915213387767 27.216105674129118 +4.085329233789193 44.7862947059312 +3.6442362467494807 26.192475320298165 +4.149348917880401 42.51480951562357 +3.8181568868287337 24.311258444313953 +4.279216002578427 38.65861160778006 +3.476363393153908 28.17548668062333 +3.8563659663706424 41.93855988339305 +3.545904319173826 27.33343716452539 +3.914662698791794 40.36223873562271 +3.683229656246209 25.755759012026232 +4.030166882722197 37.54622212661048 +3.406255216916015 29.054718139694113 +3.67085407695035 39.43061789566807 +3.458888855471709 28.39175969625551 +3.718746351485621 38.36707776649961 +3.563199103450531 27.128585713875136 +3.8133533597449003 36.39287514676418 +3.3000738288235274 30.445966983778607 +3.417749112939028 35.67049441970666 +3.3267301031997714 30.08983274169358 +3.44372873137072 35.21937606703246 +3.379822229052759 29.394269563099797 +3.4953706394630073 34.34364328560998 +3.284809534847917 30.652004192485897 +3.383949135538648 35.1346360675346 +3.307697260350026 30.343640605794324 +3.406386656967392 34.757044194891485 +3.3533134032413416 29.739287111624655 +3.451036740078834 34.020269579070956 +3.908348809084388 3.108865081786397 +4.087623979074932 19.972715929308997 +4.0129286034579845 3.0183247434276317 +4.191712242055722 19.29682948275467 +4.22230185029211 2.846535341595973 +4.397566842350057 18.01680837327316 +3.815747100231518 3.1917679584031857 +3.9238400483475946 16.269483849649273 +3.8967504801924164 3.1191063945494863 +4.005430495904181 15.879478376105006 +4.059474550624719 2.979055722018851 +4.168220355215863 15.118964935907846 +3.735169984455348 3.2660574002055056 +3.797854827319173 13.07889362940012 +3.7955571691975973 3.2101919912641126 +3.858755785091927 12.859310538669927 +3.917051109008274 3.101207430440057 +3.980892240525699 12.424723704605197 +3.6154275290534974 3.380289101370381 +3.6346046510064807 8.321271943882834 +3.6452142504186793 3.3514352316479816 +3.6645091427636416 8.255709453478648 +3.705093466793151 3.29431254847308 +3.724594579013718 8.124853533297568 +3.598456616238231 3.396860831333663 +3.6135225261188593 7.639181514464687 +3.62392687171158 3.3720258798386626 +3.6390737965129434 7.587731329460091 +3.675105561113093 3.322774066843759 +3.6903976376306282 7.48501203988716 +3.9107598038365285 6.192814589984866 +4.128129546180026 23.45108217305045 +4.015819836809277 6.006759994845302 +4.231456521404573 22.619800196702336 +4.226021516510759 5.654545164658551 +4.435476739841719 21.066349392940413 +3.8176911501937862 6.363442041173031 +3.953777489316252 19.725786415648475 +3.8991052047110113 6.213878826248435 +4.035354409872738 19.227864493738835 +4.062564553211628 5.926156902114098 +4.197834918000654 18.267127399855625 +3.7366733899889004 6.516563828643661 +3.819392081093939 16.525165452385796 +3.7973938910736336 6.401396929481101 +3.8805162939031814 16.231391817280652 +3.9195039182367633 6.177066951262556 +4.002944176093328 15.654695403687526 +3.616213073292278 6.75243421151873 +3.6447994811828646 11.775222733824421 +3.646185653070788 6.692806596947189 +3.674899026426295 11.676101145189532 +3.70642381938181 6.574858231198056 +3.7353474960114266 11.4790821067385 +3.599134034880724 6.7866954077041415 +3.622200580890714 11.0969037191217 +3.62476595905099 6.735354553693086 +3.64792343630427 11.016998637214206 +3.6762585782693087 6.6336101363388895 +3.6995720965022874 10.858025205468778 +3.9145067919674905 9.239480105544644 +4.173410781869977 26.996030161773476 +4.02029105534928 8.950728824417583 +4.275773302629496 25.985163731150834 +4.231717876748634 8.405907718746347 +4.4776131512331 24.121840713743435 +3.8207257208686296 9.504890765229623 +3.987263354769999 23.238228954042857 +3.902766751303129 9.272214017787578 +4.068834269687722 22.615914972863205 +4.067332702647032 8.825842726182112 +4.230989232566196 21.427548295183186 +3.739029169089512 9.743573174328816 +3.8434661428263137 20.016216208703522 +3.800263672267992 9.564009266531997 +3.904903708960635 19.63745139515347 +3.9233143222912528 9.21501394819729 +4.027767981832495 18.89943752500969 +3.6174509964671384 10.11219181060971 +3.6561634873725355 15.253682229702402 +3.6477143352385113 10.018895575016629 +3.686548303490367 15.116704713458162 +3.708511415931483 9.834567751926159 +3.747529518229783 14.845318901012057 +3.6002024294176396 10.165832534976305 +3.63186834938662 14.575919271757984 +3.626087725120883 10.085460486446639 +3.657844413680754 14.4640055458721 +3.67807048212982 9.926347927044375 +3.7099732776310224 14.241939470037575 +3.9193917628456068 12.241527710250839 +4.223684002771788 30.59740426691235 +4.026088449103636 11.841835305076502 +4.32470653174159 29.379547326051362 +4.23902543268306 11.090799033404062 +4.523751433601403 27.1667158324246 +3.8247013579559246 12.609954447986356 +4.024514834650609 26.799738847175167 +3.9075432490380613 12.286912424383763 +4.105981555840409 26.033978007510953 +4.073500295579001 11.669326039113024 +4.267605289374589 24.587183443155624 +3.7421288729304005 12.942150411557773 +3.8702606315354413 23.54706513260259 +3.8040274895379826 12.692157413910927 +3.9320407189432682 23.070645488700166 +3.928279695270797 12.207615954078797 +4.055367586136677 22.149218930784897 +3.6190904188513553 13.456843635000357 +3.668797981464017 18.754215795340134 +3.649735548637444 13.32638460343704 +3.6995390201787717 18.574248847855937 +3.7112626974989187 13.069015996388602 +3.7611830204980974 18.2187694102002 +3.601618645894879 13.531910432473461 +3.642613522264506 18.074132394798077 +3.6278373752469553 13.41945102278048 +3.668909017481626 17.925951289075684 +3.680462295161089 13.197101203693641 +3.7216417273045965 17.63264894178473 +3.925239234077175 15.194391507611073 +4.2790892266406235 34.24818080311649 +4.032988233461706 14.675080728462124 +4.3782260983981285 32.79323439239293 +4.247626327372278 13.70392396816517 +4.573619764523993 30.189348335330596 +3.8294852131362167 15.674652950106992 +4.065692595364379 30.40601929072115 +3.9132645794698195 15.253470373201996 +4.14685103550682 29.475501786877945 +4.080822159995202 14.451481613427902 +4.307559032234808 27.73695698288964 +3.7458759284938994 16.10900565691275 +3.8999155161984955 27.115034747789316 +3.8085616638534923 15.782017621064895 +3.9620068223478135 26.52667561797914 +3.934220528528495 15.150262357964511 +4.08570868442893 25.397456123412393 +3.6210861139360877 16.78449786720532 +3.68277927234978 22.275708820127605 +3.6521917094441188 16.61298886872555 +3.713929237086624 22.046933800277067 +3.7145943565612227 16.275219944059273 +3.7763287523070024 21.596431593957625 +3.603344351120787 16.88327565464486 +3.654501807724441 21.59059978408365 +3.629966189151337 16.735318955102304 +3.6811685065810447 21.401323179581624 +3.683363711090234 16.44322147305203 +3.7345990478505935 21.027618286617873 +3.9318951667531374 18.09527907976583 +4.339715826297882 37.94316430488797 +4.040795496120281 17.447628690023276 +4.436254051901326 36.21872151137393 +4.257249041795247 16.24281909662586 +4.626926375460785 33.181274310661394 +3.8349599702334713 18.696407692892418 +4.110923933980328 34.05452804552028 +3.919781290177452 18.169112430051822 +4.19146312075943 32.93586369044839 +4.089085677024361 17.169509707059497 +4.350702462633306 30.870307362718812 +3.7501848433368945 19.241912091493752 +3.932546154740056 30.718978819850214 +3.8137568911596937 18.83108357791831 +3.994856608626713 30.002886269570773 +3.94097934747859 18.04014971017948 +4.118736261023241 28.639570076749433 +3.6233978573027863 20.093794602904712 +3.6981689599643763 25.817958710391466 +3.6550316249119583 19.87708885733769 +3.729763041046811 25.53395288940476 +3.7184325653741848 19.451128857593204 +3.792975403573859 24.97648004599785 +3.605345468921928 20.2187307992604 +3.667585829950713 25.125176816235548 +3.632430839622556 20.03162713636777 +3.6946617716810883 24.889481963315927 +3.686712367648728 19.662860329432352 +3.748856186391246 24.42534907393265 +3.939225303186537 20.942607688783383 +4.405613822565727 41.67818211454025 +4.049342134857602 20.1581003719255 +4.498677424293217 39.649874004480395 +4.2676645069374075 18.706871393315748 +4.683373483295972 36.13617269740024 +3.8410225902436816 21.673604597866742 +4.160314422799155 37.743849779545364 +3.926962983847434 21.032243389966514 +4.239815050771396 36.411671320270706 +4.098108314316902 19.82216712557904 +4.396874857203324 33.98234404324404 +3.754980015010811 22.339375225135623 +3.9682535316078287 34.35881492567049 +3.819517005163479 21.83775701585827 +4.030629359322677 33.497725143422976 +3.9484190086207227 20.87570607240398 +4.154383590241057 31.872317921648484 +3.6259899517759853 23.38374483002503 +3.715019665426319 29.381437742744737 +3.6582099117022664 23.117528394646246 +3.7470760715507523 29.03521459815085 +3.722711912462859 22.595334636073705 +3.811124785009546 28.35791015474931 +3.6075916598128264 23.53739447555538 +3.6819100728909184 28.67831277159513 +3.6351928861062093 23.307335779489016 +3.709420126350104 28.390422053834573 +3.6904529257666594 22.85472163062361 +3.76441794274195 27.825072222818914 +3.9471132392037966 23.735644929015645 +4.4767982226879495 45.44952826690014 +4.058484015028822 22.80612358359776 +4.565354960610788 43.081385013224384 +4.278681064316986 21.096693619213227 +4.742665132026967 39.04922794847668 +3.8475828578480384 24.60531626586318 +4.213953890916272 41.47326249774689 +3.9346964673026523 23.84209915781836 +4.291886739351129 39.90029800586018 +4.107734387673601 22.409276869154148 +4.4459089158337655 37.06931203547723 +3.760194773117916 25.400427713010988 +4.0071302770356825 38.03520848800799 +3.8257576541865235 24.80108786789467 +4.069354499343646 37.01038376603931 +3.9564207152441555 23.656224038256532 +4.192577171756838 35.093359372670875 +3.628830590221926 26.65362572329798 +3.7333786287095903 32.96713448700653 +3.661686229986943 26.333483926766057 +3.7658989144799104 32.55115747373037 +3.7273745610895834 25.706893631190294 +3.830774870170644 31.740303488748822 +3.610055983206388 26.838616135220928 +3.6975139940147774 32.2509138384679 +3.6382181101226982 26.561690398742382 +3.7254702611653436 31.90461156014455 +3.694536332982547 26.01790893577716 +3.7812856419288456 31.226545077646133 +3.9554583667004457 26.4742658286521 +4.553249584019849 49.25356102042953 +4.06809806839755 25.392028696086335 +4.636120637985482 46.50841800117637 +4.290139451363941 23.413709183921608 +4.804511679300002 41.91671176973302 +3.854562008951723 27.49111426315316 +4.271919078799495 45.24241219828861 +3.9428837752726325 26.59851068559316 +4.347643616498462 43.39955759081595 +4.117831768792764 24.931397870146764 +4.4976342283594395 40.12823579736331 +3.7657704395272957 28.424490398515005 +4.049264278575017 41.74934232566381 +3.8324050325350263 27.720598524700048 +4.111054708754402 40.540540394670984 +3.96488189546363 26.38161268298664 +4.233239507758765 38.300962561413066 +3.6318915010755894 29.9029146189136 +3.7532901286594202 36.57644351445416 +3.6654247421260955 29.52438054999584 +3.786259324227151 36.082620705052776 +3.7323694757641785 28.785205208773593 +3.8519216857244345 35.12366506556005 +3.6127143918163 30.12191513637958 +3.7144341243432506 35.84424751001923 +3.6414760611887926 29.794150335316054 +3.742836198607223 35.43288123483258 +3.698919196172059 29.151822429772988 +3.799458819207397 34.629910715120936 +3.964173910165963 29.158782310815166 +4.634912241956944 53.08640155923203 +4.078079672274095 27.916637896735867 +4.710785906849352 49.926373655896874 +4.301908009413771 25.6598672953671 +4.868632724008338 44.73570525331599 +3.8618913644070556 30.33093745996967 +4.334274216497257 49.0510601109722 +3.9514402891131195 29.30173820267394 +4.407037872022774 46.9074695129167 +4.128288782280359 27.389596613619133 +4.551879167278277 43.15667664232815 +3.7716553696256327 31.41127422814225 +4.094740749875088 45.502739736952094 +3.8393946328149213 30.59616071209191 +4.155747514128309 44.08817012791144 +3.973714176909244 29.05222348457007 +4.276290577049268 41.49379575186964 +3.6351474801160117 33.131240299790946 +3.774797184805354 40.21108504841036 +3.6693936783488605 32.68983201376275 +3.8081837359252235 39.630750021743644 +3.7376515961152053 31.82992630594246 +3.8745605030803456 38.508305733815526 +3.6155454238517963 33.38694157407758 +3.732705554083098 39.45987351278162 +3.644939693514959 33.004337533159266 +3.7615406302356313 38.97634310948755 +3.703563085965852 32.256085873455525 +3.818936305619468 38.03559613615616 +4.296625019079373 3.293271335258404 +4.5612575105855635 26.30768999722597 +4.42855651399023 3.182216821934386 +4.68990929341998 25.188990530282908 +4.6917802622904095 2.9741783358302945 +4.942627549732541 23.134561042909784 +4.179646490089138 3.395681002962341 +4.335731020465086 21.193727856587167 +4.281979956888662 3.305887028147534 +4.438335353982103 20.570791296192223 +4.487191232717542 3.134335876875431 +4.642079116798669 19.37339959083259 +4.077801573755593 3.487957411912323 +4.166237752607801 16.819016601529768 +4.1541299673135414 3.4185241198787164 +4.243237608760101 16.480030910255397 +4.307611802603276 3.28384516706776 +4.397337088132671 15.812846073344028 +3.9264954869184865 3.6306166287805826 +3.952207414313577 10.337519133403795 +3.9641193061800575 3.594505282722959 +3.990013700728414 10.242429516183005 +4.039785989204787 3.523164301453667 +4.065991439131656 10.05266041742153 +3.905066242342837 3.6513773324305787 +3.9250512426296207 9.410498795319468 +3.9372296199345453 3.6202701878508803 +3.9573406431692937 9.336747021211977 +4.001887364060823 3.5586839731410285 +4.022219793479401 9.189488346364381 +4.299180055690194 6.559520240248411 +4.609782254575881 30.077486746226203 +4.43159949492609 6.332225339586545 +4.736642892261911 28.748065083223125 +4.695644594492346 5.907356566833625 +4.985734162906847 26.341032875851223 +4.181719311164899 6.769442878918336 +4.371244138568331 24.931466268641948 +4.284477275568951 6.585363374436852 +4.473480267277712 24.16573839327129 +4.490433658379738 6.234335672772711 +4.676156260153624 22.710414458071707 +4.079412948296973 6.958865919086213 +4.191558704625648 20.53883839076312 +4.156090937238276 6.8163103812072 +4.268688892220993 20.103842825771167 +4.310209655903305 6.540213866679509 +4.422834250834291 19.25517455898017 +3.9273436443210983 7.252238862953807 +3.9640557386327093 14.060809446094892 +3.9651662834983488 7.177915964294165 +4.00206810656324 13.923399101422934 +4.041214583739266 7.031207509396402 +4.078419455808144 13.650453765342895 +3.9057983756770014 7.294986741352777 +3.9351230583392263 13.138027041566403 +3.9381351360130448 7.230939959368254 +3.967596350015263 13.028483561746569 +4.003127829844909 7.104231371671823 +4.032817330522436 12.810634731155238 +4.303160058685162 9.785129864310884 +4.663561763113921 33.921619680609446 +4.43631487868625 9.434098582472322 +4.7882389345271985 32.35154051028514 +4.7015712990579175 8.780023843880393 +5.0331315136221875 29.55037588172244 +4.184963191098231 10.110055990898282 +4.410662164179316 28.733422637244328 +4.2883696350426685 9.825092826822676 +4.5124587992570895 27.804554143057487 +4.495446488044076 9.283163026964132 +4.713923648874847 26.05909391054723 +4.081944952554306 10.403866670097585 +4.219655360996911 24.310487242631833 +4.1591628995868035 10.182696446480877 +4.296981786107067 23.766341167662866 +4.314254591876748 9.755282020740646 +4.451268589845716 22.713463577925616 +3.928684427547246 10.860095400271206 +3.977166280486536 17.812819381066333 +3.966818929493996 10.744376678628045 +4.015478860063378 17.62803422256553 +4.043462805164146 10.516231308022208 +4.09237883160462 17.2623449518536 +3.9069567364626043 10.926695578632398 +3.9462609631667265 16.89055180090038 +3.9395659809228416 10.826923926445605 +3.9790044006708594 16.741060475756836 +4.005082950467711 10.629744722992353 +4.044729966089375 16.444677953261948 +4.308359167602159 12.96193323759639 +4.722762258387981 37.82718196876783 +4.442439251377028 12.478567042838407 +4.844668976532122 35.98326299550995 +4.709183393605422 11.581513360199862 +5.084519784150298 32.74394274200884 +4.189222716668599 13.410608320584435 +4.4541903076461065 32.59082204143865 +4.293457535427924 13.017050363258042 +4.555353372702086 31.475597279668516 +4.501940793562052 12.271153128641673 +4.755257063864418 29.404420865819958 +4.08528496199541 13.817385160390144 +4.250712578165823 28.12788474000985 +4.1632012790888515 13.511082652449604 +4.328231177684741 27.45932019300212 +4.31953597090209 12.920778862875919 +4.482622568191037 26.176384053113114 +3.9304652374976032 14.451086259247003 +3.9916450530083654 21.590609023905532 +3.969010198094315 14.290112850342625 +4.030330898334061 21.35243365446402 +4.046433557958165 13.973224939243893 +4.107911183354786 20.882707000719847 +3.9084967587189126 14.543806846088152 +3.9585570144994673 20.665542064890833 +3.9414654816381334 14.404925427230907 +3.99164015237632 20.47113700368933 +4.007670834617597 14.130812903828792 +4.057998638327622 20.08679420442106 +4.314593289311213 16.08479865907844 +4.787465986317709 41.7844708431645 +4.449738445558055 15.460066057018551 +4.9058284539343475 39.631006707538816 +4.718150918141123 14.306038276746019 +5.139560392624135 35.90850467988292 +4.194358408782314 16.666588263149766 +4.501974165566619 36.497765470411906 +4.299562477274956 16.15616881545891 +4.602187290916054 35.17050592603495 +4.509660416184819 15.192624441166839 +4.799987867251818 32.73568262134517 +4.089331675366897 17.195664769050456 +4.284870414459436 31.98737291341701 +4.168076262955337 16.797126614294193 +4.362507379081623 31.17726951000329 +4.325865504639117 16.031526579584913 +4.516839858919601 29.63599342253976 +3.9326388713011657 18.023028182534322 +4.007572657336246 25.392649781415052 +3.9716798826241693 17.81249450724931 +4.046684651799673 25.094273285217596 +4.050039519296111 17.398772966547458 +4.125035844333549 24.50784381317646 +3.910378481007197 18.14440959093246 +3.9720809142520923 24.4617018907903 +3.943782760942437 17.962629741241745 +4.005557470777624 24.21675577093361 +4.010817866631688 17.604395343632152 +4.072644756059316 23.73385404426318 +4.321699598469063 19.150560751320775 +4.857695093974208 45.78562578187266 +4.458007140206941 18.375413635669645 +4.971562617170429 43.284986993282764 +4.728190657278289 16.950889529102664 +5.197902856582163 39.03439410813861 +4.200246074169866 19.875045593280998 +4.5541217472395115 40.450148872229896 +4.306526439443518 19.23929840243234 +4.652946405635797 38.88297177512342 +4.518381834098764 18.0444504040082 +4.84792495273516 36.04495343457679 +4.093994615263853 20.536137246103173 +4.322242688918609 35.88689592573789 +4.173672121133249 20.03795502085618 +4.399853989699975 34.91642220678195 +4.3330767736166 19.08435005085733 +4.553842675562211 33.08653491827151 +3.9351629809011572 21.574334560096187 +4.025014548994394 29.218392274029743 +3.9747739894760534 21.309637491441173 +4.064586026755489 28.852298372869424 +4.054202619181402 20.79050319574479 +4.143759423919959 28.135336096057976 +3.9125660741608184 21.727104587064968 +3.9868888958817577 28.278595523905857 +3.946472178536453 21.498361487455167 +4.020797389607336 27.976902606531315 +4.014458185475754 21.04834783202135 +4.088678501932496 27.38385474758361 +4.329535244712697 22.157408327086788 +4.933422152642755 49.82379897682418 +4.467067246280898 21.223047987618894 +5.04168007938442 46.93700490653154 +4.739062727332439 19.515382841231915 +5.259198795388077 42.11442973709318 +4.206775850808865 23.034115832672793 +4.610714301750667 44.44499604445461 +4.31421062830084 22.264611537231826 +4.707590076121611 42.608011804026255 +4.527912114446981 20.825228789349424 +4.898866198670978 39.326212283738975 +4.099193167997394 23.837061748985416 +4.362926884422728 39.82550132689772 +4.179886304053361 23.231717801394495 +4.44029728259132 38.6741838696082 +4.341023899996514 22.077452027125233 +4.593540837086557 36.52373496110398 +3.9379996819794485 25.103839329770985 +4.044026772252479 33.06801409125024 +3.9782442636156374 24.780182615429407 +4.084071943511152 32.62602759150899 +4.058853152509948 24.146770517112582 +4.16408103017694 31.763662153383848 +3.91502737896167 25.290850909814093 +4.003028688518715 32.11642945155017 +3.949492913450399 25.010895741919803 +4.037392922153376 31.751250861322422 +4.018532866176226 24.461151128736695 +4.106103388073143 31.0355920070504 +4.3379756718174205 25.10449016694941 +5.014574421940767 53.89260211386381 +4.476765341739159 24.00253454078868 +5.115960767150345 50.579908803984864 +4.7505658054290425 22.00018229644259 +5.323109897501271 45.143233784614196 +4.21385090755111 26.14271433719897 +4.671811614908573 48.479997040453185 +4.322493864906903 25.23121967869293 +4.766057041030193 46.34148728996411 +4.538085995994649 23.534746686196883 +4.952604993078196 42.57478016051697 +4.104855746011179 27.097298976797802 +4.407009871636436 43.80299976330104 +4.186628237413981 26.37730192129916 +4.483851509566779 42.448751129838925 +4.349579814887432 25.010010619380825 +4.635836854282417 39.944346171474635 +3.941114960929954 28.610681302073804 +4.064659573078912 36.942249885908225 +3.9820474914318176 28.223151564744022 +4.105173746818253 36.41555444634886 +4.063929037015564 27.46646018890997 +4.185995360180586 35.39194636975163 +3.917733624046415 28.834872219165515 +4.020542655345416 35.97590562392116 +3.9528083555173827 28.49933437580822 +4.0553720450507935 35.53998990269578 +4.022989296948752 27.841743433160314 +4.124918976920377 34.68844201752419 +4.346912691614069 27.991645849477145 +5.101035156353174 57.98572516983114 +4.486969896104842 26.714230173922473 +5.194161081301802 54.20725009880993 +4.762532197616514 24.40684701876963 +5.389312398800658 48.11677322918954 +4.221386221529217 29.20032821285695 +4.737454119353828 52.55316346762366 +4.331270741817563 28.13891166583226 +4.8282684813969325 50.079770902397954 +4.548762709735426 26.173616700147527 +5.008934140149667 45.78694401186605 +4.1109189228863565 30.31615654535758 +4.454571223219718 47.81971374996585 +4.193818209256498 29.4741364746727 +4.530521915317156 46.23883809144059 +4.358634268246252 27.881904517917704 +4.6806288973386545 43.3458362015043 +3.944478367104196 32.09423078196513 +4.086959834246699 40.84227173018523 +3.986145010778272 31.637853319908942 +4.127919448551489 40.22140740023779 +4.069375114290699 30.74885319462562 +4.20949463541207 39.019783580287566 +3.9206589389697264 32.358588638434746 +4.039469917112389 39.85811820246798 +3.956385686979039 31.963026410808308 +4.074759677981428 39.343704696199126 +4.027780609774816 31.189405327614836 +4.145122610398154 38.342209320491946 +4.356252575254212 30.819214872336946 +5.192643394105068 62.09667738489361 +4.497568732097182 29.35904869432017 +5.276017996669405 57.81306697981264 +4.7748230334671575 26.737518021418918 +5.457499838983739 51.03204528887613 +4.229307268200338 32.206868446478566 +4.80766309478213 56.662564351701874 +4.340449796027706 30.9879680938694 +4.894129680472645 53.819512387916156 +4.559822934380409 28.743023160206185 +5.067648180045466 48.95969828825686 +4.1173265330468665 33.49327877227826 +4.505684980656787 51.87628301455961 +4.201386170660928 32.52205436463562 +4.580306275275861 50.04347459044055 +4.36809185855169 30.693518190115327 +4.727812498396633 46.72616793971989 +3.9480625661428976 35.55403484589976 +4.110972790235917 44.76960147933402 +3.990502313387137 35.02381686650323 +4.15233525144288 44.0444468052939 +4.075142384423624 33.99353002072068 +4.234569832812026 42.64711131314697 +3.923780076301686 35.86157297697184 +4.05984786075657 43.76447840832543 +3.9601955626878804 35.40151070510793 +4.095579046221573 43.16328723332317 +4.03286502972696 34.503677854130736 +4.166710533066029 41.997015827369815 +4.673732119629226 3.4530151016608976 +5.049539232584255 33.63696183559573 +4.834885143362421 3.3210978099690216 +5.201039426506809 31.869249867019203 +5.155106239550652 3.077303013093649 +5.497447059913667 28.749536642281427 +4.530588937093372 3.5755926575018986 +4.747999032893874 26.849499741640017 +4.655822458688125 3.4680697766641715 +4.872375251253572 25.89649423630904 +4.90638033805947 3.2645823966737053 +5.118011320707646 24.099713332197293 +4.405850288382115 3.6866976221728076 +4.526521989505727 21.081670736849837 +4.499343219737954 3.6030428161809263 +4.620743925972105 20.579489948141887 +4.687165576693484 3.441775367434582 +4.8087762917353585 19.59878178109411 +4.22051994468607 3.859462957416747 +4.254081967152926 12.598895374101671 +4.266592127790016 3.815631800808383 +4.300419595203669 12.465866713351463 +4.3592789051292415 3.729231591712585 +4.393544872247024 12.200480476287487 +4.194285862114022 3.8846883755461246 +4.220129837539757 11.389440356778458 +4.233662952294935 3.8468985200334034 +4.259691654718055 11.287344915552703 +4.312852354248535 3.7722153500056255 +4.33919847784346 11.083501373077327 +4.676408386942586 6.8771510337908115 +5.1065846651255935 37.68109700751374 +4.838051135876946 6.607991808288936 +5.254731449457154 35.63587800013423 +5.159078187516001 6.111583110064739 +5.5451680777171415 32.079219143809716 +4.532773375456567 7.127625550816569 +4.789402597378159 30.85350486661281 +4.658440260221136 6.907894034327297 +4.912854089538677 29.716151003973813 +4.909744130963029 6.492801713029063 +5.156349172261012 27.59720805654695 +4.407557426327994 7.354978211100013 +4.555777070795552 25.05837964682093 +4.501412556818073 7.183767575468734 +4.649972240173506 24.43503528541024 +4.689885144836241 6.854202008666799 +4.837676742587276 23.22894509036776 +4.221425229584399 7.7091378525562115 +4.267605344555467 16.57223559253033 +4.26770762015223 7.619211927939285 +4.314155059260014 16.387293819697263 +4.3607953248323845 7.442098158722161 +4.40765170772895 16.02023596920383 +4.195068089272858 7.76091467989913 +4.231609029931678 15.36725226667 +4.234628969261465 7.683354884162702 +4.271364011713206 15.221378576154843 +4.31417156438044 7.530187066787409 +4.351221774344455 14.931420266058215 +4.680584554387812 10.2576811196785 +5.169313880984121 41.8052222196605 +4.842964480922546 9.84358425234943 +5.31348697104094 39.446842098734514 +5.165176256673213 9.0821875169875 +5.597168621220117 35.4080675654123 +4.536198789762039 10.643872535680485 +4.835059049299893 34.92908048872113 +4.6625276931014366 10.305037592669377 +4.957410486584925 33.58280005763233 +4.914951792845729 9.66664228567783 +5.198475319171212 31.10497016405907 +4.410245833231266 10.995140635213223 +4.5880438936040395 29.093702751696842 +4.504660920970115 10.730548821832834 +4.682243439437657 28.33348556992944 +4.694126935499567 10.222338446662862 +4.86965148768014 26.875696403972317 +4.222859927178192 11.543755349117676 +4.282480901058191 20.578471095289192 +4.269472727419045 11.404287260245988 +4.329339073350974 20.335763799646625 +4.363187213087692 11.129931821251905 +4.423383985546395 19.85605561785317 +4.196308876696229 11.62410955224816 +4.24422776138501 19.373743715230518 +4.236159229967505 11.5037561948792 +4.2842655710630355 19.179306065394048 +4.316255713686457 11.266329583027375 +4.36464185381003 18.794151590794222 +4.686048172301919 13.58569319349137 +5.237819636646127 45.99340115241294 +4.849353964256772 13.017844057126656 +5.377192921594614 43.2833131503818 +5.17301484524348 11.977753985485082 +5.653081002995299 38.71581331047192 +4.540704622250692 14.116733990599815 +4.885152779630951 39.06564920550227 +4.66787883489252 13.65073496815126 +5.00608969212658 37.482813492352776 +4.921706260440702 12.775683282508089 +5.244217781250487 34.60623746879117 +4.413799210909703 14.60101572826436 +4.623505051160422 33.18045427701426 +4.508938971875946 14.23611846184816 +4.717660695328199 32.265286068878744 +4.699673600393109 13.537165590450565 +4.9046587801205765 30.52621354784599 +4.224769935711618 15.359852080836294 +4.298818372556628 24.614175245359558 +4.271818386261256 15.166650495484022 +4.346058447108332 24.306767675395047 +4.366354363386839 14.787173923686337 +4.440781549984434 23.701516236912138 +4.197962427748194 15.4712558956445 +4.258081651190338 23.405965487750795 +4.238195415554204 15.304421776982787 +4.2984739381820765 23.157266662594978 +4.319020370804453 14.97573761852897 +4.379499241518605 22.666184210289753 +4.6926078868303875 16.855547131121103 +5.312103182982508 50.23298573651199 +4.856976951804374 16.124711479240005 +5.445661407084718 47.130706349239915 +5.182254959975293 14.792089555008205 +5.712509824886578 41.98804824524939 +4.546145669736183 17.541209895661122 +4.939805173453007 43.25558448167982 +4.674308094782732 16.939411180516004 +5.058876129091678 41.40601701366585 +4.929742576308229 15.8137625509991 +5.293363524975639 38.08887555547149 +4.418112119519092 18.16841003587037 +4.662297194349358 37.31396343012832 +4.514111554404675 17.69565525871503 +4.756281423737992 36.22370935050143 +4.706329469636074 16.792997058491352 +4.94261822969022 34.17128343243087 +4.227106314709713 19.154965213977786 +4.316701785474545 28.677412950228263 +4.274682080656199 18.90334250276618 +4.364375084813331 28.29746673497208 +4.370205958373554 18.410001596355702 +4.459861470709673 27.552249387798412 +4.199987353307907 19.30019494368338 +4.273243687538506 27.462276366906696 +4.240684752009647 19.08274239890773 +4.3140448760403185 27.15286762531637 +4.3223890113503325 18.65499891688232 +4.395814484093129 26.543812191959127 +4.7000934453070915 20.06374136377688 +5.392097159533051 54.51322502410207 +4.865619644442993 19.16070180576205 +5.518655878685656 50.977165977739226 +5.192604603859601 17.522282544446057 +5.775057893133097 45.214290040942856 +4.552391782708476 20.91400694030914 +4.999095492931612 47.493078920003015 +4.681650183272974 20.167577971960224 +5.11571449516163 45.34442101508327 +4.938828440527271 18.777470845390337 +5.345680167637851 41.543804818653534 +4.42308972454572 21.694433869874416 +4.704529058808039 41.491215026417905 +4.5200573570822975 21.105945388758947 +4.798134710036161 40.20386090641164 +4.713920580816752 19.9863234757471 +4.983427914007613 37.80405260454898 +4.229824841331023 22.927289145653678 +4.336199667559668 32.76728666792146 +4.278007337735609 22.612225010169553 +4.384335910427735 32.30615859727246 +4.374660167673016 21.995737999451208 +4.4806275523485155 31.40525330001218 +4.202346278385919 23.109331827978824 +4.289773089250127 31.54194932513142 +4.243579554548501 22.836812939045785 +4.331020935539747 31.164720955678913 +4.326292661590831 22.30168691300246 +4.413596437308512 30.424536974395334 +4.708354715891608 23.208258410767584 +5.477676375977656 58.824442628826276 +4.875095770571782 22.124093102520476 +5.5959058590203625 54.81270695260358 +5.203815778570509 20.167590895300435 +5.840339676519533 48.38685013059463 +4.559327158240996 24.233028603865453 +5.063070951891199 51.77344684539552 +4.6897591649545864 23.33319720758961 +5.176520691119292 49.29147288257197 +4.9487625730929565 21.66526886039081 +5.400927569348124 44.96408325205119 +4.428646972798589 25.177113336995323 +4.750290981202875 45.71032301364301 +4.52666824415266 24.464904229808603 +4.843230476531731 44.20208224414831 +4.722293675102667 23.11514464133431 +5.026973497535074 41.41929603992597 +4.232885687009276 26.67548554862983 +4.3573707706148666 36.88367083181346 +4.2817433548602795 26.291742688953278 +4.405978393935513 36.331965286571 +4.379643479146347 25.54251204122984 +4.5030755825460025 35.2584935519493 +4.205005431733529 26.897469917476627 +4.307720270206512 35.644941932693925 +4.246836888131049 26.56522869366105 +4.349436272832188 35.192173768067676 +4.330669220170007 25.914068543138026 +4.432846845841756 34.30672085924883 +4.717260214965987 26.2881398659431 +5.568663389379966 63.157517020111946 +4.885244305123327 25.01439695002987 +5.677116358690629 58.62868017923167 +5.215679901956297 22.72872284917528 +5.90798871063697 51.50010085028639 +4.566849205112066 27.497044165398695 +5.131751509930848 56.09264583240812 +4.698507051887562 26.435267634440947 +5.241187942492418 53.241568351151 +4.959372022416832 24.476913841373506 +5.4588645801345965 48.344317239130966 +4.434707931372787 28.615145728723594 +4.799660307385905 49.970168997971086 +4.5338482015064425 27.77126641516701 +4.891564668020985 48.2155524691351 +4.731314673718275 26.17853570547558 +5.073133467462567 45.01294086553337 +4.2362528729676185 30.398557811888697 +4.38026767853874 41.02703181675265 +4.285844347084193 29.940766631736388 +4.429333963019667 40.374622845799564 +4.385090037701072 29.04904520904644 +4.527196446821204 39.11063676846229 +4.207934412607682 30.663709213353673 +4.327129986198721 39.77174087005314 +4.250418006335939 30.26695117642434 +4.369319643580019 39.23512647886992 +4.335462902409953 29.490922160103302 +4.453563094652249 38.18935597108936 +4.726695304678029 29.30319379902419 +5.664831988234559 67.50355022938776 +4.895926769693169 27.831994836340616 +5.761974755984846 62.417436653703284 +5.228022958262762 25.20735196094682 +5.977661423746748 54.5499721593961 +4.574867427496815 30.705462094404048 +5.205131703832796 60.44692430137483 +4.707782079431145 29.473540335115043 +5.309590326039908 57.18971789233107 +4.970509024823244 27.213066288542105 +5.519253218633604 51.68026447519619 +4.4412049190709695 32.00773047042714 +4.852704393388253 54.27013377455462 +4.541512325474122 31.024373574579297 +4.943122213360518 52.24200297935672 +4.74086681248618 29.176348564451853 +5.121782287298018 48.581740738030206 +4.239893996141359 34.09577073743648 +4.404939239742134 45.19830010018509 +4.290269122273011 33.55849229163019 +4.454430248168218 44.43433217022588 +4.390941009365836 32.51450434801963 +4.552978096182867 42.96086391752958 +4.211105721784263 34.40737167842729 +4.348043470618732 43.92325194008115 +4.254287973731771 33.94122092313502 +4.3906963956539276 43.293904326848036 +4.340623726406517 33.031412231725234 +4.475739969413595 42.07190211487484 +4.736560340531015 32.25378555352571 +5.765910009367956 71.85367399836173 +4.907024801206264 30.577882914188503 +5.850156540732119 66.17211647108549 +5.2407006763920325 27.60578030515503 +6.049039154325536 57.53359735098646 +4.583302182635626 33.85816745936055 +5.283180832439727 64.83255890287252 +4.7174869407563325 32.44831515644625 +5.381585085175357 61.131318649324484 +4.9820481509742285 29.875015047983464 +5.581861184939495 54.968556284497176 +4.448077771227765 35.35444794049514 +4.909482100991036 58.6098888956438 +4.549585690152567 34.22402215065331 +4.997878572523095 56.27950762916024 +4.75084872556279 32.10899845881136 +5.172792327390342 52.12304628262677 +4.2437798064524905 37.76659029035518 +4.431432282208308 49.3987756669704 +4.29498070866978 37.144365155246966 +4.4812926060930245 48.51164815260367 +4.397143846941279 35.938396009696895 +4.580406809476543 46.80873390177448 +4.214494504044055 38.12795302032991 +4.3704999506702285 48.10071874569099 +4.258415375415756 37.58749450062663 +4.413589844139106 47.369162181210584 +4.346106910126444 36.53499894558234 +4.499370807200539 45.954167872788446 +5.042612481753301 3.5923887484119437 +5.55966209713504 41.98678809203002 +5.234624259239879 3.439529496231852 +5.7296436030247175 39.30092524941374 +5.614441339985734 3.161001354836032 +6.063445018273508 34.78421616768239 +4.871691120230137 3.7355767879381143 +5.165862159260733 33.259162082989036 +5.021243871616503 3.6099183330222666 +5.311960616833712 31.850525118999116 +5.319637425670498 3.374479799495841 +5.598974896447084 29.259368000614007 +4.7225605844442615 3.866185095474462 +4.882832830848435 25.878372184061078 +4.834347674769884 3.767777951866454 +4.995175007001321 25.158368777618932 +5.0586368488050715 3.5793121224068503 +5.218588852566084 23.766671144287958 +4.500903517105464 4.070531640762237 +4.543760267488788 15.108930743163958 +4.555998498718202 4.0185642280187395 +4.59922191819947 14.92813578178452 +4.666859354733204 3.9163654346547263 +4.710666168315661 14.56770826732679 +4.469538649630857 4.100472620261524 +4.5022692007952525 13.57927755603757 +4.516619086412296 4.055627230001664 +4.549607688308489 13.441846675043193 +4.6113279637816085 3.967166727086067 +4.644748853509312 13.167516594753156 +5.045391113008557 7.15425567494232 +5.625606133148065 46.286473772789236 +5.237890046020143 6.8431327837493034 +5.7900481137673605 43.24565523187864 +5.618492985261896 6.277323998105751 +6.1150889495789 38.20742435652209 +4.873972859495433 7.446112303758144 +5.213479663664344 37.51702836133804 +5.023963832922081 7.1899635778978865 +5.357837500478731 35.87504875380965 +5.323097814545246 6.710866803247254 +5.641297890626531 32.892005961689264 +4.7243532421175525 7.712697052009535 +4.9161862114435655 30.099034784651373 +4.836512041586625 7.5118047744840934 +5.028262383330979 29.228982983862704 +5.061458862756775 7.127622435137217 +5.25082468218436 27.563488632860214 +4.501861359460369 8.130530179667721 +4.558983194566306 19.316794973931437 +4.557176610598458 8.024183005830732 +4.614657018037813 19.073643977547178 +4.668454851581532 7.815217053500032 +4.726455612459774 18.591570997689775 +4.470367129328447 8.191829672565088 +4.515171502204685 17.79158345628619 +4.517640661094812 8.100023295097559 +4.562708959049257 17.601734029313924 +4.612718632706399 7.919061636868084 +4.65820123198007 17.224592027693852 +5.04973294268895 10.669885475941866 +5.697577438689202 50.66897713339635 +5.24296399821203 10.192695602956839 +5.855616108816578 47.23260345628276 +5.624717954290957 9.327376587002481 +6.170947241640462 41.62592195379034 +4.877556536284087 11.118473586346305 +5.265684063380468 41.85287260708051 +5.028216698104986 10.724718906524023 +5.407996648436072 39.94849649711203 +5.328460558274293 9.990133494883656 +5.687459611835674 36.53286762059353 +4.7271813634202715 11.529054945644626 +4.95278516806364 34.38478973034262 +4.839915183706477 11.219574723398326 +5.064580313333964 33.34617929876421 +5.065866372153742 10.628996156005364 +5.286245136211107 31.37697711496819 +4.503382509686748 12.174254474033969 +4.575646129947554 23.561686805223545 +4.559044546660886 12.009840050320681 +4.631629132760244 23.24948396201041 +4.670976145770175 11.687170151176495 +4.743957630725897 22.633326319716883 +4.471684065693054 12.269086833403561 +4.529284698855879 22.03622418791163 +4.5192622681720325 12.127076340187763 +4.57711305546527 21.78851071900912 +4.614919918861467 11.847454365033109 +4.673126495376003 21.298301633309855 +5.055419963392195 14.129698964415267 +5.775570195938042 55.11508419883419 +5.249568551424798 13.477522991913428 +5.926143415484363 51.240497207720544 +5.632724253413689 12.29922395956546 +6.230595709545263 45.01826196733309 +4.882277151134824 14.744431772329792 +5.322629100678156 46.254185097440526 +5.0337911073062935 14.204754161534364 +5.462438721654581 44.055271940555485 +5.335422082263237 13.20120802476918 +5.737241187169714 40.163689383245504 +4.7309254262394465 15.308535532462681 +4.992806560279497 38.7273186837646 +4.844403583055944 14.883205153851387 +5.104216538716912 37.4990602304493 +5.071636384413329 14.073745810046818 +5.324782297608378 35.19292879948919 +4.505411523568309 16.19789436167802 +4.5938617339474686 27.839708336693334 +4.561531454958039 15.970917379616674 +4.650226090836833 27.450557677674656 +4.674320297781258 15.526152266252115 +4.763209439893795 26.685787545436302 +4.473442568613942 16.328920600694623 +4.544707321484448 26.309846928071586 +4.521424118944219 16.132739767021935 +4.59289915585857 25.99781337729891 +4.617845118083539 15.746973578567959 +4.6895640782368515 25.38247428057733 +5.062254677066698 17.527599396919705 +5.859481555947673 59.609018987265124 +5.257454209429687 16.691122744527885 +6.001356657066176 55.25268055239741 +5.642166153156423 15.186338255491854 +6.293594668740433 48.3692438190014 +4.8879845186746325 18.318537957269683 +5.3844018635236655 50.71146851907804 +5.040495447542853 17.624036934912688 +5.521102820268699 48.18341409342723 +5.343710396792351 16.337513557624508 +5.790387578648017 43.77114532835466 +4.735476321869133 19.046536273656393 +5.036380105957116 43.12089576852673 +4.849837410978275 18.497432605225534 +5.147212063549512 41.679676043188984 +5.078566998772577 17.455729286810584 +5.366330435204318 39.00095656401291 +4.507897910088897 20.19871858256391 +4.613716735183193 32.14852449451442 +4.564572754724435 19.90414162027304 +4.6705105047953746 31.67352065789145 +4.67839333005802 19.327956567903176 +4.784225107963174 30.743934557996987 +4.4755999753639255 20.368933486284455 +4.561515066678203 30.610472185504737 +4.5240717442970055 20.114122051920535 +4.610124323803158 30.22682258702704 +4.621415135171362 19.61385434437507 +4.707533096210304 29.472844681211352 +5.070060524365141 20.859782597529385 +5.949134807340454 64.13705547957447 +5.266400202403102 19.829745274008108 +6.080939880415307 59.255575395341296 +5.6527451137730385 17.985640978904673 +6.359513341077132 51.66790404630075 +4.894543290789729 21.8371824637159 +5.451042610671938 55.21706533824221 +5.048158233843317 20.97877336039872 +5.583887201944051 52.32329550429155 +5.353086173860762 19.395396894448307 +5.8466288982624794 47.345223275864875 +4.740735332830583 22.73986518795917 +5.083605829168903 47.56149041765331 +4.856090460360498 22.058727673194646 +5.193578144665138 45.88199362523777 +5.086477858508346 20.771137030030644 +5.410762872848514 42.79320219753345 +4.510795762856551 24.174710037604264 +4.635282070291262 36.48688900601826 +4.568109744450976 23.80713059234159 +4.692529617061295 35.916224184539644 +4.683110099495511 23.089617590846686 +4.807005012682314 34.804196398745084 +4.478117525901765 24.38734104046393 +4.579769615790187 34.93708246984647 +4.527155623658013 24.069096584294222 +4.628832093403087 34.47377865899272 +4.625558198100686 23.445400858414402 +4.727040585991371 33.566428027802004 +5.0786811815916115 24.124045262395093 +6.044291854330868 68.68673139327908 +5.2762135828424785 22.891528879285126 +6.164550512105538 63.237815562013616 +5.664207079695767 20.696343150055508 +6.427942179006728 54.906326753118634 +4.901832470479976 25.298054223115784 +5.522554282278688 59.76444077256277 +5.056627434023422 24.266735009236736 +5.650660386507368 56.46686294432384 +5.36334143240669 22.373200767379398 +5.905691954522837 50.87827331615739 +4.746613455427376 26.386328320178542 +5.134563178694721 52.04621671549102 +4.863049682747277 25.56478766938752 +5.243305259612673 50.10127829910979 +5.095209469491383 24.017786531758702 +5.4579411584494935 46.563580833988624 +4.514063506908594 28.12436317794095 +4.658618578089628 40.85436671541857 +4.572089325883425 27.67813987456314 +4.7163208013137865 40.17738916564327 +4.688393666622191 26.80904929881932 +4.831541077955187 38.86403324293586 +4.480959999962153 28.382794931724018 +4.599523590300031 39.28938229458405 +4.53063092188312 27.99608446849267 +4.649057277138384 38.73769943929649 +4.630209347299749 27.23967509006978 +4.748086103451089 37.66115766468291 +5.087979260218067 27.319332603357136 +6.144661579837696 73.24638553595408 +5.286727100706682 25.87593874939765 +6.251829995614694 67.1897065904853 +5.676338060332687 23.31919059415466 +6.498499162359927 58.07886021168022 +4.909744408411691 28.69978728336521 +5.5989071058594115 64.34769969086639 +5.065769238080356 27.486818889712605 +5.721267748116195 60.60714687910058 +5.374297035236377 25.27065750226946 +5.9673070243157476 54.364391516101335 +4.753030862801816 29.984468210195743 +5.189316089208498 56.572953703338825 +4.870614264676554 29.014205369918443 +5.296368225388282 54.33368294687755 +5.1046217895363455 27.194660625896365 +5.507720427347832 50.307287429983866 +4.517663382149387 32.04654915154942 +4.683780590520658 45.25114437488361 +4.576463393665444 31.515894632458778 +4.7419149601593675 44.456385134788135 +4.6941747919778605 30.484816797164378 +4.857819909385076 42.921657565797986 +4.4840955224722725 32.35427394170847 +4.620823696538416 43.66763483454232 +4.534456956381929 31.893910381335374 +4.670828961026172 43.018189276322886 +4.635309940270503 30.995301729684712 +4.77066448860684 41.75564225146118 +5.097834644119621 30.445424880560555 +6.249906760095835 77.80489043858427 +5.297796599876029 28.783379071063944 +6.342411885467098 71.10288174605598 +5.688959327065734 25.855952708402672 +6.57083251067301 61.18156948742607 +4.918183804793554 32.0417172876252 +5.68004054511642 68.96123927283836 +5.07546644285965 30.638742422151044 +5.7955356883417135 64.73793104616259 +5.385799636838932 28.08847044753578 +6.031212048003808 57.79899899416192 +4.759916081735295 33.53338135852578 +5.247915733172577 61.140063708923606 +4.878694718368583 32.40624084501974 +5.352729171159108 58.575954800476225 +5.114592524633682 30.30158774883558 +5.559952724662382 54.02045839516638 +4.5215612142541906 35.94042922712588 +4.710818353516321 49.67789548812779 +4.581188459027125 35.31947956307129 +4.769338762388507 48.753072822268834 +4.700391322550736 34.11597758204601 +4.885824784588097 46.975837738915665 +4.4874951198779485 36.301003378688506 +4.643712861265388 48.072545748190386 +4.538596872421009 35.76170873021177 +4.694172500859399 47.3153033282347 +4.640807176197426 34.71133296294132 +4.794767648374482 45.84899486568745 +5.108142622873195 33.50271165100861 +6.3596508061003565 82.35152480116909 +5.309298648584484 31.61491960402189 +6.435928480590757 74.97007677493421 +5.701922585656445 28.309065989171756 +6.644621618349742 64.2118441749145 +4.9270665142637355 35.32370551237252 +5.765863937598826 73.59950114067223 +5.085616731205672 33.72282373168372 +5.87327476861706 68.85353235751367 +5.397718875301129 30.828020261340516 +6.097155093953286 61.178542591671196 +4.767205336854856 37.03258725310013 +5.310401742318907 65.74617337091865 +4.887211802233849 35.74065659888136 +5.412339163285445 62.825221197000026 +5.125015226873074 33.33901178911564 +5.614489133709104 57.69993242010654 +4.525726012312202 39.805389432724084 +4.739779725692727 54.13567925760768 +4.586225309004975 39.088258480002025 +4.798616160556264 53.06768652732749 +4.706987513655924 37.70196684405113 +4.91553694036566 51.02575408041819 +4.4911324767498675 40.22240259950035 +4.668231748577706 52.50517660324102 +4.54301736984926 39.59885502691146 +4.719110899638165 51.62944644646137 +4.646653549512475 38.387150425705684 +4.820385727925158 49.94070710893096 +5.40531957176294 3.7146235772942324 +6.097623813952628 51.362718402481356 +5.629617861163018 3.5409711691538406 +6.2784373572920495 47.425102696811386 +6.071158038917842 3.2291340726885 +6.640522345445429 41.15841308508025 +5.205164749537758 3.878673378207228 +5.5937589924678885 40.44384808098184 +5.380319007358236 3.734639141878251 +5.760460672338562 38.42048326531987 +5.72870531791776 3.4675869507391592 +6.086724424104033 34.808994275892445 +5.030258652785904 4.029312226914656 +5.238434367189688 31.2210988042393 +5.161385665494709 3.9157305666107116 +5.369517452130821 30.215908874109434 +5.424062801609332 3.699705498794582 +5.62916828051928 28.29809307073399 +4.770109111385462 4.266537279738735 +4.82383594346378 17.87049898571484 +4.834770135326843 4.206056753268668 +4.888981505816101 17.630570683414764 +4.964891099564678 4.0874088069104255 +5.019838991791197 17.152770586842852 +4.7333047583966685 4.30142336092554 +4.774037233364231 15.982518914911504 +4.788552163934553 4.249181922051259 +4.829630868984269 15.80174062478353 +4.899713017463396 4.146335021901777 +4.941354886374946 15.441052510360361 +5.408184768834356 7.397268199122454 +6.172579658959372 55.89367071289331 +5.632964540021106 7.044526641520346 +6.3450456421589845 51.51659092516331 +6.075268399489758 6.412233194425149 +6.6953467297750215 44.64965420920644 +5.207531584039901 7.730963164020344 +5.647899693392787 44.94417635689913 +5.383125741495747 7.43795749285415 +5.811726950442294 42.63060053206229 +5.732242447632447 6.895602748965816 +6.1326879698349215 38.553765353819614 +5.032128050995383 8.037793545289311 +5.2760610257656495 35.67534658907088 +5.163633725794547 7.806405835849813 +5.40653588204892 34.48707350602949 +5.426971160841319 7.3669458207949035 +5.664642101077935 32.242683800122926 +4.771115615403666 8.521838905744827 +4.8407855259650185 22.300121294274174 +4.8360058275274485 8.398329765984617 +4.906136355327344 21.986513539174137 +4.966558176146588 8.156238521245372 +5.037314144311528 21.365607241199914 +4.734176221524511 8.593113112189979 +4.788380215649777 20.416265945532928 +4.7896250798504285 8.48638975654655 +4.844174611217117 20.173763518174912 +4.901168896653719 8.276439230025067 +4.956239744845796 19.692398648299058 +5.412666707964139 11.031336748101282 +6.253775874696591 60.50674624225321 +5.638168673920587 10.491669023821917 +6.4168134836635025 55.64641991909838 +6.08158685834187 9.526935005268188 +6.7542897838280656 48.13254317516726 +5.211253695033585 11.542909566440748 +5.706937753116001 49.52766172697398 +5.38751914257526 11.093660157006711 +5.867434497599345 46.89030363815246 +5.737728409382623 10.264151490633742 +6.182499608179052 42.30426752575033 +5.035081579129258 12.01423924801792 +5.317164504281887 40.20079452709602 +5.167173175776032 11.658713705821457 +5.44695779200098 38.80790454863507 +5.431518355634367 10.98490208862704 +5.7033852366539985 36.20352222679113 +4.77271684585521 12.759715370564301 +4.859261469250553 26.77085463546818 +4.837968352289288 12.569270410275374 +4.924913624255154 26.37597965246567 +4.96919664170284 12.196434913395496 +5.056582867579011 25.5980175732188 +4.735563958939737 12.869689383714508 +4.804003933897817 24.885970276186647 +4.791331135416341 12.705038280558187 +4.860092192496826 24.575605640614985 +4.903477110769129 12.381476381457015 +4.9726687633162685 23.962097381559353 +5.418542676289974 14.606652812842798 +6.3410854548013456 65.17935795117197 +5.644947576539755 13.871125017142916 +6.493447179954796 59.79125179504734 +6.0897166994015866 12.560832906461867 +6.8168839154215295 51.584842282517926 +5.21616221273173 15.305707365732296 +5.770985274111867 54.179713118040624 +5.393283226746309 14.69172010861936 +5.927533160377962 51.18207801444491 +5.744854417472381 13.561601000196562 +6.235896637705328 46.04098828889923 +5.038996845845236 15.951409123121056 +5.3619123182016075 44.7879523697274 +5.1718468575450744 15.464205954853904 +5.490851925377399 43.166165119685175 +5.43747663241539 14.543289704830684 +5.745302432361167 40.165127480599814 +4.7748561490693096 16.976024921601464 +4.879378681660423 31.278342458918026 +4.840585235553523 16.713870427622826 +4.94540141971665 30.793297589866185 +4.972701105072861 16.20144301769422 +5.077679003847507 29.842080719318783 +4.737420092402399 17.127534765368836 +4.821009289660325 29.387887951428823 +4.7936092187669495 16.900731538494085 +4.87746378906909 29.002415881141303 +4.906548935743679 16.45563462467179 +4.990679707519907 28.24334248905079 +5.4256099661602315 18.116709763908464 +6.434283839602999 69.89263442572603 +5.65304611297038 17.176024755665434 +6.574593986044349 63.93278316528012 +6.099307208989737 15.507123313124602 +6.88265794001302 54.990875935803665 +5.222102588425582 19.013495091310983 +5.840084027373324 58.888814218594085 +5.400221251270594 18.225693084767716 +5.991911806109069 55.49224369072195 +5.753342882403174 16.781017112080747 +6.292586246820628 49.74964413878482 +5.043761468339764 19.84431737004508 +5.410423548100963 49.429986397110575 +5.177510826026082 19.217210676646545 +5.538239533614634 47.552680409736 +5.4446388125741505 18.035558369110625 +5.790262102870538 44.11604279673671 +4.777481628419259 21.167778286235983 +4.9012259715490565 35.81985497443594 +4.843790044065324 20.82855614340211 +4.967662386623867 35.23462465280309 +4.976974774040514 20.16669376883905 +5.100612981033882 34.09214526234501 +4.739700808994218 21.364021593618837 +4.839474198232253 33.9197073392044 +4.796403327048967 21.070307104146096 +4.896347249243509 33.45095599027796 +4.910302912344466 20.49481432625097 +5.010289754963334 32.53132516290766 +5.4336866453229415 21.557431329035296 +6.533073808665931 74.63006032972393 +5.662237858939349 20.402387440451392 +6.659869018405209 68.05617618077467 +6.110055000959019 18.36259055865486 +6.951159551298322 58.33945178491771 +5.228934916466946 22.662371561837688 +5.914224438697813 63.645321545333914 +5.408156432304543 21.691509561761517 +6.060418881097242 59.80963047333835 +5.762948860285796 19.918536381187558 +6.352266255529927 53.419508338118575 +5.049273253641034 23.689487805899386 +5.462785685286159 54.12178791810657 +5.184034560614357 22.913906174813008 +5.589110951897097 51.96027483531375 +5.4528191606078185 21.457629388446414 +5.83811293976286 48.04750631957062 +4.7805458492339055 25.332754158178403 +4.924876102105794 40.39379656804165 +4.847522061811419 24.910711324585264 +4.9917434612490865 39.69736742570399 +4.981929501225888 24.08894847353616 +5.125381196414007 38.34408474823024 +4.742366092297682 25.577181711043174 +4.8594623555081915 38.480121605089664 +4.799662269915258 25.2114248971415 +4.916786620185815 37.919097006322026 +4.914664778219958 24.49606290490104 +5.031503698579355 36.82258503569051 +5.442611147403595 24.926447165626627 +6.637100938218191 79.37674177708988 +5.672324413406562 23.54822950384987 +6.748871835939272 72.14916935271448 +6.121701533543858 21.126407515778684 +7.021966167925618 61.622618343918774 +5.236533648422419 26.24982767598035 +5.993354876361504 68.4407403769167 +5.416931552433497 25.08676947061128 +6.132873806744431 64.12480743625416 +5.773459009239012 22.97240017325301 +6.414636385912537 57.04242478673059 +5.055439655384148 27.484518981662173 +5.519063342413414 58.85940142449951 +5.191300677646432 26.55178735735417 +5.643434410040458 56.38313673576978 +5.461852846436603 24.807156609992084 +5.88869310877802 51.95267353513271 +4.784005647953538 29.46928476733781 +4.950391446588104 44.99941700617678 +4.85172608535728 28.95840928194179 +5.017681332633525 44.17984236352207 +4.987485263861938 27.965916852612395 +5.151971233938768 42.594863443123515 +4.7453794031881165 29.76551912315022 +4.881028174059209 43.068579154567324 +4.803339484409232 29.322337294195115 +4.938816937835925 42.405525699412536 +4.919566966735047 28.45724620755291 +5.054318527180992 41.11463237107193 +5.452241049510178 28.22261600002229 +6.745965668467853 84.11900237685698 +5.68313344159652 26.61297619717585 +6.841197622469988 76.20151522262246 +6.134028809537141 23.799351695462622 +7.094689805813457 64.83483487503561 +5.244786675502179 29.774372530558505 +6.077386504999734 73.26724995064525 +5.426407805633968 28.410278161149584 +6.209074271064757 68.42959748115166 +5.784689244504036 25.942320019537448 +6.479404826413142 60.61216340732492 +5.062177336427664 31.227807132936068 +5.579303005195829 63.63963054977928 +5.199204103924237 30.12931517823964 +5.701161150765 60.816398790945954 +5.4715947819764335 28.083040332434486 +5.941835715154914 55.826108168771036 +4.787821638047968 33.57611247199678 +4.977827552344405 49.63661380818807 +4.856351895879005 32.97023401183038 +5.045505821344933 48.68104558442697 +4.993569748669145 31.796015051859488 +5.180365011666593 46.84224623092923 +4.748707521849586 33.927894056190816 +4.9042199171521 47.685113083192384 +4.80739252172176 33.40173618470954 +4.962467213470825 46.909545134162045 +4.924948241344658 32.37684137712305 +5.078726207680056 45.40569531163265 +5.462451536117142 31.445693598804404 +6.859233936654338 88.84416545374937 +5.69451615153407 29.597052448017934 +6.936445501796758 80.20460706981918 +6.146854611757738 26.383272333703403 +7.168978501871392 67.97238529559664 +5.253594466938471 33.235274588671 +6.1661958585057794 78.11737405773722 +5.436463335686519 31.661723174375414 +6.288801078223178 72.71674761203657 +5.796481755109693 28.829037198825684 +6.546292185922228 64.12397330735504 +5.069411414977458 34.91835129651593 +5.643535555911107 68.45974621730218 +5.20765129142393 33.64567391796485 +5.762228482486566 65.25584031369523 +5.48191797166977 31.285088551159003 +5.9973722418801625 59.66343101795505 +4.791958022498085 37.65229726241779 +5.00723553881365 54.30579034186424 +4.861353909526693 36.94516277819571 +5.075242100041862 53.200487893915636 +5.000117803887793 35.57819603393911 +5.2105407857338735 51.08459363870018 +4.752320121435002 38.0634368749494 +4.9290818299520565 52.33021940588878 +4.811782765940829 37.44865211799642 +4.987762425941428 51.43093247249463 +4.930753227312354 36.25379242491507 +5.1047154815198175 49.69454109035187 +5.473133596151182 34.59609211709822 +6.976447420776252 93.54046255579787 +5.706344971657927 32.501593526767344 +7.034225009395864 84.1512195295729 +6.160027671435566 28.880717630661152 +7.244516078474531 71.03295369854861 +5.262868904383343 36.63237341620287 +6.259626479189064 82.98375993218173 +5.446991521099336 34.8414398545113 +6.371822134936697 76.97971498664457 +5.808702234229715 31.63401277657849 +6.61503367253218 67.57426004254684 +5.0770748530470975 38.5556131676051 +5.711777322190925 73.31726219242083 +5.216559167438322 37.100594696043 +5.826561562425359 69.69767132078147 +5.492711653941011 34.41377169889321 +6.055134835231709 63.461071302628675 +4.796382209369787 41.697146607357816 +5.038663775791195 59.00774895911328 +4.866690855175867 40.8824797171333 +5.106912198169992 57.73807111180867 +5.0070707746943155 39.31182662018656 +5.242474461237735 55.32071135926759 +4.756189548373147 42.171491649456776 +4.955655648130594 57.00476563399894 +4.816475186746907 41.4623812351279 +5.014724898356249 55.969832575824675 +4.936931930986174 40.08740510878043 +5.132273052085095 53.98034395089525 +5.763330753371242 3.822241563728355 +6.667831639668449 61.733966027851324 +6.021154598155061 3.6281357753750147 +6.848316942662843 56.15889011442123 +6.526144081570459 3.284703402152532 +7.227586691400637 47.79480587568346 +5.5326321966744585 4.007236345543238 +6.035597276896144 48.420741508242585 +5.734544711184266 3.8447326613175563 +6.220416698205366 45.58571981667929 +6.134783672500834 3.5466957286958936 +6.582117586258762 40.70166020331399 +5.330671490752402 4.178300792269278 +5.596049401998094 37.12219790816294 +5.4821077410078445 4.049219851640181 +5.746146936207253 35.74962845480973 +5.784905954467674 3.8054936364209797 +6.04222061939877 33.172246939625495 +5.029984881509164 4.449543289325959 +5.096290127561509 20.886139017683405 +5.104728092614175 4.380207686808572 +5.171648870252468 20.574034315515373 +5.255134985041334 4.244539150564389 +5.322939681491013 19.95343179650299 +4.987447173098309 4.489585375205344 +5.03738530794593 18.6012376714887 +5.051302760967626 4.429635677134402 +5.101689546398496 18.36800396908278 +5.179799611679206 4.311856520508971 +5.230891360147082 17.903000257975812 +5.76626917866581 7.611209196162064 +6.751475878399966 66.46319184879806 +6.024566734415567 7.217566234466962 +6.920351132006714 60.36431932687649 +6.5302974649474335 6.522260702441946 +7.284863660905813 51.33309117254839 +5.535073610950787 7.986869864859246 +6.096522143688071 53.15152372075448 +5.737425192272414 7.656854322047576 +6.276966713875185 49.961751643620126 +6.1383816062836924 7.052555586831271 +6.631321128834335 44.53765097384491 +5.332609964708101 8.334700308150675 +5.638131147933513 41.801375350817494 +5.484429677105168 8.07219441765131 +5.787151886167336 40.20813201309286 +5.787887098006908 7.577224333075541 +6.080803708473719 37.247389903680755 +5.031036666525214 8.887186027860542 +5.114995860631194 25.52684913776503 +5.1060169852629755 8.745842627472449 +5.190544733660328 25.128822596907824 +5.256867109675461 8.46950823326494 +5.342102363967641 24.342150104225603 +4.988358786220376 8.968850491873653 +5.053188200670226 23.24545218112275 +5.052423358089595 8.846595292166993 +5.117690335447576 22.940505587563766 +5.181315258419522 8.606584770471342 +5.247211751380543 22.335789402160124 +5.770869646543805 11.349518591324824 +6.841401664537855 71.2699996337065 +6.029876230554922 10.748526681763337 +6.997449217003748 64.60304544821085 +6.536684426517073 9.68967653003647 +7.346154285839723 54.859902103374296 +5.538917057048469 11.924185784889985 +6.162617381770606 57.9690746240628 +5.74193803876167 11.419283958439717 +6.338065373814716 54.38674386585662 +6.143965189076176 10.496938347177933 +6.6843493528801545 48.37664773475804 +5.335676442440857 12.457320783162515 +5.683916985124734 46.55741187507062 +5.488089460845313 12.054871833847573 +5.831716671047779 44.7187433896079 +5.792552047066779 11.297585718217336 +6.122716479564793 41.33790761061643 +5.0327124392059694 13.306311584104446 +5.135313298380732 30.212700827709895 +5.108066915444644 13.088851890488666 +5.211145818000615 29.72022096032479 +5.259612079112473 12.664226350193058 +5.363134031954841 28.75196955724029 +4.989812683785582 13.43203747074018 +5.070340573269568 27.92921006972937 +5.054207863913448 13.243843239333284 +5.135133807336545 27.54568494969599 +5.183721501622094 12.874777890685962 +5.265143287077249 26.788539902032337 +5.776905448588826 15.026458540244246 +6.937348455093762 76.12864656812505 +6.036796200527783 14.20923996020243 +7.079238309231529 68.85016557275453 +6.54490478365576 12.774156754419701 +7.410960158110885 58.352517610419824 +5.543990281096257 15.80985064507518 +6.233941531707707 62.8565741217486 +5.747863322221688 15.121458466337916 +6.403607369422544 58.84136241639069 +6.1512215171242275 13.867730019820746 +6.740900534326966 52.198138771732694 +5.339745937055458 16.538439119903774 +5.733561206811688 51.379592827394006 +5.492926727111958 15.988282983026098 +5.879887629852011 49.2678911581415 +5.798669016276541 14.95575968799342 +6.167835966028221 45.42716444364527 +5.034954441169015 17.702460678483877 +5.1573590049977875 34.93888869432332 +5.110803957649615 17.403855490245796 +5.233539850336074 34.3419977099457 +5.26326229373309 16.821684839581593 +5.386064496133763 33.174262103192554 +4.991760087615983 17.875246521297267 +5.088945234920386 32.64838678790002 +5.05659395902708 17.616649689601303 +5.1541006326170145 32.17821591571383 +5.186927780993488 17.11020717316805 +5.284721546468267 31.253832669543755 +5.7841694816304265 18.63515473913757 +7.038965875315073 81.01755324782346 +6.045066962832963 17.592506968864388 +7.16530024637022 73.08623931423332 +6.554604156618936 15.768671072826729 +7.478789741984909 61.79501892099078 +5.550134917714343 19.637623823259347 +6.310479581509469 67.80035808768253 +5.754999955143221 18.756562999073115 +6.473428665826792 63.31033185127685 +6.159868535235225 17.1576888019216 +6.800649592657966 55.987107281770996 +5.3447031277625605 20.572710308924457 +5.787168274155847 56.2599143451963 +5.498793902581459 19.86637867864728 +5.931663136824198 53.84517871352604 +5.806026411219294 18.54483070952885 +6.216004963323584 49.50274513594105 +5.037709499934332 22.072395723145654 +5.181223317451316 39.70229236911646 +5.114159957000319 21.686992789887675 +5.2577888943785585 38.98981813895119 +5.267718418833221 20.936972777535598 +5.41089997947117 37.60276459140956 +4.994156131945747 22.295627798316893 +5.1090818949175985 37.400343776239836 +5.059524248487111 21.96159197011239 +5.174648987503574 36.834448338008606 +5.190850511209861 21.308454923685154 +5.3059613296833605 35.726331073047504 +5.792475254841489 22.17128655178426 +7.1458412612925155 85.9179752548573 +6.054457488203469 20.894145206216585 +7.25519807120917 77.29567599335732 +6.565475594995803 18.669891155048667 +7.549178787107489 65.17615138345315 +5.557207050001302 23.403335689894316 +6.392161487888628 72.78870084943999 +5.763166634463002 22.320279673865482 +6.547327024358261 67.7810900367231 +6.169656828393702 20.362770274961537 +6.863268063697099 59.73231267761053 +5.350442693959503 24.55639105647111 +5.844809050690886 61.19212085298967 +5.505556643514908 23.68506664724626 +5.987009804078081 58.44229428790286 +5.814433879310289 22.060478755538334 +6.267048362546337 53.55509210670432 +5.04092877517156 26.41369823293965 +5.206980299624625 44.50096566182826 +5.118072383546891 25.935422887292642 +5.283939008066517 43.66064547322745 +5.272889586392046 25.006592807189953 +5.437632409442465 42.03280940927343 +4.996959651033531 26.691035134754568 +5.130815868573495 42.183485840168274 +5.062946014852207 26.276124157770678 +5.19682300984109 41.51188637494119 +5.195413141615683 25.466323848047555 +5.328864797978339 40.20210953844898 +5.801656697292005 25.632333481221124 +7.257518314376817 90.8133053141418 +6.064764935866775 24.112064983046302 +7.348492284596073 81.46580352272244 +6.5772572548812445 21.47695879471887 +7.621699592262601 68.48803663369782 +5.565077116411707 27.10429457629184 +6.478871720689233 77.8110973239937 +5.772201670181302 25.810053627675767 +6.625073820157598 72.24301145895707 +6.180368783106191 23.481128774973364 +6.928434864611584 63.42526994967842 +5.356868895751069 28.486884456085285 +5.906529125646817 66.17111541631688 +5.513093731298323 27.441654170403787 +6.045871191967382 63.05236228409218 +5.823722100250569 25.500211163133017 +6.3207823174391615 57.57670679772608 +5.0445676469514975 30.72454314081139 +5.23469335350861 49.33383805567881 +5.122484097354722 30.14704353267165 +5.312025654165753 48.35238934056825 +5.27869297496642 29.028060779698713 +5.466244611977571 46.46087586054637 +5.000132893384279 31.05982885155563 +5.154202987554796 46.997002718420376 +5.06681110499024 30.55833551021815 +5.220657564387725 46.208884506886434 +5.200545735057375 29.581492214898237 +5.353426038825883 44.67826247089853 +5.8115671142427425 29.017076835522044 +7.373512334349056 95.6886947945008 +6.075812831556012 27.245657171117813 +7.4447515630707155 85.58626650142115 +6.589728192076457 24.19067802686094 +7.695964419413128 71.72530381387804 +5.573629083116144 30.738894324191286 +6.570454901673881 82.85780894703437 +5.781962000916064 29.22460784734036 +6.706422031369202 76.68692150775796 +6.191816369342949 26.512458434919953 +6.995842427591889 67.05957595301793 +5.363895227157081 32.36244862928819 +5.97235331301815 71.1925553160169 +5.5212963127691514 31.1344798684567 +6.108172995073528 67.6695178769316 +5.833741695688345 28.862854491655135 +6.377019770609587 61.56162549358858 +5.048585228144547 35.00354771375841 +5.264418746844329 54.20050909430311 +5.127342936410355 34.32030236289622 +5.342077049102705 53.06366670469896 +5.285053475807184 32.999651071020494 +5.496713460001434 50.884289890396964 +5.003641404444897 35.40075346185254 +5.179292717512281 51.84069190152039 +5.071075437030776 34.80678876991485 +5.246181215362092 50.92444052051582 +5.206184673666982 33.65229573678695 +5.379633853125844 49.1526421191999 +5.822077698309039 32.32525086078479 +7.493323287506775 100.53084022905519 +6.087448609474766 30.295365486244084 +7.5435602344001405 89.64860703715117 +6.602703622319881 26.81296431613797 +7.771625677188163 74.88447251999021 +5.582759683340022 34.30634155559052 +6.666719552234857 87.91956300904278 +5.792321784799408 32.563603376552805 +6.7911117032305865 81.10476561461729 +6.203838221859674 29.457533922896822 +7.065200191569541 70.63043570458522 +5.371443720982456 36.18199052243621 +6.042288013141961 76.25255388988019 +5.530067231506322 34.762657760873566 +6.173826253308208 72.28860771380128 +5.844361678368954 32.14819890604671 +6.4355739524807305 65.50505553895741 +5.052944227022976 39.24967286444546 +5.296207982952735 59.10110023879194 +5.132601389112664 38.45407257234047 +5.374116368101778 57.793630594837275 +5.291903197526838 36.920217047901154 +5.529011894415567 55.301011575456364 +5.007453604297561 39.71284614771102 +5.206130275994029 56.714831582907436 +5.075698780250353 39.020413513385634 +5.273418209772973 55.65804734231536 +5.212272231698671 37.677574385310436 +5.40747356125689 53.62367281472872 +5.833075770360419 35.55728871358807 +7.616447559370635 105.32787207839237 +6.0995413257930915 33.26238323155199 +7.644523625520984 93.64595877535137 +6.616030091283382 29.346458344025606 +7.84837461915647 77.96350352646729 +5.592377293322287 37.8064561020675 +6.767441231085874 92.98736543026365 +5.803170725119829 35.82739173727711 +6.878874645008571 85.48939480200247 +6.216296887267224 32.317886389253495 +7.136236336778097 74.13431770998899 +5.3794443852460745 39.944917191418824 +6.116322205761693 81.34745544230162 +5.539320007045184 38.32589003830735 +6.242729357107683 76.90497409558917 +5.8554676272090935 35.35673937331617 +6.496260752244414 69.40311601400502 +5.057610569264957 43.462149174391776 +5.330109450502598 64.03614263532934 +5.138216284296462 42.54756084270026 +5.408163230753189 62.54184082983559 +5.299180838258865 40.78906018726243 +5.5631102517954885 59.709478931717385 +5.011540602359093 43.99537702559474 +5.234758133144655 61.620084908840724 +5.080644501303565 43.19842823429498 +5.302389846981677 60.40958181935179 +5.218756127049797 41.65656080791109 +5.436928205330621 58.09021345475543 +6.117733282851219 3.917266690553024 +7.272344377466206 73.01726004181084 +6.410152432452938 3.7032047966635884 +7.438409132967252 65.39850700338845 +6.979980672315658 3.3300838918385067 +7.823007786509699 54.62390484534693 +5.855314202416206 4.12314233003184 +6.494818534603608 57.199036636223276 +6.08502604581822 3.9422043824925037 +6.693614567715616 53.31553326115208 +6.538712748629627 3.6140479023239687 +7.085329209544796 46.8885892688004 +5.625116535389927 4.314908246020278 +5.958045254481277 43.59401957678749 +5.797760724738794 4.170091315145701 +6.126997587638933 41.7548469210267 +6.142240038103007 3.8987143274697877 +6.458938972758822 38.366145555977376 +5.281957823410889 4.621164089956368 +5.362685749515917 24.158241095913336 +5.367275359084821 4.542662714307788 +5.448757078353437 23.75909178227979 +5.538939599523756 4.38947373545987 +5.62142753594671 22.966963820837872 +5.2334059006582825 4.666556497733741 +5.29384293731201 21.437264407386913 +5.306291188781292 4.5986115520768855 +5.367291898366743 21.141289201763293 +5.452964708981004 4.465411182937451 +5.514815425283107 20.551807335083694 +6.120733625229811 7.800107084723693 +7.363771504029499 77.90179883170873 +6.4136174124309 7.366586867268958 +7.514875766871318 69.68531604063163 +6.984165410467523 6.612111294794915 +7.882080548127416 58.192836226193 +5.857821046381131 8.217572322035945 +6.562692938218277 62.14606344309579 +6.087969186608091 7.850646335235984 +6.755225063360867 57.83683863537305 +6.542358712377749 7.18617739709498 +7.137336205706043 50.79702134233169 +5.627117390632364 8.60692336611789 +6.004765161091975 48.490495853008376 +5.800147969026029 8.312849382638413 +6.172020818228409 46.38815944000891 +6.145282496904775 7.762513372279675 +6.500471292204906 42.55590012384111 +5.283051913790147 9.229796584779177 +5.383179357936207 29.001003828528393 +5.368613578138575 9.070007992594364 +5.469416044291731 28.502725084614724 +5.540730981388035 8.758453204267198 +5.642277805761205 27.52004005858185 +5.234355171059801 9.322234201104393 +5.311126501790667 26.282596877622037 +5.307456238814441 9.183881823458048 +5.384765112728686 25.90421378904571 +5.454535318425997 8.912851988367386 +5.532573729379241 25.154804062096844 +6.125434306144516 11.63043377609106 +7.461334404681742 82.85602478716123 +6.419011960129264 10.969714757903406 +7.596246655937762 74.00000767727182 +6.990602298993627 9.822567401769914 +7.945068319922741 61.74782458621416 +5.861770967638315 12.26788513902988 +6.635955504887648 67.18154337405156 +6.0925835251889975 11.707540632331698 +6.821436324102464 62.40526554509691 +6.548019408412625 10.695107705377794 +7.193121088767177 54.70571097421526 +5.630285848588737 12.863540704229218 +6.055410030288156 53.468942515494405 +5.803914138347985 12.413542883688024 +6.2207413758675365 51.075333285742246 +6.150046636576242 11.573088959317298 +6.545371062620647 46.75977914534899 +5.2847973449530965 13.818873026494147 +5.405369344819673 33.892881995931816 +5.370744571674035 13.573504184227872 +5.491860737210135 33.2859446209222 +5.543573039141606 13.095670010806836 +5.665067273761558 32.09558743804803 +5.23587114395027 13.960913293357851 +5.329827504036433 31.171019910023894 +5.30931390247871 13.748345944155343 +5.403747996483048 30.702598445162398 +5.457031727750986 13.332377169135029 +5.552006245467909 29.779256579951088 +6.131605278928044 15.397056884827323 +7.564645421982214 87.85167754981019 +6.426045810360992 14.50037278280001 +7.682086922669609 78.31666059997839 +6.998888623828404 12.948339832775288 +8.011452466234347 65.26588616100194 +5.866988734838034 16.264262980435458 +6.714599212976223 72.28631473805486 +6.098645788514618 15.5018427642075 +6.8920861867178855 66.99981947519112 +6.5553786733590655 14.128310761285192 +7.252349015625598 58.593380996303 +5.634494613220261 17.07658575616496 +6.110116700513281 58.517356703422195 +5.808896041940395 16.462722713953415 +6.27318002425294 55.8014735631142 +6.156297317364805 15.319146578652452 +6.593488008206117 50.960115809247256 +5.28713534652727 18.38362957357525 +5.429373400345033 38.828629574915134 +5.37359307814576 18.047416941545382 +5.516177900820054 38.10196899893317 +5.547356144106597 17.39368844225683 +5.689821136190241 36.684295847441334 +5.237904210236015 18.57842221006376 +5.350050254173463 36.09803687852232 +5.3118007601229005 18.286954270208554 +5.424321267549048 35.53065325553972 +5.460361704696021 17.717363075946544 +5.573145913239457 34.41715361417781 +6.139035658400576 19.092772238529356 +7.673244479688792 92.86525674506535 +6.434455590761502 17.951074941957792 +7.7719317547951166 82.61520642209378 +7.008667324799478 15.982212034046883 +8.080729652674188 68.73100958612771 +5.873312625639596 20.200116814214226 +6.79854151777944 77.44449454866194 +6.10595116583122 19.22640448955248 +6.966957904403961 71.60381333670239 +6.564150912097148 17.478280181660377 +7.314668737249082 62.444483126670924 +5.639625717390429 21.240376756216 +6.168970693985904 63.626494594294485 +5.814942897724097 20.45399121456108 +6.329309450512179 60.554975377842915 +6.163819147856678 18.99344374706065 +6.644640251911206 55.14365246005384 +5.2900115717240475 22.920589719516922 +5.4552828859435545 43.80473766105346 +5.377089392028767 22.487610947028738 +5.542428452179862 42.945976842145576 +5.551978653924696 21.647274807762926 +5.716540909220954 41.27930936503431 +5.240408538692114 23.171696490062395 +5.371875878516478 41.06068689272432 +5.314858115473305 22.79603423614375 +5.446543012232274 40.38432173192608 +5.464439722168344 22.063089669961283 +5.596004758696377 39.06264552267906 +6.147535094908765 22.71303992741471 +7.786628517990057 97.87669016888042 +6.444006537331171 21.31746297344212 +7.865311563275988 86.87976038152767 +7.019628846707666 18.9207632067601 +8.152430348965822 72.13197098260216 +5.880595197275327 24.071029940628716 +6.8876429338113425 82.64225908765248 +6.114314501301337 22.876684308986533 +7.045800671052189 76.20350250930863 +6.574083162747147 20.740815915856476 +7.3797316217845 66.2474352922913 +5.645571016185121 25.35091940946861 +6.232021853298409 68.78888144375561 +5.821916973763773 24.383005740219936 +6.389070147334795 65.32641087398954 +6.172417908811205 22.591444499358243 +6.698630340242981 59.30014703089988 +5.293375891577786 27.427144385618448 +5.4831726812310295 48.81890794723317 +5.3811692608757005 26.89102983848275 +5.570657036604912 47.81448889087587 +5.557347224362741 25.852686896139097 +5.74521361743633 45.8754340382516 +5.243341900057141 27.73841741559892 +5.395370955647711 46.05708674632685 +5.318431864650586 27.272841326722304 +5.470457074728126 45.26074357128423 +5.469187134863198 26.36612636218203 +5.620581964191197 43.71135217560334 +6.156933705402778 26.255204789957293 +7.904271936177186 102.86862609495114 +6.454492207444316 24.597354854365673 +7.961766891517092 91.09763498857129 +7.031508930682542 21.763109800102818 +8.226126609022621 75.46101003159873 +5.888703332779323 27.874142477349565 +6.981717376725134 87.86714427509953 +6.123570299035543 26.44998806366126 +7.128341837586086 80.78729981166762 +6.584954423691802 23.913997637185282 +7.447201738305568 69.99356747284287 +5.652231879658775 29.405432809457217 +6.299292334481463 73.99820815617542 +5.829693638547719 28.246899778011805 +6.452379090779812 70.10787016803903 +6.181920467520921 26.110525020831894 +6.755254323698491 63.42155403148902 +5.297182333372542 31.9013162059869 +5.513106743184627 53.86974429026469 +5.38577380372261 31.25540226936437 +5.600897377945634 52.70500560595185 +5.563376484846481 30.007256654063923 +5.775816961561822 50.46867629747148 +5.246665421210226 32.27680557919955 +5.420592397239369 51.08616518987596 +5.322472379238606 31.71530702639751 +5.496097783898949 50.15794010678168 +5.474531831562678 30.623972149873047 +5.64686841460916 48.35995998025649 +6.167081154542768 29.717978813929523 +8.02564277756823 107.82602193765456 +6.465732765286408 27.790111838741215 +8.060857812887619 95.2586887272919 +7.044084477931807 24.510077933920645 +8.301434202723446 78.71293217413321 +5.897517566954884 31.607742167491082 +7.080539117046946 93.10762070050876 +6.133571854236761 29.94496471578599 +7.214295410614764 85.34528286825193 +6.59657353187666 26.99750487049814 +7.516761383141685 73.67641970805302 +5.659518937677654 33.40204485823566 +6.370780933353952 79.24892061730283 +5.838160687702043 32.04389858174588 +6.51913505946691 74.89253280522509 +6.1921738115237215 29.54944606179453 +6.814307273491595 67.50148496262553 +5.301388614631342 36.341600639325804 +5.545141590262412 58.95653970047195 +5.3908490488046015 35.57904430198286 +5.6331755989720484 57.615760341008944 +5.569988773982254 34.109123692740205 +5.808322465475275 55.05593359101162 +5.250343503561487 36.78549255237467 +5.447590542896861 56.14747907574475 +5.32693406287801 36.12186879477308 +5.523492908473927 55.07460051801321 +5.4804080056982185 34.83482724640619 +5.674849481365018 53.00595111806219 +6.177845237642574 33.10107732997137 +8.150215667461424 112.73587664449468 +6.477572585102616 30.89619453558333 +8.162169793319363 99.35485499844057 +7.057168827474399 27.163637821654984 +8.378011742260004 81.88446873308649 +5.9069313197973345 35.27097875971391 +7.183848146420741 98.35282634339292 +6.144189915405821 33.36125182075988 +7.303367968285953 89.86885691553513 +6.608776302022176 29.992141512970854 +7.588113965278852 77.29124264765466 +5.6673514161109955 37.339575641900396 +6.44646539031162 84.53591966941968 +5.847217752209284 35.77305114050231 +6.589222068066022 79.67436824541228 +6.203043569070377 32.90798224097579 +6.875586778747713 71.53483174631026 +5.30595606345608 40.746862244611485 +5.579328640777128 64.07912273671906 +5.39634570370146 39.86072845376745 +5.667512401106513 62.54554176428772 +5.5771136934839864 38.157046564661975 +5.842697507790281 59.63477497064574 +5.254343405085891 41.26342440775531 +5.476411262428532 61.24108154078971 +5.3317751885370575 40.49135802157957 +5.55266563009814 60.009928406113616 +5.486755762144053 38.997431813718606 +5.704506840025958 57.64741163368641 +6.189110155134471 36.40495292261344 +8.277482335107198 117.58704565252047 +6.489877986820286 33.916847480322744 +8.265317373383972 103.37978845292359 +7.070606928094074 29.726506498785838 +8.455558516515719 84.9738102794924 +5.916849874576622 38.863654211223675 +7.2913551170092985 103.59241286762655 +6.155311045338082 36.69921603125459 +7.395263731588171 94.35053042947226 +6.621422786156474 32.89949963496207 +7.660985190408318 80.83463147260777 +5.675656645259875 41.21738065870476 +6.526303470540573 89.85434042142322 +5.856775319618287 39.43403305354064 +6.662511650077173 84.44792154907495 +6.214412217996112 36.1866510847251 +6.938895311773803 75.51749543877871 +5.310849223655625 45.11625636682466 +5.61571582825272 69.23774005366597 +5.402218833332471 44.09958629011608 +5.703924531516546 67.49355990580355 +5.584687519898532 42.150264524909424 +5.8789066689175 64.20328002516308 +5.258635080067552 45.7097985672468 +5.5070974403760395 66.36742209908985 +5.336957631713687 44.8229174575118 +5.583635901476322 64.96352603696022 +5.493520715165611 43.11094816461151 +5.735819680570057 62.28288891815434 +6.469341189282587 4.001359052658909 +7.91009904485809 85.0683046406985 +6.797274348878895 3.7679653223614777 +8.046329579262414 75.02792666162101 +7.4330513787403145 3.367175109980388 +8.424989541435624 61.58739706401936 +6.1741477468859625 4.227922966305487 +6.9743213857897635 66.77457948602802 +6.432593286060053 4.028698699947653 +7.181090434923732 61.56797234545925 +6.941088480435831 3.6714745064195804 +7.596027813955064 53.32117174560066 +5.914620988985444 4.440558426211155 +6.326542497705147 50.6482882242997 +6.109305895139859 4.279848682873698 +6.513656419211567 48.22409567725741 +6.496866790447751 3.9810393130331283 +6.880113867874183 43.854896406637565 +5.527155555573891 4.782692898223981 +5.624289885636283 27.68915861215205 +5.623517903331052 4.694743452434308 +5.721542970586435 27.18610841566152 +5.81736092059786 4.52359959326723 +5.916464082094602 26.190242883556504 +5.472320162731678 4.833614979459694 +5.544640356069935 24.49230561699457 +5.554639069833122 4.757410585226264 +5.6276477078055125 24.122035946075933 +5.720290534572068 4.608351915284816 +5.794286865671174 23.385587127475425 +6.472393834214431 7.967265415611192 +8.007802629968609 90.05767553879518 +6.800781879295104 7.495139286862772 +8.126129888555111 79.36630494855395 +7.437258853473441 6.685547870637214 +8.485308460708215 65.17488454225577 +6.176712036792756 8.426122518424101 +7.0491553933301505 71.9199168442146 +6.43558960906248 8.0226063603557 +7.247409009735025 66.21283493029229 +6.944772260331767 7.300103735811918 +7.650389656664582 57.28554656253579 +5.916678259725789 8.857302910854807 +6.378077179610052 55.75484706266877 +6.111750916218238 8.531367910098764 +6.562698296026829 53.019890786002456 +6.49996084538573 7.926139237833648 +6.924406103959683 48.144400016984605 +5.52828929710159 9.55225381877915 +5.646605195263488 32.72624128451002 +5.624901993073644 9.373464983387244 +5.743987655241229 32.10983631143726 +5.8192064549979055 9.025841289359295 +5.9389998143433615 30.897381834834004 +5.473304876456147 9.65581813529566 +5.563426792140558 29.530703986855208 +5.55584569430094 9.500848001062636 +5.646609347878068 29.06659708405035 +5.721911818945585 9.197941570267925 +5.813484509945946 28.14881295189407 +6.477179192175173 11.87900363781126 +8.111343182428953 95.10701664231667 +6.806244912864917 11.160511165640266 +8.210647489095457 83.72756803272475 +7.443731945620703 9.931177458359437 +8.549459139222728 68.74661631766514 +6.180755439521341 12.578562611571979 +7.129520487568715 77.15315948185884 +6.440290117690235 11.963306799765984 +7.318325656467068 70.90195231942467 +6.950493671756307 10.864058267212128 +7.708468672799834 61.247584325340526 +5.919938956656862 13.23714424530865 +6.433747734253472 60.94781985202958 +6.115611224923088 12.739201792210585 +6.615553627316811 57.87057864685403 +6.5048084793593866 11.816366147548091 +6.972083208527509 52.446484733410905 +5.530100030720422 14.30126827085165 +5.670701134511778 37.816353813331716 +5.627108385744865 14.027177785936601 +5.768296475741417 37.07601032199096 +5.822137209698737 13.494905129820925 +5.9635400173818125 35.628202431436215 +5.474879282564173 14.460142280540062 +5.583698072434016 34.61569878656715 +5.557771792255768 14.222437515887899 +5.6671460119346 34.049320729632704 +5.724491374798538 13.758314985024667 +5.834415790914009 32.934871730796736 +6.483464336762344 15.72495519061965 +8.220233886611815 100.18653521767999 +6.8133704578409935 14.751483747629992 +8.299405468223316 88.08527309640989 +7.452066103059684 13.09069031055371 +8.616910702025855 72.27948960532918 +6.186100099611006 16.674981230798828 +7.2153336686243374 82.45277500743778 +6.44646868945323 15.839323282063859 +7.393622866692594 75.6128908202432 +6.957933980287432 14.350454398045162 +7.769903204127843 65.18544885634394 +5.924273702057205 17.571485663036317 +6.493668914542032 66.21384339021554 +6.120721120262849 16.893458778761858 +6.672214789444535 62.75996298057939 +6.511171666706648 15.639999892407628 +7.022968723613885 56.74257946071134 +5.5325280600074835 19.024679934074705 +5.6966960305729115 42.953814727277724 +5.630060590700676 18.649809309881185 +5.79455462809068 42.077308013655724 +5.826041737548875 17.922953137539707 +5.990104753119884 40.372736214841254 +5.476993009204444 19.24215422469004 +5.605559643603394 39.74242898921783 +5.560352926619015 18.916822958366502 +5.689338054805208 39.06396225058188 +5.727935439212029 18.282473980978015 +5.8571105992716435 37.73517908537336 +6.491035220556679 19.497620369250033 +8.333939753559193 105.27183251189601 +6.821892162943949 18.260321246776133 +8.391911486723693 92.41914210922025 +7.461902319406218 16.156716997767 +8.687153886892986 75.75748627736608 +6.192581388798595 20.70846933364646 +7.306436839353199 87.80069817586076 +6.453917321111893 19.643207703854806 +7.473034550872958 80.32779105861609 +6.966804795941177 17.75155837708788 +7.834321860706734 69.08323042609962 +5.929562138735656 21.854329625714513 +6.557902650185411 71.54236042595211 +6.126926977231235 20.987419362822987 +6.73262642734262 67.6752587888877 +6.518831855440519 19.389505778006985 +7.076857671721326 61.01870219620777 +5.5355179283665 23.718782234373204 +5.724681800815903 48.13472813086526 +5.633687463280326 23.236961304916044 +5.822821353912993 47.10842545319022 +5.830816293267947 22.304446771870243 +6.0186903838782815 45.12355307350237 +5.4795993303774155 23.99858194864126 +5.629093707039654 44.90761415457405 +5.563529207226551 23.580091330901144 +5.713243091147648 44.106064214614285 +5.732156703585509 22.765410972607885 +5.881577831036563 42.543386570834485 +6.49969821309731 23.19226259434858 +8.45190666410784 110.34251437620108 +6.831572243363628 21.682512366000232 +8.487680924071775 96.71333189857756 +7.472929145626756 19.12576087297466 +8.75971792621872 79.16945337351581 +6.200048712703662 24.6743828383573 +7.402615887615609 93.18111884826203 +6.462447584323535 23.37021742525925 +7.556266481693862 85.03198046341785 +6.976850356083947 21.063036433744635 +7.9013614952619236 72.92914317662846 +5.935693561783784 26.081446016503662 +6.6264731178198915 76.92460585422278 +6.134088061074471 25.016509334028278 +6.796701077840081 72.60595956600945 +6.52759155123213 23.060154629985917 +7.133532226876026 65.26403745654243 +5.539018332326785 28.380781887332322 +5.754733678649822 53.35644042551472 +5.637923184274911 27.78537048540979 +5.8531393757926775 52.1654414121934 +5.836365273074264 26.63541205412136 +6.049278651792195 49.874947987451236 +5.482655032935839 28.726939384344718 +5.654367777594464 50.109083701206416 +5.567245235736965 28.20930671368488 +5.7389043109607725 49.172403331028164 +5.737074598697777 27.203472968136158 +5.907813375590606 47.354670298589866 +6.509280196438045 26.806105974784348 +8.57358098965114 115.38141770555163 +6.842201346088131 25.015794804988417 +8.586249811894938 100.9553907119389 +7.484880567451002 21.996917696503587 +8.834177096350537 82.5077531068476 +6.208365843279812 28.56970704811909 +7.503612334857728 98.57980674755039 +6.471890738652227 27.017532983917548 +7.643008692602276 89.71317477072809 +6.987846979408855 24.282904217079544 +7.970676472054642 76.71443920250192 +5.942566719061537 30.249880800662744 +6.6993744968127436 82.35299413748945 +6.142076691639461 28.97770125264692 +6.864327865611367 77.54317029388791 +6.537274398326411 26.649205351296914 +7.1927706437242 69.47009727061624 +5.542982037546719 33.00855395936682 +5.786915725451083 58.61722439117459 +5.642707236061392 32.2926028826903 +5.885540194448063 57.245445225759305 +5.842600974180932 30.91300653877413 +6.081841815517267 54.62246761924125 +5.486120195162238 33.42531127837896 +5.681439530920386 55.34550257466466 +5.571450017447021 32.80224797807597 +5.766355165712086 54.260667248212464 +5.742615011224694 31.593987683389184 +5.935804644547086 52.16531711876744 +6.519627743190933 30.33780050157473 +8.698423988605247 120.37411584447256 +6.853596915745197 28.259504282058238 +8.687182459578203 105.13555824136486 +7.497531918944331 24.771031893862318 +8.91015182290521 85.76734478008784 +6.217410270111274 32.39263205404231 +7.60913195308279 103.98371685053122 +6.48209696863982 30.58373689460796 +7.732944196362073 94.36096996197023 +6.999601027076115 27.410828947554688 +8.041943508551503 80.4326810171395 +5.950089651144731 34.35763924767947 +6.776575240411174 87.82070697518725 +6.150777716033416 32.869115502013095 +6.935378029537762 82.47917746887059 +6.547724309437524 30.155359998518264 +7.254352685122504 73.63016578858704 +5.547365459509233 37.600475590089836 +5.821284258173445 63.91605820181406 +5.6479839774831415 36.75684762586445 +5.920047367890215 62.34628246345918 +5.849443400290487 35.13524186315681 +6.116345793078564 59.36259276303899 +5.489958154839251 38.092219005916114 +5.710359872920336 60.61618226998238 +5.57609654532591 37.35723112879226 +5.795622301744594 59.36923388216671 +5.748710114740864 35.935025627046606 +5.965533347117531 56.9724460408219 +6.530605764151257 33.787044868787845 +8.82592214145656 125.30855861668016 +6.865600852464057 31.41411649297859 +8.790075435654535 109.24624770996002 +7.510695175587094 27.450118827814787 +8.987306990968788 88.94512943841461 +6.227072495658619 36.142255396681904 +7.718851883918115 109.38074997566686 +6.492934113249173 34.06844588931907 +7.825755029147258 98.96649134120338 +7.01194608536871 30.44764000196869 +8.114863879557518 84.07921862728203 +5.958179028520718 38.40345956789156 +6.858020463548179 93.32139636005421 +6.160087890885004 36.68974091511065 +7.009708616739515 87.40714996905525 +6.558804050934874 33.57837962355706 +7.31806304838767 77.7389095066354 +5.552128640682424 42.155317550231295 +5.8578901513757575 69.25246657719725 +5.653702456774405 41.17677997409467 +5.9566786672593794 67.4663720108614 +5.856819855738101 39.30078642949767 +6.152752201924086 64.092512640458 +5.494135079847531 42.72651981295289 +5.741175020559746 65.92094409157987 +5.581141698848473 41.87299217189207 +5.826727517194454 64.49701308971161 +5.75529801278038 40.22523125720152 +5.996977315515082 61.773810576619276 +6.542095810302232 37.154309552431634 +8.955594457341277 130.17479131014255 +6.8780772644319255 34.480921063868614 +8.8945595408443 113.28165280663808 +7.5242141258679105 30.036958436940733 +9.065349136305937 92.03947199450128 +6.237255068496328 39.81836288056347 +7.832427281871246 114.75962124782163 +6.504286053949266 37.472040682728036 +7.921127290347602 103.52215035703978 +7.024740234828668 33.39498223749954 +8.189164017670532 87.65080463091584 +5.966759746298032 42.386648713463686 +6.943633272251609 98.84897280243938 +6.1699150126008835 40.43922870070397 +7.087165060929372 92.32092613411217 +6.570393477333739 36.918802409678435 +7.383693637495204 81.7920931576327 +5.557234832380177 46.67216174522831 +5.896780420389613 74.62639908408106 +5.6598161028703995 45.55145873402355 +5.995447530024214 72.6045678522345 +5.864664392329313 43.40882033093332 +6.191019727779201 68.80995959869739 +5.498619848268347 47.32733961053654 +5.773927969128776 71.26001544691715 +5.5865459609055605 46.348600092225496 +5.859689111259451 69.64332707830143 +5.7623223677743525 44.46369941748129 +6.030111724217048 66.56765196289909 +6.818772082751164 4.075903942127354 +8.576682388043865 97.69093148413661 +7.183004121112221 3.823902057807342 +8.668685598967475 84.93127820465087 +7.885610687089018 3.3975082894291804 +9.031833997685348 68.63944348762458 +6.489863471253687 4.322852450134045 +7.476268540328627 77.12361080312353 +6.777878728168735 4.105587683165958 +7.683122762238294 70.28978601984214 +7.342337308172145 3.7204898734728244 +8.113534401660052 59.95299543021193 +6.199999934261706 4.556427919303697 +6.703473986557139 58.29519176901134 +6.417496547919538 4.379741159528664 +6.90741271766465 55.14647967048535 +6.849392709855746 4.053863547361905 +7.306209798966739 49.612102462238944 +5.76648635400616 4.9351861054815815 +5.882154264989343 31.481276151518372 +5.87434422070223 4.8375323497573115 +5.9910267030483455 30.85530337954039 +6.091241094699569 4.648059497139566 +6.208991104435121 29.619804657850516 +5.705108726941292 4.991803553175709 +5.79078917386365 27.768019095379664 +5.797249226670226 4.907096329173594 +5.883748373355615 27.310539151379754 +5.982643663035394 4.741790014342634 +6.070246952916052 26.402185507119608 +6.821868848993853 8.115440420920232 +8.678745118371822 102.73376053604926 +7.186545819695327 7.606172878088842 +8.750751322061255 89.29650549327238 +7.889834570040719 6.745603223674007 +9.092969569953103 72.23710580304896 +6.492478196730668 8.615059123484972 +7.557851847710254 82.44437949271703 +6.78092017767749 8.17546490859281 +7.753670965961301 75.0357642458373 +7.346050764487133 7.397340774263587 +8.169814471129067 63.959222754278464 +6.202108293077305 9.088185761276925 +6.75998316425681 63.604449353362945 +6.419992713270964 8.730239611605143 +6.960434450096048 60.09227577171472 +6.8525301189271115 8.0708770222231 +7.353047394761798 53.98749014445309 +5.7676573577625945 9.85666786861094 +5.906327088265882 36.70600120446704 +5.875771068747231 9.658375056239976 +6.015279964683782 35.95137204765573 +6.093136176337615 9.273952735104004 +6.233207621604525 34.4716878039955 +5.706126891025476 9.971685519366982 +5.811102046288729 32.99248628831854 +5.798494833493187 9.799618066850181 +5.904214899573328 32.428973331962275 +5.984311732587895 9.464071763384059 +6.090884170684928 31.316670445843076 +6.826725628369566 12.099332813982736 +8.786274618454566 107.82717679059672 +7.192063848650407 11.325298569444477 +8.837354614149223 93.6805940345139 +7.8963337896977945 10.01999471605594 +9.157872216953757 75.81777158524196 +6.496603677009305 12.860006311276097 +7.645016994292425 87.85014069639485 +6.785693758456407 12.190649921251751 +7.828765426436219 79.82239797127815 +7.3518198224831455 11.008252768981132 +8.229745555549911 67.96080017709441 +6.205452521289503 13.581638735542072 +6.820824388196751 69.00375659576734 +6.423936350980526 13.035564975245931 +7.017361983310399 65.09325721736599 +6.857447897144159 12.031549230830434 +7.403269696852642 58.37371390724922 +5.7695294708815075 14.75665724092837 +5.932364547066975 41.98760769905465 +5.878047755682786 14.453107974439991 +6.041473786935735 41.09262506572352 +6.096148096256198 13.865341394560593 +6.2594891224039095 39.34830309428248 +5.707756456578629 14.932844963046337 +5.832966796922278 38.267012917063155 +5.800485110275063 14.669298583519781 +5.926320330379352 37.58818902817844 +5.986968115318691 14.15591001309341 +6.113311004586049 36.255246346921304 +6.8331070870016575 16.015579056422286 +8.898725932729903 112.94089653320691 +7.199263022515529 14.968350158070763 +8.9279948311111 98.05695442068219 +7.9047024661031315 13.207094982705552 +9.226004347542156 79.35830387334279 +6.502059763557074 17.047024588012444 +7.737598742233711 93.31710266428142 +6.7919709510155375 16.139277960091142 +7.908138092367364 84.62608607237908 +7.359323729165014 14.540036876369857 +8.29294526041123 71.93550435395507 +6.20990135868711 18.027794883069237 +6.886085346046582 74.47831513047988 +6.429159527977123 17.28541774924015 +7.078157020686533 70.13196452213946 +6.863905618884061 15.923780082969438 +7.456675813542935 62.75142555757513 +5.7720421320826745 19.629816818671422 +5.960385144478692 47.319982299177916 +5.881096655241184 19.215335486098475 +6.069691284364868 46.271205904820825 +6.1001637461171025 18.41400832168679 +6.28785007122574 44.23904639075458 +5.709946343722144 19.870596222885546 +5.856489676366129 43.58637918871094 +5.80315467689618 19.510487598807174 +5.950144276554512 42.78149472345326 +5.990517610759958 18.809951125180277 +6.137553926661937 41.20877361479227 +6.840796489572329 19.856414673470606 +9.01553061581861 118.05055338313693 +7.207874593046776 18.527374531732722 +9.022167765778114 102.4053770182876 +7.914580186634262 16.299409191312296 +9.296854397919901 82.8427186746734 +6.508679223088344 21.168910842620146 +7.835360576375867 98.82520841758574 +6.799541054402785 20.013631516893792 +7.99147986604193 89.42807310051545 +7.368271894311355 17.984764097760326 +8.35902749812559 75.86719835156151 +6.215332287972664 22.420360527840714 +6.955799455312517 80.01618285630067 +6.4355061482467475 21.47278131160203 +7.142734435366851 75.19447932420901 +6.871682054934059 19.739773716936547 +7.513039733748118 67.10603501008096 +5.775138878945725 24.472218101612764 +5.990480907050898 52.69884162858956 +5.884845290497759 23.940407494477657 +6.099989519540803 51.4813312495577 +6.105077464241552 22.914126976411684 +6.318281282340348 49.13593182004392 +5.712648984277304 24.781466757070568 +5.881753642924818 48.94698757306254 +5.8064425475576895 24.31904072805 +5.9757435244048756 48.00403630786654 +5.994871271623852 23.42091393904944 +6.16361837782779 46.17041867434382 +6.84959742867527 23.616926829001006 +9.136122976528075 123.13621328729462 +7.217658343178286 21.997727245737916 +9.119386550094097 106.71024778667957 +7.925654132785175 19.29337850996729 +9.369952461769453 86.2599421690137 +6.51630876326126 25.22081212899268 +7.9380147205376375 104.35693591342566 +6.808212847401018 23.80878706687248 +8.078460707270791 94.21303410175739 +7.378406352962258 21.337985860087418 +8.427619392309236 79.74399412588339 +6.221632286566955 26.75488403284786 +7.029960470272956 85.60724137021451 +6.442832761687847 25.592867252454212 +7.210977699977846 80.2692746679577 +6.880576949637971 23.474628226255884 +7.5721256073222944 71.42626049748831 +5.778767265566222 29.280890010028187 +6.022726978143321 58.12117351888792 +5.889226388052133 28.62486205033027 +6.132408803332824 56.71863801741087 +6.110791573705095 27.361504016401586 +6.350758884733018 54.03275902070586 +5.715820254983218 29.662808065399062 +5.908826843347405 54.346379526406444 +5.810292076963603 29.091837497097423 +6.003160268804752 53.25222898091313 +5.9999467516993885 27.984933396118933 +6.191496675222769 51.13492483907013 +6.859334046901053 27.29423147458791 +9.25995667257333 128.1815057064513 +7.228402573033436 25.37707623760127 +9.219192514834129 110.95945643164774 +7.937657020965501 22.188081795757956 +9.444875896723287 89.60244288339123 +6.524809360311944 29.199571143985107 +8.045235237309345 109.89664264095241 +6.81781482562251 27.521812513158462 +8.16874192800137 98.96825590144219 +7.389501313372228 24.597662917352338 +8.498369683526306 83.55713832876428 +6.228697724621718 31.028248529941802 +7.108530113894767 91.24257899137926 +6.451009006481013 29.642499293938478 +7.282747616273572 85.34654080422771 +6.890411228758356 27.12549770948241 +7.6336964370430795 75.7032694913787 +5.7828789189439425 34.05356451285903 +6.05718708278474 63.58491301665542 +5.894177909676216 33.26610872363313 +6.1669779453790845 61.97980247052269 +6.11721623901858 31.753131689460048 +6.385249425420688 58.92462892585316 +5.719419260154529 34.51257273801457 +5.937767419155777 59.78295373183006 +5.814651039674701 33.82650914243226 +6.03242676048691 58.52342589230909 +6.0056680921225505 32.49917365643083 +6.221172512904825 56.098188315425126 +6.869850296002213 30.88692317439377 +9.386515609988763 133.1730308124488 +7.239922535799304 28.66473519223123 +9.321161028149096 115.14365643303826 +7.950363051850017 24.984378596617912 +9.521249673944455 92.86530064864371 +6.5340558303718055 33.10328824964847 +8.156668235561785 115.43019029344254 +6.8281945242670075 31.151230752246192 +8.261984719166676 103.6831053323044 +7.401361187017338 27.763452103249872 +8.57095287430663 87.3002625878398 +6.236434279813474 35.23834388836563 +7.191442450199408 96.9140817281236 +6.4599170868975735 33.61970078097418 +7.357888124724143 90.41775503682076 +6.901026209526999 30.691031893844336 +7.69751934822058 79.93010738725832 +5.787429024049216 38.7885038978599 +6.093916890375162 69.08871477136385 +5.8996426615227415 37.86221428828526 +6.203717486734554 67.26227887590858 +6.1242693220517275 36.086900174990205 +6.421713008007998 63.80761876923849 +5.723408332730527 39.32917491964065 +5.96862654769711 65.25577138193671 +5.819471093163006 38.52125401651493 +6.0635682088534795 63.81569130810471 +6.011965597649137 36.961581124905834 +6.252623732262991 61.05697265214148 +6.881008641810265 34.39468598935801 +9.515320631375374 138.09990836319218 +7.252058138814728 31.861193204929226 +9.424903800321994 119.25571255584389 +7.963583214824822 27.684321438259737 +9.598744097177024 96.04554147577457 +6.543936101318581 36.93101308414 +8.271940420339668 120.9447169831038 +6.839217302584333 34.696640053743124 +8.35785595944015 108.34865788584015 +7.413817807473528 30.836204926214034 +8.645070757582017 90.9688407359856 +6.2447563044038565 39.383831222569064 +7.278606511767388 102.61414381423103 +6.469451212830693 37.523404994943554 +7.436230252767525 95.47538227052206 +6.912282241421659 34.170979983253595 +7.763368871934121 84.10129420291871 +5.792376421029868 43.484387713923226 +6.132966284216759 74.63178933699089 +5.90556814322713 42.411760110317694 +6.242641831221388 72.56411178372423 +6.131876021116344 40.361392252975364 +6.4601053376958655 68.67855063831658 +5.727752616347529 44.11138479290756 +6.001450500080353 70.76441541325549 +5.8247078206459495 43.17471551192747 +6.0966047277008695 69.12763777591978 +6.0187755193221975 41.37070859833498 +6.285824150602574 66.00870567649397 +6.892688392052893 37.81800680214632 +9.645933216235994 142.95341413869465 +7.2646717066719715 34.967779479496265 +9.530069386885387 123.29027953823935 +7.9771604598080605 30.29074199823282 +9.677071578205629 99.1416532685685 +6.554350306925338 40.6825168338049 +8.390666861079128 126.42850672009465 +6.850764759990474 38.15843414812498 +8.456032829617937 112.95742906832864 +7.4267277021859295 33.817611866618485 +8.720452458175483 94.5597879876209 +6.253586475725057 43.46397183451993 +7.369908066210068 108.3354695211715 +6.479516779644679 41.35324079197711 +7.517594982132029 100.51266249954433 +6.924056967607245 37.565899006826726 +7.8310290467489 88.21252714340679 +5.797683148413959 48.140226269278415 +6.174380896879521 80.21377649912382 +5.91190625448385 46.913734646116964 +6.283760683287785 77.88379387862335 +6.139968335147441 44.575731813263054 +6.500379113907963 73.5348224314952 +5.732419982430514 48.858258533246065 +6.036282087396467 76.30888309772806 +5.830320394279175 47.78589088286285 +6.131552668013755 74.45830272801747 +6.026039708698575 45.725586211309235 +6.320744790710411 70.95132905099797 +7.166499288451092 4.142073090859587 +9.265102481702996 110.66723608077035 +7.567697412920115 3.8722615360981636 +9.301713047250376 95.00516227994558 +8.33782803853016 3.4223240862995086 +9.642092311073492 75.74617433098464 +6.803038358551734 4.409007603619208 +8.001816775091902 88.19678682585909 +7.121369048628273 4.174032307935983 +8.1992535509534 79.41794300190767 +7.742766362468542 3.7623579314461923 +8.636965361141595 66.74160040647384 +6.4819096017460724 4.663505297574643 +7.090607474266351 66.54215881833834 +6.722930831695338 4.470823303345824 +7.309279558219274 62.50706375766059 +7.200281127183776 4.118366597954716 +7.737424253590776 55.610380843297776 +6.000693683822239 5.079521034872208 +6.137170192329095 35.53705023704585 +6.120479496277977 4.971930845119692 +6.2580658070398085 34.76677553684624 +6.36126184481861 4.763810827037095 +6.4997835001461155 33.2518749822321 +5.932524657497255 5.1419869613173255 +6.0331373974513784 31.26606278237569 +6.034860107424434 5.048552722695147 +6.136421391289259 30.70699037507293 +6.240728719059953 4.866653657563574 +6.343472009811439 29.599221573250297 +7.169633216600948 8.246963072641893 +9.369559138551592 115.71968848047702 +7.571266472399731 7.702163496479056 +9.38512344647803 99.37895398059513 +8.342063711209846 6.794734336636792 +9.703729337066198 79.34857390201475 +6.805697352441557 8.786527313263322 +8.089664384006618 93.66466498891512 +7.1244486991551 8.311531854294053 +8.273448118333953 84.2426381742825 +7.746503030939467 7.480397207139309 +8.69475960079198 70.77803241288063 +6.484064255697698 9.301543562357782 +7.1522203715098716 72.04598443334285 +6.725472265851035 8.911565932252547 +7.366196040846758 67.58997603808601 +7.203454903588541 8.199072972710589 +7.786573215678005 60.058785063461116 +6.001899781828435 10.144791140944223 +6.163238215914125 40.94360543606153 +6.121946277820355 9.926538157739603 +6.2841504557510595 40.028232834984 +6.3632023173843395 9.50469774860775 +6.525673242563564 38.239969854871994 +5.933574465050085 10.271563962489727 +6.055001573381491 36.67047244267007 +6.036142343788488 10.081957466807292 +6.158409571366394 35.992368826013184 +6.242440107899849 9.713095265508427 +6.365547626329679 34.65681546790168 +7.17455015383191 12.294891936592329 +9.479135319313295 120.81550242698152 +7.576828540740798 11.467755039719629 +9.472922346036952 103.76877942156183 +8.348581703300848 10.09265441768957 +9.769086540111767 82.93318608765013 +6.8098948752906665 13.115416567967156 +8.183043890960116 99.21243552274656 +7.129284159403308 12.393010040146889 +8.352098879218879 89.10402851757347 +7.752309358985038 11.13141449226836 +8.756139945167465 74.80785601116908 +6.487484186795503 13.899970428712527 +7.2183408065351555 77.64253419299327 +6.729489662928856 13.305768849428079 +7.427084913974571 72.72763590811343 +7.2084315247877635 12.222130928776059 +7.839092321029147 64.51617079023508 +6.003829712093032 15.187664042559 +6.191254757921958 46.41082723193424 +6.124288623920555 14.85398875762931 +6.3122501536785505 45.33748570594586 +6.366288598440117 14.209834863264035 +6.553683846485456 43.253688129980375 +5.935256217996095 15.381608954167652 +6.078484425983943 42.128359969853804 +6.038192934679376 15.091572429442085 +6.182099170180582 41.32106112640432 +6.245167588411863 14.5279338110808 +6.389465535565342 39.73963818350563 +7.181012733872813 16.27351447951422 +9.59326677641476 125.92467701277123 +7.584086675268948 15.155818834922346 +9.56459923216978 108.14813378844673 +8.35697518159283 13.302320922728093 +9.837623129185623 86.47688407649207 +6.81544876767024 17.384632263376517 +8.281708305632678 104.81433764098533 +7.135644864267984 16.406253835101857 +8.434894805217763 93.97764294156815 +7.759863013658684 14.701958900114008 +8.820708673794373 78.8085957515861 +6.492036436114129 18.449423946507814 +7.2890241806377185 83.31550431722044 +6.734813126402034 17.642756423674776 +7.491875642240841 77.90137931372755 +7.214968545921615 16.175101262839863 +7.89475847268894 68.96253662514125 +6.006422108142317 20.202531941845336 +6.221338051760357 51.93217207351944 +6.12742785521823 19.747578186889104 +6.342445424163306 50.68614914496861 +6.37040606516053 18.870647434042585 +6.5838237688764725 48.28181198669522 +5.937518176431157 20.467192624362024 +6.103692698140513 47.63415091681678 +6.040945622450815 20.07146489610171 +6.20756869439985 46.68593542136009 +6.24881466524676 19.30347810486343 +6.41524845462156 44.83801182912499 +7.188801955277015 20.174829161190225 +9.711385025387063 131.02345141640026 +7.592770199074986 18.758212796412995 +9.659648525001606 112.49701640986895 +8.366882721634646 16.41613620234869 +9.90882724316716 89.96373179865813 +6.8221895133110015 21.58670125797887 +8.385347239834106 110.44868952392264 +7.1433178253964 20.343303735354095 +8.521492243575938 98.84411394941118 +7.768871677653259 18.183933860987825 +8.888069555139248 82.76398671864524 +6.497596522617691 22.943332688903038 +7.364270421826129 89.0515077286487 +6.741284316925525 21.915237568641764 +7.56045240266752 83.09622299338258 +7.22284248991074 20.04995483976802 +7.953327048838093 73.38279551725483 +6.009619559612319 25.185252332283653 +6.253579799926831 57.50296733380109 +6.131290256946878 24.602415010931313 +6.374790658596158 56.067962511167465 +6.37544730033358 23.481036787121145 +6.616077931408115 53.31581822861734 +5.9403119861904985 25.524648205338096 +6.130709810436756 53.18393265366841 +6.044338389965273 25.017267752718364 +6.23487376548102 52.08174443318374 +6.253290878348022 24.03418661461706 +6.442898077054891 49.94463106036305 +7.197719063083529 23.993765274076527 +9.832939659427325 136.09272320864213 +7.6026369487758965 22.270176711754363 +9.75758842252144 116.8001051919121 +8.37799051978373 19.430490458431724 +9.982230687226444 93.38272658645754 +6.8299614142043525 25.716578525793505 +8.493607825115175 116.09669302263833 +7.152109447431871 24.199073668088776 +8.611534362186605 103.68772976907279 +7.779075661154854 21.572792295317896 +8.957843729496396 86.66210750003732 +6.504049321026269 27.37703697178141 +7.444038250732146 94.83902800035973 +6.748757457659181 26.118226147021907 +7.6326693511098105 88.29969939483722 +7.231850777197515 23.841635419875193 +8.01454671952858 77.7652987913325 +6.013368575435365 30.132682143589356 +6.28805462007011 63.119837736224774 +6.135807202596793 29.414846519689938 +6.4093232912841485 61.47812097694455 +6.381312727644595 28.03660194858521 +6.650416567585694 58.34902740053262 +5.943592671709383 30.55116972639428 +6.159604243928604 58.77495689682996 +6.048313455191952 29.925682350431213 +6.264055257704169 57.50454300670235 +6.258512238874494 28.715992098144064 +6.4724028942080665 55.05381743453956 +7.207585884052028 27.727341930331292 +9.957411295892838 141.1170937909097 +7.613473336968968 25.689317923127845 +9.85796999834827 121.0456302559583 +8.39003037856016 22.344448731044704 +10.057413849894786 96.72641991818212 +6.838623016996434 29.770975745513283 +8.60610896611771 121.74178204661611 +7.161845877869932 27.970530352494166 +8.704663002549378 108.4955895069877 +7.790247516625787 24.86644790887819 +9.029677285871152 90.49424243534385 +6.511289041022429 31.747266842043164 +7.528252838995679 100.66780283427418 +6.757099746994475 30.248408054795895 +7.7083594445094175 93.50117678027829 +7.241812044001049 27.547203497142462 +8.078167858286802 82.10095747398636 +6.017619693939304 35.042414856214094 +6.324825463074243 68.78037357132636 +6.140915237103194 34.182131776083594 +6.446068983190188 66.91288568452559 +6.387910548632161 32.534178521788 +6.686800291175061 63.37610961544048 +5.947318417046172 35.544581309724386 +6.190434326530134 64.40535354156161 +6.052817413853588 34.79419730032717 +6.295143908616101 62.95134891594664 +6.264401078584577 33.34590249569342 +6.503742667671829 60.161085924589365 +7.218244154702733 31.374104168709625 +10.084318893441349 146.08419718436872 +7.6250928562597675 29.014931080428067 +9.960381558393278 125.22460635398241 +8.402775682919566 25.158883030445377 +10.134005542755876 99.98997884568158 +6.848046767808494 33.74791079358061 +8.722452531857016 127.36924524310672 +7.172372399051334 31.656143217812005 +8.800526608348617 113.25704174008122 +7.802190126927757 28.064548741655926 +9.103244761073016 94.25411363644531 +6.519219196175476 36.05180359283138 +7.616810459006525 106.52842302115181 +6.766191054629009 34.303716996832705 +7.787340480408753 98.69142654715408 +7.252565422688825 31.165262171487736 +8.143947572708695 86.38265534628772 +6.022326952653258 39.91260123977614 +6.363946913528104 74.48289711344867 +6.146555718007617 38.902219665624855 +6.485044821010091 72.369316565796 +6.395156668185356 36.971541491231655 +6.725183228960258 68.39275276142095 +5.951450616666412 40.503192544433674 +6.223251219840947 70.0739284725313 +6.05780068561414 39.620896930738446 +6.3281631925302015 68.41991077139664 +6.270885972705895 37.92174557671438 +6.536891191690739 65.26285303808 +7.229554271189959 34.93372314727412 +10.213223216442161 150.98418501968192 +7.637333814823047 32.247517135745696 +10.064449657149213 129.33025838391805 +8.416036712843086 27.875876426393194 +10.211680322686723 103.17051525744768 +6.858118325896707 37.646389379211314 +8.84223254628074 132.96598559829994 +7.183552260712182 35.25549415087842 +8.898785498783663 117.96328860990214 +7.814733950730997 31.167965471095023 +9.178250037502428 97.93732187647169 +6.527752069631152 40.28923588528229 +7.709581536130491 112.41205499477125 +6.775923324907824 38.28303497072192 +7.869419330259032 103.86232332094082 +7.263969226232369 34.695550262866064 +8.211652781749132 90.6048317747542 +6.027448060652269 44.741832170743 +6.405467405922438 80.22629352385458 +6.152674695929106 43.573600939942146 +6.526261419113251 77.84508007780047 +6.402974341880639 41.347192380591196 +6.765515070006527 73.39542581799162 +5.955953447437106 45.42568861501522 +6.258100964153727 75.78001985306011 +6.0632176229919255 44.40433487486765 +6.363131244666489 73.90854041953203 +6.277901453032079 42.44198640149965 +6.571818121992672 70.35622867743038 +7.241393645102208 38.4067007330369 +10.343727768496834 155.80931313835077 +7.650057103646967 35.38843994070083 +10.16983859173614 133.35758419731857 +8.429655814819245 30.498303099989876 +10.290154985670352 106.26659794758851 +6.868735694249435 41.466169469326516 +8.965043126401474 138.52036582179628 +7.195265121897812 38.76898885886508 +8.999115751269951 122.60708886341597 +7.827734298094775 34.178427932156644 +9.254425892840977 101.54093340365834 +6.536808271909618 44.458782049110425 +7.806412999469502 118.31025821941807 +6.78619981741046 42.18597034059276 +7.954395026235261 109.00663070050022 +7.275899235851666 38.13864193025107 +8.28106209311133 94.76317124310944 +6.032943918733476 49.5290482119846 +6.4494307316223525 86.00988079138898 +6.159222643358743 48.19519608789226 +6.5697243439810045 83.33830349339019 +6.411293683695386 45.66020172346611 +6.807742482504549 78.38120636793518 +5.960793832071986 50.31105751734546 +6.295025895792237 81.52338686288314 +6.069026180652642 49.14343860308781 +6.40006218606778 79.41598665913952 +6.28538769085406 46.905593113860355 +6.608490219514957 75.43886254584096 diff --git a/drone_simulation_api_call/data/config.json b/drone_simulation_api_call/data/config.json new file mode 100644 index 0000000..8e0713c --- /dev/null +++ b/drone_simulation_api_call/data/config.json @@ -0,0 +1,23 @@ +{ + "drone": { + "position": { + "x": 150, + "y": 150 + }, + "altitude": 100, + "initialDirection": 0, + "attackSpeed": 10, + "accelerationPath": 10, + "angularSpeed": 1, + "turnThreshold": 0.1 + }, + "ammo": "VOG-17", + "simulation": { + "timeStep": 0.1, + "physicsTimeStep": 0.01, + "hitRadius": 3, + "timeScale": 10, + "targetTimeStep": 0.05 + }, + "targetArrayTimeStep": 10 +} \ No newline at end of file diff --git a/drone_simulation_api_call/data/simulation_example.json b/drone_simulation_api_call/data/simulation_example.json new file mode 100644 index 0000000..a7f6662 --- /dev/null +++ b/drone_simulation_api_call/data/simulation_example.json @@ -0,0 +1,215 @@ +{ + "totalSteps": 10, + "steps": [ + { + "position": { + "x": 0, + "y": 0 + }, + "direction": 0.785, + "state": 3, + "targetIndex": 0, + "dropPoint": { + "x": 285.3, + "y": 192.1 + }, + "aimPoint": { + "x": 6.41, + "y": 6.41 + }, + "predictedTarget": { + "x": 301.2, + "y": 200.8 + } + }, + { + "position": { + "x": 0, + "y": 0 + }, + "direction": 0.835, + "state": 3, + "targetIndex": 0, + "dropPoint": { + "x": 285.1, + "y": 193 + }, + "aimPoint": { + "x": 6.03, + "y": 6.71 + }, + "predictedTarget": { + "x": 301.5, + "y": 201.1 + } + }, + { + "position": { + "x": 0, + "y": 0 + }, + "direction": 0.885, + "state": 3, + "targetIndex": 0, + "dropPoint": { + "x": 284.8, + "y": 193.9 + }, + "aimPoint": { + "x": 5.64, + "y": 7 + }, + "predictedTarget": { + "x": 301.8, + "y": 201.4 + } + }, + { + "position": { + "x": 0, + "y": 0 + }, + "direction": 0.588, + "state": 1, + "targetIndex": 0, + "dropPoint": { + "x": 285.6, + "y": 191.2 + }, + "aimPoint": { + "x": 7.55, + "y": 5.03 + }, + "predictedTarget": { + "x": 302.1, + "y": 201.7 + } + }, + { + "position": { + "x": 1.17, + "y": 0.78 + }, + "direction": 0.588, + "state": 1, + "targetIndex": 0, + "dropPoint": { + "x": 285.5, + "y": 191.3 + }, + "aimPoint": { + "x": 8.72, + "y": 5.81 + }, + "predictedTarget": { + "x": 302.4, + "y": 201.9 + } + }, + { + "position": { + "x": 3.51, + "y": 2.34 + }, + "direction": 0.588, + "state": 1, + "targetIndex": 0, + "dropPoint": { + "x": 285.3, + "y": 191.5 + }, + "aimPoint": { + "x": 11.06, + "y": 7.37 + }, + "predictedTarget": { + "x": 302.7, + "y": 202.2 + } + }, + { + "position": { + "x": 7.02, + "y": 4.68 + }, + "direction": 0.588, + "state": 1, + "targetIndex": 0, + "dropPoint": { + "x": 285, + "y": 191.8 + }, + "aimPoint": { + "x": 14.57, + "y": 9.71 + }, + "predictedTarget": { + "x": 303, + "y": 202.5 + } + }, + { + "position": { + "x": 11.7, + "y": 7.8 + }, + "direction": 0.587, + "state": 1, + "targetIndex": 0, + "dropPoint": { + "x": 284.8, + "y": 192 + }, + "aimPoint": { + "x": 19.25, + "y": 12.82 + }, + "predictedTarget": { + "x": 303.3, + "y": 202.8 + } + }, + { + "position": { + "x": 17.55, + "y": 11.7 + }, + "direction": 0.587, + "state": 4, + "targetIndex": 0, + "dropPoint": { + "x": 284.5, + "y": 192.3 + }, + "aimPoint": { + "x": 25.1, + "y": 16.72 + }, + "predictedTarget": { + "x": 303.6, + "y": 203.1 + } + }, + { + "position": { + "x": 19.05, + "y": 12.7 + }, + "direction": 0.586, + "state": 4, + "targetIndex": 0, + "dropPoint": { + "x": 284.4, + "y": 192.4 + }, + "aimPoint": { + "x": 26.6, + "y": 17.72 + }, + "predictedTarget": { + "x": 303.9, + "y": 203.4 + } + } + ] +} \ No newline at end of file diff --git a/drone_simulation_api_call/data/targets.json b/drone_simulation_api_call/data/targets.json new file mode 100644 index 0000000..6c37bc4 --- /dev/null +++ b/drone_simulation_api_call/data/targets.json @@ -0,0 +1,2426 @@ +{ + "targetCount": 5, + "timeSteps": 120, + "targets": [ + { + "positions": [ + { + "x": 340, + "y": 250 + }, + { + "x": 339.95, + "y": 252.09 + }, + { + "x": 339.78, + "y": 254.18 + }, + { + "x": 339.51, + "y": 256.26 + }, + { + "x": 339.13, + "y": 258.32 + }, + { + "x": 338.64, + "y": 260.35 + }, + { + "x": 338.04, + "y": 262.36 + }, + { + "x": 337.34, + "y": 264.33 + }, + { + "x": 336.54, + "y": 266.27 + }, + { + "x": 335.64, + "y": 268.16 + }, + { + "x": 334.64, + "y": 270 + }, + { + "x": 333.55, + "y": 271.79 + }, + { + "x": 332.36, + "y": 273.51 + }, + { + "x": 331.09, + "y": 275.17 + }, + { + "x": 329.73, + "y": 276.77 + }, + { + "x": 328.28, + "y": 278.28 + }, + { + "x": 326.77, + "y": 279.73 + }, + { + "x": 325.17, + "y": 281.09 + }, + { + "x": 323.51, + "y": 282.36 + }, + { + "x": 321.79, + "y": 283.55 + }, + { + "x": 320, + "y": 284.64 + }, + { + "x": 318.16, + "y": 285.64 + }, + { + "x": 316.27, + "y": 286.54 + }, + { + "x": 314.33, + "y": 287.34 + }, + { + "x": 312.36, + "y": 288.04 + }, + { + "x": 310.35, + "y": 288.64 + }, + { + "x": 308.32, + "y": 289.13 + }, + { + "x": 306.26, + "y": 289.51 + }, + { + "x": 304.18, + "y": 289.78 + }, + { + "x": 302.09, + "y": 289.95 + }, + { + "x": 300, + "y": 290 + }, + { + "x": 297.91, + "y": 289.95 + }, + { + "x": 295.82, + "y": 289.78 + }, + { + "x": 293.74, + "y": 289.51 + }, + { + "x": 291.68, + "y": 289.13 + }, + { + "x": 289.65, + "y": 288.64 + }, + { + "x": 287.64, + "y": 288.04 + }, + { + "x": 285.67, + "y": 287.34 + }, + { + "x": 283.73, + "y": 286.54 + }, + { + "x": 281.84, + "y": 285.64 + }, + { + "x": 280, + "y": 284.64 + }, + { + "x": 278.21, + "y": 283.55 + }, + { + "x": 276.49, + "y": 282.36 + }, + { + "x": 274.83, + "y": 281.09 + }, + { + "x": 273.23, + "y": 279.73 + }, + { + "x": 271.72, + "y": 278.28 + }, + { + "x": 270.27, + "y": 276.77 + }, + { + "x": 268.91, + "y": 275.17 + }, + { + "x": 267.64, + "y": 273.51 + }, + { + "x": 266.45, + "y": 271.79 + }, + { + "x": 265.36, + "y": 270 + }, + { + "x": 264.36, + "y": 268.16 + }, + { + "x": 263.46, + "y": 266.27 + }, + { + "x": 262.66, + "y": 264.33 + }, + { + "x": 261.96, + "y": 262.36 + }, + { + "x": 261.36, + "y": 260.35 + }, + { + "x": 260.87, + "y": 258.32 + }, + { + "x": 260.49, + "y": 256.26 + }, + { + "x": 260.22, + "y": 254.18 + }, + { + "x": 260.05, + "y": 252.09 + }, + { + "x": 260, + "y": 250 + }, + { + "x": 260.05, + "y": 247.91 + }, + { + "x": 260.22, + "y": 245.82 + }, + { + "x": 260.49, + "y": 243.74 + }, + { + "x": 260.87, + "y": 241.68 + }, + { + "x": 261.36, + "y": 239.65 + }, + { + "x": 261.96, + "y": 237.64 + }, + { + "x": 262.66, + "y": 235.67 + }, + { + "x": 263.46, + "y": 233.73 + }, + { + "x": 264.36, + "y": 231.84 + }, + { + "x": 265.36, + "y": 230 + }, + { + "x": 266.45, + "y": 228.21 + }, + { + "x": 267.64, + "y": 226.49 + }, + { + "x": 268.91, + "y": 224.83 + }, + { + "x": 270.27, + "y": 223.23 + }, + { + "x": 271.72, + "y": 221.72 + }, + { + "x": 273.23, + "y": 220.27 + }, + { + "x": 274.83, + "y": 218.91 + }, + { + "x": 276.49, + "y": 217.64 + }, + { + "x": 278.21, + "y": 216.45 + }, + { + "x": 280, + "y": 215.36 + }, + { + "x": 281.84, + "y": 214.36 + }, + { + "x": 283.73, + "y": 213.46 + }, + { + "x": 285.67, + "y": 212.66 + }, + { + "x": 287.64, + "y": 211.96 + }, + { + "x": 289.65, + "y": 211.36 + }, + { + "x": 291.68, + "y": 210.87 + }, + { + "x": 293.74, + "y": 210.49 + }, + { + "x": 295.82, + "y": 210.22 + }, + { + "x": 297.91, + "y": 210.05 + }, + { + "x": 300, + "y": 210 + }, + { + "x": 302.09, + "y": 210.05 + }, + { + "x": 304.18, + "y": 210.22 + }, + { + "x": 306.26, + "y": 210.49 + }, + { + "x": 308.32, + "y": 210.87 + }, + { + "x": 310.35, + "y": 211.36 + }, + { + "x": 312.36, + "y": 211.96 + }, + { + "x": 314.33, + "y": 212.66 + }, + { + "x": 316.27, + "y": 213.46 + }, + { + "x": 318.16, + "y": 214.36 + }, + { + "x": 320, + "y": 215.36 + }, + { + "x": 321.79, + "y": 216.45 + }, + { + "x": 323.51, + "y": 217.64 + }, + { + "x": 325.17, + "y": 218.91 + }, + { + "x": 326.77, + "y": 220.27 + }, + { + "x": 328.28, + "y": 221.72 + }, + { + "x": 329.73, + "y": 223.23 + }, + { + "x": 331.09, + "y": 224.83 + }, + { + "x": 332.36, + "y": 226.49 + }, + { + "x": 333.55, + "y": 228.21 + }, + { + "x": 334.64, + "y": 230 + }, + { + "x": 335.64, + "y": 231.84 + }, + { + "x": 336.54, + "y": 233.73 + }, + { + "x": 337.34, + "y": 235.67 + }, + { + "x": 338.04, + "y": 237.64 + }, + { + "x": 338.64, + "y": 239.65 + }, + { + "x": 339.13, + "y": 241.68 + }, + { + "x": 339.51, + "y": 243.74 + }, + { + "x": 339.78, + "y": 245.82 + }, + { + "x": 339.95, + "y": 247.91 + } + ] + }, + { + "positions": [ + { + "x": 327.5, + "y": 270.31 + }, + { + "x": 325.89, + "y": 271.19 + }, + { + "x": 324.24, + "y": 271.97 + }, + { + "x": 322.54, + "y": 272.68 + }, + { + "x": 320.82, + "y": 273.29 + }, + { + "x": 319.06, + "y": 273.81 + }, + { + "x": 317.28, + "y": 274.24 + }, + { + "x": 315.48, + "y": 274.57 + }, + { + "x": 313.66, + "y": 274.81 + }, + { + "x": 311.83, + "y": 274.95 + }, + { + "x": 310, + "y": 275 + }, + { + "x": 308.17, + "y": 274.95 + }, + { + "x": 306.34, + "y": 274.81 + }, + { + "x": 304.52, + "y": 274.57 + }, + { + "x": 302.72, + "y": 274.24 + }, + { + "x": 300.94, + "y": 273.81 + }, + { + "x": 299.18, + "y": 273.29 + }, + { + "x": 297.46, + "y": 272.68 + }, + { + "x": 295.76, + "y": 271.97 + }, + { + "x": 294.11, + "y": 271.19 + }, + { + "x": 292.5, + "y": 270.31 + }, + { + "x": 290.94, + "y": 269.35 + }, + { + "x": 289.43, + "y": 268.32 + }, + { + "x": 287.97, + "y": 267.2 + }, + { + "x": 286.58, + "y": 266.01 + }, + { + "x": 285.25, + "y": 264.75 + }, + { + "x": 283.99, + "y": 263.42 + }, + { + "x": 282.8, + "y": 262.03 + }, + { + "x": 281.68, + "y": 260.57 + }, + { + "x": 280.65, + "y": 259.06 + }, + { + "x": 279.69, + "y": 257.5 + }, + { + "x": 278.81, + "y": 255.89 + }, + { + "x": 278.03, + "y": 254.24 + }, + { + "x": 277.32, + "y": 252.54 + }, + { + "x": 276.71, + "y": 250.82 + }, + { + "x": 276.19, + "y": 249.06 + }, + { + "x": 275.76, + "y": 247.28 + }, + { + "x": 275.43, + "y": 245.48 + }, + { + "x": 275.19, + "y": 243.66 + }, + { + "x": 275.05, + "y": 241.83 + }, + { + "x": 275, + "y": 240 + }, + { + "x": 275.05, + "y": 238.17 + }, + { + "x": 275.19, + "y": 236.34 + }, + { + "x": 275.43, + "y": 234.52 + }, + { + "x": 275.76, + "y": 232.72 + }, + { + "x": 276.19, + "y": 230.94 + }, + { + "x": 276.71, + "y": 229.18 + }, + { + "x": 277.32, + "y": 227.46 + }, + { + "x": 278.03, + "y": 225.76 + }, + { + "x": 278.81, + "y": 224.11 + }, + { + "x": 279.69, + "y": 222.5 + }, + { + "x": 280.65, + "y": 220.94 + }, + { + "x": 281.68, + "y": 219.43 + }, + { + "x": 282.8, + "y": 217.97 + }, + { + "x": 283.99, + "y": 216.58 + }, + { + "x": 285.25, + "y": 215.25 + }, + { + "x": 286.58, + "y": 213.99 + }, + { + "x": 287.97, + "y": 212.8 + }, + { + "x": 289.43, + "y": 211.68 + }, + { + "x": 290.94, + "y": 210.65 + }, + { + "x": 292.5, + "y": 209.69 + }, + { + "x": 294.11, + "y": 208.81 + }, + { + "x": 295.76, + "y": 208.03 + }, + { + "x": 297.46, + "y": 207.32 + }, + { + "x": 299.18, + "y": 206.71 + }, + { + "x": 300.94, + "y": 206.19 + }, + { + "x": 302.72, + "y": 205.76 + }, + { + "x": 304.52, + "y": 205.43 + }, + { + "x": 306.34, + "y": 205.19 + }, + { + "x": 308.17, + "y": 205.05 + }, + { + "x": 310, + "y": 205 + }, + { + "x": 311.83, + "y": 205.05 + }, + { + "x": 313.66, + "y": 205.19 + }, + { + "x": 315.48, + "y": 205.43 + }, + { + "x": 317.28, + "y": 205.76 + }, + { + "x": 319.06, + "y": 206.19 + }, + { + "x": 320.82, + "y": 206.71 + }, + { + "x": 322.54, + "y": 207.32 + }, + { + "x": 324.24, + "y": 208.03 + }, + { + "x": 325.89, + "y": 208.81 + }, + { + "x": 327.5, + "y": 209.69 + }, + { + "x": 329.06, + "y": 210.65 + }, + { + "x": 330.57, + "y": 211.68 + }, + { + "x": 332.03, + "y": 212.8 + }, + { + "x": 333.42, + "y": 213.99 + }, + { + "x": 334.75, + "y": 215.25 + }, + { + "x": 336.01, + "y": 216.58 + }, + { + "x": 337.2, + "y": 217.97 + }, + { + "x": 338.32, + "y": 219.43 + }, + { + "x": 339.35, + "y": 220.94 + }, + { + "x": 340.31, + "y": 222.5 + }, + { + "x": 341.19, + "y": 224.11 + }, + { + "x": 341.97, + "y": 225.76 + }, + { + "x": 342.68, + "y": 227.46 + }, + { + "x": 343.29, + "y": 229.18 + }, + { + "x": 343.81, + "y": 230.94 + }, + { + "x": 344.24, + "y": 232.72 + }, + { + "x": 344.57, + "y": 234.52 + }, + { + "x": 344.81, + "y": 236.34 + }, + { + "x": 344.95, + "y": 238.17 + }, + { + "x": 345, + "y": 240 + }, + { + "x": 344.95, + "y": 241.83 + }, + { + "x": 344.81, + "y": 243.66 + }, + { + "x": 344.57, + "y": 245.48 + }, + { + "x": 344.24, + "y": 247.28 + }, + { + "x": 343.81, + "y": 249.06 + }, + { + "x": 343.29, + "y": 250.82 + }, + { + "x": 342.68, + "y": 252.54 + }, + { + "x": 341.97, + "y": 254.24 + }, + { + "x": 341.19, + "y": 255.89 + }, + { + "x": 340.31, + "y": 257.5 + }, + { + "x": 339.35, + "y": 259.06 + }, + { + "x": 338.32, + "y": 260.57 + }, + { + "x": 337.2, + "y": 262.03 + }, + { + "x": 336.01, + "y": 263.42 + }, + { + "x": 334.75, + "y": 264.75 + }, + { + "x": 333.42, + "y": 266.01 + }, + { + "x": 332.03, + "y": 267.2 + }, + { + "x": 330.57, + "y": 268.32 + }, + { + "x": 329.06, + "y": 269.35 + } + ] + }, + { + "positions": [ + { + "x": 235, + "y": 255 + }, + { + "x": 235.07, + "y": 252.38 + }, + { + "x": 235.27, + "y": 249.77 + }, + { + "x": 235.62, + "y": 247.18 + }, + { + "x": 236.09, + "y": 244.6 + }, + { + "x": 236.7, + "y": 242.06 + }, + { + "x": 237.45, + "y": 239.55 + }, + { + "x": 238.32, + "y": 237.08 + }, + { + "x": 239.32, + "y": 234.66 + }, + { + "x": 240.45, + "y": 232.3 + }, + { + "x": 241.7, + "y": 230 + }, + { + "x": 243.07, + "y": 227.77 + }, + { + "x": 244.55, + "y": 225.61 + }, + { + "x": 246.14, + "y": 223.53 + }, + { + "x": 247.84, + "y": 221.54 + }, + { + "x": 249.64, + "y": 219.64 + }, + { + "x": 251.54, + "y": 217.84 + }, + { + "x": 253.53, + "y": 216.14 + }, + { + "x": 255.61, + "y": 214.55 + }, + { + "x": 257.77, + "y": 213.07 + }, + { + "x": 260, + "y": 211.7 + }, + { + "x": 262.3, + "y": 210.45 + }, + { + "x": 264.66, + "y": 209.32 + }, + { + "x": 267.08, + "y": 208.32 + }, + { + "x": 269.55, + "y": 207.45 + }, + { + "x": 272.06, + "y": 206.7 + }, + { + "x": 274.6, + "y": 206.09 + }, + { + "x": 277.18, + "y": 205.62 + }, + { + "x": 279.77, + "y": 205.27 + }, + { + "x": 282.38, + "y": 205.07 + }, + { + "x": 285, + "y": 205 + }, + { + "x": 287.62, + "y": 205.07 + }, + { + "x": 290.23, + "y": 205.27 + }, + { + "x": 292.82, + "y": 205.62 + }, + { + "x": 295.4, + "y": 206.09 + }, + { + "x": 297.94, + "y": 206.7 + }, + { + "x": 300.45, + "y": 207.45 + }, + { + "x": 302.92, + "y": 208.32 + }, + { + "x": 305.34, + "y": 209.32 + }, + { + "x": 307.7, + "y": 210.45 + }, + { + "x": 310, + "y": 211.7 + }, + { + "x": 312.23, + "y": 213.07 + }, + { + "x": 314.39, + "y": 214.55 + }, + { + "x": 316.47, + "y": 216.14 + }, + { + "x": 318.46, + "y": 217.84 + }, + { + "x": 320.36, + "y": 219.64 + }, + { + "x": 322.16, + "y": 221.54 + }, + { + "x": 323.86, + "y": 223.53 + }, + { + "x": 325.45, + "y": 225.61 + }, + { + "x": 326.93, + "y": 227.77 + }, + { + "x": 328.3, + "y": 230 + }, + { + "x": 329.55, + "y": 232.3 + }, + { + "x": 330.68, + "y": 234.66 + }, + { + "x": 331.68, + "y": 237.08 + }, + { + "x": 332.55, + "y": 239.55 + }, + { + "x": 333.3, + "y": 242.06 + }, + { + "x": 333.91, + "y": 244.6 + }, + { + "x": 334.38, + "y": 247.18 + }, + { + "x": 334.73, + "y": 249.77 + }, + { + "x": 334.93, + "y": 252.38 + }, + { + "x": 335, + "y": 255 + }, + { + "x": 334.93, + "y": 257.62 + }, + { + "x": 334.73, + "y": 260.23 + }, + { + "x": 334.38, + "y": 262.82 + }, + { + "x": 333.91, + "y": 265.4 + }, + { + "x": 333.3, + "y": 267.94 + }, + { + "x": 332.55, + "y": 270.45 + }, + { + "x": 331.68, + "y": 272.92 + }, + { + "x": 330.68, + "y": 275.34 + }, + { + "x": 329.55, + "y": 277.7 + }, + { + "x": 328.3, + "y": 280 + }, + { + "x": 326.93, + "y": 282.23 + }, + { + "x": 325.45, + "y": 284.39 + }, + { + "x": 323.86, + "y": 286.47 + }, + { + "x": 322.16, + "y": 288.46 + }, + { + "x": 320.36, + "y": 290.36 + }, + { + "x": 318.46, + "y": 292.16 + }, + { + "x": 316.47, + "y": 293.86 + }, + { + "x": 314.39, + "y": 295.45 + }, + { + "x": 312.23, + "y": 296.93 + }, + { + "x": 310, + "y": 298.3 + }, + { + "x": 307.7, + "y": 299.55 + }, + { + "x": 305.34, + "y": 300.68 + }, + { + "x": 302.92, + "y": 301.68 + }, + { + "x": 300.45, + "y": 302.55 + }, + { + "x": 297.94, + "y": 303.3 + }, + { + "x": 295.4, + "y": 303.91 + }, + { + "x": 292.82, + "y": 304.38 + }, + { + "x": 290.23, + "y": 304.73 + }, + { + "x": 287.62, + "y": 304.93 + }, + { + "x": 285, + "y": 305 + }, + { + "x": 282.38, + "y": 304.93 + }, + { + "x": 279.77, + "y": 304.73 + }, + { + "x": 277.18, + "y": 304.38 + }, + { + "x": 274.6, + "y": 303.91 + }, + { + "x": 272.06, + "y": 303.3 + }, + { + "x": 269.55, + "y": 302.55 + }, + { + "x": 267.08, + "y": 301.68 + }, + { + "x": 264.66, + "y": 300.68 + }, + { + "x": 262.3, + "y": 299.55 + }, + { + "x": 260, + "y": 298.3 + }, + { + "x": 257.77, + "y": 296.93 + }, + { + "x": 255.61, + "y": 295.45 + }, + { + "x": 253.53, + "y": 293.86 + }, + { + "x": 251.54, + "y": 292.16 + }, + { + "x": 249.64, + "y": 290.36 + }, + { + "x": 247.84, + "y": 288.46 + }, + { + "x": 246.14, + "y": 286.47 + }, + { + "x": 244.55, + "y": 284.39 + }, + { + "x": 243.07, + "y": 282.23 + }, + { + "x": 241.7, + "y": 280 + }, + { + "x": 240.45, + "y": 277.7 + }, + { + "x": 239.32, + "y": 275.34 + }, + { + "x": 238.32, + "y": 272.92 + }, + { + "x": 237.45, + "y": 270.45 + }, + { + "x": 236.7, + "y": 267.94 + }, + { + "x": 236.09, + "y": 265.4 + }, + { + "x": 235.62, + "y": 262.82 + }, + { + "x": 235.27, + "y": 260.23 + }, + { + "x": 235.07, + "y": 257.62 + } + ] + }, + { + "positions": [ + { + "x": 320, + "y": 295 + }, + { + "x": 318.43, + "y": 294.96 + }, + { + "x": 316.86, + "y": 294.84 + }, + { + "x": 315.31, + "y": 294.63 + }, + { + "x": 313.76, + "y": 294.34 + }, + { + "x": 312.24, + "y": 293.98 + }, + { + "x": 310.73, + "y": 293.53 + }, + { + "x": 309.25, + "y": 293.01 + }, + { + "x": 307.8, + "y": 292.41 + }, + { + "x": 306.38, + "y": 291.73 + }, + { + "x": 305, + "y": 290.98 + }, + { + "x": 303.66, + "y": 290.16 + }, + { + "x": 302.37, + "y": 289.27 + }, + { + "x": 301.12, + "y": 288.31 + }, + { + "x": 299.93, + "y": 287.29 + }, + { + "x": 298.79, + "y": 286.21 + }, + { + "x": 297.71, + "y": 285.07 + }, + { + "x": 296.69, + "y": 283.88 + }, + { + "x": 295.73, + "y": 282.63 + }, + { + "x": 294.84, + "y": 281.34 + }, + { + "x": 294.02, + "y": 280 + }, + { + "x": 293.27, + "y": 278.62 + }, + { + "x": 292.59, + "y": 277.2 + }, + { + "x": 291.99, + "y": 275.75 + }, + { + "x": 291.47, + "y": 274.27 + }, + { + "x": 291.02, + "y": 272.76 + }, + { + "x": 290.66, + "y": 271.24 + }, + { + "x": 290.37, + "y": 269.69 + }, + { + "x": 290.16, + "y": 268.14 + }, + { + "x": 290.04, + "y": 266.57 + }, + { + "x": 290, + "y": 265 + }, + { + "x": 290.04, + "y": 263.43 + }, + { + "x": 290.16, + "y": 261.86 + }, + { + "x": 290.37, + "y": 260.31 + }, + { + "x": 290.66, + "y": 258.76 + }, + { + "x": 291.02, + "y": 257.24 + }, + { + "x": 291.47, + "y": 255.73 + }, + { + "x": 291.99, + "y": 254.25 + }, + { + "x": 292.59, + "y": 252.8 + }, + { + "x": 293.27, + "y": 251.38 + }, + { + "x": 294.02, + "y": 250 + }, + { + "x": 294.84, + "y": 248.66 + }, + { + "x": 295.73, + "y": 247.37 + }, + { + "x": 296.69, + "y": 246.12 + }, + { + "x": 297.71, + "y": 244.93 + }, + { + "x": 298.79, + "y": 243.79 + }, + { + "x": 299.93, + "y": 242.71 + }, + { + "x": 301.12, + "y": 241.69 + }, + { + "x": 302.37, + "y": 240.73 + }, + { + "x": 303.66, + "y": 239.84 + }, + { + "x": 305, + "y": 239.02 + }, + { + "x": 306.38, + "y": 238.27 + }, + { + "x": 307.8, + "y": 237.59 + }, + { + "x": 309.25, + "y": 236.99 + }, + { + "x": 310.73, + "y": 236.47 + }, + { + "x": 312.24, + "y": 236.02 + }, + { + "x": 313.76, + "y": 235.66 + }, + { + "x": 315.31, + "y": 235.37 + }, + { + "x": 316.86, + "y": 235.16 + }, + { + "x": 318.43, + "y": 235.04 + }, + { + "x": 320, + "y": 235 + }, + { + "x": 321.57, + "y": 235.04 + }, + { + "x": 323.14, + "y": 235.16 + }, + { + "x": 324.69, + "y": 235.37 + }, + { + "x": 326.24, + "y": 235.66 + }, + { + "x": 327.76, + "y": 236.02 + }, + { + "x": 329.27, + "y": 236.47 + }, + { + "x": 330.75, + "y": 236.99 + }, + { + "x": 332.2, + "y": 237.59 + }, + { + "x": 333.62, + "y": 238.27 + }, + { + "x": 335, + "y": 239.02 + }, + { + "x": 336.34, + "y": 239.84 + }, + { + "x": 337.63, + "y": 240.73 + }, + { + "x": 338.88, + "y": 241.69 + }, + { + "x": 340.07, + "y": 242.71 + }, + { + "x": 341.21, + "y": 243.79 + }, + { + "x": 342.29, + "y": 244.93 + }, + { + "x": 343.31, + "y": 246.12 + }, + { + "x": 344.27, + "y": 247.37 + }, + { + "x": 345.16, + "y": 248.66 + }, + { + "x": 345.98, + "y": 250 + }, + { + "x": 346.73, + "y": 251.38 + }, + { + "x": 347.41, + "y": 252.8 + }, + { + "x": 348.01, + "y": 254.25 + }, + { + "x": 348.53, + "y": 255.73 + }, + { + "x": 348.98, + "y": 257.24 + }, + { + "x": 349.34, + "y": 258.76 + }, + { + "x": 349.63, + "y": 260.31 + }, + { + "x": 349.84, + "y": 261.86 + }, + { + "x": 349.96, + "y": 263.43 + }, + { + "x": 350, + "y": 265 + }, + { + "x": 349.96, + "y": 266.57 + }, + { + "x": 349.84, + "y": 268.14 + }, + { + "x": 349.63, + "y": 269.69 + }, + { + "x": 349.34, + "y": 271.24 + }, + { + "x": 348.98, + "y": 272.76 + }, + { + "x": 348.53, + "y": 274.27 + }, + { + "x": 348.01, + "y": 275.75 + }, + { + "x": 347.41, + "y": 277.2 + }, + { + "x": 346.73, + "y": 278.62 + }, + { + "x": 345.98, + "y": 280 + }, + { + "x": 345.16, + "y": 281.34 + }, + { + "x": 344.27, + "y": 282.63 + }, + { + "x": 343.31, + "y": 283.88 + }, + { + "x": 342.29, + "y": 285.07 + }, + { + "x": 341.21, + "y": 286.21 + }, + { + "x": 340.07, + "y": 287.29 + }, + { + "x": 338.88, + "y": 288.31 + }, + { + "x": 337.63, + "y": 289.27 + }, + { + "x": 336.34, + "y": 290.16 + }, + { + "x": 335, + "y": 290.98 + }, + { + "x": 333.62, + "y": 291.73 + }, + { + "x": 332.2, + "y": 292.41 + }, + { + "x": 330.75, + "y": 293.01 + }, + { + "x": 329.27, + "y": 293.53 + }, + { + "x": 327.76, + "y": 293.98 + }, + { + "x": 326.24, + "y": 294.34 + }, + { + "x": 324.69, + "y": 294.63 + }, + { + "x": 323.14, + "y": 294.84 + }, + { + "x": 321.57, + "y": 294.96 + } + ] + }, + { + "positions": [ + { + "x": 295, + "y": 185 + }, + { + "x": 297.36, + "y": 185.06 + }, + { + "x": 299.7, + "y": 185.25 + }, + { + "x": 302.04, + "y": 185.55 + }, + { + "x": 304.36, + "y": 185.98 + }, + { + "x": 306.65, + "y": 186.53 + }, + { + "x": 308.91, + "y": 187.2 + }, + { + "x": 311.13, + "y": 187.99 + }, + { + "x": 313.3, + "y": 188.89 + }, + { + "x": 315.43, + "y": 189.9 + }, + { + "x": 317.5, + "y": 191.03 + }, + { + "x": 319.51, + "y": 192.26 + }, + { + "x": 321.45, + "y": 193.59 + }, + { + "x": 323.32, + "y": 195.03 + }, + { + "x": 325.11, + "y": 196.56 + }, + { + "x": 326.82, + "y": 198.18 + }, + { + "x": 328.44, + "y": 199.89 + }, + { + "x": 329.97, + "y": 201.68 + }, + { + "x": 331.41, + "y": 203.55 + }, + { + "x": 332.74, + "y": 205.49 + }, + { + "x": 333.97, + "y": 207.5 + }, + { + "x": 335.1, + "y": 209.57 + }, + { + "x": 336.11, + "y": 211.7 + }, + { + "x": 337.01, + "y": 213.87 + }, + { + "x": 337.8, + "y": 216.09 + }, + { + "x": 338.47, + "y": 218.35 + }, + { + "x": 339.02, + "y": 220.64 + }, + { + "x": 339.45, + "y": 222.96 + }, + { + "x": 339.75, + "y": 225.3 + }, + { + "x": 339.94, + "y": 227.64 + }, + { + "x": 340, + "y": 230 + }, + { + "x": 339.94, + "y": 232.36 + }, + { + "x": 339.75, + "y": 234.7 + }, + { + "x": 339.45, + "y": 237.04 + }, + { + "x": 339.02, + "y": 239.36 + }, + { + "x": 338.47, + "y": 241.65 + }, + { + "x": 337.8, + "y": 243.91 + }, + { + "x": 337.01, + "y": 246.13 + }, + { + "x": 336.11, + "y": 248.3 + }, + { + "x": 335.1, + "y": 250.43 + }, + { + "x": 333.97, + "y": 252.5 + }, + { + "x": 332.74, + "y": 254.51 + }, + { + "x": 331.41, + "y": 256.45 + }, + { + "x": 329.97, + "y": 258.32 + }, + { + "x": 328.44, + "y": 260.11 + }, + { + "x": 326.82, + "y": 261.82 + }, + { + "x": 325.11, + "y": 263.44 + }, + { + "x": 323.32, + "y": 264.97 + }, + { + "x": 321.45, + "y": 266.41 + }, + { + "x": 319.51, + "y": 267.74 + }, + { + "x": 317.5, + "y": 268.97 + }, + { + "x": 315.43, + "y": 270.1 + }, + { + "x": 313.3, + "y": 271.11 + }, + { + "x": 311.13, + "y": 272.01 + }, + { + "x": 308.91, + "y": 272.8 + }, + { + "x": 306.65, + "y": 273.47 + }, + { + "x": 304.36, + "y": 274.02 + }, + { + "x": 302.04, + "y": 274.45 + }, + { + "x": 299.7, + "y": 274.75 + }, + { + "x": 297.36, + "y": 274.94 + }, + { + "x": 295, + "y": 275 + }, + { + "x": 292.64, + "y": 274.94 + }, + { + "x": 290.3, + "y": 274.75 + }, + { + "x": 287.96, + "y": 274.45 + }, + { + "x": 285.64, + "y": 274.02 + }, + { + "x": 283.35, + "y": 273.47 + }, + { + "x": 281.09, + "y": 272.8 + }, + { + "x": 278.87, + "y": 272.01 + }, + { + "x": 276.7, + "y": 271.11 + }, + { + "x": 274.57, + "y": 270.1 + }, + { + "x": 272.5, + "y": 268.97 + }, + { + "x": 270.49, + "y": 267.74 + }, + { + "x": 268.55, + "y": 266.41 + }, + { + "x": 266.68, + "y": 264.97 + }, + { + "x": 264.89, + "y": 263.44 + }, + { + "x": 263.18, + "y": 261.82 + }, + { + "x": 261.56, + "y": 260.11 + }, + { + "x": 260.03, + "y": 258.32 + }, + { + "x": 258.59, + "y": 256.45 + }, + { + "x": 257.26, + "y": 254.51 + }, + { + "x": 256.03, + "y": 252.5 + }, + { + "x": 254.9, + "y": 250.43 + }, + { + "x": 253.89, + "y": 248.3 + }, + { + "x": 252.99, + "y": 246.13 + }, + { + "x": 252.2, + "y": 243.91 + }, + { + "x": 251.53, + "y": 241.65 + }, + { + "x": 250.98, + "y": 239.36 + }, + { + "x": 250.55, + "y": 237.04 + }, + { + "x": 250.25, + "y": 234.7 + }, + { + "x": 250.06, + "y": 232.36 + }, + { + "x": 250, + "y": 230 + }, + { + "x": 250.06, + "y": 227.64 + }, + { + "x": 250.25, + "y": 225.3 + }, + { + "x": 250.55, + "y": 222.96 + }, + { + "x": 250.98, + "y": 220.64 + }, + { + "x": 251.53, + "y": 218.35 + }, + { + "x": 252.2, + "y": 216.09 + }, + { + "x": 252.99, + "y": 213.87 + }, + { + "x": 253.89, + "y": 211.7 + }, + { + "x": 254.9, + "y": 209.57 + }, + { + "x": 256.03, + "y": 207.5 + }, + { + "x": 257.26, + "y": 205.49 + }, + { + "x": 258.59, + "y": 203.55 + }, + { + "x": 260.03, + "y": 201.68 + }, + { + "x": 261.56, + "y": 199.89 + }, + { + "x": 263.18, + "y": 198.18 + }, + { + "x": 264.89, + "y": 196.56 + }, + { + "x": 266.68, + "y": 195.03 + }, + { + "x": 268.55, + "y": 193.59 + }, + { + "x": 270.49, + "y": 192.26 + }, + { + "x": 272.5, + "y": 191.03 + }, + { + "x": 274.57, + "y": 189.9 + }, + { + "x": 276.7, + "y": 188.89 + }, + { + "x": 278.87, + "y": 187.99 + }, + { + "x": 281.09, + "y": 187.2 + }, + { + "x": 283.35, + "y": 186.53 + }, + { + "x": 285.64, + "y": 185.98 + }, + { + "x": 287.96, + "y": 185.55 + }, + { + "x": 290.3, + "y": 185.25 + }, + { + "x": 292.64, + "y": 185.06 + } + ] + } + ] +} \ No newline at end of file diff --git a/drone_simulation_api_call/include/api/api_client.hpp b/drone_simulation_api_call/include/api/api_client.hpp new file mode 100644 index 0000000..db92a60 --- /dev/null +++ b/drone_simulation_api_call/include/api/api_client.hpp @@ -0,0 +1,18 @@ +#pragma once +#include +#include "httplib.h" + +class ApiClient { +public: + ApiClient(const std::string url, int port, int connectionTimeout, int readTimeout); + + auto get(const std::string& path, const httplib::Headers& headers = {}) -> httplib::Result; + + auto post(const std::string& path, + const std::string& body, + const std::string& contentType = "application/json", + const httplib::Headers& headers = {}) -> httplib::Result; + +private: + httplib::Client client; +}; diff --git a/drone_simulation_api_call/include/api/network.hpp b/drone_simulation_api_call/include/api/network.hpp new file mode 100644 index 0000000..2d61e0c --- /dev/null +++ b/drone_simulation_api_call/include/api/network.hpp @@ -0,0 +1,6 @@ +#pragma once +#include "httplib.h" + +enum class ApiCallOutcome { Success, DoNotRetry, Retry }; + +ApiCallOutcome classifyApiCallResult(const httplib::Result& result); diff --git a/drone_simulation_api_call/include/config_loaders/factory.hpp b/drone_simulation_api_call/include/config_loaders/factory.hpp new file mode 100644 index 0000000..6fb38c8 --- /dev/null +++ b/drone_simulation_api_call/include/config_loaders/factory.hpp @@ -0,0 +1,15 @@ +#pragma once +#include +#include +#include "simulation.hpp" +#include "interfaces/ballistic_solver.hpp" +#include "interfaces/target_provider.hpp" +#include "interfaces/config_loader.hpp" + +enum class SolverType { ANALYTICAL, TABLE }; +enum class ProviderType { JSON, THREAD_SAFE }; +enum class LoaderType { FILE }; + +std::unique_ptr createSolver(SolverType type, const DroneConfig& config, const std::string tablePath = ""); +std::unique_ptr createProvider(ProviderType type, const std::string path, float arrayTimeStep, float timeScale); +std::unique_ptr createLoader(LoaderType type, const std::string c, const std::string a); \ No newline at end of file diff --git a/drone_simulation_api_call/include/config_loaders/file_config_loader.hpp b/drone_simulation_api_call/include/config_loaders/file_config_loader.hpp new file mode 100644 index 0000000..a504bcb --- /dev/null +++ b/drone_simulation_api_call/include/config_loaders/file_config_loader.hpp @@ -0,0 +1,18 @@ +#pragma once +#include +#include "interfaces/config_loader.hpp" + +class FileConfigLoader : public IConfigLoader { + AmmoParams ammoParams; + DroneConfig droneConfig; + const std::string droneConfigPath; + const std::string ammoConfigPath; + bool loaded = false; + +public: + FileConfigLoader(const std::string droneConfigPath, const std::string ammoConfigPath); + + auto load() -> void override; + auto getConfig() -> DroneConfig override; + auto getAmmoParams() -> AmmoParams override; +}; \ No newline at end of file diff --git a/drone_simulation_api_call/include/drone_physics.hpp b/drone_simulation_api_call/include/drone_physics.hpp new file mode 100644 index 0000000..2de968d --- /dev/null +++ b/drone_simulation_api_call/include/drone_physics.hpp @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include +#include "simulation.hpp" +#include "thread_safe_queue.hpp" + +class DronePhysics { +public: + DronePhysics(const DroneConfig& config); + + auto sendCommand(const DroneCommand& cmd) -> void; + auto getTelemetry() const -> DroneTelemetry; + auto run() -> void; + auto start() -> void; + auto stop() -> void; + auto isThreadReady() const -> bool; + +private: + auto stepPhysics(float dt) -> void; + auto processState(float dt) -> void; + + DroneContext droneContext; + DroneConfig config; + ThreadSafeQueue commandQueue; + mutable std::mutex mtx; + float elapsedTime = 0.0f; + float angleSpeed = 0.0f; + DroneStates currentState = DroneStates::Stopped; + std::atomic ready{false}; + std::atomic running{false}; + std::atomic stopFlag{false}; +}; diff --git a/drone_simulation_api_call/include/drone_states/state_accelerating.hpp b/drone_simulation_api_call/include/drone_states/state_accelerating.hpp new file mode 100644 index 0000000..2c6b747 --- /dev/null +++ b/drone_simulation_api_call/include/drone_states/state_accelerating.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "interfaces/drone_state.hpp" + +class StateAccelerating : public IDroneState { +public: + StateAccelerating(); + + auto execute(const DroneStateInput& input) -> std::unique_ptr override; + auto name() const -> DroneStates override; +}; \ No newline at end of file diff --git a/drone_simulation_api_call/include/drone_states/state_decelerating.hpp b/drone_simulation_api_call/include/drone_states/state_decelerating.hpp new file mode 100644 index 0000000..e22d9a7 --- /dev/null +++ b/drone_simulation_api_call/include/drone_states/state_decelerating.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "interfaces/drone_state.hpp" + +class StateDecelerating : public IDroneState { +public: + StateDecelerating(); + + auto execute(const DroneStateInput& input) -> std::unique_ptr override; + auto name() const -> DroneStates override; +}; \ No newline at end of file diff --git a/drone_simulation_api_call/include/drone_states/state_moving.hpp b/drone_simulation_api_call/include/drone_states/state_moving.hpp new file mode 100644 index 0000000..2ce0f4b --- /dev/null +++ b/drone_simulation_api_call/include/drone_states/state_moving.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "interfaces/drone_state.hpp" + +class StateMoving : public IDroneState { +public: + StateMoving(); + + auto execute(const DroneStateInput& input) -> std::unique_ptr override; + auto name() const -> DroneStates override; +}; \ No newline at end of file diff --git a/drone_simulation_api_call/include/drone_states/state_stopped.hpp b/drone_simulation_api_call/include/drone_states/state_stopped.hpp new file mode 100644 index 0000000..f69b3ad --- /dev/null +++ b/drone_simulation_api_call/include/drone_states/state_stopped.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "interfaces/drone_state.hpp" + +class StateStopped : public IDroneState { +public: + StateStopped(); + + auto execute(const DroneStateInput& input) -> std::unique_ptr override; + auto name() const -> DroneStates override; +}; \ No newline at end of file diff --git a/drone_simulation_api_call/include/drone_states/state_turning.hpp b/drone_simulation_api_call/include/drone_states/state_turning.hpp new file mode 100644 index 0000000..87e54b9 --- /dev/null +++ b/drone_simulation_api_call/include/drone_states/state_turning.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "interfaces/drone_state.hpp" + +class StateTurning : public IDroneState { +public: + StateTurning(); + + auto execute(const DroneStateInput& input) -> std::unique_ptr override; + auto name() const -> DroneStates override; +}; \ No newline at end of file diff --git a/drone_simulation_api_call/include/httplib.h b/drone_simulation_api_call/include/httplib.h new file mode 100644 index 0000000..b130487 --- /dev/null +++ b/drone_simulation_api_call/include/httplib.h @@ -0,0 +1,23633 @@ +// +// httplib.h +// +// Copyright (c) 2026 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_HTTPLIB_H + +#define CPPHTTPLIB_VERSION "0.52.0" +#define CPPHTTPLIB_VERSION_NUM "0x003400" + +#ifdef _WIN32 +#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00 +#error "cpp-httplib doesn't support Windows 8 or lower. Please use Windows 10 or later." +#endif +#endif + +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND +#define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND 0 +#endif + +#ifndef CPPHTTPLIB_EXPECT_100_THRESHOLD +#define CPPHTTPLIB_EXPECT_100_THRESHOLD 1024 +#endif + +#ifndef CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND +#define CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND 1000 +#endif + +#ifndef CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD +#define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD (1024 * 1024) +#endif + +#ifndef CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND +#define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND 50 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 1000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_COUNT +#define CPPHTTPLIB_HEADER_MAX_COUNT 100 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (100 * 1024 * 1024) // 100MB +#endif + +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_RANGE_MAX_COUNT +#define CPPHTTPLIB_RANGE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_IPV6_V6ONLY +#define CPPHTTPLIB_IPV6_V6ONLY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_SEND_BUFSIZ +#define CPPHTTPLIB_SEND_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT ((std::max)(8u, std::thread::hardware_concurrency() > 0 ? std::thread::hardware_concurrency() - 1 : 0)) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_MAX_COUNT +#define CPPHTTPLIB_THREAD_POOL_MAX_COUNT (CPPHTTPLIB_THREAD_POOL_COUNT * 4) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT +#define CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT 3 // seconds +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 128 +#endif + +#ifndef CPPHTTPLIB_MAX_LINE_LENGTH +#define CPPHTTPLIB_MAX_LINE_LENGTH 32768 +#endif + +#ifndef CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH +#define CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH 16777216 +#endif + +#ifndef CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND +#define CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND +#define CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND 30 +#endif + +#ifndef CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS +#define CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS 0 +#endif + +/* + * Headers + */ + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif //_CRT_SECURE_NO_WARNINGS + +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif //_CRT_NONSTDC_NO_DEPRECATE + +#if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported +#endif + +#pragma comment(lib, "ws2_32.lib") + +#ifndef _SSIZE_T_DEFINED +using ssize_t = __int64; +#define _SSIZE_T_DEFINED +#endif +#endif // _MSC_VER + +#ifndef S_ISREG +#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG) +#endif // S_ISREG + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR) +#endif // S_ISDIR + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include +#include + +#if defined(__has_include) +#if __has_include() +// afunix.h uses types declared in winsock2.h, so has to be included after it. +#include +#define CPPHTTPLIB_HAVE_AFUNIX_H 1 +#endif +#endif + +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 +#endif + +using nfds_t = unsigned long; +using socket_t = SOCKET; +using socklen_t = int; + +#else // not _WIN32 + +#include +#if !defined(_AIX) && !defined(__MVS__) +#include +#endif +#ifdef __MVS__ +#include +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif +#endif +#include +#include +#include +#ifdef __linux__ +#include +#undef _res // Undefine _res macro to avoid conflicts with user code (#2278) +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +using socket_t = int; +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif +#endif //_WIN32 + +#if defined(__APPLE__) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// On macOS with a TLS backend, enable Keychain root certificates by default +// unless the user explicitly opts out. Not enabled on iOS/tvOS/watchOS since +// the SecTrustSettings APIs used to enumerate anchor certificates are macOS +// only; on those platforms the user must provide a CA bundle explicitly. +#if defined(__APPLE__) && defined(__clang__) && !defined(CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES) && \ + (defined(CPPHTTPLIB_OPENSSL_SUPPORT) || defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)) +#if TARGET_OS_OSX +#ifndef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN +#define CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN +#endif +#endif +#endif + +#if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) && !TARGET_OS_OSX +#error \ + "CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN is only supported on macOS. On iOS/tvOS/watchOS, supply a CA bundle via set_ca_cert_path()." +#endif + +// On Windows, enable Schannel certificate verification by default +// unless the user explicitly opts out. +#if defined(_WIN32) && !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE) +#define CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE +#endif + +#if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) || defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) +#if TARGET_OS_MAC && defined(__clang__) +#include +#include +#endif +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#endif +#endif // _WIN32 + +#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN +#if TARGET_OS_OSX +#include +#endif +#endif + +#include +#include +#include +#include + +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER < 0x1010107f +#error Please use OpenSSL or a current version of BoringSSL +#endif +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#elif OPENSSL_VERSION_NUMBER < 0x30000000L +#error Sorry, OpenSSL versions prior to 3.0.0 are not supported +#endif + +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT +// version.h defines MBEDTLS_VERSION_MAJOR (on 2.x/3.x/4.x alike); it is pulled +// in with this first include group so the version gating below can use it. +#include +#include +#include +#include +#include +#include +#include +#if MBEDTLS_VERSION_MAJOR >= 4 +// Mbed TLS 4.x moved hashing/RNG to PSA Crypto and removed these headers. +#include +#else +#include +#include +#include +#include +#include +#include +#endif +#ifdef _WIN32 +#include +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#endif +#endif // _WIN32 +#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN +#if TARGET_OS_OSX +#include +#endif +#endif + +// Mbed TLS version API compatibility. Note: V4 implies V3 (both defined on +// 4.x), so version-specific 3.x-only code must check V3 && !V4. +#if MBEDTLS_VERSION_MAJOR >= 4 +#define CPPHTTPLIB_MBEDTLS_V4 +#endif +#if MBEDTLS_VERSION_MAJOR >= 3 +#define CPPHTTPLIB_MBEDTLS_V3 +#endif + +#endif // CPPHTTPLIB_MBEDTLS_SUPPORT + +#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT +#include + +#include + +// Fallback definitions for older wolfSSL versions (e.g., 5.6.6) +#ifndef WOLFSSL_GEN_EMAIL +#define WOLFSSL_GEN_EMAIL 1 +#endif +#ifndef WOLFSSL_GEN_DNS +#define WOLFSSL_GEN_DNS 2 +#endif +#ifndef WOLFSSL_GEN_URI +#define WOLFSSL_GEN_URI 6 +#endif +#ifndef WOLFSSL_GEN_IPADD +#define WOLFSSL_GEN_IPADD 7 +#endif + +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#endif +#endif // _WIN32 +#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN +#if TARGET_OS_OSX +#include +#endif +#endif +#endif // CPPHTTPLIB_WOLFSSL_SUPPORT + +// Define CPPHTTPLIB_SSL_ENABLED if any SSL backend is available +#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) || defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT) +#define CPPHTTPLIB_SSL_ENABLED +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +#include +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + +#ifdef CPPHTTPLIB_ZSTD_SUPPORT +#include +#endif + +/* + * Declaration + */ +namespace httplib { + +namespace ws { +class WebSocket; +} // namespace ws + +namespace detail { + +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type make_unique(Args &&...args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type make_unique(std::size_t n) +{ + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + +// Locale-independent ASCII character classification. The +// counterparts (std::isalnum, std::isdigit, ...) consult the global C locale, +// so e.g. std::isalnum(0xC5) can return true once an embedder calls +// setlocale(). HTTP grammars are defined over ASCII, so raw bytes must be +// classified without regard to the locale. +inline bool is_ascii_digit(char c) +{ + return '0' <= c && c <= '9'; +} + +inline bool is_ascii_alpha(char c) +{ + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); +} + +inline bool is_ascii_alnum(char c) +{ + return is_ascii_digit(c) || is_ascii_alpha(c); +} + +namespace case_ignore { + +inline unsigned char to_lower(int c) +{ + const static unsigned char table[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, + 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, + 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, + 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, + 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, + }; + return table[(unsigned char)(char)c]; +} + +inline std::string to_lower(const std::string &s) +{ + std::string result = s; + std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return static_cast(to_lower(c)); }); + return result; +} + +inline bool equal(const std::string &a, const std::string &b) +{ + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) { return to_lower(ca) == to_lower(cb); }); +} + +struct equal_to { + bool operator()(const std::string &a, const std::string &b) const { return equal(a, b); } +}; + +struct hash { + size_t operator()(const std::string &key) const { return hash_core(key.data(), key.size(), 0); } + + size_t hash_core(const char *s, size_t l, size_t h) const + { + return (l == 0) ? h + : hash_core(s + 1, + l - 1, + // Unsets the 6 high bits of h, therefore no + // overflow happens + (((std::numeric_limits::max)() >> 6) & h * 33) ^ static_cast(to_lower(*s))); + } +}; + +template +using unordered_set = std::unordered_set; + +} // namespace case_ignore + +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)) + , execute_on_destruction{true} + { + } + + scope_exit(scope_exit &&rhs) noexcept + : exit_function(std::move(rhs.exit_function)) + , execute_on_destruction{rhs.execute_on_destruction} + { + rhs.release(); + } + + ~scope_exit() + { + if (execute_on_destruction) { + this->exit_function(); + } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + +// Simple from_chars implementation for integer and double types (C++17 +// substitute) +template +struct from_chars_result { + const char *ptr; + std::errc ec; +}; + +template +inline from_chars_result from_chars(const char *first, const char *last, T &value, int base = 10) +{ + value = 0; + const char *p = first; + bool negative = false; + + if (p != last && *p == '-') { + negative = true; + ++p; + } + if (p == last) { + return {first, std::errc::invalid_argument}; + } + + T result = 0; + for (; p != last; ++p) { + char c = *p; + int digit = -1; + if (is_ascii_digit(c)) { + digit = c - '0'; + } + else if ('a' <= c && c <= 'z') { + digit = c - 'a' + 10; + } + else if ('A' <= c && c <= 'Z') { + digit = c - 'A' + 10; + } + else { + break; + } + + if (digit < 0 || digit >= base) { + break; + } + if (result > ((std::numeric_limits::max)() - digit) / base) { + return {p, std::errc::result_out_of_range}; + } + result = result * base + digit; + } + + if (p == first || (negative && p == first + 1)) { + return {first, std::errc::invalid_argument}; + } + + value = negative ? T(0) - result : result; + return {p, std::errc{}}; +} + +// from_chars for double (hand-written, locale-independent) +// +// The only double consumed by this library is the HTTP quality value, whose +// grammar is (RFC 9110 12.4.2): +// qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] ) +// i.e. a non-negative decimal with no sign, exponent, "inf"/"nan", or wide +// magnitude. So this parser recognizes exactly 1*DIGIT [ "." *DIGIT ] with +// '.' always the decimal separator (std::strtod would instead read it from the +// global C locale, mis-parsing q-values once an embedder calls +// setlocale(LC_ALL, "") into a comma-decimal locale). The caller range-checks +// the result to [0, 1], so inputs outside that range need not be distinguished +// here. Allocation-free, single pass, and free of the overflow/rounding edge +// cases that exponent and wide-range handling would introduce. +inline from_chars_result from_chars(const char *first, const char *last, double &value) +{ + value = 0.0; + const char *p = first; + + // Each 1eN is exactly representable, so a single final division by the + // matching entry yields a correctly-rounded result. + static const double powers_of_ten[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18}; + const int max_frac_digits = static_cast(sizeof(powers_of_ten) / sizeof(powers_of_ten[0])) - 1; + + // Accumulate digits into a 64-bit integer and remember how many were + // fractional. Two independent caps keep this bounded and safe: + // * accumulation saturates before mantissa could overflow uint64_t, and + // * frac_digits is capped at max_frac_digits so it is always a valid index + // into powers_of_ten (without this an input like "0.000...0" would never + // grow mantissa, so the saturation cap alone would not bound it). + // Both caps only drop digits far beyond the precision a q-value needs; any + // value they would change is well outside [0, 1] and rejected by the caller. + uint64_t mantissa = 0; + int frac_digits = 0; + bool seen_digit = false; + + const uint64_t limit = ((std::numeric_limits::max)() - 9) / 10; + auto accumulate = [&](char c) { + if (mantissa <= limit) { + mantissa = mantissa * 10 + static_cast(c - '0'); + return true; + } + return false; + }; + + for (; p != last && is_ascii_digit(*p); ++p) { + seen_digit = true; + accumulate(*p); + } + + if (p != last && *p == '.') { + ++p; + for (; p != last && is_ascii_digit(*p); ++p) { + seen_digit = true; + if (frac_digits < max_frac_digits && accumulate(*p)) { + ++frac_digits; + } + } + } + + if (!seen_digit) { + return {first, std::errc::invalid_argument}; + } + + value = static_cast(mantissa) / powers_of_ten[frac_digits]; + return {p, std::errc{}}; +} + +inline bool parse_port(const char *s, size_t len, int &port) +{ + int val = 0; + auto r = from_chars(s, s + len, val); + if (r.ec != std::errc{} || val < 1 || val > 65535) { + return false; + } + port = val; + return true; +} + +inline bool parse_port(const std::string &s, int &port) +{ + return parse_port(s.data(), s.size(), port); +} + +struct UrlComponents { + std::string scheme; + std::string host; + std::string port; + std::string path; + std::string query; +}; + +inline bool parse_url(const std::string &url, UrlComponents &uc) +{ + uc = {}; + size_t pos = 0; + + auto sep = url.find("://"); + if (sep != std::string::npos) { + uc.scheme = url.substr(0, sep); + + // Scheme must be [a-z]+ only + if (uc.scheme.empty()) { + return false; + } + for (auto c : uc.scheme) { + if (c < 'a' || c > 'z') { + return false; + } + } + + pos = sep + 3; + } + else if (url.compare(0, 2, "//") == 0) { + pos = 2; + } + + auto has_authority_prefix = pos > 0; + auto has_authority = has_authority_prefix || (!url.empty() && url[0] != '/' && url[0] != '?' && url[0] != '#'); + if (has_authority) { + if (pos < url.size() && url[pos] == '[') { + auto close = url.find(']', pos); + if (close == std::string::npos) { + return false; + } + uc.host = url.substr(pos + 1, close - pos - 1); + + // IPv6 host must be [a-fA-F0-9:]+ only + if (uc.host.empty()) { + return false; + } + for (auto c : uc.host) { + if (!(is_ascii_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c == ':')) { + return false; + } + } + + pos = close + 1; + } + else { + auto end = url.find_first_of(":/?#", pos); + if (end == std::string::npos) { + end = url.size(); + } + uc.host = url.substr(pos, end - pos); + pos = end; + } + + if (pos < url.size() && url[pos] == ':') { + ++pos; + auto end = url.find_first_of("/?#", pos); + if (end == std::string::npos) { + end = url.size(); + } + uc.port = url.substr(pos, end - pos); + pos = end; + } + + // Without :// or //, the entire input must be consumed as host[:port]. + // If there is leftover (path, query, etc.), this is not a valid + // host[:port] string — clear and reparse as a plain path. + if (!has_authority_prefix && pos < url.size()) { + uc.host.clear(); + uc.port.clear(); + pos = 0; + } + } + + if (pos < url.size() && url[pos] != '?' && url[pos] != '#') { + auto end = url.find_first_of("?#", pos); + if (end == std::string::npos) { + end = url.size(); + } + uc.path = url.substr(pos, end - pos); + pos = end; + } + + if (pos < url.size() && url[pos] == '?') { + auto end = url.find('#', pos); + if (end == std::string::npos) { + end = url.size(); + } + uc.query = url.substr(pos, end - pos); + } + + return true; +} + +} // namespace detail + +enum class SSLVerifierResponse { + // no decision has been made, use the built-in certificate verifier + NoDecisionMade, + // connection certificate is verified and accepted + CertificateAccepted, + // connection certificate was processed but is rejected + CertificateRejected +}; + +// System CA loading policy for SSL clients. Auto (the default) loads system +// CA certs only when no custom CA is configured; enable_system_ca() switches +// to an explicit policy. +enum class SystemCAMode { Auto, Enabled, Disabled }; + +enum StatusCode { + // Information responses + Continue_100 = 100, + SwitchingProtocol_101 = 101, + Processing_102 = 102, + EarlyHints_103 = 103, + + // Successful responses + OK_200 = 200, + Created_201 = 201, + Accepted_202 = 202, + NonAuthoritativeInformation_203 = 203, + NoContent_204 = 204, + ResetContent_205 = 205, + PartialContent_206 = 206, + MultiStatus_207 = 207, + AlreadyReported_208 = 208, + IMUsed_226 = 226, + + // Redirection messages + MultipleChoices_300 = 300, + MovedPermanently_301 = 301, + Found_302 = 302, + SeeOther_303 = 303, + NotModified_304 = 304, + UseProxy_305 = 305, + unused_306 = 306, + TemporaryRedirect_307 = 307, + PermanentRedirect_308 = 308, + + // Client error responses + BadRequest_400 = 400, + Unauthorized_401 = 401, + PaymentRequired_402 = 402, + Forbidden_403 = 403, + NotFound_404 = 404, + MethodNotAllowed_405 = 405, + NotAcceptable_406 = 406, + ProxyAuthenticationRequired_407 = 407, + RequestTimeout_408 = 408, + Conflict_409 = 409, + Gone_410 = 410, + LengthRequired_411 = 411, + PreconditionFailed_412 = 412, + PayloadTooLarge_413 = 413, + UriTooLong_414 = 414, + UnsupportedMediaType_415 = 415, + RangeNotSatisfiable_416 = 416, + ExpectationFailed_417 = 417, + ImATeapot_418 = 418, + MisdirectedRequest_421 = 421, + UnprocessableContent_422 = 422, + Locked_423 = 423, + FailedDependency_424 = 424, + TooEarly_425 = 425, + UpgradeRequired_426 = 426, + PreconditionRequired_428 = 428, + TooManyRequests_429 = 429, + RequestHeaderFieldsTooLarge_431 = 431, + UnavailableForLegalReasons_451 = 451, + + // Server error responses + InternalServerError_500 = 500, + NotImplemented_501 = 501, + BadGateway_502 = 502, + ServiceUnavailable_503 = 503, + GatewayTimeout_504 = 504, + HttpVersionNotSupported_505 = 505, + VariantAlsoNegotiates_506 = 506, + InsufficientStorage_507 = 507, + LoopDetected_508 = 508, + NotExtended_510 = 510, + NetworkAuthenticationRequired_511 = 511, +}; + +namespace detail { + +// A multimap that keeps its entries in the order they were inserted. +// +// HTTP needs that order in two places. RFC 9110 5.3 makes the order of header +// fields sharing a field name significant and forbids a proxy from reordering +// them, and a query string's parameters are meaningful in the order the caller +// wrote them. Neither standard container expresses it: std::unordered_multimap +// gives no ordering guarantee at all for equivalent keys (libstdc++ yields +// reverse insertion order, libc++ insertion order), and std::multimap sorts by +// key, which would drop control data such as Host behind whatever else the +// message carries and alphabetise a query string. +// +// Entries are therefore kept in a flat vector, in order. Lookup is a linear +// scan, which beats hashing for the handful of entries a message carries +// (headers are capped at CPPHTTPLIB_HEADER_MAX_COUNT). +// +// KeyEqual compares keys; it is what makes Headers case-insensitive and +// Params, whose parameter names are case-sensitive, not. +template +class insertion_ordered_multimap { +public: + using key_type = std::string; + using mapped_type = Mapped; + using value_type = std::pair; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = value_type &; + using const_reference = const value_type &; + +private: + static size_type npos() { return static_cast(-1); } + + static bool keys_equal(const std::string &a, const std::string &b) { return KeyEqual()(a, b); } + + // Iterating yields every entry in insertion order, but equal_range() and + // find() have to walk only the entries sharing one key, which are not + // adjacent. Both are the same iterator type: key_idx_ selects between the + // two traversals, and since equality compares only the position, an iterator + // restricted to one key still compares equal to end(). + template + class iterator_t { + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = insertion_ordered_multimap::value_type; + using difference_type = insertion_ordered_multimap::difference_type; + using pointer = V *; + using reference = V &; + + iterator_t() + : data_(nullptr) + , idx_(0) + , size_(0) + , key_idx_(npos()) + { + } + + template ::value, int>::type = 0> + iterator_t(const iterator_t &rhs) + : data_(rhs.data_) + , idx_(rhs.idx_) + , size_(rhs.size_) + , key_idx_(rhs.key_idx_) + { + } + + reference operator*() const { return data_[idx_]; } + pointer operator->() const { return data_ + idx_; } + + iterator_t &operator++() + { + // Saturating, so that advancing past the last entry of a key (which + // get_multimap_value() does when asked for an out-of-range id) stays at + // end() instead of running off the container. + if (idx_ >= size_) { + return *this; + } + ++idx_; + if (key_idx_ != npos()) { + while (idx_ < size_ && !matches(idx_)) { + ++idx_; + } + } + return *this; + } + + iterator_t operator++(int) + { + auto tmp = *this; + ++*this; + return tmp; + } + + iterator_t &operator--() + { + if (idx_ == 0) { + return *this; + } + --idx_; + if (key_idx_ != npos()) { + while (idx_ > 0 && !matches(idx_)) { + --idx_; + } + } + return *this; + } + + iterator_t operator--(int) + { + auto tmp = *this; + --*this; + return tmp; + } + + template + bool operator==(const iterator_t &rhs) const + { + return idx_ == rhs.idx_; + } + + template + bool operator!=(const iterator_t &rhs) const + { + return idx_ != rhs.idx_; + } + + private: + friend class insertion_ordered_multimap; + template + friend class iterator_t; + + iterator_t(V *data, size_type idx, size_type size, size_type key_idx) + : data_(data) + , idx_(idx) + , size_(size) + , key_idx_(key_idx) + { + } + + bool matches(size_type i) const { return keys_equal(data_[i].first, data_[key_idx_].first); } + + V *data_; + size_type idx_; + size_type size_; + size_type key_idx_; + }; + +public: + using iterator = iterator_t; + using const_iterator = iterator_t; + + insertion_ordered_multimap() = default; + insertion_ordered_multimap(std::initializer_list il) + : entries_(il) + { + } + template + insertion_ordered_multimap(InputIt first, InputIt last) + : entries_(first, last) + { + } + + iterator begin() { return make_iter(0, npos()); } + iterator end() { return make_iter(entries_.size(), npos()); } + const_iterator begin() const { return make_citer(0, npos()); } + const_iterator end() const { return make_citer(entries_.size(), npos()); } + const_iterator cbegin() const { return begin(); } + const_iterator cend() const { return end(); } + + bool empty() const { return entries_.empty(); } + size_type size() const { return entries_.size(); } + void clear() { entries_.clear(); } + void swap(insertion_ordered_multimap &rhs) { entries_.swap(rhs.entries_); } + + iterator insert(const value_type &val) + { + entries_.push_back(val); + return make_iter(entries_.size() - 1, npos()); + } + + iterator insert(value_type &&val) + { + entries_.push_back(std::move(val)); + return make_iter(entries_.size() - 1, npos()); + } + + template + iterator emplace(Args &&...args) + { + entries_.emplace_back(std::forward(args)...); + return make_iter(entries_.size() - 1, npos()); + } + + // For entries that have to lead the message, such as the Host header field + // (RFC 9110 5.3 recommends sending control data first). + template + iterator emplace_front(Args &&...args) + { + entries_.emplace(entries_.begin(), std::forward(args)...); + return make_iter(0, npos()); + } + + iterator find(const std::string &key) + { + auto i = index_of(key); + return i == npos() ? end() : make_iter(i, i); + } + + const_iterator find(const std::string &key) const + { + auto i = index_of(key); + return i == npos() ? end() : make_citer(i, i); + } + + size_type count(const std::string &key) const + { + size_type n = 0; + for (const auto &entry : entries_) { + if (keys_equal(entry.first, key)) { + n++; + } + } + return n; + } + + std::pair equal_range(const std::string &key) + { + auto i = index_of(key); + return i == npos() ? std::make_pair(end(), end()) : std::make_pair(make_iter(i, i), end()); + } + + std::pair equal_range(const std::string &key) const + { + auto i = index_of(key); + return i == npos() ? std::make_pair(end(), end()) : std::make_pair(make_citer(i, i), end()); + } + + size_type erase(const std::string &key) + { + auto before = entries_.size(); + entries_.erase(std::remove_if(entries_.begin(), entries_.end(), [&](const value_type &entry) { return keys_equal(entry.first, key); }), + entries_.end()); + return before - entries_.size(); + } + + iterator erase(const_iterator pos) + { + entries_.erase(entries_.begin() + static_cast(pos.idx_)); + return make_iter(pos.idx_, npos()); + } + + // Erases what iterating [first, last) would actually visit, so erasing an + // equal_range() removes only the entries with that key, not everything + // positioned between them. + iterator erase(const_iterator first, const_iterator last) + { + auto from = first.idx_; + auto to = last.idx_; + if (from >= to) { + return make_iter(from, npos()); + } + + auto begin_it = entries_.begin(); + auto from_it = begin_it + static_cast(from); + auto to_it = begin_it + static_cast(to); + + if (first.key_idx_ == npos()) { + entries_.erase(from_it, to_it); + } + else { + auto key = entries_[first.key_idx_].first; + auto keep = from_it; + for (auto it = from_it; it != to_it; ++it) { + if (!keys_equal(it->first, key)) { + if (keep != it) { + *keep = std::move(*it); + } + ++keep; + } + } + if (keep != to_it) { + keep = std::move(to_it, entries_.end(), keep); + } + else { + keep = entries_.end(); + } + entries_.erase(keep, entries_.end()); + } + return make_iter(from, npos()); + } + + friend bool operator==(const insertion_ordered_multimap &lhs, const insertion_ordered_multimap &rhs) + { + return lhs.entries_ == rhs.entries_; + } + + friend bool operator!=(const insertion_ordered_multimap &lhs, const insertion_ordered_multimap &rhs) { return !(lhs == rhs); } + +private: + size_type index_of(const std::string &key) const + { + for (size_type i = 0; i < entries_.size(); i++) { + if (keys_equal(entries_[i].first, key)) { + return i; + } + } + return npos(); + } + + iterator make_iter(size_type idx, size_type key_idx) { return iterator(entries_.data(), idx, entries_.size(), key_idx); } + + const_iterator make_citer(size_type idx, size_type key_idx) const + { + return const_iterator(entries_.data(), idx, entries_.size(), key_idx); + } + + std::vector entries_; +}; + +} // namespace detail + +using Headers = detail::insertion_ordered_multimap; + +// Query parameter names are case-sensitive, unlike header field names. +using Params = detail::insertion_ordered_multimap>; +using Match = std::smatch; + +using DownloadProgress = std::function; +using UploadProgress = std::function; + +/* + * detail: type-erased storage used by UserData. + * ABI-stable regardless of C++ standard — always uses this custom + * implementation instead of std::any. + */ +namespace detail { + +using any_type_id = const void *; + +template +any_type_id any_typeid() noexcept +{ + static const char id = 0; + return &id; +} + +struct any_storage { + virtual ~any_storage() = default; + virtual std::unique_ptr clone() const = 0; + virtual any_type_id type_id() const noexcept = 0; +}; + +template +struct any_value final : any_storage { + T value; + template + explicit any_value(U &&v) + : value(std::forward(v)) + { + } + std::unique_ptr clone() const override { return std::unique_ptr(new any_value(value)); } + any_type_id type_id() const noexcept override { return any_typeid(); } +}; + +} // namespace detail + +class UserData { +public: + UserData() = default; + UserData(UserData &&) noexcept = default; + UserData &operator=(UserData &&) noexcept = default; + + UserData(const UserData &o) + { + for (const auto &e : o.entries_) { + if (e.second) { + entries_[e.first] = e.second->clone(); + } + } + } + + UserData &operator=(const UserData &o) + { + if (this != &o) { + entries_.clear(); + for (const auto &e : o.entries_) { + if (e.second) { + entries_[e.first] = e.second->clone(); + } + } + } + return *this; + } + + template + void set(const std::string &key, T &&value) + { + using D = typename std::decay::type; + entries_[key].reset(new detail::any_value(std::forward(value))); + } + + template + T *get(const std::string &key) noexcept + { + auto it = entries_.find(key); + if (it == entries_.end() || !it->second) { + return nullptr; + } + if (it->second->type_id() != detail::any_typeid()) { + return nullptr; + } + return &static_cast *>(it->second.get())->value; + } + + template + const T *get(const std::string &key) const noexcept + { + auto it = entries_.find(key); + if (it == entries_.end() || !it->second) { + return nullptr; + } + if (it->second->type_id() != detail::any_typeid()) { + return nullptr; + } + return &static_cast *>(it->second.get())->value; + } + + bool has(const std::string &key) const noexcept { return entries_.find(key) != entries_.end(); } + + void erase(const std::string &key) { entries_.erase(key); } + + void clear() noexcept { entries_.clear(); } + +private: + std::unordered_map> entries_; +}; + +struct Response; +using ResponseHandler = std::function; + +struct FormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; + Headers headers; +}; + +struct FormField { + std::string name; + std::string content; + Headers headers; +}; +// RFC 7578 5.2: a form processor "SHOULD send back results in order" and +// "Intermediaries MUST NOT reorder the results", so a handler walking these +// should see the parts as they were sent. A std::multimap sorts by field name +// and loses that. Field names are case-sensitive, hence std::equal_to rather +// than the case-insensitive predicate Headers uses. +using FormFields = detail::insertion_ordered_multimap>; + +using FormFiles = detail::insertion_ordered_multimap>; + +struct MultipartFormData { + FormFields fields; // Text fields from multipart + FormFiles files; // Files from multipart + + // Text field access + std::string get_field(const std::string &key, size_t id = 0) const; + std::vector get_fields(const std::string &key) const; + bool has_field(const std::string &key) const; + size_t get_field_count(const std::string &key) const; + + // File access + FormData get_file(const std::string &key, size_t id = 0) const; + std::vector get_files(const std::string &key) const; + bool has_file(const std::string &key) const; + size_t get_file_count(const std::string &key) const; +}; + +struct UploadFormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; +}; +using UploadFormDataItems = std::vector; + +class DataSink { +public: + DataSink() + : os(&sb_) + , sb_(*this) + { + } + + DataSink(const DataSink &) = delete; + DataSink &operator=(const DataSink &) = delete; + DataSink(DataSink &&) = delete; + DataSink &operator=(DataSink &&) = delete; + + std::function write; + std::function is_writable; + std::function done; + std::function done_with_trailer; + std::ostream os; + +private: + class data_sink_streambuf final : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) + : sink_(sink) + { + } + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) override + { + if (sink_.write(s, static_cast(n))) { + return n; + } + return 0; + } + + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; +}; + +using ContentProvider = std::function; + +using ContentProviderWithoutLength = std::function; + +using ContentProviderResourceReleaser = std::function; + +struct FormDataProvider { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; +}; +using FormDataProviderItems = std::vector; + +inline FormDataProvider make_file_provider(const std::string &name, + const std::string &filepath, + const std::string &filename = std::string(), + const std::string &content_type = std::string()) +{ + FormDataProvider fdp; + fdp.name = name; + fdp.filename = filename.empty() ? filepath : filename; + fdp.content_type = content_type; + fdp.provider = [filepath](size_t offset, DataSink &sink) -> bool { + std::ifstream f(filepath, std::ios::binary); + if (!f) { + return false; + } + if (offset > 0) { + f.seekg(static_cast(offset)); + if (!f.good()) { + sink.done(); + return true; + } + } + char buf[8192]; + f.read(buf, sizeof(buf)); + auto n = static_cast(f.gcount()); + if (n > 0) { + return sink.write(buf, n); + } + sink.done(); // EOF + return true; + }; + return fdp; +} + +inline std::pair make_file_body(const std::string &filepath) +{ + size_t size = 0; + { + std::ifstream f(filepath, std::ios::binary | std::ios::ate); + if (!f) { + return {0, ContentProvider{}}; + } + size = static_cast(f.tellg()); + } + + ContentProvider provider = [filepath](size_t offset, size_t length, DataSink &sink) -> bool { + std::ifstream f(filepath, std::ios::binary); + if (!f) { + return false; + } + f.seekg(static_cast(offset)); + if (!f.good()) { + return false; + } + char buf[8192]; + while (length > 0) { + auto to_read = (std::min)(sizeof(buf), length); + f.read(buf, static_cast(to_read)); + auto n = static_cast(f.gcount()); + if (n == 0) { + break; + } + if (!sink.write(buf, n)) { + return false; + } + length -= n; + } + return true; + }; + return {size, std::move(provider)}; +} + +using ContentReceiverWithProgress = std::function; + +using ContentReceiver = std::function; + +using FormDataHeader = std::function; + +class ContentReader { +public: + using Reader = std::function; + using FormDataReader = std::function; + + ContentReader(Reader reader, FormDataReader multipart_reader) + : reader_(std::move(reader)) + , formdata_reader_(std::move(multipart_reader)) + { + } + + bool operator()(FormDataHeader header, ContentReceiver receiver) const + { + return formdata_reader_(std::move(header), std::move(receiver)); + } + + bool operator()(ContentReceiver receiver) const { return reader_(std::move(receiver)); } + + Reader reader_; + FormDataReader formdata_reader_; +}; + +using Range = std::pair; +using Ranges = std::vector; + +#ifdef CPPHTTPLIB_SSL_ENABLED +// TLS abstraction layer - public type definitions and API +namespace tls { + +// Opaque handles (defined as void* for abstraction) +using ctx_t = void *; +using session_t = void *; +using const_session_t = const void *; // For read-only session access +using cert_t = void *; +using ca_store_t = void *; + +// TLS versions +enum class Version { + TLS1_2 = 0x0303, + TLS1_3 = 0x0304, +}; + +// Subject Alternative Names (SAN) entry types +enum class SanType { DNS, IP, EMAIL, URI, OTHER }; + +// SAN entry structure +struct SanEntry { + SanType type; + std::string value; +}; + +// Verification context for certificate verification callback +struct VerifyContext { + session_t session; // TLS session handle + cert_t cert; // Current certificate being verified + int depth; // Certificate chain depth (0 = leaf) + bool preverify_ok; // OpenSSL/Mbed TLS pre-verification result + long error_code; // Backend-specific error code (0 = no error) + const char *error_string; // Human-readable error description + + // Certificate introspection methods + std::string subject_cn() const; + std::string issuer_name() const; + bool check_hostname(const char *hostname) const; + std::vector sans() const; + bool validity(time_t ¬_before, time_t ¬_after) const; + std::string serial() const; +}; + +using VerifyCallback = std::function; + +// TlsError codes for TLS operations (backend-independent) +enum class ErrorCode : int { + Success = 0, + WantRead, // Non-blocking: need to wait for read + WantWrite, // Non-blocking: need to wait for write + PeerClosed, // Peer closed the connection + Fatal, // Unrecoverable error + SyscallError, // System call error (check sys_errno) + CertVerifyFailed, // Certificate verification failed + HostnameMismatch, // Hostname verification failed +}; + +// TLS error information +struct TlsError { + ErrorCode code = ErrorCode::Fatal; + uint64_t backend_code = 0; // OpenSSL: ERR_get_error(), mbedTLS: return value + int sys_errno = 0; // errno when SyscallError + + // Convert verification error code to human-readable string + static std::string verify_error_to_string(long error_code); +}; + +// RAII wrapper for peer certificate +class PeerCert { +public: + PeerCert(); + PeerCert(PeerCert &&other) noexcept; + PeerCert &operator=(PeerCert &&other) noexcept; + ~PeerCert(); + + PeerCert(const PeerCert &) = delete; + PeerCert &operator=(const PeerCert &) = delete; + + explicit operator bool() const; + std::string subject_cn() const; + std::string issuer_name() const; + bool check_hostname(const char *hostname) const; + std::vector sans() const; + bool validity(time_t ¬_before, time_t ¬_after) const; + std::string serial() const; + +private: + explicit PeerCert(cert_t cert); + cert_t cert_ = nullptr; + friend PeerCert get_peer_cert_from_session(const_session_t session); +}; + +// Callback for TLS context setup (used by SSLServer constructor) +using ContextSetupCallback = std::function; + +} // namespace tls +#endif + +struct Request { + std::string method; + std::string path; + std::string matched_route; + Params params; + Headers headers; + Headers trailers; + std::string body; + + std::string remote_addr; + int remote_port = -1; + std::string local_addr; + int local_port = -1; + + // for server + std::string version; + std::string target; + MultipartFormData form; + Ranges ranges; + Match matches; + std::unordered_map path_params; + std::function is_connection_closed = []() { return true; }; + + // for client + std::vector accept_content_types; + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; + DownloadProgress download_progress; + UploadProgress upload_progress; + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, const char *def = "", size_t id = 0) const; + size_t get_header_value_u64(const std::string &key, size_t def = 0, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + bool has_trailer(const std::string &key) const; + std::string get_trailer_value(const std::string &key, size_t id = 0) const; + size_t get_trailer_value_count(const std::string &key) const; + + bool has_param(const std::string &key) const; + std::string get_param_value(const std::string &key, size_t id = 0) const; + std::vector get_param_values(const std::string &key) const; + size_t get_param_value_count(const std::string &key) const; + + bool is_multipart_form_data() const; + + // private members... + bool body_consumed_ = false; + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; + std::chrono::time_point start_time_ = (std::chrono::steady_clock::time_point::min)(); + +#ifdef CPPHTTPLIB_SSL_ENABLED + tls::const_session_t ssl = nullptr; + tls::PeerCert peer_cert() const; + std::string sni() const; +#endif +}; + +struct Response { + std::string version; + int status = -1; + std::string reason; + Headers headers; + Headers trailers; + std::string body; + std::string location; // Redirect location + + // User-defined context — set by pre-routing/pre-request handlers and read + // by route handlers to pass arbitrary data (e.g. decoded auth tokens). + UserData user_data; + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, const char *def = "", size_t id = 0) const; + size_t get_header_value_u64(const std::string &key, size_t def = 0, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + bool has_trailer(const std::string &key) const; + std::string get_trailer_value(const std::string &key, size_t id = 0) const; + size_t get_trailer_value_count(const std::string &key) const; + + void set_redirect(const std::string &url, int status = StatusCode::Found_302); + void set_content(const char *s, size_t n, const std::string &content_type); + void set_content(const std::string &s, const std::string &content_type); + void set_content(std::string &&s, const std::string &content_type); + + void set_content_provider(size_t length, + const std::string &content_type, + ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_content_provider(const std::string &content_type, + ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_chunked_content_provider(const std::string &content_type, + ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_file_content(const std::string &path, const std::string &content_type); + void set_file_content(const std::string &path); + + Response() = default; + Response(const Response &) = default; + Response &operator=(const Response &) = default; + Response(Response &&) = default; + Response &operator=(Response &&) = default; + ~Response() + { + if (content_provider_resource_releaser_) { + content_provider_resource_releaser_(content_provider_success_); + } + } + + // private members... + size_t content_length_ = 0; + ContentProvider content_provider_; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; + std::string file_content_path_; + std::string file_content_content_type_; +}; + +enum class Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + SSLServerHostnameVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + ProxyConnection, + ConnectionClosed, + Timeout, + ResourceExhaustion, + TooManyFormDataFiles, + ExceedMaxPayloadSize, + ExceedUriMaxLength, + ExceedMaxSocketDescriptorCount, + InvalidRequestLine, + InvalidHTTPMethod, + InvalidHTTPVersion, + InvalidHeaders, + MultipartParsing, + OpenFile, + Listen, + GetSockName, + UnsupportedAddressFamily, + HTTPParsing, + InvalidRangeHeader, + UnsupportedContentEncoding, + + // For internal use only + SSLPeerCouldBeClosed_, +}; + +std::string to_string(Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + +class Stream { +public: + virtual ~Stream() = default; + + virtual bool is_readable() const = 0; + virtual bool wait_readable() const = 0; + virtual bool wait_writable() const = 0; + virtual bool is_peer_alive() const { return wait_writable(); } + + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; + + virtual time_t duration() const = 0; + + virtual void set_read_timeout(time_t sec, time_t usec = 0) + { + (void)sec; + (void)usec; + } + + // Bytes already pulled off the socket and sitting in this stream's own + // buffer. Exposing them lets a line reader scan for a terminator in one + // pass instead of asking for a byte at a time. A stream that does no + // buffering of its own reports none, and readers fall back to read(). + virtual const char *buffered_data(size_t &size) const + { + size = 0; + return nullptr; + } + + // Discards `size` bytes previously returned by buffered_data(). + virtual void consume_buffered(size_t size) { (void)size; } + + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); + + Error get_error() const { return error_; } + +protected: + Error error_ = Error::Success; +}; + +class TaskQueue { +public: + TaskQueue() = default; + virtual ~TaskQueue() = default; + + virtual bool enqueue(std::function fn) = 0; + virtual void shutdown() = 0; + + virtual void on_idle() {} +}; + +class ThreadPool final : public TaskQueue { +public: + explicit ThreadPool(size_t n, size_t max_n = 0, size_t mqr = 0, time_t idle_timeout_sec = CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT); + ThreadPool(const ThreadPool &) = delete; + ~ThreadPool() override = default; + + bool enqueue(std::function fn) override; + void shutdown() override; + +private: + void worker(bool is_dynamic); + void move_to_finished(std::thread::id id); + void cleanup_finished_threads(); + + size_t base_thread_count_; + size_t max_thread_count_; + size_t max_queued_requests_; + time_t idle_timeout_sec_; + size_t idle_thread_count_; + + bool shutdown_; + + std::list> jobs_; + std::vector threads_; // base threads + std::list dynamic_threads_; // dynamic threads + std::vector finished_threads_; // exited dynamic threads awaiting join + + std::condition_variable cond_; + std::mutex mutex_; +}; + +using Logger = std::function; + +// Forward declaration for Error type +enum class Error; +using ErrorLogger = std::function; + +using SocketOptions = std::function; + +void default_socket_options(socket_t sock); + +bool set_socket_opt(socket_t sock, int level, int optname, int optval); + +const char *status_message(int status); + +std::string to_string(Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + +std::string get_bearer_token_auth(const Request &req); + +namespace detail { + +class MatcherBase { +public: + MatcherBase(std::string pattern) + : pattern_(std::move(pattern)) + { + } + virtual ~MatcherBase() = default; + + const std::string &pattern() const { return pattern_; } + + // Match request path and populate its matches and + virtual bool match(Request &request) const = 0; + +private: + std::string pattern_; +}; + +/** + * Captures parameters in request path and stores them in Request::path_params + * + * Capture name is a substring of a pattern from : to /. + * The rest of the pattern is matched against the request path directly + * Parameters are captured starting from the next character after + * the end of the last matched static pattern fragment until the next /. + * + * Example pattern: + * "/path/fragments/:capture/more/fragments/:second_capture" + * Static fragments: + * "/path/fragments/", "more/fragments/" + * + * Given the following request path: + * "/path/fragments/:1/more/fragments/:2" + * the resulting capture will be + * {{"capture", "1"}, {"second_capture", "2"}} + */ +class PathParamsMatcher final : public MatcherBase { +public: + PathParamsMatcher(const std::string &pattern); + + bool match(Request &request) const override; + +private: + // Treat segment separators as the end of path parameter capture + // Does not need to handle query parameters as they are parsed before path + // matching + static constexpr char separator = '/'; + + // Contains static path fragments to match against, excluding the '/' after + // path params + // Fragments are separated by path params + std::vector static_fragments_; + // Stores the names of the path parameters to be used as keys in the + // Request::path_params map + std::vector param_names_; +}; + +/** + * Performs std::regex_match on request path + * and stores the result in Request::matches + * + * Note that regex match is performed directly on the whole request. + * This means that wildcard patterns may match multiple path segments with /: + * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end". + */ +class RegexMatcher final : public MatcherBase { +public: + RegexMatcher(const std::string &pattern) + : MatcherBase(pattern) + , regex_(pattern) + { + } + + bool match(Request &request) const override; + +private: + std::regex regex_; +}; + +int close_socket(socket_t sock) noexcept; + +ssize_t write_headers(Stream &strm, const Headers &headers); + +bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec, time_t usec); + +size_t get_multipart_content_length(const UploadFormDataItems &items, const std::string &boundary); + +ContentProvider make_multipart_content_provider(const UploadFormDataItems &items, const std::string &boundary); + +} // namespace detail + +bool is_valid_multipart_boundary(const std::string &boundary); + +// Serializer for multipart/form-data request bodies. The boundary is owned +// by the writer so that per-part framing and the final terminator always +// agree. Field names and filenames are escaped following the WHATWG HTML +// standard ('"' -> %22, CR -> %0D, LF -> %0A); CR and LF are also escaped +// in content types. +class MultipartFormDataWriter { +public: + MultipartFormDataWriter(); + // precondition: is_valid_multipart_boundary(boundary) + explicit MultipartFormDataWriter(std::string boundary); + + const std::string &boundary() const; + std::string content_type() const; + + // In-memory items -> whole body (known length) + std::string serialize(const UploadFormDataItems &items) const; + size_t content_length(const UploadFormDataItems &items) const; + + // Per-part framing for streaming via a content provider + std::string item_begin(const UploadFormData &item) const; + static std::string item_end(); + std::string finish() const; + +private: + std::string boundary_; +}; + +class Server { +public: + using Handler = std::function; + + using ExceptionHandler = std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = std::function; + + using StartHandler = std::function; + + using WebSocketHandler = std::function; + using SubProtocolSelector = std::function &protocols)>; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server &Get(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, HandlerWithContentReader handler); + Server &Put(const std::string &pattern, Handler handler); + Server &Put(const std::string &pattern, HandlerWithContentReader handler); + Server &Patch(const std::string &pattern, Handler handler); + Server &Patch(const std::string &pattern, HandlerWithContentReader handler); + Server &Delete(const std::string &pattern, Handler handler); + Server &Delete(const std::string &pattern, HandlerWithContentReader handler); + Server &Options(const std::string &pattern, Handler handler); + + Server &WebSocket(const std::string &pattern, WebSocketHandler handler); + Server &WebSocket(const std::string &pattern, WebSocketHandler handler, SubProtocolSelector sub_protocol_selector); + + bool set_base_dir(const std::string &dir, const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, Headers headers = Headers()); + bool remove_mount_point(const std::string &mount_point); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, const std::string &mime); + Server &set_default_file_mimetype(const std::string &mime); + Server &set_file_request_handler(Handler handler); + + template + Server &set_error_handler(ErrorHandlerFunc &&handler) + { + return set_error_handler_core(std::forward(handler), std::is_convertible{}); + } + + Server &set_exception_handler(ExceptionHandler handler); + + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); + + Server &set_pre_request_handler(HandlerWithResponse handler); + + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + + Server &set_start_handler(StartHandler handler); + + Server &set_logger(Logger logger); + Server &set_pre_compression_logger(Logger logger); + Server &set_error_logger(ErrorLogger error_logger); + + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_ipv6_v6only(bool on); + Server &set_socket_options(SocketOptions socket_options); + + Server &set_default_headers(Headers headers); + Server &set_header_writer(std::function const &writer); + + Server &set_trusted_proxies(const std::vector &proxies); + + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + template + Server &set_keep_alive_timeout(const std::chrono::duration &duration); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + Server &set_websocket_ping_interval(time_t sec); + template + Server &set_websocket_ping_interval(const std::chrono::duration &duration); + + Server &set_websocket_max_missed_pongs(int count); + + bool bind_to_port(const std::string &host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string &host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const std::string &host, int port, int socket_flags = 0); + + bool is_running() const; + void wait_until_ready() const; + void stop() noexcept; + void decommission(); + + std::function new_task_queue; + +protected: + bool process_request(Stream &strm, + const std::string &remote_addr, + int remote_port, + const std::string &local_addr, + int local_port, + bool close_connection, + bool &connection_closed, + const std::function &setup_request, + bool *websocket_upgraded = nullptr); + + std::atomic svr_sock_{INVALID_SOCKET}; + + std::vector trusted_proxies_; + + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + time_t websocket_ping_interval_sec_ = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND; + int websocket_max_missed_pongs_ = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS; + +private: + using Handlers = std::vector, Handler>>; + using HandlersForContentReader = std::vector, HandlerWithContentReader>>; + + static std::unique_ptr make_matcher(const std::string &pattern); + + template + Server &add_handler(std::vector, H>> &handlers, const std::string &pattern, H handler) + { + handlers.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; + } + + Server &set_error_handler_core(HandlerWithResponse handler, std::true_type); + Server &set_error_handler_core(Handler handler, std::false_type); + + socket_t create_server_socket(const std::string &host, int port, int socket_flags, SocketOptions socket_options) const; + int bind_internal(const std::string &host, int port, int socket_flags); + bool listen_internal(); + + bool routing(Request &req, Response &res, Stream &strm); + bool handle_file_request(Request &req, Response &res); + bool check_if_not_modified(const Request &req, Response &res, const std::string &etag, time_t mtime) const; + bool check_if_range(Request &req, const std::string &etag, time_t mtime) const; + bool dispatch_request(Request &req, Response &res, const Handlers &handlers, Stream &strm); + bool dispatch_request_for_content_reader(Request &req, + Response &res, + ContentReader content_reader, + const HandlersForContentReader &handlers) const; + + bool parse_request_line(const char *s, Request &req) const; + void apply_ranges(const Request &req, Response &res, std::string &content_type, std::string &boundary) const; + bool write_response(Stream &strm, bool close_connection, Request &req, Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, const Request &req, Response &res, bool need_apply_ranges); + bool write_content_with_provider( + Stream &strm, const Request &req, Response &res, const std::string &boundary, const std::string &content_type); + bool read_content(Stream &strm, Request &req, Response &res); + bool read_content_with_content_receiver(Stream &strm, + Request &req, + Response &res, + ContentReceiver receiver, + FormDataHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, + Request &req, + Response &res, + ContentReceiver receiver, + FormDataHeader multipart_header, + ContentReceiver multipart_receiver) const; + + virtual bool process_and_close_socket(socket_t sock); + + void output_log(const Request &req, const Response &res) const; + void output_pre_compression_log(const Request &req, const Response &res) const; + void output_error_log(const Error &err, const Request *req) const; + + std::atomic is_running_{false}; + std::atomic is_decommissioned{false}; + + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + std::string resolved_base_dir; + Headers headers; + }; + std::vector base_dirs_; + std::map file_extension_and_mimetype_map_; + std::string default_file_mimetype_ = "application/octet-stream"; + Handler file_request_handler_; + + Handlers get_handlers_; + Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; + Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; + Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; + Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; + Handlers options_handlers_; + + struct WebSocketHandlerEntry { + std::unique_ptr matcher; + WebSocketHandler handler; + SubProtocolSelector sub_protocol_selector; + }; + using WebSocketHandlers = std::vector; + WebSocketHandlers websocket_handlers_; + + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + HandlerWithResponse pre_request_handler_; + Expect100ContinueHandler expect_100_continue_handler_; + StartHandler start_handler_; + + mutable std::mutex logger_mutex_; + Logger logger_; + Logger pre_compression_logger_; + ErrorLogger error_logger_; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; + SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; + std::function header_writer_ = detail::write_headers; +}; + +class Result { +public: + Result() = default; + Result(std::unique_ptr &&res, Error err, Headers &&request_headers = Headers{}) + : res_(std::move(res)) + , err_(err) + , request_headers_(std::move(request_headers)) + { + } + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + + // Request Headers + bool has_request_header(const std::string &key) const; + std::string get_request_header_value(const std::string &key, const char *def = "", size_t id = 0) const; + size_t get_request_header_value_u64(const std::string &key, size_t def = 0, size_t id = 0) const; + size_t get_request_header_value_count(const std::string &key) const; + +private: + std::unique_ptr res_; + Error err_ = Error::Unknown; + Headers request_headers_; + +#ifdef CPPHTTPLIB_SSL_ENABLED +public: + Result(std::unique_ptr &&res, Error err, Headers &&request_headers, int ssl_error) + : res_(std::move(res)) + , err_(err) + , request_headers_(std::move(request_headers)) + , ssl_error_(ssl_error) + { + } + Result(std::unique_ptr &&res, Error err, Headers &&request_headers, int ssl_error, uint64_t ssl_backend_error) + : res_(std::move(res)) + , err_(err) + , request_headers_(std::move(request_headers)) + , ssl_error_(ssl_error) + , ssl_backend_error_(ssl_backend_error) + { + } + + int ssl_error() const { return ssl_error_; } + uint64_t ssl_backend_error() const { return ssl_backend_error_; } + +private: + int ssl_error_ = 0; + uint64_t ssl_backend_error_ = 0; +#endif +}; + +struct ClientConnection { + socket_t sock = INVALID_SOCKET; + + bool is_open() const { return sock != INVALID_SOCKET; } + + ClientConnection() = default; + + ~ClientConnection(); + + ClientConnection(const ClientConnection &) = delete; + ClientConnection &operator=(const ClientConnection &) = delete; + + ClientConnection(ClientConnection &&other) noexcept + : sock(other.sock) +#ifdef CPPHTTPLIB_SSL_ENABLED + , session(other.session) +#endif + { + other.sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_SSL_ENABLED + other.session = nullptr; +#endif + } + + ClientConnection &operator=(ClientConnection &&other) noexcept + { + if (this != &other) { + sock = other.sock; + other.sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_SSL_ENABLED + session = other.session; + other.session = nullptr; +#endif + } + return *this; + } + +#ifdef CPPHTTPLIB_SSL_ENABLED + tls::session_t session = nullptr; +#endif +}; + +namespace detail { + +struct ChunkedDecoder; + +struct BodyReader { + Stream *stream = nullptr; + bool has_content_length = false; + size_t content_length = 0; + size_t payload_max_length = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + size_t bytes_read = 0; + bool chunked = false; + bool eof = false; + std::unique_ptr chunked_decoder; + Error last_error = Error::Success; + + ssize_t read(char *buf, size_t len); + bool has_error() const { return last_error != Error::Success; } +}; + +inline ssize_t read_body_content(Stream *stream, BodyReader &br, char *buf, size_t len) +{ + (void)stream; + return br.read(buf, len); +} + +class decompressor; + +enum class NoProxyKind { + Wildcard, // "*" + HostnameSuffix, // "example.com" or ".example.com" + IPv4Cidr, // "10.0.0.0/8" (or single IP, treated as /32) + IPv6Cidr, // "fe80::/10" (or single IP, treated as /128) +}; + +// Unified 16-byte buffer holding either a v4 (first 4 bytes) or v6 address. +// Lets one CIDR matcher cover both families. +using IPBytes = std::array; + +struct NoProxyEntry { + NoProxyKind kind = NoProxyKind::Wildcard; + std::string hostname_pattern; // lowercased, leading/trailing dot stripped + IPBytes net{}; + int prefix_bits = 0; +}; + +struct NormalizedTarget { + std::string hostname; // lowercase; brackets and trailing dot removed + bool is_ipv4 = false; + bool is_ipv6 = false; + IPBytes ip{}; +}; + +} // namespace detail + +class ClientImpl { +public: + explicit ClientImpl(const std::string &host); + + explicit ClientImpl(const std::string &host, int port); + + explicit ClientImpl(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path); + + virtual ~ClientImpl(); + + virtual bool is_valid() const; + + struct StreamHandle { + std::unique_ptr response; + Error error = Error::Success; + + StreamHandle() = default; + StreamHandle(const StreamHandle &) = delete; + StreamHandle &operator=(const StreamHandle &) = delete; + StreamHandle(StreamHandle &&) = default; + StreamHandle &operator=(StreamHandle &&) = default; + ~StreamHandle() = default; + + bool is_valid() const { return response != nullptr && error == Error::Success; } + + ssize_t read(char *buf, size_t len); + void parse_trailers_if_needed(); + Error get_read_error() const { return body_reader_.last_error; } + bool has_read_error() const { return body_reader_.has_error(); } + + bool trailers_parsed_ = false; + + private: + friend class ClientImpl; + + ssize_t read_with_decompression(char *buf, size_t len); + + std::unique_ptr connection_; + std::unique_ptr socket_stream_; + Stream *stream_ = nullptr; + detail::BodyReader body_reader_; + + std::unique_ptr decompressor_; + std::string decompress_buffer_; + size_t decompress_offset_ = 0; + size_t decompressed_bytes_read_ = 0; + }; + + // clang-format off + Result Get(const std::string &path, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers); + Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Params ¶ms); + Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const Params ¶ms); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Delete(const std::string &path, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Params ¶ms, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const Params ¶ms, DownloadProgress progress = nullptr); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + // clang-format on + + // Streaming API: Open a stream for reading response body incrementally + // Socket ownership is transferred to StreamHandle for true streaming + // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.) + StreamHandle open_stream(const std::string &method, + const std::string &path, + const Params ¶ms = {}, + const Headers &headers = {}, + const std::string &body = {}, + const std::string &content_type = {}); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void set_header_writer(std::function const &writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_ipv6_v6only(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_max_timeout(time_t msec); + template + void set_max_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_path_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_payload_max_length(size_t length); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); + void set_no_proxy(const std::vector &patterns); + + void set_logger(Logger logger); + void set_error_logger(ErrorLogger error_logger); + +protected: + struct Socket { + socket_t sock = INVALID_SOCKET; + + // For Mbed TLS compatibility: start_time for request timeout tracking + std::chrono::time_point start_time_; + + bool is_open() const { return sock != INVALID_SOCKET; } + +#ifdef CPPHTTPLIB_SSL_ENABLED + tls::session_t ssl = nullptr; +#endif + }; + + virtual bool create_and_connect_socket(Socket &socket, Error &error); + virtual bool ensure_socket_connection(Socket &socket, Error &error); + virtual bool setup_proxy_connection( + Socket &socket, std::chrono::time_point start_time, Response &res, bool &success, Error &error); + + bool is_proxy_enabled_for_host(const std::string &host) const; + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // disconnect + // should ONLY be called when socket_mutex_ is locked, and only when + // no other thread is using the socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket) const; + void close_socket(Socket &socket); + void disconnect(bool gracefully); + + bool process_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, Error &error) const; + + void copy_settings(const ClientImpl &rhs); + + void output_log(const Request &req, const Response &res) const; + void output_error_log(const Error &err, const Request *req) const; + + // Socket endpoint information + const std::string host_; + const int port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname to connection target map. The value is an IP literal or another + // hostname; only the connection target changes, never the identity. + std::map addr_map_; + + // Default headers + Headers default_headers_; + + // Header writer + std::function header_writer_ = detail::write_headers; + + // Settings + std::string client_cert_path_; + std::string client_key_path_; + + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND; + time_t max_timeout_msec_ = CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool path_encode_ = true; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + bool has_payload_max_length_ = false; + + std::string interface_; + + std::string proxy_host_; + int proxy_port_ = -1; + + std::string proxy_basic_auth_username_; + std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; + + std::vector no_proxy_entries_; + + mutable detail::NormalizedTarget host_normalized_; + mutable bool host_normalized_valid_ = false; + + mutable std::mutex logger_mutex_; + Logger logger_; + ErrorLogger error_logger_; + +private: + bool send_(Request &req, Response &res, Error &error); + Result send_(Request &&req); + + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, Response &res, bool skip_100_continue = true) const; + bool write_request(Stream &strm, Request &req, bool close_connection, Error &error, bool skip_body = false); + bool write_request_body(Stream &strm, Request &req, Error &error); + void prepare_default_headers(Request &r, bool for_stream, const std::string &ct); + bool redirect(Request &req, Response &res, Error &error); + bool create_redirect_client(const std::string &scheme, + const std::string &host, + int port, + Request &req, + Response &res, + const std::string &path, + const std::string &location, + Error &error); + template + void setup_redirect_client(ClientType &client); + bool handle_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error); + std::unique_ptr send_with_content_provider_and_receiver(Request &req, + const char *body, + size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, + ContentReceiver content_receiver, + Error &error); + Result send_with_content_provider_and_receiver(const std::string &method, + const std::string &path, + const Headers &headers, + const char *body, + size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress); + ContentProviderWithoutLength get_multipart_content_provider(const std::string &boundary, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items) const; + + virtual bool process_socket(const Socket &socket, + std::chrono::time_point start_time, + std::function callback); + virtual bool is_ssl() const; + + void transfer_socket_ownership_to_handle(StreamHandle &handle); + +#ifdef CPPHTTPLIB_SSL_ENABLED +public: + void set_digest_auth(const std::string &username, const std::string &password); + void set_proxy_digest_auth(const std::string &username, const std::string &password); + void set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path = std::string()); + void enable_server_certificate_verification(bool enabled); + void enable_server_hostname_verification(bool enabled); + void enable_system_ca(bool enabled); + +protected: + std::string digest_auth_username_; + std::string digest_auth_password_; + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + bool server_certificate_verification_ = true; + bool server_hostname_verification_ = true; + SystemCAMode system_ca_mode_ = SystemCAMode::Auto; + std::string ca_cert_pem_; // Store CA cert PEM for redirect transfer + int last_ssl_error_ = 0; + uint64_t last_backend_error_ = 0; +#endif +}; + +class Client { +public: + // Universal interface + explicit Client(const std::string &scheme_host_port); + + explicit Client(const std::string &scheme_host_port, const std::string &client_cert_path, const std::string &client_key_path); + + // HTTP only interface + explicit Client(const std::string &host, int port); + + explicit Client(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path); + + Client(Client &&) = default; + Client &operator=(Client &&) = default; + + ~Client(); + + bool is_valid() const; + + // clang-format off + Result Get(const std::string &path, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers); + Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Params ¶ms); + Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers); + Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const Params ¶ms); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Delete(const std::string &path, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Params ¶ms, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const Params ¶ms, DownloadProgress progress = nullptr); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + // clang-format on + + // Streaming API: Open a stream for reading response body incrementally + // Socket ownership is transferred to StreamHandle for true streaming + // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.) + ClientImpl::StreamHandle open_stream(const std::string &method, + const std::string &path, + const Params ¶ms = {}, + const Headers &headers = {}, + const std::string &body = {}, + const std::string &content_type = {}); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void set_header_writer(std::function const &writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_max_timeout(time_t msec); + template + void set_max_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_path_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_payload_max_length(size_t length); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); + void set_no_proxy(const std::vector &patterns); + void set_logger(Logger logger); + void set_error_logger(ErrorLogger error_logger); + +private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_SSL_ENABLED +public: + void set_digest_auth(const std::string &username, const std::string &password); + void set_proxy_digest_auth(const std::string &username, const std::string &password); + void enable_server_certificate_verification(bool enabled); + void enable_server_hostname_verification(bool enabled); + void enable_system_ca(bool enabled); + void set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path = std::string()); + + void set_ca_cert_store(tls::ca_store_t ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + void set_server_certificate_verifier(tls::VerifyCallback verifier); + + void set_session_verifier(std::function verifier); + + tls::ctx_t tls_context() const; + +#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE + void enable_windows_certificate_verification(bool enabled); +#endif + +private: + bool is_ssl_ = false; +#endif +}; + +#ifdef CPPHTTPLIB_SSL_ENABLED +class SSLServer : public Server { +public: + SSLServer(const char *cert_path, + const char *private_key_path, + const char *client_ca_cert_file_path = nullptr, + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); + + struct PemMemory { + const char *cert_pem; + size_t cert_pem_len; + const char *key_pem; + size_t key_pem_len; + const char *client_ca_pem; + size_t client_ca_pem_len; + const char *private_key_password; + }; + explicit SSLServer(const PemMemory &pem); + + // The callback receives the ctx_t handle which can be cast to the + // appropriate backend type (SSL_CTX* for OpenSSL, + // tls::impl::MbedTlsContext* for Mbed TLS) + explicit SSLServer(const tls::ContextSetupCallback &setup_callback); + + ~SSLServer() override; + + bool is_valid() const override; + + bool update_certs_pem(const char *cert_pem, const char *key_pem, const char *client_ca_pem = nullptr, const char *password = nullptr); + + tls::ctx_t tls_context() const { return ctx_; } + + int ssl_last_error() const { return last_ssl_error_; } + +private: + bool process_and_close_socket(socket_t sock) override; + + tls::ctx_t ctx_ = nullptr; + std::mutex ctx_mutex_; + + int last_ssl_error_ = 0; +}; + +class SSLClient final : public ClientImpl { +public: + explicit SSLClient(const std::string &host); + + explicit SSLClient(const std::string &host, int port); + + explicit SSLClient(const std::string &host, + int port, + const std::string &client_cert_path, + const std::string &client_key_path, + const std::string &private_key_password = std::string()); + + struct PemMemory { + const char *cert_pem; + size_t cert_pem_len; + const char *key_pem; + size_t key_pem_len; + const char *private_key_password; + }; + explicit SSLClient(const std::string &host, int port, const PemMemory &pem); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_store(tls::ca_store_t ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + void set_server_certificate_verifier(tls::VerifyCallback verifier); + + // Post-handshake session verifier (backend-independent) + void set_session_verifier(std::function verifier); + + tls::ctx_t tls_context() const { return ctx_; } + +#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE + void enable_windows_certificate_verification(bool enabled); +#endif + +private: + bool create_and_connect_socket(Socket &socket, Error &error) override; + bool ensure_socket_connection(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully); + + bool process_socket(const Socket &socket, + std::chrono::time_point start_time, + std::function callback) override; + bool is_ssl() const override; + + bool setup_proxy_connection( + Socket &socket, std::chrono::time_point start_time, Response &res, bool &success, Error &error) override; + bool connect_with_proxy( + Socket &sock, std::chrono::time_point start_time, Response &res, bool &success, Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + void init_ctx(); + void reset_ctx_on_error(); + + bool load_certs(); + + tls::ctx_t ctx_ = nullptr; + std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + + // Tracks whether a custom CA store was applied via set_ca_cert_store(), + // since the store handle itself is owned by ctx_ and leaves no other trace. + // Used to keep custom CA configuration exclusive with system CA loading. + bool ca_cert_store_set_ = false; + + long verify_result_ = 0; + + std::function session_verifier_; + +#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE + bool enable_windows_cert_verification_ = true; +#endif + + friend class ClientImpl; +}; +#endif // CPPHTTPLIB_SSL_ENABLED + +namespace detail { + +template +inline void duration_to_sec_and_usec(const T &duration, U callback) +{ + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast(duration - std::chrono::seconds(sec)).count(); + callback(static_cast(sec), static_cast(usec)); +} + +template +inline constexpr size_t str_len(const char (&)[N]) +{ + return N - 1; +} + +inline bool is_numeric(const std::string &str) +{ + return !str.empty() && std::all_of(str.cbegin(), str.cend(), is_ascii_digit); +} + +inline size_t get_header_value_u64(const Headers &headers, const std::string &key, size_t def, size_t id, bool &is_invalid_value) +{ + is_invalid_value = false; + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + if (is_numeric(it->second)) { + // Parse at size_t width so an out-of-range Content-Length is reported + // rather than silently saturated/truncated (a value above 2^32 would + // otherwise wrap to a small framing length on 32-bit builds). Flag it + // and return SIZE_MAX so the existing oversized-value guards reject it. + size_t val = 0; + const auto &s = it->second; + auto r = from_chars(s.data(), s.data() + s.size(), val); + if (r.ec == std::errc::result_out_of_range) { + is_invalid_value = true; + return (std::numeric_limits::max)(); + } + return val; + } + else { + is_invalid_value = true; + } + } + return def; +} + +inline size_t get_header_value_u64(const Headers &headers, const std::string &key, size_t def, size_t id) +{ + auto dummy = false; + return get_header_value_u64(headers, key, def, id, dummy); +} + +} // namespace detail + +template +inline Server &Server::set_read_timeout(const std::chrono::duration &duration) +{ + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; +} + +template +inline Server &Server::set_write_timeout(const std::chrono::duration &duration) +{ + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; +} + +template +inline Server &Server::set_idle_interval(const std::chrono::duration &duration) +{ + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; +} + +template +inline void ClientImpl::set_connection_timeout(const std::chrono::duration &duration) +{ + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { set_connection_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_read_timeout(const std::chrono::duration &duration) +{ + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_write_timeout(const std::chrono::duration &duration) +{ + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_max_timeout(const std::chrono::duration &duration) +{ + auto msec = std::chrono::duration_cast(duration).count(); + set_max_timeout(msec); +} + +template +inline void Client::set_connection_timeout(const std::chrono::duration &duration) +{ + cli_->set_connection_timeout(duration); +} + +template +inline void Client::set_read_timeout(const std::chrono::duration &duration) +{ + cli_->set_read_timeout(duration); +} + +template +inline void Client::set_write_timeout(const std::chrono::duration &duration) +{ + cli_->set_write_timeout(duration); +} + +inline void Client::set_max_timeout(time_t msec) +{ + cli_->set_max_timeout(msec); +} + +template +inline void Client::set_max_timeout(const std::chrono::duration &duration) +{ + cli_->set_max_timeout(duration); +} + +/* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + +std::string hosted_at(const std::string &hostname); + +void hosted_at(const std::string &hostname, std::vector &addrs); + +// JavaScript-style URL encoding/decoding functions +std::string encode_uri_component(const std::string &value); +std::string encode_uri(const std::string &value); +std::string decode_uri_component(const std::string &value); +std::string decode_uri(const std::string &value); + +// RFC 3986 compliant URL component encoding/decoding functions +std::string encode_path_component(const std::string &component); +std::string decode_path_component(const std::string &component); +std::string encode_query_component(const std::string &component, bool space_as_plus = true); +std::string decode_query_component(const std::string &component, bool plus_as_space = true); + +std::string sanitize_filename(const std::string &filename); + +std::string append_query_params(const std::string &path, const Params ¶ms); + +std::pair make_range_header(const Ranges &ranges); + +std::pair make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false); + +namespace detail { + +#if defined(_WIN32) +inline std::wstring u8string_to_wstring(const char *s) +{ + if (!s) { + return std::wstring(); + } + + auto len = static_cast(strlen(s)); + if (!len) { + return std::wstring(); + } + + auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0); + if (!wlen) { + return std::wstring(); + } + + std::wstring ws; + ws.resize(wlen); + wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, const_cast(reinterpret_cast(ws.data())), wlen); + if (wlen != static_cast(ws.size())) { + ws.clear(); + } + return ws; +} +#endif + +struct FileStat { + FileStat(const std::string &path); + bool is_file() const; + bool is_dir() const; + time_t mtime() const; + size_t size() const; + +private: +#if defined(_WIN32) + struct _stat st_; +#else + struct stat st_; +#endif + int ret_ = -1; +}; + +std::string make_host_and_port_string(const std::string &host, int port, bool is_ssl); + +template +bool check_and_write_headers(Stream &strm, Headers &headers, T header_writer, Error &error); + +std::string trim_copy(const std::string &s); + +void divide(const char *data, std::size_t size, char d, std::function fn); + +void divide(const std::string &str, char d, std::function fn); + +void split(const char *b, const char *e, char d, std::function fn); + +void split(const char *b, const char *e, char d, size_t m, std::function fn); + +bool process_client_socket(socket_t sock, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, + std::function callback); + +socket_t create_client_socket(const std::string &host, + const std::string &ip, + int port, + int address_family, + bool tcp_nodelay, + bool ipv6_v6only, + SocketOptions socket_options, + time_t connection_timeout_sec, + time_t connection_timeout_usec, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + const std::string &intf, + Error &error); + +const char *get_header_value(const Headers &headers, const std::string &key, const char *def, size_t id); + +std::string params_to_query_str(const Params ¶ms); + +void parse_query_text(const char *data, std::size_t size, Params ¶ms); + +void parse_query_text(const std::string &s, Params ¶ms); + +bool parse_multipart_boundary(const std::string &content_type, std::string &boundary); + +bool parse_range_header(const std::string &s, Ranges &ranges); + +bool parse_accept_header(const std::string &s, std::vector &content_types); + +ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); + +ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); + +enum class EncodingType { None = 0, Gzip, Brotli, Zstd }; + +EncodingType encoding_type(const Request &req, const Response &res); + +class BufferStream final : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool wait_readable() const override; + bool wait_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + time_t duration() const override; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +class compressor { +public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, Callback callback) = 0; +}; + +class nocompressor final : public compressor { +public: + ~nocompressor() override = default; + + bool compress(const char *data, size_t data_length, bool /*last*/, Callback callback) override; +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor final : public compressor { +public: + gzip_compressor(); + ~gzip_compressor() override; + + bool compress(const char *data, size_t data_length, bool last, Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor final : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor() override; + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor final : public compressor { +public: + brotli_compressor(); + ~brotli_compressor(); + + bool compress(const char *data, size_t data_length, bool last, Callback callback) override; + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor final : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +#ifdef CPPHTTPLIB_ZSTD_SUPPORT +class zstd_compressor : public compressor { +public: + zstd_compressor(); + ~zstd_compressor(); + + bool compress(const char *data, size_t data_length, bool last, Callback callback) override; + +private: + ZSTD_CCtx *ctx_ = nullptr; +}; + +class zstd_decompressor : public decompressor { +public: + zstd_decompressor(); + ~zstd_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, Callback callback) override; + +private: + ZSTD_DCtx *ctx_ = nullptr; +}; +#endif + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size); + const char *ptr() const; + size_t size() const; + bool end_with_crlf() const; + bool getline(); + +private: + void append(char c); + void append(const char *data, size_t size); + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string growable_buffer_; +}; + +bool parse_trailers(stream_line_reader &line_reader, Headers &dest, const Headers &src_headers); + +struct ChunkedDecoder { + Stream &strm; + size_t chunk_remaining = 0; + bool finished = false; + char line_buf[64]; + size_t last_chunk_total = 0; + size_t last_chunk_offset = 0; + + explicit ChunkedDecoder(Stream &s); + + ssize_t read_payload(char *buf, size_t len, size_t &out_chunk_offset, size_t &out_chunk_total); + + bool parse_trailers_into(Headers &dest, const Headers &src_headers); +}; + +class mmap { +public: + mmap(const char *path); + ~mmap(); + + bool open(const char *path); + void close(); + + bool is_open() const; + size_t size() const; + const char *data() const; + +private: +#if defined(_WIN32) + HANDLE hFile_ = NULL; + HANDLE hMapping_ = NULL; +#else + int fd_ = -1; +#endif + size_t size_ = 0; + void *addr_ = nullptr; + bool is_open_empty_file = false; +}; + +// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5 +namespace fields { + +bool is_token_char(char c); +bool is_token(const std::string &s); +bool is_field_name(const std::string &s); +bool is_vchar(char c); +bool is_obs_text(char c); +bool is_field_vchar(char c); +bool is_field_content(const std::string &s); +bool is_field_value(const std::string &s); +bool is_field_valid(const std::string &name, const std::string &value); + +} // namespace fields +} // namespace detail + +/* + * TLS Abstraction Layer Declarations + */ + +#ifdef CPPHTTPLIB_SSL_ENABLED +// TLS abstraction layer - backend-specific type declarations +#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT +namespace tls { +namespace impl { + +// Mbed TLS context wrapper (holds config, entropy, DRBG, CA chain, own +// cert/key). This struct is accessible via tls::impl for use in SSL context +// setup callbacks (cast ctx_t to tls::impl::MbedTlsContext*). +struct MbedTlsContext { + mbedtls_ssl_config conf; +#ifndef CPPHTTPLIB_MBEDTLS_V4 + // Mbed TLS 4.x uses PSA Crypto's internal RNG; no explicit entropy/DRBG. + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; +#endif + mbedtls_x509_crt ca_chain; + mbedtls_x509_crt own_cert; + mbedtls_pk_context own_key; + bool is_server = false; + bool verify_client = false; + bool has_verify_callback = false; + + MbedTlsContext(); + ~MbedTlsContext(); + + MbedTlsContext(const MbedTlsContext &) = delete; + MbedTlsContext &operator=(const MbedTlsContext &) = delete; +}; + +} // namespace impl +} // namespace tls +#endif + +#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT +namespace tls { +namespace impl { + +// wolfSSL context wrapper (holds WOLFSSL_CTX and related state). +// This struct is accessible via tls::impl for use in SSL context +// setup callbacks (cast ctx_t to tls::impl::WolfSSLContext*). +struct WolfSSLContext { + WOLFSSL_CTX *ctx = nullptr; + bool is_server = false; + bool verify_client = false; + bool has_verify_callback = false; + std::string ca_pem_data_; // accumulated PEM for get_ca_names/get_ca_certs + + WolfSSLContext(); + ~WolfSSLContext(); + + WolfSSLContext(const WolfSSLContext &) = delete; + WolfSSLContext &operator=(const WolfSSLContext &) = delete; +}; + +// CA store for wolfSSL: holds raw PEM bytes to allow reloading into any ctx +struct WolfSSLCAStore { + std::string pem_data; +}; + +} // namespace impl +} // namespace tls +#endif + +#endif // CPPHTTPLIB_SSL_ENABLED + +namespace stream { + +class Result { +public: + Result(); + explicit Result(ClientImpl::StreamHandle &&handle, size_t chunk_size = 8192); + Result(Result &&other) noexcept; + Result &operator=(Result &&other) noexcept; + Result(const Result &) = delete; + Result &operator=(const Result &) = delete; + + // Response info + bool is_valid() const; + explicit operator bool() const; + int status() const; + const Headers &headers() const; + std::string get_header_value(const std::string &key, const char *def = "") const; + bool has_header(const std::string &key) const; + Error error() const; + Error read_error() const; + bool has_read_error() const; + + // Stream reading + bool next(); + const char *data() const; + size_t size() const; + std::string read_all(); + +private: + ClientImpl::StreamHandle handle_; + std::string buffer_; + size_t current_size_ = 0; + size_t chunk_size_; + bool finished_ = false; +}; + +// GET +template +inline Result Get(ClientType &cli, const std::string &path, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("GET", path), chunk_size}; +} + +template +inline Result Get(ClientType &cli, const std::string &path, const Headers &headers, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("GET", path, {}, headers), chunk_size}; +} + +template +inline Result Get(ClientType &cli, const std::string &path, const Params ¶ms, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("GET", path, params), chunk_size}; +} + +template +inline Result Get(ClientType &cli, const std::string &path, const Params ¶ms, const Headers &headers, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("GET", path, params, headers), chunk_size}; +} + +// POST +template +inline Result Post( + ClientType &cli, const std::string &path, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("POST", path, {}, {}, body, content_type), chunk_size}; +} + +template +inline Result Post(ClientType &cli, + const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + size_t chunk_size = 8192) +{ + return Result{cli.open_stream("POST", path, {}, headers, body, content_type), chunk_size}; +} + +template +inline Result Post(ClientType &cli, + const std::string &path, + const Params ¶ms, + const std::string &body, + const std::string &content_type, + size_t chunk_size = 8192) +{ + return Result{cli.open_stream("POST", path, params, {}, body, content_type), chunk_size}; +} + +template +inline Result Post(ClientType &cli, + const std::string &path, + const Params ¶ms, + const Headers &headers, + const std::string &body, + const std::string &content_type, + size_t chunk_size = 8192) +{ + return Result{cli.open_stream("POST", path, params, headers, body, content_type), chunk_size}; +} + +// PUT +template +inline Result Put( + ClientType &cli, const std::string &path, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("PUT", path, {}, {}, body, content_type), chunk_size}; +} + +template +inline Result Put(ClientType &cli, + const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + size_t chunk_size = 8192) +{ + return Result{cli.open_stream("PUT", path, {}, headers, body, content_type), chunk_size}; +} + +template +inline Result Put(ClientType &cli, + const std::string &path, + const Params ¶ms, + const std::string &body, + const std::string &content_type, + size_t chunk_size = 8192) +{ + return Result{cli.open_stream("PUT", path, params, {}, body, content_type), chunk_size}; +} + +template +inline Result Put(ClientType &cli, + const std::string &path, + const Params ¶ms, + const Headers &headers, + const std::string &body, + const std::string &content_type, + size_t chunk_size = 8192) +{ + return Result{cli.open_stream("PUT", path, params, headers, body, content_type), chunk_size}; +} + +// PATCH +template +inline Result Patch( + ClientType &cli, const std::string &path, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("PATCH", path, {}, {}, body, content_type), chunk_size}; +} + +template +inline Result Patch(ClientType &cli, + const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + size_t chunk_size = 8192) +{ + return Result{cli.open_stream("PATCH", path, {}, headers, body, content_type), chunk_size}; +} + +template +inline Result Patch(ClientType &cli, + const std::string &path, + const Params ¶ms, + const std::string &body, + const std::string &content_type, + size_t chunk_size = 8192) +{ + return Result{cli.open_stream("PATCH", path, params, {}, body, content_type), chunk_size}; +} + +template +inline Result Patch(ClientType &cli, + const std::string &path, + const Params ¶ms, + const Headers &headers, + const std::string &body, + const std::string &content_type, + size_t chunk_size = 8192) +{ + return Result{cli.open_stream("PATCH", path, params, headers, body, content_type), chunk_size}; +} + +// DELETE +template +inline Result Delete(ClientType &cli, const std::string &path, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("DELETE", path), chunk_size}; +} + +template +inline Result Delete(ClientType &cli, const std::string &path, const Headers &headers, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("DELETE", path, {}, headers), chunk_size}; +} + +template +inline Result Delete( + ClientType &cli, const std::string &path, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("DELETE", path, {}, {}, body, content_type), chunk_size}; +} + +template +inline Result Delete(ClientType &cli, + const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + size_t chunk_size = 8192) +{ + return Result{cli.open_stream("DELETE", path, {}, headers, body, content_type), chunk_size}; +} + +template +inline Result Delete(ClientType &cli, const std::string &path, const Params ¶ms, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("DELETE", path, params), chunk_size}; +} + +template +inline Result Delete(ClientType &cli, const std::string &path, const Params ¶ms, const Headers &headers, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("DELETE", path, params, headers), chunk_size}; +} + +template +inline Result Delete(ClientType &cli, + const std::string &path, + const Params ¶ms, + const std::string &body, + const std::string &content_type, + size_t chunk_size = 8192) +{ + return Result{cli.open_stream("DELETE", path, params, {}, body, content_type), chunk_size}; +} + +template +inline Result Delete(ClientType &cli, + const std::string &path, + const Params ¶ms, + const Headers &headers, + const std::string &body, + const std::string &content_type, + size_t chunk_size = 8192) +{ + return Result{cli.open_stream("DELETE", path, params, headers, body, content_type), chunk_size}; +} + +// HEAD +template +inline Result Head(ClientType &cli, const std::string &path, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("HEAD", path), chunk_size}; +} + +template +inline Result Head(ClientType &cli, const std::string &path, const Headers &headers, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("HEAD", path, {}, headers), chunk_size}; +} + +template +inline Result Head(ClientType &cli, const std::string &path, const Params ¶ms, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("HEAD", path, params), chunk_size}; +} + +template +inline Result Head(ClientType &cli, const std::string &path, const Params ¶ms, const Headers &headers, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("HEAD", path, params, headers), chunk_size}; +} + +// OPTIONS +template +inline Result Options(ClientType &cli, const std::string &path, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("OPTIONS", path), chunk_size}; +} + +template +inline Result Options(ClientType &cli, const std::string &path, const Headers &headers, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("OPTIONS", path, {}, headers), chunk_size}; +} + +template +inline Result Options(ClientType &cli, const std::string &path, const Params ¶ms, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("OPTIONS", path, params), chunk_size}; +} + +template +inline Result Options(ClientType &cli, const std::string &path, const Params ¶ms, const Headers &headers, size_t chunk_size = 8192) +{ + return Result{cli.open_stream("OPTIONS", path, params, headers), chunk_size}; +} + +} // namespace stream + +namespace sse { + +struct SSEMessage { + std::string event; // Event type (default: "message") + std::string data; // Event payload + std::string id; // Event ID for Last-Event-ID header + + SSEMessage(); + void clear(); +}; + +class SSEClient { +public: + using MessageHandler = std::function; + using ErrorHandler = std::function; + using OpenHandler = std::function; + + SSEClient(Client &client, const std::string &path); + SSEClient(Client &client, const std::string &path, const Headers &headers); + ~SSEClient(); + + SSEClient(const SSEClient &) = delete; + SSEClient &operator=(const SSEClient &) = delete; + + // Event handlers + SSEClient &on_message(MessageHandler handler); + SSEClient &on_event(const std::string &type, MessageHandler handler); + SSEClient &on_open(OpenHandler handler); + SSEClient &on_error(ErrorHandler handler); + SSEClient &set_reconnect_interval(int ms); + SSEClient &set_max_reconnect_attempts(int n); + + // Update headers (thread-safe) + SSEClient &set_headers(const Headers &headers); + + // State accessors + bool is_connected() const; + const std::string &last_event_id() const; + + // Blocking start - runs event loop with auto-reconnect + void start(); + + // Non-blocking start - runs in background thread + void start_async(); + + // Stop the client (thread-safe) + void stop(); + +private: + bool parse_sse_line(const std::string &line, SSEMessage &msg, int &retry_ms); + void run_event_loop(); + void dispatch_event(const SSEMessage &msg); + bool should_reconnect(int count) const; + void wait_for_reconnect(); + + // Client and path + Client &client_; + std::string path_; + Headers headers_; + mutable std::mutex headers_mutex_; + + // Callbacks + MessageHandler on_message_; + std::map event_handlers_; + OpenHandler on_open_; + ErrorHandler on_error_; + + // Configuration + int reconnect_interval_ms_ = 3000; + int max_reconnect_attempts_ = 0; // 0 = unlimited + + // State + std::atomic running_{false}; + std::atomic connected_{false}; + std::string last_event_id_; + + // Async support + std::thread async_thread_; +}; + +} // namespace sse + +namespace ws { + +enum class Opcode : uint8_t { + Continuation = 0x0, + Text = 0x1, + Binary = 0x2, + Close = 0x8, + Ping = 0x9, + Pong = 0xA, +}; + +enum class CloseStatus : uint16_t { + Normal = 1000, + GoingAway = 1001, + ProtocolError = 1002, + UnsupportedData = 1003, + NoStatus = 1005, + Abnormal = 1006, + InvalidPayload = 1007, + PolicyViolation = 1008, + MessageTooBig = 1009, + MandatoryExtension = 1010, + InternalError = 1011, +}; + +enum ReadResult : int { Fail = 0, Text = 1, Binary = 2 }; + +class WebSocket { +public: + WebSocket(const WebSocket &) = delete; + WebSocket &operator=(const WebSocket &) = delete; + ~WebSocket(); + + ReadResult read(std::string &msg); + bool send(const std::string &data); + bool send(const char *data, size_t len); + void close(CloseStatus status = CloseStatus::Normal, const std::string &reason = ""); + const Request &request() const; + bool is_open() const; + +private: + friend class httplib::Server; + friend class WebSocketClient; + + WebSocket(Stream &strm, + const Request &req, + bool is_server, + time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND, + int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS) + : strm_(strm) + , req_(req) + , is_server_(is_server) + , ping_interval_sec_(ping_interval_sec) + , max_missed_pongs_(max_missed_pongs) + { + start_heartbeat(); + } + + WebSocket(std::unique_ptr &&owned_strm, + const Request &req, + bool is_server, + time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND, + int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS) + : strm_(*owned_strm) + , owned_strm_(std::move(owned_strm)) + , req_(req) + , is_server_(is_server) + , ping_interval_sec_(ping_interval_sec) + , max_missed_pongs_(max_missed_pongs) + { + start_heartbeat(); + } + + void start_heartbeat(); + bool send_frame(Opcode op, const char *data, size_t len, bool fin = true); + + Stream &strm_; + std::unique_ptr owned_strm_; + Request req_; + bool is_server_; + time_t ping_interval_sec_; + int max_missed_pongs_; + int unacked_pings_ = 0; + std::atomic closed_{false}; + std::mutex write_mutex_; + std::thread ping_thread_; + std::mutex ping_mutex_; + std::condition_variable ping_cv_; +}; + +class WebSocketClient { +public: + explicit WebSocketClient(const std::string &scheme_host_port_path, const Headers &headers = {}); + + ~WebSocketClient(); + WebSocketClient(const WebSocketClient &) = delete; + WebSocketClient &operator=(const WebSocketClient &) = delete; + + bool is_valid() const; + + bool connect(); + ReadResult read(std::string &msg); + bool send(const std::string &data); + bool send(const char *data, size_t len); + void close(CloseStatus status = CloseStatus::Normal, const std::string &reason = ""); + bool is_open() const; + const std::string &subprotocol() const; + void set_read_timeout(time_t sec, time_t usec = 0); + void set_write_timeout(time_t sec, time_t usec = 0); + void set_websocket_ping_interval(time_t sec); + void set_websocket_max_missed_pongs(int count); + void set_tcp_nodelay(bool on); + void set_address_family(int family); + void set_ipv6_v6only(bool on); + void set_socket_options(SocketOptions socket_options); + void set_connection_timeout(time_t sec, time_t usec = 0); + void set_interface(const std::string &intf); + void set_hostname_addr_map(std::map addr_map); + +#ifdef CPPHTTPLIB_SSL_ENABLED + void set_ca_cert_path(const std::string &path); + void set_ca_cert_store(tls::ca_store_t store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + void enable_server_certificate_verification(bool enabled); + void enable_system_ca(bool enabled); +#endif + +private: + void shutdown_and_close(); + bool create_stream(std::unique_ptr &strm); + void prepare_default_headers(Request &req); + + std::string host_; + int port_; + std::string path_; + Headers headers_; + std::string subprotocol_; + bool is_valid_ = false; + socket_t sock_ = INVALID_SOCKET; + std::unique_ptr ws_; + time_t read_timeout_sec_ = CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = 0; + time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND; + time_t websocket_ping_interval_sec_ = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND; + int websocket_max_missed_pongs_ = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS; + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; + SocketOptions socket_options_ = nullptr; + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + std::string interface_; + + // Hostname to connection target map. The value is an IP literal or another + // hostname; only the connection target changes, never the identity. + std::map addr_map_; + +#ifdef CPPHTTPLIB_SSL_ENABLED + bool is_ssl_ = false; + tls::ctx_t tls_ctx_ = nullptr; + tls::session_t tls_session_ = nullptr; + std::string ca_cert_file_path_; + bool custom_ca_loaded_ = false; + bool certs_loaded_ = false; + SystemCAMode system_ca_mode_ = SystemCAMode::Auto; + bool server_certificate_verification_ = true; +#endif +}; + +namespace impl { + +bool is_valid_utf8(const std::string &s); + +bool read_websocket_frame(Stream &strm, Opcode &opcode, std::string &payload, bool &fin, bool expect_masked, size_t max_len); + +} // namespace impl + +} // namespace ws + +// ---------------------------------------------------------------------------- + +/* + * Implementation that will be part of the .cc file if split into .h + .cc. + */ + +namespace stream { + +// stream::Result implementations +inline Result::Result() + : chunk_size_(8192) +{ +} + +inline Result::Result(ClientImpl::StreamHandle &&handle, size_t chunk_size) + : handle_(std::move(handle)) + , chunk_size_(chunk_size) +{ +} + +inline Result::Result(Result &&other) noexcept + : handle_(std::move(other.handle_)) + , buffer_(std::move(other.buffer_)) + , current_size_(other.current_size_) + , chunk_size_(other.chunk_size_) + , finished_(other.finished_) +{ + other.current_size_ = 0; + other.finished_ = true; +} + +inline Result &Result::operator=(Result &&other) noexcept +{ + if (this != &other) { + handle_ = std::move(other.handle_); + buffer_ = std::move(other.buffer_); + current_size_ = other.current_size_; + chunk_size_ = other.chunk_size_; + finished_ = other.finished_; + other.current_size_ = 0; + other.finished_ = true; + } + return *this; +} + +inline bool Result::is_valid() const +{ + return handle_.is_valid(); +} +inline Result::operator bool() const +{ + return is_valid(); +} + +inline int Result::status() const +{ + return handle_.response ? handle_.response->status : -1; +} + +inline const Headers &Result::headers() const +{ + static const Headers empty_headers; + return handle_.response ? handle_.response->headers : empty_headers; +} + +inline std::string Result::get_header_value(const std::string &key, const char *def) const +{ + return handle_.response ? handle_.response->get_header_value(key, def) : def; +} + +inline bool Result::has_header(const std::string &key) const +{ + return handle_.response ? handle_.response->has_header(key) : false; +} + +inline Error Result::error() const +{ + return handle_.error; +} +inline Error Result::read_error() const +{ + return handle_.get_read_error(); +} +inline bool Result::has_read_error() const +{ + return handle_.has_read_error(); +} + +inline bool Result::next() +{ + if (!handle_.is_valid() || finished_) { + return false; + } + + if (buffer_.size() < chunk_size_) { + buffer_.resize(chunk_size_); + } + + ssize_t n = handle_.read(&buffer_[0], chunk_size_); + if (n > 0) { + current_size_ = static_cast(n); + return true; + } + + current_size_ = 0; + finished_ = true; + return false; +} + +inline const char *Result::data() const +{ + return buffer_.data(); +} +inline size_t Result::size() const +{ + return current_size_; +} + +inline std::string Result::read_all() +{ + std::string result; + while (next()) { + result.append(data(), size()); + } + return result; +} + +} // namespace stream + +namespace sse { + +// SSEMessage implementations +inline SSEMessage::SSEMessage() + : event("message") +{ +} + +inline void SSEMessage::clear() +{ + event = "message"; + data.clear(); + id.clear(); +} + +// SSEClient implementations +inline SSEClient::SSEClient(Client &client, const std::string &path) + : client_(client) + , path_(path) +{ +} + +inline SSEClient::SSEClient(Client &client, const std::string &path, const Headers &headers) + : client_(client) + , path_(path) + , headers_(headers) +{ +} + +inline SSEClient::~SSEClient() +{ + stop(); +} + +inline SSEClient &SSEClient::on_message(MessageHandler handler) +{ + on_message_ = std::move(handler); + return *this; +} + +inline SSEClient &SSEClient::on_event(const std::string &type, MessageHandler handler) +{ + event_handlers_[type] = std::move(handler); + return *this; +} + +inline SSEClient &SSEClient::on_open(OpenHandler handler) +{ + on_open_ = std::move(handler); + return *this; +} + +inline SSEClient &SSEClient::on_error(ErrorHandler handler) +{ + on_error_ = std::move(handler); + return *this; +} + +inline SSEClient &SSEClient::set_reconnect_interval(int ms) +{ + reconnect_interval_ms_ = ms; + return *this; +} + +inline SSEClient &SSEClient::set_max_reconnect_attempts(int n) +{ + max_reconnect_attempts_ = n; + return *this; +} + +inline SSEClient &SSEClient::set_headers(const Headers &headers) +{ + std::lock_guard lock(headers_mutex_); + headers_ = headers; + return *this; +} + +inline bool SSEClient::is_connected() const +{ + return connected_.load(); +} + +inline const std::string &SSEClient::last_event_id() const +{ + return last_event_id_; +} + +inline void SSEClient::start() +{ + running_.store(true); + run_event_loop(); +} + +inline void SSEClient::start_async() +{ + running_.store(true); + async_thread_ = std::thread([this]() { run_event_loop(); }); +} + +inline void SSEClient::stop() +{ + running_.store(false); + client_.stop(); // Cancel any pending operations + if (async_thread_.joinable()) { + async_thread_.join(); + } +} + +inline bool SSEClient::parse_sse_line(const std::string &line, SSEMessage &msg, int &retry_ms) +{ + // Blank line signals end of event + if (line.empty() || line == "\r") { + return true; + } + + // Lines starting with ':' are comments (ignored) + if (!line.empty() && line[0] == ':') { + return false; + } + + // Find the colon separator + auto colon_pos = line.find(':'); + if (colon_pos == std::string::npos) { + // Line with no colon is treated as field name with empty value + return false; + } + + auto field = line.substr(0, colon_pos); + std::string value; + + // Value starts after colon, skip optional single space + if (colon_pos + 1 < line.size()) { + auto value_start = colon_pos + 1; + if (line[value_start] == ' ') { + value_start++; + } + value = line.substr(value_start); + // Remove trailing \r if present + if (!value.empty() && value.back() == '\r') { + value.pop_back(); + } + } + + // Handle known fields + if (field == "event") { + msg.event = value; + } + else if (field == "data") { + // Multiple data lines are concatenated with newlines + if (!msg.data.empty()) { + msg.data += "\n"; + } + msg.data += value; + } + else if (field == "id") { + // Empty id is valid (clears the last event ID) + msg.id = value; + } + else if (field == "retry") { + // Parse retry interval in milliseconds + { + int v = 0; + auto res = detail::from_chars(value.data(), value.data() + value.size(), v); + if (res.ec == std::errc{}) { + retry_ms = v; + } + } + } + // Unknown fields are ignored per SSE spec + + return false; +} + +inline void SSEClient::run_event_loop() +{ + auto reconnect_count = 0; + + while (running_.load()) { + // Build headers, including Last-Event-ID if we have one + Headers request_headers; + { + std::lock_guard lock(headers_mutex_); + request_headers = headers_; + } + if (!last_event_id_.empty()) { + request_headers.emplace("Last-Event-ID", last_event_id_); + } + + // Open streaming connection + auto result = stream::Get(client_, path_, request_headers); + + // Connection error handling + if (!result) { + connected_.store(false); + if (on_error_) { + on_error_(result.error()); + } + + if (!should_reconnect(reconnect_count)) { + break; + } + wait_for_reconnect(); + reconnect_count++; + continue; + } + + if (result.status() != StatusCode::OK_200) { + connected_.store(false); + if (on_error_) { + on_error_(Error::Connection); + } + + // For certain errors, don't reconnect. + // Note: 401 is intentionally absent so that handlers can refresh + // credentials via set_headers() and let the client reconnect. + if (result.status() == StatusCode::NoContent_204 || result.status() == StatusCode::NotFound_404 || + result.status() == StatusCode::Forbidden_403) { + break; + } + + if (!should_reconnect(reconnect_count)) { + break; + } + wait_for_reconnect(); + reconnect_count++; + continue; + } + + // Connection successful + connected_.store(true); + reconnect_count = 0; + if (on_open_) { + on_open_(); + } + + // Event receiving loop + std::string buffer; + SSEMessage current_msg; + + while (running_.load() && result.next()) { + buffer.append(result.data(), result.size()); + + // Process complete lines in the buffer + size_t line_start = 0; + size_t newline_pos; + + while ((newline_pos = buffer.find('\n', line_start)) != std::string::npos) { + auto line = buffer.substr(line_start, newline_pos - line_start); + line_start = newline_pos + 1; + + // Parse the line and check if event is complete + auto event_complete = parse_sse_line(line, current_msg, reconnect_interval_ms_); + + if (event_complete && !current_msg.data.empty()) { + // Update last_event_id for reconnection + if (!current_msg.id.empty()) { + last_event_id_ = current_msg.id; + } + + // Dispatch event to appropriate handler + dispatch_event(current_msg); + + current_msg.clear(); + } + } + + // Keep unprocessed data in buffer + buffer.erase(0, line_start); + } + + // Connection ended + connected_.store(false); + + if (!running_.load()) { + break; + } + + // Check for read errors + if (result.has_read_error()) { + if (on_error_) { + on_error_(result.read_error()); + } + } + + if (!should_reconnect(reconnect_count)) { + break; + } + wait_for_reconnect(); + reconnect_count++; + } + + connected_.store(false); +} + +inline void SSEClient::dispatch_event(const SSEMessage &msg) +{ + // Check for specific event type handler first + auto it = event_handlers_.find(msg.event); + if (it != event_handlers_.end()) { + it->second(msg); + return; + } + + // Fall back to generic message handler + if (on_message_) { + on_message_(msg); + } +} + +inline bool SSEClient::should_reconnect(int count) const +{ + if (!running_.load()) { + return false; + } + if (max_reconnect_attempts_ == 0) { + return true; + } // unlimited + return count < max_reconnect_attempts_; +} + +inline void SSEClient::wait_for_reconnect() +{ + // Use small increments to check running_ flag frequently + auto waited = 0; + while (running_.load() && waited < reconnect_interval_ms_) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + waited += 100; + } +} + +} // namespace sse + +#ifdef CPPHTTPLIB_SSL_ENABLED +/* + * TLS abstraction layer - internal function declarations + * These are implementation details and not part of the public API. + */ +namespace tls { + +// Client context +ctx_t create_client_context(); +void free_context(ctx_t ctx); +bool set_min_version(ctx_t ctx, Version version); +bool load_ca_pem(ctx_t ctx, const char *pem, size_t len); +bool load_ca_file(ctx_t ctx, const char *file_path); +bool load_ca_dir(ctx_t ctx, const char *dir_path); +bool load_system_certs(ctx_t ctx); +bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key, const char *password); +bool set_client_cert_file(ctx_t ctx, const char *cert_path, const char *key_path, const char *password); + +// Server context +ctx_t create_server_context(); +bool set_server_cert_pem(ctx_t ctx, const char *cert, const char *key, const char *password); +bool set_server_cert_file(ctx_t ctx, const char *cert_path, const char *key_path, const char *password); +bool set_client_ca_file(ctx_t ctx, const char *ca_file, const char *ca_dir); +void set_verify_client(ctx_t ctx, bool require); + +// Session management +session_t create_session(ctx_t ctx, socket_t sock); +void free_session(session_t session); +bool set_sni(session_t session, const char *hostname); +bool set_hostname(session_t session, const char *hostname); + +// Handshake (non-blocking capable) +TlsError connect(session_t session); +TlsError accept(session_t session); + +// Handshake with timeout (blocking until timeout) +bool connect_nonblocking(session_t session, socket_t sock, time_t timeout_sec, time_t timeout_usec, TlsError *err); +bool accept_nonblocking(session_t session, socket_t sock, time_t timeout_sec, time_t timeout_usec, TlsError *err); + +// I/O (non-blocking capable) +ssize_t read(session_t session, void *buf, size_t len, TlsError &err); +ssize_t write(session_t session, const void *buf, size_t len, TlsError &err); +int pending(const_session_t session); +void shutdown(session_t session, bool graceful); + +// Connection state +bool is_peer_closed(session_t session, socket_t sock); + +// Certificate verification +cert_t get_peer_cert(const_session_t session); +void free_cert(cert_t cert); +bool verify_hostname(cert_t cert, const char *hostname); +uint64_t hostname_mismatch_code(); +long get_verify_result(const_session_t session); + +// Certificate introspection +std::string get_cert_subject_cn(cert_t cert); +std::string get_cert_issuer_name(cert_t cert); +bool get_cert_sans(cert_t cert, std::vector &sans); +bool get_cert_validity(cert_t cert, time_t ¬_before, time_t ¬_after); +std::string get_cert_serial(cert_t cert); +bool get_cert_der(cert_t cert, std::vector &der); +const char *get_sni(const_session_t session); + +// CA store management +ca_store_t create_ca_store(const char *pem, size_t len); +void free_ca_store(ca_store_t store); +bool set_ca_store(ctx_t ctx, ca_store_t store); +size_t get_ca_certs(ctx_t ctx, std::vector &certs); +std::vector get_ca_names(ctx_t ctx); + +// Dynamic certificate update (for servers) +bool update_server_cert(ctx_t ctx, const char *cert_pem, const char *key_pem, const char *password); +bool update_server_client_ca(ctx_t ctx, const char *ca_pem); + +// Certificate verification callback +bool set_verify_callback(ctx_t ctx, VerifyCallback callback); +long get_verify_error(const_session_t session); +std::string verify_error_string(long error_code); + +// TlsError information +uint64_t peek_error(); +uint64_t get_error(); +std::string error_string(uint64_t code); + +} // namespace tls +#endif // CPPHTTPLIB_SSL_ENABLED + +/* + * Group 1: detail namespace - Non-SSL utilities + */ + +namespace detail { + +inline bool set_socket_opt_impl(socket_t sock, int level, int optname, const void *optval, socklen_t optlen) +{ + return setsockopt(sock, + level, + optname, +#ifdef _WIN32 + reinterpret_cast(optval), +#else + optval, +#endif + optlen) == 0; +} + +inline bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec, time_t usec) +{ +#ifdef _WIN32 + auto timeout = static_cast(sec * 1000 + usec / 1000); +#else + timeval timeout; + timeout.tv_sec = static_cast(sec); + timeout.tv_usec = static_cast(usec); +#endif + return set_socket_opt_impl(sock, level, optname, &timeout, sizeof(timeout)); +} + +inline bool is_hex(char c, int &v) +{ + if (is_ascii_digit(c)) { + v = c - '0'; + return true; + } + else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } + else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } + return false; +} + +inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, int &val) +{ + if (i >= s.size()) { + return false; + } + + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { + return false; + } + auto v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; + } + else { + return false; + } + } + return true; +} + +inline std::string from_i_to_hex(size_t n) +{ + static const auto charset = "0123456789abcdef"; + std::string ret; + do { + ret = charset[n & 15] + ret; + n >>= 4; + } while (n > 0); + return ret; +} + +inline std::string compute_etag(const FileStat &fs) +{ + if (!fs.is_file()) { + return std::string(); + } + + // If mtime cannot be determined (negative value indicates an error + // or sentinel), do not generate an ETag. Returning a neutral / fixed + // value like 0 could collide with a real file that legitimately has + // mtime == 0 (epoch) and lead to misleading validators. + auto mtime_raw = fs.mtime(); + if (mtime_raw < 0) { + return std::string(); + } + + auto mtime = static_cast(mtime_raw); + auto size = fs.size(); + + return std::string("W/\"") + from_i_to_hex(mtime) + "-" + from_i_to_hex(size) + "\""; +} + +// Format time_t as HTTP-date (RFC 9110 Section 5.6.7): "Sun, 06 Nov 1994 +// 08:49:37 GMT" This implementation is defensive: it validates `mtime`, checks +// return values from `gmtime_r`/`gmtime_s`, and ensures `strftime` succeeds. +inline std::string file_mtime_to_http_date(time_t mtime) +{ + if (mtime < 0) { + return std::string(); + } + + struct tm tm_buf; +#ifdef _WIN32 + if (gmtime_s(&tm_buf, &mtime) != 0) { + return std::string(); + } +#else + if (gmtime_r(&mtime, &tm_buf) == nullptr) { + return std::string(); + } +#endif + char buf[64]; + if (strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", &tm_buf) == 0) { + return std::string(); + } + + return std::string(buf); +} + +// Parse HTTP-date (RFC 9110 Section 5.6.7) to time_t. Returns -1 on failure. +inline time_t parse_http_date(const std::string &date_str) +{ + struct tm tm_buf; + + // Create a classic locale object once for all parsing attempts + const std::locale classic_locale = std::locale::classic(); + + // Try to parse using std::get_time (C++11, cross-platform) + auto try_parse = [&](const char *fmt) -> bool { + std::istringstream ss(date_str); + ss.imbue(classic_locale); + + memset(&tm_buf, 0, sizeof(tm_buf)); + ss >> std::get_time(&tm_buf, fmt); + + return !ss.fail(); + }; + + // RFC 9110 preferred format (HTTP-date): "Sun, 06 Nov 1994 08:49:37 GMT" + if (!try_parse("%a, %d %b %Y %H:%M:%S")) { + // RFC 850 format: "Sunday, 06-Nov-94 08:49:37 GMT" + if (!try_parse("%A, %d-%b-%y %H:%M:%S")) { + // asctime format: "Sun Nov 6 08:49:37 1994" + if (!try_parse("%a %b %d %H:%M:%S %Y")) { + return static_cast(-1); + } + } + } + +#ifdef _WIN32 + return _mkgmtime(&tm_buf); +#elif defined _AIX + return mktime(&tm_buf); +#else + return timegm(&tm_buf); +#endif +} + +inline bool is_weak_etag(const std::string &s) +{ + // Check if the string is a weak ETag (starts with 'W/"') + return s.size() > 3 && s[0] == 'W' && s[1] == '/' && s[2] == '"'; +} + +inline bool is_strong_etag(const std::string &s) +{ + // Check if the string is a strong ETag (starts and ends with '"', at least 2 + // chars) + return s.size() >= 2 && s[0] == '"' && s.back() == '"'; +} + +inline size_t to_utf8(int code, char *buff) +{ + if (code < 0x0080) { + buff[0] = static_cast(code & 0x7F); + return 1; + } + else if (code < 0x0800) { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } + else if (code < 0xD800) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } + else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } + else if (code < 0x10000) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } + else if (code < 0x110000) { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; +} + +} // namespace detail + +namespace ws { +namespace impl { + +inline bool is_valid_utf8(const std::string &s) +{ + size_t i = 0; + auto n = s.size(); + while (i < n) { + auto c = static_cast(s[i]); + size_t len; + uint32_t cp; + if (c < 0x80) { + i++; + continue; + } + else if ((c & 0xE0) == 0xC0) { + len = 2; + cp = c & 0x1F; + } + else if ((c & 0xF0) == 0xE0) { + len = 3; + cp = c & 0x0F; + } + else if ((c & 0xF8) == 0xF0) { + len = 4; + cp = c & 0x07; + } + else { + return false; + } + if (i + len > n) { + return false; + } + for (size_t j = 1; j < len; j++) { + auto b = static_cast(s[i + j]); + if ((b & 0xC0) != 0x80) { + return false; + } + cp = (cp << 6) | (b & 0x3F); + } + // Overlong encoding check + if (len == 2 && cp < 0x80) { + return false; + } + if (len == 3 && cp < 0x800) { + return false; + } + if (len == 4 && cp < 0x10000) { + return false; + } + // Surrogate halves (U+D800..U+DFFF) and beyond U+10FFFF are invalid + if (cp >= 0xD800 && cp <= 0xDFFF) { + return false; + } + if (cp > 0x10FFFF) { + return false; + } + i += len; + } + return true; +} + +} // namespace impl +} // namespace ws + +namespace detail { + +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c +inline std::string base64_encode(const std::string &in) +{ + static const auto lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(in.size()); + + // Unsigned: the accumulator is never masked, so with a signed int the + // `val << 8` below overflows once enough bytes are folded in (undefined + // behaviour before C++20). Only the low bits are ever emitted, so the + // wrap-around of an unsigned accumulator does not affect the output. + uint32_t val = 0; + auto valb = -6; + + for (auto c : in) { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { + out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); + } + + while (out.size() % 4) { + out.push_back('='); + } + + return out; +} + +inline std::string sha1(const std::string &input) +{ + // RFC 3174 SHA-1 implementation + auto left_rotate = [](uint32_t x, uint32_t n) -> uint32_t { return (x << n) | (x >> (32 - n)); }; + + uint32_t h0 = 0x67452301; + uint32_t h1 = 0xEFCDAB89; + uint32_t h2 = 0x98BADCFE; + uint32_t h3 = 0x10325476; + uint32_t h4 = 0xC3D2E1F0; + + // Pre-processing: adding padding bits + std::string msg = input; + uint64_t original_bit_len = static_cast(msg.size()) * 8; + msg.push_back(static_cast(0x80u)); + while (msg.size() % 64 != 56) { + msg.push_back(0); + } + + // Append original length in bits as 64-bit big-endian + for (int i = 56; i >= 0; i -= 8) { + msg.push_back(static_cast((original_bit_len >> i) & 0xFF)); + } + + // Process each 512-bit chunk + for (size_t offset = 0; offset < msg.size(); offset += 64) { + uint32_t w[80]; + + for (size_t i = 0; i < 16; i++) { + w[i] = (static_cast(static_cast(msg[offset + i * 4])) << 24) | + (static_cast(static_cast(msg[offset + i * 4 + 1])) << 16) | + (static_cast(static_cast(msg[offset + i * 4 + 2])) << 8) | + (static_cast(static_cast(msg[offset + i * 4 + 3]))); + } + + for (int i = 16; i < 80; i++) { + w[i] = left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + } + + uint32_t a = h0, b = h1, c = h2, d = h3, e = h4; + + for (int i = 0; i < 80; i++) { + uint32_t f, k; + if (i < 20) { + f = (b & c) | ((~b) & d); + k = 0x5A827999; + } + else if (i < 40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } + else if (i < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } + else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + + uint32_t temp = left_rotate(a, 5) + f + e + k + w[i]; + e = d; + d = c; + c = left_rotate(b, 30); + b = a; + a = temp; + } + + h0 += a; + h1 += b; + h2 += c; + h3 += d; + h4 += e; + } + + // Produce the final hash as a 20-byte binary string + std::string hash(20, '\0'); + for (size_t i = 0; i < 4; i++) { + hash[i] = static_cast((h0 >> (24 - i * 8)) & 0xFF); + hash[4 + i] = static_cast((h1 >> (24 - i * 8)) & 0xFF); + hash[8 + i] = static_cast((h2 >> (24 - i * 8)) & 0xFF); + hash[12 + i] = static_cast((h3 >> (24 - i * 8)) & 0xFF); + hash[16 + i] = static_cast((h4 >> (24 - i * 8)) & 0xFF); + } + return hash; +} + +inline std::string websocket_accept_key(const std::string &client_key) +{ + const std::string magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + return base64_encode(sha1(client_key + magic)); +} + +inline bool is_websocket_upgrade(const Request &req) +{ + if (req.method != "GET") { + return false; + } + + // Check Upgrade: websocket (case-insensitive) + auto upgrade_it = req.headers.find("Upgrade"); + if (upgrade_it == req.headers.end()) { + return false; + } + auto upgrade_val = case_ignore::to_lower(upgrade_it->second); + if (upgrade_val != "websocket") { + return false; + } + + // Check Connection header contains "Upgrade" + auto connection_it = req.headers.find("Connection"); + if (connection_it == req.headers.end()) { + return false; + } + auto connection_val = case_ignore::to_lower(connection_it->second); + if (connection_val.find("upgrade") == std::string::npos) { + return false; + } + + // Check Sec-WebSocket-Key is a valid base64-encoded 16-byte value (24 chars) + // RFC 6455 Section 4.2.1 + auto ws_key = req.get_header_value("Sec-WebSocket-Key"); + if (ws_key.size() != 24 || ws_key[22] != '=' || ws_key[23] != '=') { + return false; + } + static const std::string b64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + for (size_t i = 0; i < 22; i++) { + if (b64chars.find(ws_key[i]) == std::string::npos) { + return false; + } + } + + // Check Sec-WebSocket-Version: 13 + auto version = req.get_header_value("Sec-WebSocket-Version"); + if (version != "13") { + return false; + } + + return true; +} + +inline bool write_websocket_frame(Stream &strm, ws::Opcode opcode, const char *data, size_t len, bool fin, bool mask) +{ + // First byte: FIN + opcode + uint8_t header[2]; + header[0] = static_cast((fin ? 0x80 : 0x00) | (static_cast(opcode) & 0x0F)); + + // Second byte: MASK + payload length + if (len < 126) { + header[1] = static_cast(len); + if (mask) { + header[1] |= 0x80; + } + if (strm.write(reinterpret_cast(header), 2) < 0) { + return false; + } + } + else if (len <= 0xFFFF) { + header[1] = 126; + if (mask) { + header[1] |= 0x80; + } + if (strm.write(reinterpret_cast(header), 2) < 0) { + return false; + } + uint8_t ext[2]; + ext[0] = static_cast((len >> 8) & 0xFF); + ext[1] = static_cast(len & 0xFF); + if (strm.write(reinterpret_cast(ext), 2) < 0) { + return false; + } + } + else { + header[1] = 127; + if (mask) { + header[1] |= 0x80; + } + if (strm.write(reinterpret_cast(header), 2) < 0) { + return false; + } + uint8_t ext[8]; + for (int i = 7; i >= 0; i--) { + ext[7 - i] = static_cast((static_cast(len) >> (i * 8)) & 0xFF); + } + if (strm.write(reinterpret_cast(ext), 8) < 0) { + return false; + } + } + + if (mask) { + // Generate random mask key + thread_local std::mt19937 rng(std::random_device{}()); + uint8_t mask_key[4]; + auto r = rng(); + std::memcpy(mask_key, &r, 4); + if (strm.write(reinterpret_cast(mask_key), 4) < 0) { + return false; + } + + // Write masked payload in chunks + const size_t chunk_size = 4096; + std::vector buf((std::min)(len, chunk_size)); + for (size_t offset = 0; offset < len; offset += chunk_size) { + size_t n = (std::min)(chunk_size, len - offset); + for (size_t i = 0; i < n; i++) { + buf[i] = data[offset + i] ^ static_cast(mask_key[(offset + i) % 4]); + } + if (strm.write(buf.data(), n) < 0) { + return false; + } + } + } + else { + if (len > 0) { + if (strm.write(data, len) < 0) { + return false; + } + } + } + + return true; +} + +} // namespace detail + +namespace ws { +namespace impl { + +inline bool read_websocket_frame(Stream &strm, Opcode &opcode, std::string &payload, bool &fin, bool expect_masked, size_t max_len) +{ + // Read first 2 bytes + uint8_t header[2]; + if (strm.read(reinterpret_cast(header), 2) != 2) { + return false; + } + + fin = (header[0] & 0x80) != 0; + + // RSV1, RSV2, RSV3 must be 0 when no extension is negotiated + if (header[0] & 0x70) { + return false; + } + + opcode = static_cast(header[0] & 0x0F); + bool masked = (header[1] & 0x80) != 0; + uint64_t payload_len = header[1] & 0x7F; + + // RFC 6455 Section 5.5: control frames MUST NOT be fragmented and + // MUST have a payload length of 125 bytes or less + bool is_control = (static_cast(opcode) & 0x08) != 0; + if (is_control) { + if (!fin) { + return false; + } + if (payload_len > 125) { + return false; + } + } + + if (masked != expect_masked) { + return false; + } + + // Extended payload length + if (payload_len == 126) { + uint8_t ext[2]; + if (strm.read(reinterpret_cast(ext), 2) != 2) { + return false; + } + payload_len = (static_cast(ext[0]) << 8) | ext[1]; + } + else if (payload_len == 127) { + uint8_t ext[8]; + if (strm.read(reinterpret_cast(ext), 8) != 8) { + return false; + } + // RFC 6455 Section 5.2: the most significant bit MUST be 0 + if (ext[0] & 0x80) { + return false; + } + payload_len = 0; + for (int i = 0; i < 8; i++) { + payload_len = (payload_len << 8) | ext[i]; + } + } + + if (payload_len > max_len) { + return false; + } + + // Read mask key if present + uint8_t mask_key[4] = {0}; + if (masked) { + if (strm.read(reinterpret_cast(mask_key), 4) != 4) { + return false; + } + } + + // Read payload + payload.resize(static_cast(payload_len)); + if (payload_len > 0) { + size_t total_read = 0; + while (total_read < payload_len) { + auto n = strm.read(&payload[total_read], static_cast(payload_len - total_read)); + if (n <= 0) { + return false; + } + total_read += static_cast(n); + } + } + + // Unmask if needed + if (masked) { + for (size_t i = 0; i < payload.size(); i++) { + payload[i] ^= static_cast(mask_key[i % 4]); + } + } + + return true; +} + +} // namespace impl +} // namespace ws + +namespace detail { + +inline bool is_valid_path(const std::string &path) +{ + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + + while (i < path.size()) { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') { + if (path[i] == '\0') { + return false; + } + else if (path[i] == '\\') { + return false; + } + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) { + ; + } + else if (!path.compare(beg, len, "..")) { + if (level == 0) { + return false; + } + level--; + } + else { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + } + + return true; +} + +inline bool canonicalize_path(const char *path, std::string &resolved) +{ +#if defined(_WIN32) + char buf[_MAX_PATH]; + if (_fullpath(buf, path, _MAX_PATH) == nullptr) { + return false; + } + resolved = buf; +#elif defined(PATH_MAX) + char buf[PATH_MAX]; + if (realpath(path, buf) == nullptr) { + return false; + } + resolved = buf; +#else + auto buf = realpath(path, nullptr); + auto guard = scope_exit([&]() { std::free(buf); }); + if (buf == nullptr) { + return false; + } + resolved = buf; +#endif + return true; +} + +inline bool is_path_within_base(const std::string &resolved_path, const std::string &resolved_base) +{ +#if defined(_WIN32) + return _strnicmp(resolved_path.c_str(), resolved_base.c_str(), resolved_base.size()) == 0; +#else + return strncmp(resolved_path.c_str(), resolved_base.c_str(), resolved_base.size()) == 0; +#endif +} + +inline FileStat::FileStat(const std::string &path) +{ +#if defined(_WIN32) + auto wpath = u8string_to_wstring(path.c_str()); + ret_ = _wstat(wpath.c_str(), &st_); +#else + ret_ = stat(path.c_str(), &st_); +#endif +} +inline bool FileStat::is_file() const +{ + return ret_ >= 0 && S_ISREG(st_.st_mode); +} +inline bool FileStat::is_dir() const +{ + return ret_ >= 0 && S_ISDIR(st_.st_mode); +} + +inline time_t FileStat::mtime() const +{ + return ret_ >= 0 ? static_cast(st_.st_mtime) : static_cast(-1); +} + +inline size_t FileStat::size() const +{ + return ret_ >= 0 ? static_cast(st_.st_size) : 0; +} + +inline std::string encode_path(const std::string &s) +{ + std::string result; + result.reserve(s.size()); + + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': + result += "%20"; + break; + case '+': + result += "%2B"; + break; + case '\r': + result += "%0D"; + break; + case '\n': + result += "%0A"; + break; + case '\'': + result += "%27"; + break; + case ',': + result += "%2C"; + break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': + result += "%3B"; + break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } + else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline std::string file_extension(const std::string &path) +{ + std::smatch m; + thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { + return m[1].str(); + } + return std::string(); +} + +inline bool is_space_or_tab(char c) +{ + return c == ' ' || c == '\t'; +} + +template +inline bool parse_header(const char *beg, const char *end, T fn); + +template +inline bool parse_header(const char *beg, const char *end, T fn) +{ + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + auto name = std::string(beg, p); + if (!detail::fields::is_field_name(name)) { + return false; + } + + if (p == end) { + return false; + } + + auto key_end = p; + + if (*p++ != ':') { + return false; + } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p <= end) { + auto key_len = key_end - beg; + if (!key_len) { + return false; + } + + auto key = std::string(beg, key_end); + auto val = std::string(p, end); + + if (!detail::fields::is_field_value(val)) { + return false; + } + + // RFC 9110 §5.5: header field values are opaque octets and MUST NOT be + // percent-decoded by the recipient. Applications that need to interpret a + // value as a URI component should call httplib::decode_uri_component() + // (or decode_path_component()) explicitly. + fn(key, val); + + return true; + } + + return false; +} + +inline bool parse_trailers(stream_line_reader &line_reader, Headers &dest, const Headers &src_headers) +{ + // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "The chunked + // transfer coding is complete when a chunk with a chunk-size of zero is + // received, possibly followed by a trailer section, and finally terminated by + // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1 + // + // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section + // doesn't care for the existence of the final CRLF. In other words, it seems + // to be ok whether the final CRLF exists or not in the chunked data. + // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3 + // + // According to the reference code in RFC 9112, cpp-httplib now allows + // chunked transfer coding data without the final CRLF. + + // RFC 7230 Section 4.1.2 - Headers prohibited in trailers + thread_local case_ignore::unordered_set prohibited_trailers = {"transfer-encoding", + "content-length", + "host", + "authorization", + "www-authenticate", + "proxy-authenticate", + "proxy-authorization", + "cookie", + "set-cookie", + "cache-control", + "expect", + "max-forwards", + "pragma", + "range", + "te", + "age", + "expires", + "date", + "location", + "retry-after", + "vary", + "warning", + "content-encoding", + "content-type", + "content-range", + "trailer"}; + + case_ignore::unordered_set declared_trailers; + auto trailer_header = get_header_value(src_headers, "Trailer", "", 0); + if (trailer_header && std::strlen(trailer_header)) { + auto len = std::strlen(trailer_header); + split(trailer_header, trailer_header + len, ',', [&](const char *b, const char *e) { + const char *kbeg = b; + const char *kend = e; + while (kbeg < kend && (*kbeg == ' ' || *kbeg == '\t')) { + ++kbeg; + } + while (kend > kbeg && (kend[-1] == ' ' || kend[-1] == '\t')) { + --kend; + } + std::string key(kbeg, static_cast(kend - kbeg)); + if (!key.empty() && prohibited_trailers.find(key) == prohibited_trailers.end()) { + declared_trailers.insert(key); + } + }); + } + + size_t trailer_header_count = 0; + while (strcmp(line_reader.ptr(), "\r\n") != 0) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { + return false; + } + if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { + return false; + } + + constexpr auto line_terminator_len = 2; + auto line_beg = line_reader.ptr(); + auto line_end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + if (!parse_header(line_beg, line_end, [&](const std::string &key, const std::string &val) { + if (declared_trailers.find(key) != declared_trailers.end()) { + dest.emplace(key, val); + trailer_header_count++; + } + })) { + return false; + } + + if (!line_reader.getline()) { + return false; + } + } + + return true; +} + +inline std::pair trim(const char *b, const char *e, size_t left, size_t right) +{ + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); +} + +inline std::string trim_copy(const std::string &s) +{ + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); +} + +inline std::string trim_double_quotes_copy(const std::string &s) +{ + if (s.length() >= 2 && s.front() == '"' && s.back() == '"') { + return s.substr(1, s.size() - 2); + } + return s; +} + +inline void divide(const char *data, std::size_t size, char d, std::function fn) +{ + const auto it = std::find(data, data + size, d); + const auto found = static_cast(it != data + size); + const auto lhs_data = data; + const auto lhs_size = static_cast(it - data); + const auto rhs_data = it + found; + const auto rhs_size = size - lhs_size - found; + + fn(lhs_data, lhs_size, rhs_data, rhs_size); +} + +inline void divide(const std::string &str, char d, std::function fn) +{ + divide(str.data(), str.size(), d, std::move(fn)); +} + +inline void split(const char *b, const char *e, char d, std::function fn) +{ + return split(b, e, d, (std::numeric_limits::max)(), std::move(fn)); +} + +inline void split(const char *b, const char *e, char d, size_t m, std::function fn) +{ + size_t i = 0; + size_t beg = 0; + size_t count = 1; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d && count < m) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { + fn(&b[r.first], &b[r.second]); + } + beg = i + 1; + count++; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { + fn(&b[r.first], &b[r.second]); + } + } +} + +inline bool split_find(const char *b, const char *e, char d, size_t m, std::function fn) +{ + size_t i = 0; + size_t beg = 0; + size_t count = 1; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d && count < m) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { + auto found = fn(&b[r.first], &b[r.second]); + if (found) { + return true; + } + } + beg = i + 1; + count++; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { + auto found = fn(&b[r.first], &b[r.second]); + if (found) { + return true; + } + } + } + + return false; +} + +inline bool split_find(const char *b, const char *e, char d, std::function fn) +{ + return split_find(b, e, d, (std::numeric_limits::max)(), std::move(fn)); +} + +inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size) + : strm_(strm) + , fixed_buffer_(fixed_buffer) + , fixed_buffer_size_(fixed_buffer_size) +{ +} + +inline const char *stream_line_reader::ptr() const +{ + if (growable_buffer_.empty()) { + return fixed_buffer_; + } + else { + return growable_buffer_.data(); + } +} + +inline size_t stream_line_reader::size() const +{ + if (growable_buffer_.empty()) { + return fixed_buffer_used_size_; + } + else { + return growable_buffer_.size(); + } +} + +inline bool stream_line_reader::end_with_crlf() const +{ + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; +} + +inline bool stream_line_reader::getline() +{ + fixed_buffer_used_size_ = 0; + growable_buffer_.clear(); + +#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + char prev_byte = 0; +#endif + + for (size_t i = 0;; i++) { + // Fast path: whatever the stream has already buffered can be scanned for + // the terminator in one pass. Asking for a byte at a time costs a virtual + // call, a bounds check and a one-byte copy per character of the request. + size_t buffered_size = 0; + if (auto buffered = strm_.buffered_data(buffered_size)) { + auto take = buffered_size; + auto terminated = false; + + for (size_t at = 0; at < buffered_size;) { + auto nl = static_cast(memchr(buffered + at, '\n', buffered_size - at)); + if (!nl) { + break; + } + auto pos = static_cast(nl - buffered); +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + take = pos + 1; + terminated = true; + break; +#else + // A bare LF does not end the line; keep looking for CRLF. The CR may + // be the last byte of an earlier chunk, hence prev_byte. + if ((pos > 0 ? buffered[pos - 1] : prev_byte) == '\r') { + take = pos + 1; + terminated = true; + break; + } + at = pos + 1; +#endif + } + + if (size() + take > CPPHTTPLIB_MAX_LINE_LENGTH) { + return false; + } +#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + prev_byte = buffered[take - 1]; +#endif + append(buffered, take); + strm_.consume_buffered(take); + i += take; + if (terminated) { + return true; + } + continue; + } + + if (size() >= CPPHTTPLIB_MAX_LINE_LENGTH) { + // Treat exceptionally long lines as an error to + // prevent infinite loops/memory exhaustion + return false; + } + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } + else if (n == 0) { + if (i == 0) { + return false; + } + else { + break; + } + } + + append(byte); + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + if (byte == '\n') { + break; + } +#else + if (prev_byte == '\r' && byte == '\n') { + break; + } + prev_byte = byte; +#endif + } + + return true; +} + +inline void stream_line_reader::append(char c) +{ + append(&c, 1); +} + +inline void stream_line_reader::append(const char *data, size_t size) +{ + // Once the line has outgrown the fixed buffer everything must keep going to + // the growable one, even if a later chunk would have fit. Without the + // emptiness check a short append after a long one would land in the fixed + // buffer, which ptr() and size() no longer look at, and be lost. + if (growable_buffer_.empty() && fixed_buffer_used_size_ + size < fixed_buffer_size_) { + memcpy(fixed_buffer_ + fixed_buffer_used_size_, data, size); + fixed_buffer_used_size_ += size; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } + else { + // Unlike the per-character overload, this can be the very first append of + // the line, so the fixed buffer may hold nothing and carry no terminator + // yet. assign() takes an explicit length and does not need one. + if (growable_buffer_.empty()) { + growable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + growable_buffer_.append(data, size); + } +} + +inline mmap::mmap(const char *path) +{ + open(path); +} + +inline mmap::~mmap() +{ + close(); +} + +inline bool mmap::open(const char *path) +{ + close(); + +#if defined(_WIN32) + auto wpath = u8string_to_wstring(path); + if (wpath.empty()) { + return false; + } + + hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, NULL); + + if (hFile_ == INVALID_HANDLE_VALUE) { + return false; + } + + LARGE_INTEGER size{}; + if (!::GetFileSizeEx(hFile_, &size)) { + return false; + } + // If the following line doesn't compile due to QuadPart, update Windows SDK. + // See: + // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721 + if (static_cast(size.QuadPart) > (std::numeric_limits::max)()) { + // `size_t` might be 32-bits, on 32-bits Windows. + return false; + } + size_ = static_cast(size.QuadPart); + + hMapping_ = ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL); + + // Special treatment for an empty file... + if (hMapping_ == NULL && size_ == 0) { + close(); + is_open_empty_file = true; + return true; + } + + if (hMapping_ == NULL) { + close(); + return false; + } + + addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0); + + if (addr_ == nullptr) { + close(); + return false; + } +#else + fd_ = ::open(path, O_RDONLY); + if (fd_ == -1) { + return false; + } + + struct stat sb; + if (fstat(fd_, &sb) == -1) { + close(); + return false; + } + size_ = static_cast(sb.st_size); + + addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0); + + // Special treatment for an empty file... + if (addr_ == MAP_FAILED && size_ == 0) { + close(); + is_open_empty_file = true; + return false; + } + + if (addr_ == MAP_FAILED) { + // Clear the sentinel before `close()`, since `is_open()` only checks + // `addr_` against nullptr and `munmap()` must not be called with it. + addr_ = nullptr; + close(); + return false; + } +#endif + + return true; +} + +inline bool mmap::is_open() const +{ + return is_open_empty_file ? true : addr_ != nullptr; +} + +inline size_t mmap::size() const +{ + return size_; +} + +inline const char *mmap::data() const +{ + return is_open_empty_file ? "" : static_cast(addr_); +} + +inline void mmap::close() +{ +#if defined(_WIN32) + if (addr_) { + ::UnmapViewOfFile(addr_); + addr_ = nullptr; + } + + if (hMapping_) { + ::CloseHandle(hMapping_); + hMapping_ = NULL; + } + + if (hFile_ != INVALID_HANDLE_VALUE) { + ::CloseHandle(hFile_); + hFile_ = INVALID_HANDLE_VALUE; + } + + is_open_empty_file = false; +#else + if (addr_ != nullptr) { + munmap(addr_, size_); + addr_ = nullptr; + } + + if (fd_ != -1) { + ::close(fd_); + fd_ = -1; + } +#endif + size_ = 0; +} +inline int close_socket(socket_t sock) noexcept +{ +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +template +inline ssize_t handle_EINTR(T fn) +{ + ssize_t res = 0; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { + std::this_thread::sleep_for(std::chrono::microseconds{1}); + continue; + } + break; + } + return res; +} + +inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) +{ + return handle_EINTR([&]() { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), + static_cast(size), +#else + ptr, + size, +#endif + flags); + }); +} + +inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags) +{ + return handle_EINTR([&]() { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), + static_cast(size), +#else + ptr, + size, +#endif + flags); + }); +} + +inline int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) +{ +#ifdef _WIN32 + return ::WSAPoll(fds, nfds, timeout); +#else + return ::poll(fds, nfds, timeout); +#endif +} + +inline ssize_t select_impl(socket_t sock, short events, time_t sec, time_t usec) +{ + struct pollfd pfd; + pfd.fd = sock; + pfd.events = events; + pfd.revents = 0; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll_wrapper(&pfd, 1, timeout); }); +} + +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) +{ + return select_impl(sock, POLLIN, sec, usec); +} + +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) +{ + return select_impl(sock, POLLOUT, sec, usec); +} + +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) +{ + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + pfd_read.revents = 0; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = handle_EINTR([&]() { return poll_wrapper(&pfd_read, 1, timeout); }); + + if (poll_res == 0) { + return Error::ConnectionTimeout; + } + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +} + +inline bool is_socket_alive(socket_t sock) +{ + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) { + return true; + } + else if (val < 0 && errno == EBADF) { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; +} + +class SocketStream final : public Stream { +public: + SocketStream(socket_t sock, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + time_t max_timeout_msec = 0, + std::chrono::time_point start_time = (std::chrono::steady_clock::time_point::min)()); + ~SocketStream() override; + + bool is_readable() const override; + bool wait_readable() const override; + bool wait_writable() const override; + bool is_peer_alive() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + time_t duration() const override; + void set_read_timeout(time_t sec, time_t usec = 0) override; + const char *buffered_data(size_t &size) const override; + void consume_buffered(size_t size) override; + + // The caller has just seen this socket become readable. Lets the next read + // skip its own readiness wait, which would otherwise ask the kernel a + // question that was answered a moment ago. Consumed by that read. + void set_readable_hint() { readable_hint_ = true; } + +private: + bool ensure_readable(); + + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + time_t max_timeout_msec_; + const std::chrono::time_point start_time_; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + bool readable_hint_ = false; + + static const size_t read_buff_size_ = 1024l * 4; +}; + +inline bool keep_alive(const std::atomic &svr_sock, socket_t sock, time_t keep_alive_timeout_sec) +{ + using namespace std::chrono; + + const auto interval_usec = CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND; + + // Avoid expensive `steady_clock::now()` call for the first time + if (select_read(sock, 0, interval_usec) > 0) { + return true; + } + + const auto start = steady_clock::now() - microseconds{interval_usec}; + const auto timeout = seconds{keep_alive_timeout_sec}; + + while (true) { + if (svr_sock == INVALID_SOCKET) { + break; // Server socket is closed + } + + auto val = select_read(sock, 0, interval_usec); + if (val < 0) { + break; // Ssocket error + } + else if (val == 0) { + if (steady_clock::now() - start > timeout) { + break; // Timeout + } + } + else { + return true; // Ready for read + } + } + + return false; +} + +template +inline bool process_server_socket_core( + const std::atomic &svr_sock, socket_t sock, size_t keep_alive_max_count, time_t keep_alive_timeout_sec, T callback) +{ + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { + break; + } + count--; + } + return ret; +} + +template +inline bool process_server_socket(const std::atomic &svr_sock, + socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + T callback) +{ + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); + // process_server_socket_core() only gets here once keep_alive() has + // seen the socket go readable. + strm.set_readable_hint(); + return callback(strm, close_connection, connection_closed); + }); +} + +inline bool process_client_socket(socket_t sock, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, + std::function callback) +{ + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec, max_timeout_msec, start_time); + return callback(strm); +} + +inline int shutdown_socket(socket_t sock) noexcept +{ +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif +} + +inline std::string escape_abstract_namespace_unix_domain(const std::string &s) +{ + if (s.size() > 1 && s[0] == '\0') { + auto ret = s; + ret[0] = '@'; + return ret; + } + return s; +} + +inline std::string unescape_abstract_namespace_unix_domain(const std::string &s) +{ + if (s.size() > 1 && s[0] == '@') { + auto ret = s; + ret[0] = '\0'; + return ret; + } + return s; +} + +inline int getaddrinfo_with_timeout( + const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res, time_t timeout_sec) +{ +#ifdef CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO + if (timeout_sec <= 0) { + // No timeout specified, use standard getaddrinfo + return getaddrinfo(node, service, hints, res); + } + +#ifdef _WIN32 + // Windows-specific implementation using GetAddrInfoEx with overlapped I/O + OVERLAPPED overlapped = {}; + HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!event) { + return EAI_FAIL; + } + + overlapped.hEvent = event; + + PADDRINFOEXW result_addrinfo = nullptr; + HANDLE cancel_handle = nullptr; + + ADDRINFOEXW hints_ex = {}; + if (hints) { + hints_ex.ai_flags = hints->ai_flags; + hints_ex.ai_family = hints->ai_family; + hints_ex.ai_socktype = hints->ai_socktype; + hints_ex.ai_protocol = hints->ai_protocol; + } + + auto wnode = u8string_to_wstring(node); + auto wservice = u8string_to_wstring(service); + + auto ret = ::GetAddrInfoExW(wnode.data(), + wservice.data(), + NS_DNS, + nullptr, + hints ? &hints_ex : nullptr, + &result_addrinfo, + nullptr, + &overlapped, + nullptr, + &cancel_handle); + + if (ret == WSA_IO_PENDING) { + auto wait_result = ::WaitForSingleObject(event, static_cast(timeout_sec * 1000)); + if (wait_result == WAIT_TIMEOUT) { + if (cancel_handle) { + ::GetAddrInfoExCancel(&cancel_handle); + } + ::CloseHandle(event); + return EAI_AGAIN; + } + + DWORD bytes_returned; + if (!::GetOverlappedResult((HANDLE)INVALID_SOCKET, &overlapped, &bytes_returned, FALSE)) { + ::CloseHandle(event); + return ::WSAGetLastError(); + } + } + + ::CloseHandle(event); + + if (ret == NO_ERROR || ret == WSA_IO_PENDING) { + *res = reinterpret_cast(result_addrinfo); + return 0; + } + + return ret; +#elif TARGET_OS_MAC && defined(__clang__) + if (!node) { + return EAI_NONAME; + } + // macOS implementation using CFHost API for asynchronous DNS resolution + CFStringRef hostname_ref = CFStringCreateWithCString(kCFAllocatorDefault, node, kCFStringEncodingUTF8); + if (!hostname_ref) { + return EAI_MEMORY; + } + + CFHostRef host_ref = CFHostCreateWithName(kCFAllocatorDefault, hostname_ref); + CFRelease(hostname_ref); + if (!host_ref) { + return EAI_MEMORY; + } + + // Set up context for callback + struct CFHostContext { + bool completed = false; + bool success = false; + CFArrayRef addresses = nullptr; + std::mutex mutex; + std::condition_variable cv; + } context; + + CFHostClientContext client_context; + memset(&client_context, 0, sizeof(client_context)); + client_context.info = &context; + + // Set callback + auto callback = [](CFHostRef theHost, CFHostInfoType /*typeInfo*/, const CFStreamError *error, void *info) { + auto ctx = static_cast(info); + std::lock_guard lock(ctx->mutex); + + if (error && error->error != 0) { + ctx->success = false; + } + else { + Boolean hasBeenResolved; + ctx->addresses = CFHostGetAddressing(theHost, &hasBeenResolved); + if (ctx->addresses && hasBeenResolved) { + CFRetain(ctx->addresses); + ctx->success = true; + } + else { + ctx->success = false; + } + } + ctx->completed = true; + ctx->cv.notify_one(); + }; + + if (!CFHostSetClient(host_ref, callback, &client_context)) { + CFRelease(host_ref); + return EAI_SYSTEM; + } + + // Schedule on run loop + CFRunLoopRef run_loop = CFRunLoopGetCurrent(); + CFHostScheduleWithRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode); + + // Start resolution + CFStreamError stream_error; + if (!CFHostStartInfoResolution(host_ref, kCFHostAddresses, &stream_error)) { + CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode); + CFRelease(host_ref); + return EAI_FAIL; + } + + // Wait for completion with timeout + auto timeout_time = std::chrono::steady_clock::now() + std::chrono::seconds(timeout_sec); + bool timed_out = false; + + { + std::unique_lock lock(context.mutex); + + while (!context.completed) { + auto now = std::chrono::steady_clock::now(); + if (now >= timeout_time) { + timed_out = true; + break; + } + + // Run the runloop for a short time + lock.unlock(); + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true); + lock.lock(); + } + } + + // Clean up + CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode); + CFHostSetClient(host_ref, nullptr, nullptr); + + if (timed_out || !context.completed) { + CFHostCancelInfoResolution(host_ref, kCFHostAddresses); + CFRelease(host_ref); + return EAI_AGAIN; + } + + if (!context.success || !context.addresses) { + CFRelease(host_ref); + return EAI_NODATA; + } + + // Convert CFArray to addrinfo + CFIndex count = CFArrayGetCount(context.addresses); + if (count == 0) { + CFRelease(context.addresses); + CFRelease(host_ref); + return EAI_NODATA; + } + + struct addrinfo *result_addrinfo = nullptr; + struct addrinfo **current = &result_addrinfo; + + for (CFIndex i = 0; i < count; i++) { + CFDataRef addr_data = static_cast(CFArrayGetValueAtIndex(context.addresses, i)); + if (!addr_data) + continue; + + const struct sockaddr *sockaddr_ptr = reinterpret_cast(CFDataGetBytePtr(addr_data)); + socklen_t sockaddr_len = static_cast(CFDataGetLength(addr_data)); + + // Allocate addrinfo structure + *current = static_cast(malloc(sizeof(struct addrinfo))); + if (!*current) { + freeaddrinfo(result_addrinfo); + CFRelease(context.addresses); + CFRelease(host_ref); + return EAI_MEMORY; + } + + memset(*current, 0, sizeof(struct addrinfo)); + + // Set up addrinfo fields + (*current)->ai_family = sockaddr_ptr->sa_family; + (*current)->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM; + (*current)->ai_protocol = hints ? hints->ai_protocol : IPPROTO_TCP; + (*current)->ai_addrlen = sockaddr_len; + + // Copy sockaddr + (*current)->ai_addr = static_cast(malloc(sockaddr_len)); + if (!(*current)->ai_addr) { + freeaddrinfo(result_addrinfo); + CFRelease(context.addresses); + CFRelease(host_ref); + return EAI_MEMORY; + } + memcpy((*current)->ai_addr, sockaddr_ptr, sockaddr_len); + + // Set port if service is specified + if (service && *service) { + int port = 0; + if (parse_port(service, strlen(service), port)) { + if (sockaddr_ptr->sa_family == AF_INET) { + reinterpret_cast((*current)->ai_addr)->sin_port = htons(static_cast(port)); + } + else if (sockaddr_ptr->sa_family == AF_INET6) { + reinterpret_cast((*current)->ai_addr)->sin6_port = htons(static_cast(port)); + } + } + } + + current = &((*current)->ai_next); + } + + CFRelease(context.addresses); + CFRelease(host_ref); + + *res = result_addrinfo; + return 0; +#elif defined(_GNU_SOURCE) && defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2)) + // #2431: gai_cancel() is non-blocking and may return EAI_NOTCANCELED while + // the resolver worker still references the stack-local gaicb. The cancel + // path therefore waits (gai_suspend with no timeout) for the worker to + // actually finish before letting the stack frame go. The trade-off is that + // a wedged DNS server can hold this thread for the system resolver timeout + // (~30s by default) past the caller's connection timeout. + struct gaicb request {}; + struct gaicb *requests[1] = {&request}; + struct sigevent sevp {}; + struct timespec timeout { + timeout_sec, 0 + }; + + request.ar_name = node; + request.ar_service = service; + request.ar_request = hints; + sevp.sigev_notify = SIGEV_NONE; + + int rc = getaddrinfo_a(GAI_NOWAIT, requests, 1, &sevp); + if (rc != 0) { + return rc; + } + + auto cleanup = scope_exit([&] { + if (request.ar_result) { + freeaddrinfo(request.ar_result); + } + }); + + int wait_result = gai_suspend(requests, 1, &timeout); + + if (wait_result == 0 || wait_result == EAI_ALLDONE) { + int gai_result = gai_error(&request); + if (gai_result == 0) { + *res = request.ar_result; + request.ar_result = nullptr; + return 0; + } + return gai_result; + } + + gai_cancel(&request); + while (gai_error(&request) == EAI_INPROGRESS) { + gai_suspend(requests, 1, nullptr); + } + return wait_result; +#else + // Fallback implementation using thread-based timeout for other Unix systems. + + struct GetAddrInfoState { + ~GetAddrInfoState() + { + if (info) { + freeaddrinfo(info); + } + } + + std::mutex mutex; + std::condition_variable result_cv; + bool completed = false; + int result = EAI_SYSTEM; + std::string node; + std::string service; + struct addrinfo hints; + struct addrinfo *info = nullptr; + }; + + // Allocate on the heap, so the resolver thread can keep using the data. + auto state = std::make_shared(); + if (node) { + state->node = node; + } + state->service = service; + state->hints = *hints; + + std::thread resolve_thread([state]() { + auto thread_result = getaddrinfo(state->node.c_str(), state->service.c_str(), &state->hints, &state->info); + + std::lock_guard lock(state->mutex); + state->result = thread_result; + state->completed = true; + state->result_cv.notify_one(); + }); + + // Wait for completion or timeout + std::unique_lock lock(state->mutex); + auto finished = state->result_cv.wait_for(lock, std::chrono::seconds(timeout_sec), [&] { return state->completed; }); + + if (finished) { + // Operation completed within timeout + resolve_thread.join(); + *res = state->info; + state->info = nullptr; // Pass ownership to caller + return state->result; + } + else { + // Timeout occurred + resolve_thread.detach(); // Let the thread finish in background + return EAI_AGAIN; // Return timeout error + } +#endif +#else + (void)(timeout_sec); // Unused parameter for non-blocking getaddrinfo + return getaddrinfo(node, service, hints, res); +#endif +} + +template +socket_t create_socket(const std::string &host, + const std::string &ip, + int port, + int address_family, + int socket_flags, + bool tcp_nodelay, + bool ipv6_v6only, + SocketOptions socket_options, + BindOrConnect bind_or_connect, + time_t timeout_sec = 0) +{ + // Get address info + const char *node = nullptr; + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_IP; + + if (!ip.empty()) { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + } + else { + if (!host.empty()) { + node = host.c_str(); + } + hints.ai_family = address_family; + hints.ai_flags = socket_flags; + } + +#if !defined(_WIN32) || defined(CPPHTTPLIB_HAVE_AFUNIX_H) + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) { + return INVALID_SOCKET; + } + +#ifdef SOCK_CLOEXEC + auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC, hints.ai_protocol); +#else + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); +#endif + + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + + auto unescaped_host = unescape_abstract_namespace_unix_domain(host); + std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast(sizeof(addr) - sizeof(addr.sun_path) + addrlen); + +#ifndef SOCK_CLOEXEC +#ifndef _WIN32 + fcntl(sock, F_SETFD, FD_CLOEXEC); +#endif +#endif + + if (socket_options) { + socket_options(sock); + } + +#ifdef _WIN32 + // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so + // remove the option. + set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0); +#endif + + bool dummy; + if (!bind_or_connect(sock, hints, dummy)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + + auto service = std::to_string(port); + + if (getaddrinfo_with_timeout(node, service.c_str(), &hints, &result, timeout_sec)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return INVALID_SOCKET; + } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket +#ifdef _WIN32 + auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + +#ifdef SOCK_CLOEXEC + auto sock = socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol); +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + +#endif + if (sock == INVALID_SOCKET) { + continue; + } + +#if !defined _WIN32 && !defined SOCK_CLOEXEC + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } +#endif + + if (tcp_nodelay) { + set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); + } + + if (rp->ai_family == AF_INET6) { + set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ipv6_v6only ? 1 : 0); + } + + if (socket_options) { + socket_options(sock); + } + + // bind or connect + auto quit = false; + if (bind_or_connect(sock, *rp, quit)) { + return sock; + } + + close_socket(sock); + + if (quit) { + break; + } + } + + return INVALID_SOCKET; +} + +inline void set_nonblocking(socket_t sock, bool nonblocking) +{ +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif +} + +inline bool is_connection_error() +{ +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif +} + +inline bool bind_ip_address(socket_t sock, const std::string &host) +{ + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo_with_timeout(host.c_str(), "0", &hints, &result, 0)) { + return false; + } + + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + ret = true; + break; + } + } + + return ret; +} + +#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__ +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP +inline std::string if2ip(int address_family, const std::string &ifn) +{ + struct ifaddrs *ifap; + getifaddrs(&ifap); + auto se = detail::scope_exit([&] { freeifaddrs(ifap); }); + + std::string addr_candidate; + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && (AF_UNSPEC == address_family || ifa->ifa_addr->sa_family == address_family)) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + return std::string(buf, INET_ADDRSTRLEN); + } + } + else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } + else { + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } + } + } + } + return addr_candidate; +} +#endif + +inline socket_t create_client_socket(const std::string &host, + const std::string &ip, + int port, + int address_family, + bool tcp_nodelay, + bool ipv6_v6only, + SocketOptions socket_options, + time_t connection_timeout_sec, + time_t connection_timeout_usec, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + const std::string &intf, + Error &error) +{ + auto sock = create_socket( + host, + ip, + port, + address_family, + 0, + tcp_nodelay, + ipv6_v6only, + std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool { + if (!intf.empty()) { +#ifdef USE_IF2IP + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { + ip_from_if = intf; + } + if (!bind_ip_address(sock2, ip_from_if)) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock2, true); + + auto ret = ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) { + if (is_connection_error()) { + error = Error::Connection; + return false; + } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, connection_timeout_usec); + if (error != Error::Success) { + if (error == Error::ConnectionTimeout) { + quit = true; + } + return false; + } + } + + set_nonblocking(sock2, false); + set_socket_opt_time(sock2, SOL_SOCKET, SO_RCVTIMEO, read_timeout_sec, read_timeout_usec); + set_socket_opt_time(sock2, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec, write_timeout_usec); + + error = Error::Success; + return true; + }, + connection_timeout_sec); // Pass DNS timeout + + if (sock != INVALID_SOCKET) { + error = Error::Success; + } + else { + if (error == Error::Success) { + error = Error::Connection; + } + } + + return sock; +} + +inline bool get_ip_and_port(const struct sockaddr_storage &addr, socklen_t addr_len, std::string &ip, int &port) +{ + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } + else if (addr.ss_family == AF_INET6) { + port = ntohs(reinterpret_cast(&addr)->sin6_port); + } + else { + return false; + } + + std::array ipstr{}; + if (getnameinfo(reinterpret_cast(&addr), + addr_len, + ipstr.data(), + static_cast(ipstr.size()), + nullptr, + 0, + NI_NUMERICHOST)) { + return false; + } + + ip = ipstr.data(); + return true; +} + +inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) +{ + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), &addr_len)) { + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) +{ + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), &addr_len)) { +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); + } +} + +// Recursive form retained so operator""_t below can compute hashes for +// switch-case labels at compile time (C++11 constexpr forbids loops). Do not +// call from runtime paths with arbitrary-length inputs — use str2tag() +// instead, which is iterative and stack-safe. +inline constexpr unsigned int str2tag_core(const char *s, size_t l, unsigned int h) +{ + return (l == 0) ? h + : str2tag_core(s + 1, + l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & h * 33) ^ static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) +{ + // Iterative form of str2tag_core: the recursive constexpr version is kept + // for compile-time UDL evaluation of short string literals, but at runtime + // we may receive arbitrarily long inputs (e.g. fuzzed Content-Type) that + // would blow the stack with one frame per character. + unsigned int h = 0; + for (auto c : s) { + h = (((std::numeric_limits::max)() >> 6) & h * 33) ^ static_cast(c); + } + return h; +} + +namespace udl { + +inline constexpr unsigned int operator""_t(const char *s, size_t l) +{ + return str2tag_core(s, l, 0); +} + +} // namespace udl + +inline std::string find_content_type(const std::string &path, + const std::map &user_data, + const std::string &default_content_type) +{ + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { + return it->second; + } + + using udl::operator""_t; + + switch (str2tag(ext)) { + default: + return default_content_type; + + case "css"_t: + return "text/css"; + case "csv"_t: + return "text/csv"; + case "htm"_t: + case "html"_t: + return "text/html"; + case "js"_t: + case "mjs"_t: + return "text/javascript"; + case "txt"_t: + return "text/plain"; + case "vtt"_t: + return "text/vtt"; + + case "apng"_t: + return "image/apng"; + case "avif"_t: + return "image/avif"; + case "bmp"_t: + return "image/bmp"; + case "gif"_t: + return "image/gif"; + case "png"_t: + return "image/png"; + case "svg"_t: + return "image/svg+xml"; + case "webp"_t: + return "image/webp"; + case "ico"_t: + return "image/x-icon"; + case "tif"_t: + return "image/tiff"; + case "tiff"_t: + return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: + return "image/jpeg"; + + case "mp4"_t: + return "video/mp4"; + case "mpeg"_t: + return "video/mpeg"; + case "webm"_t: + return "video/webm"; + + case "mp3"_t: + return "audio/mp3"; + case "mpga"_t: + return "audio/mpeg"; + case "weba"_t: + return "audio/webm"; + case "wav"_t: + return "audio/wave"; + + case "otf"_t: + return "font/otf"; + case "ttf"_t: + return "font/ttf"; + case "woff"_t: + return "font/woff"; + case "woff2"_t: + return "font/woff2"; + + case "7z"_t: + return "application/x-7z-compressed"; + case "atom"_t: + return "application/atom+xml"; + case "pdf"_t: + return "application/pdf"; + case "json"_t: + return "application/json"; + case "rss"_t: + return "application/rss+xml"; + case "tar"_t: + return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: + return "application/xhtml+xml"; + case "xslt"_t: + return "application/xslt+xml"; + case "xml"_t: + return "application/xml"; + case "gz"_t: + return "application/gzip"; + case "zip"_t: + return "application/zip"; + case "wasm"_t: + return "application/wasm"; + } +} + +inline std::string extract_media_type(const std::string &content_type, std::map *params = nullptr) +{ + // Extract type/subtype from Content-Type value (RFC 2045) + // e.g. "application/json; charset=utf-8" -> "application/json" + auto media_type = content_type; + auto semicolon_pos = media_type.find(';'); + if (semicolon_pos != std::string::npos) { + auto param_str = media_type.substr(semicolon_pos + 1); + media_type = media_type.substr(0, semicolon_pos); + + if (params) { + // Parse parameters: key=value pairs separated by ';' + split(param_str.data(), param_str.data() + param_str.size(), ';', [&](const char *b, const char *e) { + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } + else { + val.assign(b2, e2); + } + }); + if (!key.empty()) { + params->emplace(trim_copy(key), trim_double_quotes_copy(val)); + } + }); + } + } + + // Trim whitespace from media type + return trim_copy(media_type); +} + +inline bool can_compress_content_type(const std::string &content_type) +{ + using udl::operator""_t; + + auto mime_type = extract_media_type(content_type); + auto tag = str2tag(mime_type); + + switch (tag) { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/x-javascript"_t: + case "application/json"_t: + case "application/ld+json"_t: + case "application/xml"_t: + case "application/xhtml+xml"_t: + case "application/rss+xml"_t: + case "application/atom+xml"_t: + case "application/xslt+xml"_t: + case "application/protobuf"_t: + return true; + + case "text/event-stream"_t: + return false; + + default: + return !mime_type.rfind("text/", 0); + } +} + +inline bool parse_quality(const char *b, const char *e, std::string &token, double &quality) +{ + quality = 1.0; + token.clear(); + + // Split on first ';': left = token name, right = parameters + const char *params_b = nullptr; + std::size_t params_len = 0; + + divide(b, static_cast(e - b), ';', [&](const char *lb, std::size_t llen, const char *rb, std::size_t rlen) { + auto r = trim(lb, lb + llen, 0, llen); + if (r.first < r.second) { + token.assign(lb + r.first, lb + r.second); + } + params_b = rb; + params_len = rlen; + }); + + if (token.empty()) { + return false; + } + if (params_len == 0) { + return true; + } + + // Scan parameters for q= (stops on first match) + bool invalid = false; + split_find(params_b, params_b + params_len, ';', (std::numeric_limits::max)(), [&](const char *pb, const char *pe) -> bool { + // Match exactly "q=" or "Q=" (not "query=" etc.) + auto len = static_cast(pe - pb); + if (len < 2) { + return false; + } + if ((pb[0] != 'q' && pb[0] != 'Q') || pb[1] != '=') { + return false; + } + + // Trim the value portion + auto r = trim(pb, pe, 2, len); + if (r.first >= r.second) { + invalid = true; + return true; + } + + double v = 0.0; + auto res = from_chars(pb + r.first, pb + r.second, v); + if (res.ec != std::errc{} || v < 0.0 || v > 1.0) { + invalid = true; + return true; + } + quality = v; + return true; + }); + + return !invalid; +} + +inline EncodingType encoding_type(const Request &req, const Response &res) +{ + if (!can_compress_content_type(res.get_header_value("Content-Type"))) { + return EncodingType::None; + } + + const auto &s = req.get_header_value("Accept-Encoding"); + if (s.empty()) { + return EncodingType::None; + } + + // Single-pass: iterate tokens and track the best supported encoding. + // Server preference breaks ties (br > gzip > zstd). + EncodingType best = EncodingType::None; + double best_q = 0.0; // q=0 means "not acceptable" + + // Server preference: Brotli > Gzip > Zstd (lower = more preferred) + auto priority = [](EncodingType t) -> int { + switch (t) { + case EncodingType::Brotli: + return 0; + case EncodingType::Gzip: + return 1; + case EncodingType::Zstd: + return 2; + default: + return 3; + } + }; + + std::string name; + split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) { + double quality = 1.0; + if (!parse_quality(b, e, name, quality)) { + return; + } + if (quality <= 0.0) { + return; + } + + EncodingType type = EncodingType::None; +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + if (case_ignore::equal(name, "br")) { + type = EncodingType::Brotli; + } +#endif +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (type == EncodingType::None && case_ignore::equal(name, "gzip")) { + type = EncodingType::Gzip; + } +#endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + if (type == EncodingType::None && case_ignore::equal(name, "zstd")) { + type = EncodingType::Zstd; + } +#endif + + if (type == EncodingType::None) { + return; + } + + // Higher q-value wins; for equal q, server preference breaks ties + if (quality > best_q || (quality == best_q && priority(type) < priority(best))) { + best_q = quality; + best = type; + } + }); + + return best; +} + +inline std::unique_ptr make_compressor(EncodingType type) +{ +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (type == EncodingType::Gzip) { + return detail::make_unique(); + } +#endif +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + if (type == EncodingType::Brotli) { + return detail::make_unique(); + } +#endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + if (type == EncodingType::Zstd) { + return detail::make_unique(); + } +#endif + (void)type; + return nullptr; +} + +inline const char *encoding_name(EncodingType type) +{ + switch (type) { + case EncodingType::Gzip: + return "gzip"; + case EncodingType::Brotli: + return "br"; + case EncodingType::Zstd: + return "zstd"; + default: + return ""; + } +} + +inline bool nocompressor::compress(const char *data, size_t data_length, bool /*last*/, Callback callback) +{ + if (!data_length) { + return true; + } + return callback(data, data_length); +} + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +inline gzip_compressor::gzip_compressor() +{ + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) == Z_OK; +} + +inline gzip_compressor::~gzip_compressor() +{ + deflateEnd(&strm_); +} + +inline bool gzip_compressor::compress(const char *data, size_t data_length, bool last, Callback callback) +{ + assert(is_valid_); + + do { + constexpr size_t max_avail_in = (std::numeric_limits::max)(); + + strm_.avail_in = static_cast((std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; + auto ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { + return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((flush == Z_FINISH && ret == Z_STREAM_END) || (flush == Z_NO_FLUSH && ret == Z_OK)); + assert(strm_.avail_in == 0); + } while (data_length > 0); + + return true; +} + +inline gzip_decompressor::gzip_decompressor() +{ + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} + +inline gzip_decompressor::~gzip_decompressor() +{ + inflateEnd(&strm_); +} + +inline bool gzip_decompressor::is_valid() const +{ + return is_valid_; +} + +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, Callback callback) +{ + assert(is_valid_); + + auto ret = Z_OK; + + do { + constexpr size_t max_avail_in = (std::numeric_limits::max)(); + + strm_.avail_in = static_cast((std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + std::array buff{}; + while (strm_.avail_in > 0 && ret == Z_OK) { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = inflate(&strm_, Z_NO_FLUSH); + + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&strm_); + return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + if (ret != Z_OK && ret != Z_STREAM_END) { + return false; + } + + } while (data_length > 0); + + return true; +} +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +inline brotli_compressor::brotli_compressor() +{ + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} + +inline brotli_compressor::~brotli_compressor() +{ + BrotliEncoderDestroyInstance(state_); +} + +inline bool brotli_compressor::compress(const char *data, size_t data_length, bool last, Callback callback) +{ + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { + break; + } + } + else { + if (!available_in) { + break; + } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, &available_out, &next_out, nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; +} + +inline brotli_decompressor::brotli_decompressor() +{ + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() +{ + if (decoder_s) { + BrotliDecoderDestroyInstance(decoder_s); + } +} + +inline bool brotli_decompressor::is_valid() const +{ + return decoder_s; +} + +inline bool brotli_decompressor::decompress(const char *data, size_t data_length, Callback callback) +{ + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + auto next_in = reinterpret_cast(data); + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = + BrotliDecoderDecompressStream(decoder_s, &avail_in, &next_in, &avail_out, reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return false; + } + + if (!callback(buff.data(), buff.size() - avail_out)) { + return false; + } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} +#endif + +#ifdef CPPHTTPLIB_ZSTD_SUPPORT +inline zstd_compressor::zstd_compressor() +{ + ctx_ = ZSTD_createCCtx(); + ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast); +} + +inline zstd_compressor::~zstd_compressor() +{ + ZSTD_freeCCtx(ctx_); +} + +inline bool zstd_compressor::compress(const char *data, size_t data_length, bool last, Callback callback) +{ + std::array buff{}; + + ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue; + ZSTD_inBuffer input = {data, data_length, 0}; + + bool finished; + do { + ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0}; + size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode); + + if (ZSTD_isError(remaining)) { + return false; + } + + if (!callback(buff.data(), output.pos)) { + return false; + } + + finished = last ? (remaining == 0) : (input.pos == input.size); + + } while (!finished); + + return true; +} + +inline zstd_decompressor::zstd_decompressor() +{ + ctx_ = ZSTD_createDCtx(); +} + +inline zstd_decompressor::~zstd_decompressor() +{ + ZSTD_freeDCtx(ctx_); +} + +inline bool zstd_decompressor::is_valid() const +{ + return ctx_ != nullptr; +} + +inline bool zstd_decompressor::decompress(const char *data, size_t data_length, Callback callback) +{ + std::array buff{}; + ZSTD_inBuffer input = {data, data_length, 0}; + + while (input.pos < input.size) { + ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0}; + size_t const remaining = ZSTD_decompressStream(ctx_, &output, &input); + + if (ZSTD_isError(remaining)) { + return false; + } + + if (!callback(buff.data(), output.pos)) { + return false; + } + } + + return true; +} +#endif + +inline bool contains_case_ignore(const std::string &s, const char *token) +{ + auto token_end = token + std::strlen(token); + return std::search(s.begin(), s.end(), token, token_end, [](char a, char b) { + return case_ignore::to_lower(a) == case_ignore::to_lower(b); + }) != s.end(); +} + +// Content codings are case-insensitive (RFC 9110 8.4.1). Matching them +// case-sensitively would make a response labeled e.g. "GZIP" look like an +// unknown coding, and its payload would be handed back still compressed. +inline bool is_zlib_encoding(const std::string &encoding) +{ + return case_ignore::equal(encoding, "gzip") || case_ignore::equal(encoding, "deflate"); +} + +inline bool is_brotli_encoding(const std::string &encoding) +{ + return contains_case_ignore(encoding, "br"); +} + +inline bool is_zstd_encoding(const std::string &encoding) +{ + return contains_case_ignore(encoding, "zstd"); +} + +// Returns true if the content coding is one cpp-httplib is able to decompress +// when the corresponding support is compiled in. +inline bool is_known_content_encoding(const std::string &encoding) +{ + return is_zlib_encoding(encoding) || is_brotli_encoding(encoding) || is_zstd_encoding(encoding); +} + +inline std::unique_ptr create_decompressor(const std::string &encoding) +{ + std::unique_ptr decompressor; + + if (is_zlib_encoding(encoding)) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#endif + } + else if (is_brotli_encoding(encoding)) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#endif + } + else if (is_zstd_encoding(encoding)) { +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + decompressor = detail::make_unique(); +#endif + } + + return decompressor; +} + +// Returns the best available compressor and its Content-Encoding name. +// Priority: Brotli > Gzip > Zstd (matches server-side preference). +inline std::pair, const char *> create_compressor() +{ +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + return {detail::make_unique(), "br"}; +#elif defined(CPPHTTPLIB_ZLIB_SUPPORT) + return {detail::make_unique(), "gzip"}; +#elif defined(CPPHTTPLIB_ZSTD_SUPPORT) + return {detail::make_unique(), "zstd"}; +#else + return {nullptr, nullptr}; +#endif +} + +inline bool is_prohibited_header_name(const std::string &name) +{ + using udl::operator""_t; + + switch (str2tag(name)) { + case "REMOTE_ADDR"_t: + case "REMOTE_PORT"_t: + case "LOCAL_ADDR"_t: + case "LOCAL_PORT"_t: + return true; + default: + return false; + } +} + +inline bool has_header(const Headers &headers, const std::string &key) +{ + if (is_prohibited_header_name(key)) { + return false; + } + return headers.find(key) != headers.end(); +} + +inline const char *get_header_value(const Headers &headers, const std::string &key, const char *def, size_t id) +{ + if (is_prohibited_header_name(key)) { +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "Prohibited header name '" + key + "' is specified."; + throw std::invalid_argument(msg); +#else + return ""; +#endif + } + + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return it->second.c_str(); + } + return def; +} + +inline size_t get_header_value_count(const Headers &headers, const std::string &key) +{ + return headers.count(key); +} + +template +inline typename Map::mapped_type get_multimap_value(const Map &m, const std::string &key, size_t id) +{ + auto rng = m.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return it->second; + } + return typename Map::mapped_type(); +} + +inline void set_header(Headers &headers, const std::string &key, const std::string &val) +{ + if (fields::is_field_valid(key, val)) { + headers.emplace(key, val); + } +} + +inline bool read_headers(Stream &strm, Headers &headers) +{ + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + size_t header_count = 0; + + for (;;) { + if (!line_reader.getline()) { + return false; + } + + // Check if the line ends with CRLF. + auto line_terminator_len = 2; + if (line_reader.end_with_crlf()) { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { + break; + } + } + else { +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + // Blank line indicates end of headers. + if (line_reader.size() == 1) { + break; + } + line_terminator_len = 1; +#else + continue; // Skip invalid line. +#endif + } + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { + return false; + } + + // Check header count limit + if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { + return false; + } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + if (!parse_header(line_reader.ptr(), end, [&](const std::string &key, const std::string &val) { headers.emplace(key, val); })) { + return false; + } + + header_count++; + } + + // RFC 9110 Section 8.6: Reject requests with multiple Content-Length + // headers that have different values to prevent request smuggling. + auto cl_range = headers.equal_range("Content-Length"); + if (cl_range.first != cl_range.second) { + const auto &first_val = cl_range.first->second; + for (auto it = std::next(cl_range.first); it != cl_range.second; ++it) { + if (it->second != first_val) { + return false; + } + } + } + + return true; +} + +inline bool read_websocket_upgrade_response(Stream &strm, const std::string &expected_accept, std::string &selected_subprotocol) +{ + // Read status line + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + if (!line_reader.getline()) { + return false; + } + + // Check for "HTTP/1.1 101" + auto line = std::string(line_reader.ptr(), line_reader.size()); + if (line.find("HTTP/1.1 101") == std::string::npos) { + return false; + } + + // Parse headers using existing read_headers + Headers headers; + if (!read_headers(strm, headers)) { + return false; + } + + // Verify Upgrade: websocket (case-insensitive) + auto upgrade_it = headers.find("Upgrade"); + if (upgrade_it == headers.end()) { + return false; + } + auto upgrade_val = case_ignore::to_lower(upgrade_it->second); + if (upgrade_val != "websocket") { + return false; + } + + // Verify Connection header contains "Upgrade" (case-insensitive) + auto connection_it = headers.find("Connection"); + if (connection_it == headers.end()) { + return false; + } + auto connection_val = case_ignore::to_lower(connection_it->second); + if (connection_val.find("upgrade") == std::string::npos) { + return false; + } + + // Verify Sec-WebSocket-Accept header value + auto it = headers.find("Sec-WebSocket-Accept"); + if (it == headers.end() || it->second != expected_accept) { + return false; + } + + // Extract negotiated subprotocol + auto proto_it = headers.find("Sec-WebSocket-Protocol"); + if (proto_it != headers.end()) { + selected_subprotocol = proto_it->second; + } + + return true; +} + +enum class ReadContentResult { + Success, // Successfully read the content + PayloadTooLarge, // The content exceeds the specified payload limit + Error // An error occurred while reading the content +}; + +inline ReadContentResult read_content_with_length(Stream &strm, + size_t len, + DownloadProgress progress, + ContentReceiverWithProgress out, + size_t payload_max_length = (std::numeric_limits::max)()) +{ + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + detail::BodyReader br; + br.stream = &strm; + br.has_content_length = true; + br.content_length = len; + br.payload_max_length = payload_max_length; + br.chunked = false; + br.bytes_read = 0; + br.last_error = Error::Success; + + size_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto to_read = (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ); + auto n = detail::read_body_content(&strm, br, buf, to_read); + if (n <= 0) { + // Check if it was a payload size error + if (br.last_error == Error::ExceedMaxPayloadSize) { + return ReadContentResult::PayloadTooLarge; + } + return ReadContentResult::Error; + } + + if (!out(buf, static_cast(n), r, len)) { + return ReadContentResult::Error; + } + r += static_cast(n); + + if (progress) { + if (!progress(r, len)) { + return ReadContentResult::Error; + } + } + } + + return ReadContentResult::Success; +} + +inline ReadContentResult read_content_without_length(Stream &strm, size_t payload_max_length, ContentReceiverWithProgress out) +{ + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + size_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n == 0) { + return ReadContentResult::Success; + } + if (n < 0) { + return ReadContentResult::Error; + } + + // Check if adding this data would exceed the payload limit + if (r > payload_max_length || payload_max_length - r < static_cast(n)) { + return ReadContentResult::PayloadTooLarge; + } + + if (!out(buf, static_cast(n), r, 0)) { + return ReadContentResult::Error; + } + r += static_cast(n); + } + + return ReadContentResult::Success; +} + +template +inline ReadContentResult read_content_chunked(Stream &strm, T &x, size_t payload_max_length, ContentReceiverWithProgress out) +{ + detail::ChunkedDecoder dec(strm); + + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + size_t total_len = 0; + + for (;;) { + size_t chunk_offset = 0; + size_t chunk_total = 0; + auto n = dec.read_payload(buf, sizeof(buf), chunk_offset, chunk_total); + if (n < 0) { + return ReadContentResult::Error; + } + + if (n == 0) { + if (!dec.parse_trailers_into(x.trailers, x.headers)) { + return ReadContentResult::Error; + } + return ReadContentResult::Success; + } + + if (total_len > payload_max_length || payload_max_length - total_len < static_cast(n)) { + return ReadContentResult::PayloadTooLarge; + } + + if (!out(buf, static_cast(n), chunk_offset, chunk_total)) { + return ReadContentResult::Error; + } + + total_len += static_cast(n); + } +} + +inline bool is_chunked_transfer_encoding(const Headers &headers) +{ + // RFC 9112 6.1: a message is framed with the chunked coding when "chunked" + // is the final transfer coding. A single field value may list several + // codings ("gzip, chunked"), and RFC 9110 5.3 lets that list be split across + // several Transfer-Encoding lines, which combine into one comma-separated + // list in the order the lines were received. Headers preserves that order, + // so the final coding is the last token of the last line. Match it + // case-insensitively rather than comparing the whole value against + // "chunked". + // + // Security: reading a chunked message as unframed leaves its body in the + // socket, where a keep-alive connection parses it as a smuggled request. + // Server::process_request() answers 400 and closes when the final coding is + // not chunked, so a request whose framing cannot be determined never + // reaches the "no body" path. + auto rng = headers.equal_range("Transfer-Encoding"); + if (rng.first == rng.second) { + return false; + } + + // Cleared per line, so a trailing line carrying no coding at all leaves the + // combined list ending in nothing rather than inheriting the line before it. + std::string last_coding; + + for (auto it = rng.first; it != rng.second; ++it) { + const auto &value = it->second; + last_coding.clear(); + split(value.data(), value.data() + value.size(), ',', [&](const char *b, const char *e) { last_coding.assign(b, e); }); + } + + return case_ignore::equal(last_coding, "chunked"); +} + +template +bool prepare_content_receiver(T &x, + int &status, + ContentReceiverWithProgress receiver, + bool decompress, + size_t payload_max_length, + bool &exceed_payload_max_length, + U callback) +{ + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (!encoding.empty()) { + // A coding we know about but were not built with is an error. An + // unrecognized coding (including "identity") is left alone and the + // payload is passed through as-is, since some servers misuse the header, + // e.g. by sending a character set such as "Content-Encoding: UTF-8". + decompressor = detail::create_decompressor(encoding); + if (!decompressor && detail::is_known_content_encoding(encoding)) { + status = StatusCode::UnsupportedMediaType_415; + return false; + } + } + + if (decompressor) { + if (decompressor->is_valid()) { + size_t decompressed_size = 0; + ContentReceiverWithProgress out = [&](const char *buf, size_t n, size_t off, size_t len) { + return decompressor->decompress(buf, n, [&](const char *buf2, size_t n2) { + // Guard against zip-bomb: check + // decompressed size against limit. + if (payload_max_length > 0 && (decompressed_size >= payload_max_length || n2 > payload_max_length - decompressed_size)) { + exceed_payload_max_length = true; + return false; + } + decompressed_size += n2; + return receiver(buf2, n2, off, len); + }); + }; + return callback(std::move(out)); + } + else { + status = StatusCode::InternalServerError_500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char *buf, size_t n, size_t off, size_t len) { return receiver(buf, n, off, len); }; + return callback(std::move(out)); +} + +template +bool read_content(Stream &strm, + T &x, + size_t payload_max_length, + int &status, + DownloadProgress progress, + ContentReceiverWithProgress receiver, + bool decompress) +{ + bool exceed_payload_max_length = false; + return prepare_content_receiver( + x, status, std::move(receiver), decompress, payload_max_length, exceed_payload_max_length, [&](const ContentReceiverWithProgress &out) { + auto ret = true; + // Note: exceed_payload_max_length may also be set by the decompressor + // wrapper in prepare_content_receiver when the decompressed payload + // size exceeds the limit. + + if (is_chunked_transfer_encoding(x.headers)) { + auto result = read_content_chunked(strm, x, payload_max_length, out); + if (result == ReadContentResult::Success) { + ret = true; + } + else if (result == ReadContentResult::PayloadTooLarge) { + exceed_payload_max_length = true; + ret = false; + } + else { + ret = false; + } + } + else if (!has_header(x.headers, "Content-Length")) { + auto result = read_content_without_length(strm, payload_max_length, out); + if (result == ReadContentResult::Success) { + ret = true; + } + else if (result == ReadContentResult::PayloadTooLarge) { + exceed_payload_max_length = true; + ret = false; + } + else { + ret = false; + } + } + else { + auto is_invalid_value = false; + auto len = get_header_value_u64(x.headers, "Content-Length", (std::numeric_limits::max)(), 0, is_invalid_value); + + if (is_invalid_value) { + ret = false; + } + else if (len > 0) { + auto result = read_content_with_length(strm, len, std::move(progress), out, payload_max_length); + ret = (result == ReadContentResult::Success); + if (result == ReadContentResult::PayloadTooLarge) { + exceed_payload_max_length = true; + } + } + } + + if (!ret) { + status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413 : StatusCode::BadRequest_400; + } + return ret; + }); +} + +inline ssize_t write_request_line(Stream &strm, const std::string &method, const std::string &path) +{ + // A request target must not carry CR/LF (or other control octets); otherwise + // a value smuggled into it splits the request line and injects headers or a + // whole request. The same field-value check already guards header values in + // check_and_write_headers and the request target in + // perform_websocket_handshake; apply it here too. + if (!fields::is_field_value(path)) { + return -1; + } + + std::string s = method; + s += ' '; + s += path; + s += " HTTP/1.1\r\n"; + return strm.write(s.data(), s.size()); +} + +inline ssize_t write_response_line(Stream &strm, int status) +{ + std::string s = "HTTP/1.1 "; + s += std::to_string(status); + s += ' '; + s += httplib::status_message(status); + s += "\r\n"; + return strm.write(s.data(), s.size()); +} + +inline ssize_t write_headers(Stream &strm, const Headers &headers) +{ + ssize_t write_len = 0; + for (const auto &x : headers) { + // Skip fields with invalid names or values to prevent response splitting + // via CR/LF injection, matching set_header(). The client validates request + // headers up front in check_and_write_headers, but the server passes + // res.headers straight to this writer, and res.headers is a public field + // an application can populate directly with request-derived values. + if (!fields::is_field_valid(x.first, x.second)) { + continue; + } + + std::string s; + s = x.first; + s += ": "; + s += x.second; + s += "\r\n"; + + auto len = strm.write(s.data(), s.size()); + if (len < 0) { + return len; + } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { + return len; + } + write_len += len; + return write_len; +} + +inline bool write_data(Stream &strm, const char *d, size_t l) +{ + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { + return false; + } + offset += static_cast(length); + } + return true; +} + +template +inline bool write_content_with_progress(Stream &strm, + const ContentProvider &content_provider, + size_t offset, + size_t length, + T is_shutting_down, + const UploadProgress &upload_progress, + Error &error) +{ + size_t end_offset = offset + length; + size_t start_offset = offset; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + if (write_data(strm, d, l)) { + offset += l; + + if (upload_progress && length > 0) { + size_t current_written = offset - start_offset; + if (!upload_progress(current_written, length)) { + ok = false; + return false; + } + } + } + else { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); }; + + while (offset < end_offset && !is_shutting_down()) { + if (!strm.wait_writable() || !strm.is_peer_alive()) { + error = Error::Write; + return false; + } + else if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } + else if (!ok) { + error = Error::Write; + return false; + } + } + + if (offset < end_offset) { // exited due to is_shutting_down(), not completion + error = Error::Write; + return false; + } + + error = Error::Success; + return true; +} + +template +inline bool write_content( + Stream &strm, const ContentProvider &content_provider, size_t offset, size_t length, T is_shutting_down, Error &error) +{ + return write_content_with_progress(strm, content_provider, offset, length, is_shutting_down, nullptr, error); +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, size_t offset, size_t length, const T &is_shutting_down) +{ + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, error); +} + +template +inline bool write_content_without_length(Stream &strm, const ContentProvider &content_provider, const T &is_shutting_down) +{ + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + offset += l; + if (!write_data(strm, d, l)) { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); }; + + data_sink.done = [&](void) { data_available = false; }; + + while (data_available && !is_shutting_down()) { + if (!strm.wait_writable() || !strm.is_peer_alive()) { + return false; + } + else if (!content_provider(offset, 0, data_sink)) { + return false; + } + else if (!ok) { + return false; + } + } + return !data_available; // true only if done() was called, false if shutting + // down +} + +template +inline bool write_content_chunked( + Stream &strm, const ContentProvider &content_provider, const T &is_shutting_down, U &compressor, Error &error) +{ + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; + + std::string payload; + if (compressor.compress(d, l, false, [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!write_data(strm, chunk.data(), chunk.size())) { + ok = false; + } + } + } + else { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); }; + + auto done_with_trailer = [&](const Headers *trailer) { + if (!ok) { + return; + } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + + constexpr const char done_marker[] = "0\r\n"; + if (!write_data(strm, done_marker, str_len(done_marker))) { + ok = false; + } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + // Skip fields with invalid names or values to prevent response + // splitting via CR/LF injection, matching set_header(). + if (!fields::is_field_valid(kv.first, kv.second)) { + continue; + } + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + constexpr const char crlf[] = "\r\n"; + if (!write_data(strm, crlf, str_len(crlf))) { + ok = false; + } + }; + + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { done_with_trailer(&trailer); }; + + while (data_available && !is_shutting_down()) { + if (!strm.wait_writable() || !strm.is_peer_alive()) { + error = Error::Write; + return false; + } + else if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } + else if (!ok) { + error = Error::Write; + return false; + } + } + + if (data_available) { // exited due to is_shutting_down(), not done() + error = Error::Write; + return false; + } + + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, const ContentProvider &content_provider, const T &is_shutting_down, U &compressor) +{ + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, compressor, error); +} + +template +inline bool redirect(T &cli, Request &req, Response &res, const std::string &path, const std::string &location, Error &error) +{ + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == StatusCode::SeeOther_303 && (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = std::move(new_req); + res = std::move(new_res); + + if (res.location.empty()) { + res.location = location; + } + } + return ret; +} + +inline std::string params_to_query_str(const Params ¶ms) +{ + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { + query += '&'; + } + query += encode_query_component(it->first); + query += '='; + query += encode_query_component(it->second); + } + return query; +} + +// Splits one "key=value" span of a query string at its first '='. A span with +// no '=' at all lands entirely in key, leaving val empty, which is how a bare +// "?flag" keeps its name. +inline void divide_query_pair(const char *b, const char *e, std::string &key, std::string &val) +{ + divide( + b, static_cast(e - b), '=', [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data, std::size_t rhs_size) { + key.assign(lhs_data, lhs_size); + val.assign(rhs_data, rhs_size); + }); +} + +inline void parse_query_text(const char *data, std::size_t size, Params ¶ms) +{ + std::set cache; + split(data, data + size, '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { + return; + } + cache.insert(std::move(kv)); + + std::string key; + std::string val; + divide_query_pair(b, e, key, val); + + if (!key.empty()) { + params.emplace(decode_query_component(key), decode_query_component(val)); + } + }); +} + +inline void parse_query_text(const std::string &s, Params ¶ms) +{ + parse_query_text(s.data(), s.size(), params); +} + +// Normalize a query string by decoding and re-encoding each key/value pair +// while preserving the original parameter order. This avoids double-encoding +// and ensures consistent encoding. It works on the raw string rather than +// parsing into Params and re-serializing, because that round trip cannot +// reproduce the input: params_to_query_str() always emits '=', so a bare +// "flag" would come back as "flag=", and parse_query_text() drops exactly +// duplicated pairs. +inline std::string normalize_query_string(const std::string &query) +{ + std::string result; + split(query.data(), query.data() + query.size(), '&', [&](const char *b, const char *e) { + std::string key; + std::string val; + divide_query_pair(b, e, key, val); + + if (!key.empty()) { + auto dec_key = decode_query_component(key); + auto dec_val = decode_query_component(val); + + if (!result.empty()) { + result += '&'; + } + result += encode_query_component(dec_key); + if (!val.empty() || std::find(b, e, '=') != e) { + result += '='; + result += encode_query_component(dec_val); + } + } + }); + return result; +} + +// Build the request target that goes on the wire from a caller-supplied path. +// Shared by the buffered send path and the streaming API so that both put the +// same bytes in the request line for the same input. +inline std::string encode_request_target(const std::string &target, bool path_encode) +{ + // `substr(0, npos)` yields the whole string, which is what the no-query + // case needs. + auto query_pos = target.find('?'); + auto path_part = target.substr(0, query_pos); + std::string query_part; + if (query_pos != std::string::npos) { + query_part = target.substr(query_pos + 1); + } + + auto result = path_encode ? encode_path(path_part) : std::move(path_part); + + if (!query_part.empty()) { + // When path encoding is disabled the caller has supplied an already-encoded + // target and expects the exact bytes to be sent on the wire, so skip + // normalization for the query too. Normalizing would decode-then-re-encode + // it and corrupt pre-encoded binary payloads (e.g. turning `%20` into `+`, + // which a strict RFC 3986 server decodes back as `+`, not a space). + if (path_encode) { + auto normalized = normalize_query_string(query_part); + if (!normalized.empty()) { + result += '?'; + result += normalized; + } + } + else { + result += '?'; + result += query_part; + } + } + + return result; +} + +inline bool parse_multipart_boundary(const std::string &content_type, std::string &boundary) +{ + std::map params; + extract_media_type(content_type, ¶ms); + auto it = params.find("boundary"); + if (it == params.end()) { + return false; + } + boundary = it->second; + return !boundary.empty(); +} + +inline void parse_disposition_params(const std::string &s, Params ¶ms) +{ + std::set cache; + split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { + return; + } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } + else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(trim_double_quotes_copy((key)), trim_double_quotes_copy((val))); + } + }); +} + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +inline bool parse_range_header(const std::string &s, Ranges &ranges) +{ +#else +inline bool parse_range_header(const std::string &s, Ranges &ranges) +try { +#endif + auto is_valid = [](const std::string &str) { return std::all_of(str.cbegin(), str.cend(), is_ascii_digit); }; + + if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) { + const auto pos = static_cast(6); + const auto len = static_cast(s.size() - 6); + auto all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { + if (!all_valid_ranges) { + return; + } + + const auto it = std::find(b, e, '-'); + if (it == e) { + all_valid_ranges = false; + return; + } + + const auto lhs = std::string(b, it); + const auto rhs = std::string(it + 1, e); + if (!is_valid(lhs) || !is_valid(rhs)) { + all_valid_ranges = false; + return; + } + + ssize_t first = -1; + if (!lhs.empty()) { + ssize_t v; + auto res = detail::from_chars(lhs.data(), lhs.data() + lhs.size(), v); + if (res.ec == std::errc{}) { + first = v; + } + } + + ssize_t last = -1; + if (!rhs.empty()) { + ssize_t v; + auto res = detail::from_chars(rhs.data(), rhs.data() + rhs.size(), v); + if (res.ec == std::errc{}) { + last = v; + } + } + + if ((first == -1 && last == -1) || (first != -1 && last != -1 && first > last)) { + all_valid_ranges = false; + return; + } + + ranges.emplace_back(first, last); + }); + return all_valid_ranges && !ranges.empty(); + } + return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +} +#else +} +catch (...) { + return false; +} +#endif + +inline bool parse_accept_header(const std::string &s, std::vector &content_types) +{ + content_types.clear(); + + // Empty string is considered valid (no preference) + if (s.empty()) { + return true; + } + + // Check for invalid patterns: leading/trailing commas or consecutive commas + if (s.front() == ',' || s.back() == ',' || s.find(",,") != std::string::npos) { + return false; + } + + struct AcceptEntry { + std::string media_type; + double quality; + int order; + }; + + std::vector entries; + int order = 0; + bool has_invalid_entry = false; + + // Split by comma and parse each entry + split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) { + std::string entry(b, e); + entry = trim_copy(entry); + + if (entry.empty()) { + has_invalid_entry = true; + return; + } + + AcceptEntry accept_entry; + accept_entry.order = order++; + + if (!parse_quality(entry.data(), entry.data() + entry.size(), accept_entry.media_type, accept_entry.quality)) { + has_invalid_entry = true; + return; + } + + // Remove additional parameters from media type + accept_entry.media_type = extract_media_type(accept_entry.media_type); + + // Basic validation of media type format + if (accept_entry.media_type.empty()) { + has_invalid_entry = true; + return; + } + + // Check for basic media type format (should contain '/' or be '*') + if (accept_entry.media_type != "*" && accept_entry.media_type.find('/') == std::string::npos) { + has_invalid_entry = true; + return; + } + + entries.push_back(std::move(accept_entry)); + }); + + // Return false if any invalid entry was found + if (has_invalid_entry) { + return false; + } + + // Sort by quality (descending), then by original order (ascending) + std::sort(entries.begin(), entries.end(), [](const AcceptEntry &a, const AcceptEntry &b) { + if (a.quality != b.quality) { + return a.quality > b.quality; // Higher quality first + } + return a.order < b.order; // Earlier order first for same quality + }); + + // Extract sorted media types + content_types.reserve(entries.size()); + for (auto &entry : entries) { + content_types.push_back(std::move(entry.media_type)); + } + + return true; +} + +class FormDataParser { +public: + FormDataParser() = default; + + void set_boundary(std::string &&boundary) + { + boundary_ = std::move(boundary); + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } + + bool is_valid() const { return is_valid_; } + + bool parse(const char *buf, size_t n, const FormDataHeader &header_callback, const ContentReceiver &content_callback) + { + buf_append(buf, n); + + while (buf_size() > 0) { + switch (state_) { + case 0: { // Initial boundary + auto pos = buf_find(dash_boundary_crlf_); + if (pos == buf_size()) { + return true; + } + buf_erase(pos + dash_boundary_crlf_.size()); + state_ = 1; + break; + } + case 1: { // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: { // Headers + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { + return false; + } + while (pos < buf_size()) { + // Empty line + if (pos == 0) { + if (!header_callback(file_)) { + is_valid_ = false; + return false; + } + buf_erase(crlf_.size()); + state_ = 3; + break; + } + + // Check header count limit + if (header_count_ >= CPPHTTPLIB_HEADER_MAX_COUNT) { + is_valid_ = false; + return false; + } + header_count_++; + + const auto header = buf_head(pos); + + if (!parse_header(header.data(), header.data() + header.size(), [&](const std::string &, const std::string &) {})) { + is_valid_ = false; + return false; + } + + // Parse and emplace space trimmed headers into a map + if (!parse_header(header.data(), header.data() + header.size(), [&](const std::string &key, const std::string &val) { + file_.headers.emplace(key, val); + })) { + is_valid_ = false; + return false; + } + + constexpr const char header_content_type[] = "Content-Type:"; + + if (start_with_case_ignore(header, header_content_type)) { + file_.content_type = trim_copy(header.substr(str_len(header_content_type))); + } + else { + std::string disposition_params; + if (parse_content_disposition(header, disposition_params)) { + Params params; + parse_disposition_params(disposition_params, params); + + auto it = params.find("name"); + if (it != params.end()) { + file_.name = it->second; + } + else { + is_valid_ = false; + return false; + } + + it = params.find("filename"); + if (it != params.end()) { + file_.filename = it->second; + } + + it = params.find("filename*"); + if (it != params.end()) { + // RFC 5987: only UTF-8 encoding is allowed + const auto &val = it->second; + constexpr const char utf8_prefix[] = "UTF-8''"; + constexpr size_t prefix_len = str_len(utf8_prefix); + if (val.size() > prefix_len && start_with_case_ignore(val, utf8_prefix)) { + file_.filename = decode_path_component(val.substr(prefix_len)); // override... + } + else { + is_valid_ = false; + return false; + } + } + } + } + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); + } + if (state_ != 3) { + return true; + } + break; + } + case 3: { // Body + if (crlf_dash_boundary_.size() > buf_size()) { + return true; + } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) { + if (!content_callback(buf_data(), pos)) { + is_valid_ = false; + return false; + } + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } + else { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) { + if (!content_callback(buf_data(), len)) { + is_valid_ = false; + return false; + } + buf_erase(len); + } + return true; + } + break; + } + case 4: { // Boundary + if (crlf_.size() > buf_size()) { + return true; + } + if (buf_start_with(crlf_)) { + buf_erase(crlf_.size()); + state_ = 1; + } + else { + if (dash_.size() > buf_size()) { + return true; + } + if (buf_start_with(dash_)) { + buf_erase(dash_.size()); + is_valid_ = true; + buf_erase(buf_size()); // Remove epilogue + } + else { + return true; + } + } + break; + } + } + } + + return true; + } + +private: + void clear_file_info() + { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + file_.headers.clear(); + header_count_ = 0; + } + + bool start_with_case_ignore(const std::string &a, const char *b, size_t offset = 0) const + { + const auto b_len = strlen(b); + if (a.size() < offset + b_len) { + return false; + } + for (size_t i = 0; i < b_len; i++) { + if (case_ignore::to_lower(a[offset + i]) != case_ignore::to_lower(b[i])) { + return false; + } + } + return true; + } + + // Parses "Content-Disposition: form-data; " without std::regex. + // Returns true if header matches, with the params portion in `params_out`. + bool parse_content_disposition(const std::string &header, std::string ¶ms_out) const + { + constexpr const char prefix[] = "Content-Disposition:"; + constexpr size_t prefix_len = str_len(prefix); + + if (!start_with_case_ignore(header, prefix)) { + return false; + } + + // Skip whitespace after "Content-Disposition:" + auto pos = prefix_len; + while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) { + pos++; + } + + // Match "form-data;" (case-insensitive) + constexpr const char form_data[] = "form-data;"; + constexpr size_t form_data_len = str_len(form_data); + if (!start_with_case_ignore(header, form_data, pos)) { + return false; + } + pos += form_data_len; + + // Skip whitespace after "form-data;" + while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) { + pos++; + } + + params_out = header.substr(pos); + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; + + size_t state_ = 0; + bool is_valid_ = false; + FormData file_; + size_t header_count_ = 0; + + // Buffer + bool start_with(const std::string &a, size_t spos, size_t epos, const std::string &b) const + { + if (epos - spos < b.size()) { + return false; + } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + spos] != b[i]) { + return false; + } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char *buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string &s) const { return start_with(buf_, buf_spos_, buf_epos_, s); } + + size_t buf_find(const std::string &s) const + { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) { + auto pos = off; + while (true) { + if (pos == buf_epos_) { + return buf_size(); + } + if (buf_[pos] == c) { + break; + } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { + return buf_size(); + } + + if (start_with(buf_, pos, buf_epos_, s)) { + return pos - buf_spos_; + } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char *data, size_t n) + { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) { + for (size_t i = 0; i < remaining_size; i++) { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { + buf_.resize(remaining_size + n); + } + + for (size_t i = 0; i < n; i++) { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; +}; + +inline std::string random_string(size_t length) +{ + constexpr const char data[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + thread_local auto engine([]() { + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + std::random_device seed_gen; + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + return std::mt19937(seed_sequence); + }()); + + std::string result; + for (size_t i = 0; i < length; i++) { + result += data[engine() % (sizeof(data) - 1)]; + } + return result; +} + +inline std::string make_multipart_data_boundary() +{ + return "--cpp-httplib-multipart-data-" + detail::random_string(16); +} + +inline bool is_multipart_boundary_chars_valid(const std::string &boundary) +{ + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) { + auto c = boundary[i]; + if (!is_ascii_alnum(c) && c != '-' && c != '_') { + valid = false; + break; + } + } + return valid; +} + +// Escape a multipart field name/filename following the WHATWG HTML standard +// ("escape a multipart form-data name"), which is what browsers send: +// '"' -> %22, CR -> %0D, LF -> %0A +// With escape_quote = false, only CR and LF are escaped; this is for header +// values outside a quoted-string (e.g. Content-Type), where '"' is legal. +inline std::string escape_multipart_field(const std::string &s, bool escape_quote = true) +{ + std::string result; + result.reserve(s.size()); + for (auto c : s) { + switch (c) { + case '"': + if (escape_quote) { + result += "%22"; + } + else { + result += c; + } + break; + case '\r': + result += "%0D"; + break; + case '\n': + result += "%0A"; + break; + default: + result += c; + break; + } + } + return result; +} + +template +inline std::string serialize_multipart_formdata_item_begin(const T &item, const std::string &boundary) +{ + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + escape_multipart_field(item.name) + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + escape_multipart_field(item.filename) + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + escape_multipart_field(item.content_type, false) + "\r\n"; + } + body += "\r\n"; + + return body; +} + +inline std::string serialize_multipart_formdata_item_end() +{ + return "\r\n"; +} + +inline std::string serialize_multipart_formdata_finish(const std::string &boundary) +{ + return "--" + boundary + "--\r\n"; +} + +inline std::string serialize_multipart_formdata_get_content_type(const std::string &boundary) +{ + return "multipart/form-data; boundary=" + boundary; +} + +inline std::string serialize_multipart_formdata(const UploadFormDataItems &items, const std::string &boundary, bool finish = true) +{ + std::string body; + + for (const auto &item : items) { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) { + body += serialize_multipart_formdata_finish(boundary); + } + + return body; +} + +inline size_t get_multipart_content_length(const UploadFormDataItems &items, const std::string &boundary) +{ + size_t total = 0; + for (const auto &item : items) { + total += serialize_multipart_formdata_item_begin(item, boundary).size(); + total += item.content.size(); + total += serialize_multipart_formdata_item_end().size(); + } + total += serialize_multipart_formdata_finish(boundary).size(); + return total; +} + +struct MultipartSegment { + const char *data; + size_t size; +}; + +// NOTE: items must outlive the returned ContentProvider +// (safe for synchronous use inside Post/Put/Patch) +inline ContentProvider make_multipart_content_provider(const UploadFormDataItems &items, const std::string &boundary) +{ + // Own the per-item header strings and the finish string + std::vector owned; + owned.reserve(items.size() + 1); + for (const auto &item : items) + owned.push_back(serialize_multipart_formdata_item_begin(item, boundary)); + owned.push_back(serialize_multipart_formdata_finish(boundary)); + + // Flat segment list: [header, content, "\r\n"] * N + [finish] + std::vector segs; + segs.reserve(items.size() * 3 + 1); + static const char crlf[] = "\r\n"; + for (size_t i = 0; i < items.size(); i++) { + segs.push_back({owned[i].data(), owned[i].size()}); + segs.push_back({items[i].content.data(), items[i].content.size()}); + segs.push_back({crlf, 2}); + } + segs.push_back({owned.back().data(), owned.back().size()}); + + struct MultipartState { + std::vector owned; + std::vector segs; + std::vector buf = std::vector(CPPHTTPLIB_SEND_BUFSIZ); + }; + auto state = std::make_shared(); + state->owned = std::move(owned); + // `segs` holds raw pointers into owned strings; std::string move preserves + // the data pointer, so these pointers remain valid after the move above. + state->segs = std::move(segs); + + return [state](size_t offset, size_t length, DataSink &sink) -> bool { + // Buffer multiple small segments into fewer, larger writes to avoid + // excessive TCP packets when there are many form data items (#2410) + auto &buf = state->buf; + auto buf_size = buf.size(); + size_t buf_len = 0; + size_t remaining = length; + + // Find the first segment containing 'offset' + size_t pos = 0; + size_t seg_idx = 0; + for (; seg_idx < state->segs.size(); seg_idx++) { + const auto &seg = state->segs[seg_idx]; + if (seg.size > 0 && offset - pos < seg.size) { + break; + } + pos += seg.size; + } + + size_t seg_offset = (seg_idx < state->segs.size()) ? offset - pos : 0; + + for (; seg_idx < state->segs.size() && remaining > 0; seg_idx++) { + const auto &seg = state->segs[seg_idx]; + size_t available = seg.size - seg_offset; + size_t to_copy = (std::min)(available, remaining); + const char *src = seg.data + seg_offset; + seg_offset = 0; // only the first segment has a non-zero offset + + while (to_copy > 0) { + size_t space = buf_size - buf_len; + size_t chunk = (std::min)(to_copy, space); + std::memcpy(buf.data() + buf_len, src, chunk); + buf_len += chunk; + src += chunk; + to_copy -= chunk; + remaining -= chunk; + + if (buf_len == buf_size) { + if (!sink.write(buf.data(), buf_len)) { + return false; + } + buf_len = 0; + } + } + } + + if (buf_len > 0) { + return sink.write(buf.data(), buf_len); + } + return true; + }; +} + +inline void coalesce_ranges(Ranges &ranges, size_t content_length) +{ + if (ranges.size() <= 1) + return; + + // Sort ranges by start position + std::sort(ranges.begin(), ranges.end(), [](const Range &a, const Range &b) { return a.first < b.first; }); + + Ranges coalesced; + coalesced.reserve(ranges.size()); + + for (auto &r : ranges) { + auto first_pos = r.first; + auto last_pos = r.second; + + // Handle special cases like in range_error + if (first_pos == -1 && last_pos == -1) { + first_pos = 0; + last_pos = static_cast(content_length); + } + + if (first_pos == -1) { + first_pos = static_cast(content_length) - last_pos; + last_pos = static_cast(content_length) - 1; + } + + if (last_pos == -1 || last_pos >= static_cast(content_length)) { + last_pos = static_cast(content_length) - 1; + } + + // Skip invalid ranges + if (!(0 <= first_pos && first_pos <= last_pos && last_pos < static_cast(content_length))) { + continue; + } + + // Coalesce with previous range if overlapping or adjacent (but not + // identical) + if (!coalesced.empty()) { + auto &prev = coalesced.back(); + // Check if current range overlaps or is adjacent to previous range + // but don't coalesce identical ranges (allow duplicates) + if (first_pos <= prev.second + 1 && !(first_pos == prev.first && last_pos == prev.second)) { + // Extend the previous range + prev.second = (std::max)(prev.second, last_pos); + continue; + } + } + + // Add new range + coalesced.emplace_back(first_pos, last_pos); + } + + ranges = std::move(coalesced); +} + +inline bool range_error(Request &req, Response &res) +{ + if (!req.ranges.empty() && 200 <= res.status && res.status < 300) { + if (res.body.empty() && res.content_provider_ && res.content_length_ == 0) { + req.ranges.clear(); + if (res.status == StatusCode::PartialContent_206) { + res.status = StatusCode::OK_200; + } + return false; + } + + ssize_t content_len = static_cast(res.content_length_ ? res.content_length_ : res.body.size()); + + std::vector> processed_ranges; + size_t overwrapping_count = 0; + + // NOTE: The following Range check is based on '14.2. Range' in RFC 9110 + // 'HTTP Semantics' to avoid potential denial-of-service attacks. + // https://www.rfc-editor.org/rfc/rfc9110#section-14.2 + + // Too many ranges + if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { + return true; + } + + for (auto &r : req.ranges) { + auto &first_pos = r.first; + auto &last_pos = r.second; + + if (first_pos == -1 && last_pos == -1) { + first_pos = 0; + last_pos = content_len; + } + + if (first_pos == -1) { + first_pos = content_len - last_pos; + last_pos = content_len - 1; + } + + // NOTE: RFC-9110 '14.1.2. Byte Ranges': + // A client can limit the number of bytes requested without knowing the + // size of the selected representation. If the last-pos value is absent, + // or if the value is greater than or equal to the current length of the + // representation data, the byte range is interpreted as the remainder of + // the representation (i.e., the server replaces the value of last-pos + // with a value that is one less than the current length of the selected + // representation). + // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6 + if (last_pos == -1 || last_pos >= content_len) { + last_pos = content_len - 1; + } + + // Range must be within content length + if (!(0 <= first_pos && first_pos <= last_pos && last_pos <= content_len - 1)) { + return true; + } + + // Request must not have more than two overlapping ranges + for (const auto &processed_range : processed_ranges) { + if (!(last_pos < processed_range.first || first_pos > processed_range.second)) { + overwrapping_count++; + if (overwrapping_count > 2) { + return true; + } + break; // Only count once per range + } + } + + processed_ranges.emplace_back(first_pos, last_pos); + } + + // After validation, coalesce overlapping ranges as per RFC 9110 + coalesce_ranges(req.ranges, static_cast(content_len)); + } + + return false; +} + +inline std::pair get_range_offset_and_length(Range r, size_t content_length) +{ + assert(r.first != -1 && r.second != -1); + assert(0 <= r.first && r.first < static_cast(content_length)); + assert(r.first <= r.second && r.second < static_cast(content_length)); + (void)(content_length); + return std::make_pair(static_cast(r.first), static_cast(r.second - r.first) + 1); +} + +inline std::string make_content_range_header_field(const std::pair &offset_and_length, size_t content_length) +{ + auto st = offset_and_length.first; + auto ed = st + offset_and_length.second - 1; + + std::string field = "bytes "; + field += std::to_string(st); + field += '-'; + field += std::to_string(ed); + field += '/'; + field += std::to_string(content_length); + return field; +} + +template +bool process_multipart_ranges_data(const Request &req, + const std::string &boundary, + const std::string &content_type, + size_t content_length, + SToken stoken, + CToken ctoken, + Content content) +{ + for (size_t i = 0; i < req.ranges.size(); i++) { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + auto offset_and_length = get_range_offset_and_length(req.ranges[i], content_length); + + ctoken("Content-Range: "); + stoken(make_content_range_header_field(offset_and_length, content_length)); + ctoken("\r\n"); + ctoken("\r\n"); + + if (!content(offset_and_length.first, offset_and_length.second)) { + return false; + } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--"); + + return true; +} + +inline void make_multipart_ranges_data( + const Request &req, Response &res, const std::string &boundary, const std::string &content_type, size_t content_length, std::string &data) +{ + process_multipart_ranges_data( + req, + boundary, + content_type, + content_length, + [&](const std::string &token) { data += token; }, + [&](const std::string &token) { data += token; }, + [&](size_t offset, size_t length) { + assert(offset + length <= content_length); + data += res.body.substr(offset, length); + return true; + }); +} + +inline size_t get_multipart_ranges_data_length(const Request &req, + const std::string &boundary, + const std::string &content_type, + size_t content_length) +{ + size_t data_length = 0; + + process_multipart_ranges_data( + req, + boundary, + content_type, + content_length, + [&](const std::string &token) { data_length += token.size(); }, + [&](const std::string &token) { data_length += token.size(); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); + + return data_length; +} + +template +inline bool write_multipart_ranges_data(Stream &strm, + const Request &req, + Response &res, + const std::string &boundary, + const std::string &content_type, + size_t content_length, + const T &is_shutting_down) +{ + return process_multipart_ranges_data( + req, + boundary, + content_type, + content_length, + [&](const std::string &token) { strm.write(token); }, + [&](const std::string &token) { strm.write(token); }, + [&](size_t offset, size_t length) { return write_content(strm, res.content_provider_, offset, length, is_shutting_down); }); +} + +inline bool has_framed_body(const Request &req) +{ + return is_chunked_transfer_encoding(req.headers) || req.get_header_value_u64("Content-Length") > 0; +} + +inline bool is_connection_persistent(const Request &req) +{ + auto conn = req.get_header_value("Connection"); + if (conn == "close") { + return false; + } + if (req.version == "HTTP/1.0" && conn != "Keep-Alive") { + return false; + } + return true; +} + +inline bool expect_content(const Request &req) +{ + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || req.method == "DELETE") { + return true; + } + return has_framed_body(req); +} + +#ifdef _WIN32 +class WSInit { +public: + WSInit() + { + WSADATA wsaData; + if (WSAStartup(0x0002, &wsaData) == 0) + is_valid_ = true; + } + + ~WSInit() + { + if (is_valid_) + WSACleanup(); + } + + bool is_valid_ = false; +}; + +static WSInit wsinit_; +#endif + +inline bool parse_www_authenticate(const Response &res, std::map &auth, bool is_proxy) +{ + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) { + thread_local auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) { + auto type = s.substr(0, pos); + if (type == "Basic") { + return false; + } + else if (type == "Digest") { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) { + const auto &m = *i; + auto key = s.substr(static_cast(m.position(1)), static_cast(m.length(1))); + auto val = m.length(2) > 0 ? s.substr(static_cast(m.position(2)), static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), static_cast(m.length(3))); + auth[std::move(key)] = std::move(val); + } + return true; + } + } + } + return false; +} + +class ContentProviderAdapter { +public: + explicit ContentProviderAdapter(ContentProviderWithoutLength &&content_provider) + : content_provider_(std::move(content_provider)) + { + } + + bool operator()(size_t offset, size_t, DataSink &sink) { return content_provider_(offset, sink); } + +private: + ContentProviderWithoutLength content_provider_; +}; + +// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5 +namespace fields { + +inline bool is_token_char(char c) +{ + return is_ascii_alnum(c) || c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || + c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~'; +} + +inline bool is_token(const std::string &s) +{ + if (s.empty()) { + return false; + } + for (auto c : s) { + if (!is_token_char(c)) { + return false; + } + } + return true; +} + +inline bool is_field_name(const std::string &s) +{ + return is_token(s); +} + +inline bool is_vchar(char c) +{ + return c >= 33 && c <= 126; +} + +inline bool is_obs_text(char c) +{ + return 128 <= static_cast(c); +} + +inline bool is_field_vchar(char c) +{ + return is_vchar(c) || is_obs_text(c); +} + +inline bool is_field_content(const std::string &s) +{ + if (s.empty()) { + return true; + } + + if (s.size() == 1) { + return is_field_vchar(s[0]); + } + else if (s.size() == 2) { + return is_field_vchar(s[0]) && is_field_vchar(s[1]); + } + else { + size_t i = 0; + + if (!is_field_vchar(s[i])) { + return false; + } + i++; + + while (i < s.size() - 1) { + auto c = s[i++]; + if (c == ' ' || c == '\t' || is_field_vchar(c)) { + } + else { + return false; + } + } + + return is_field_vchar(s[i]); + } +} + +inline bool is_field_value(const std::string &s) +{ + return is_field_content(s); +} + +inline bool is_field_valid(const std::string &name, const std::string &value) +{ + return is_field_name(name) && is_field_value(value); +} + +} // namespace fields + +inline bool perform_websocket_handshake(Stream &strm, Request &req, std::string &selected_subprotocol) +{ + // Generate random Sec-WebSocket-Key + thread_local std::mt19937 rng(std::random_device{}()); + std::string key_bytes(16, '\0'); + for (size_t i = 0; i < 16; i += 4) { + auto r = rng(); + std::memcpy(&key_bytes[i], &r, (std::min)(size_t(4), size_t(16 - i))); + } + auto client_key = base64_encode(key_bytes); + + req.headers.erase("Upgrade"); + req.headers.erase("Connection"); + req.headers.erase("Sec-WebSocket-Key"); + req.headers.erase("Sec-WebSocket-Version"); + req.headers.emplace("Upgrade", "websocket"); + req.headers.emplace("Connection", "Upgrade"); + req.headers.emplace("Sec-WebSocket-Key", client_key); + req.headers.emplace("Sec-WebSocket-Version", "13"); + + // Build the request in memory first, like ClientImpl::write_request does. + // Writing straight to the socket would leak a request line onto the wire + // before check_and_write_headers gets a chance to reject an invalid header, + // and would emit one small write per header. + BufferStream bstrm; + + if (write_request_line(bstrm, req.method, req.path) < 0) { + return false; + } + + auto error = Error::Success; + if (!check_and_write_headers(bstrm, req.headers, write_headers, error)) { + return false; + } + + const auto &data = bstrm.get_buffer(); + if (!write_data(strm, data.data(), data.size())) { + return false; + } + + // Verify 101 response and Sec-WebSocket-Accept header + auto expected_accept = websocket_accept_key(client_key); + return read_websocket_upgrade_response(strm, expected_accept, selected_subprotocol); +} + +inline bool is_ip_address(const std::string &host) +{ + struct in_addr addr4; + struct in6_addr addr6; + return inet_pton(AF_INET, host.c_str(), &addr4) == 1 || inet_pton(AF_INET6, host.c_str(), &addr6) == 1; +} + +// Resolve where a client should connect for `host`, honoring a user-supplied +// hostname-to-address map. `host` itself is never rewritten, so it keeps +// supplying the Host header and SNI; only the connection target changes. +// +// A mapped IP literal goes to `ip`, which keeps create_socket's AI_NUMERICHOST +// path. Anything else goes to `connect_host`, which create_socket resolves as +// a name, or uses as the socket path when the address family is AF_UNIX. An +// absent or empty mapping leaves `host` as the connection target; without the +// empty check the value would reach getaddrinfo as a null node and silently +// resolve to loopback. +inline void apply_addr_map(const std::map &addr_map, + const std::string &host, + std::string &connect_host, + std::string &ip) +{ + connect_host = host; + ip.clear(); + + auto it = addr_map.find(host); + if (it == addr_map.end() || it->second.empty()) { + return; + } + + if (is_ip_address(it->second)) { + ip = it->second; + } + else { + connect_host = it->second; + } +} + +} // namespace detail + +/* + * Group 2: detail namespace - SSL common utilities + */ + +#ifdef CPPHTTPLIB_SSL_ENABLED +namespace detail { + +class SSLSocketStream final : public Stream { +public: + SSLSocketStream(socket_t sock, + tls::session_t session, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + time_t max_timeout_msec = 0, + std::chrono::time_point start_time = (std::chrono::steady_clock::time_point::min)()); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool wait_readable() const override; + bool wait_writable() const override; + bool is_peer_alive() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + time_t duration() const override; + void set_read_timeout(time_t sec, time_t usec = 0) override; + + // See SocketStream::set_readable_hint(). + void set_readable_hint() { readable_hint_ = true; } + +private: + bool ensure_readable(); + + socket_t sock_; + tls::session_t session_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + time_t max_timeout_msec_; + const std::chrono::time_point start_time_; + bool readable_hint_ = false; +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::string message_digest(const std::string &s, const EVP_MD *algo) +{ + auto context = std::unique_ptr(EVP_MD_CTX_new(), EVP_MD_CTX_free); + + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; + + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(hash[i]); + } + + return ss.str(); +} + +inline std::string MD5(const std::string &s) +{ + return message_digest(s, EVP_md5()); +} + +inline std::string SHA_256(const std::string &s) +{ + return message_digest(s, EVP_sha256()); +} + +inline std::string SHA_512(const std::string &s) +{ + return message_digest(s, EVP_sha512()); +} +#elif defined(CPPHTTPLIB_MBEDTLS_SUPPORT) +namespace { +template +inline std::string hash_to_hex(const unsigned char (&hash)[N]) +{ + std::stringstream ss; + for (size_t i = 0; i < N; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(hash[i]); + } + return ss.str(); +} +} // namespace + +#ifdef CPPHTTPLIB_MBEDTLS_V4 +// Mbed TLS 4.x provides hashing (and TLS RNG) via PSA Crypto, which must be +// initialized once. PSA state is process-global; do not free it. +inline bool ensure_mbedtls_psa_crypto() +{ + static std::once_flag once; + static bool ok = false; + std::call_once(once, []() { ok = (psa_crypto_init() == PSA_SUCCESS); }); + return ok; +} + +inline bool psa_hash(psa_algorithm_t alg, const std::string &s, unsigned char *out, size_t out_size) +{ + if (!ensure_mbedtls_psa_crypto()) { + return false; + } + size_t olen = 0; + return psa_hash_compute(alg, reinterpret_cast(s.data()), s.size(), out, out_size, &olen) == PSA_SUCCESS && + olen == out_size; +} +#endif + +inline std::string MD5(const std::string &s) +{ + unsigned char hash[16]; +#ifdef CPPHTTPLIB_MBEDTLS_V4 + if (!psa_hash(PSA_ALG_MD5, s, hash, sizeof(hash))) { + return {}; + } +#elif defined(CPPHTTPLIB_MBEDTLS_V3) + mbedtls_md5(reinterpret_cast(s.c_str()), s.size(), hash); +#else + mbedtls_md5_ret(reinterpret_cast(s.c_str()), s.size(), hash); +#endif + return hash_to_hex(hash); +} + +inline std::string SHA_256(const std::string &s) +{ + unsigned char hash[32]; +#ifdef CPPHTTPLIB_MBEDTLS_V4 + if (!psa_hash(PSA_ALG_SHA_256, s, hash, sizeof(hash))) { + return {}; + } +#elif defined(CPPHTTPLIB_MBEDTLS_V3) + mbedtls_sha256(reinterpret_cast(s.c_str()), s.size(), hash, 0); +#else + mbedtls_sha256_ret(reinterpret_cast(s.c_str()), s.size(), hash, 0); +#endif + return hash_to_hex(hash); +} + +inline std::string SHA_512(const std::string &s) +{ + unsigned char hash[64]; +#ifdef CPPHTTPLIB_MBEDTLS_V4 + if (!psa_hash(PSA_ALG_SHA_512, s, hash, sizeof(hash))) { + return {}; + } +#elif defined(CPPHTTPLIB_MBEDTLS_V3) + mbedtls_sha512(reinterpret_cast(s.c_str()), s.size(), hash, 0); +#else + mbedtls_sha512_ret(reinterpret_cast(s.c_str()), s.size(), hash, 0); +#endif + return hash_to_hex(hash); +} +#elif defined(CPPHTTPLIB_WOLFSSL_SUPPORT) +namespace { +template +inline std::string hash_to_hex(const unsigned char (&hash)[N]) +{ + std::stringstream ss; + for (size_t i = 0; i < N; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(hash[i]); + } + return ss.str(); +} +} // namespace + +inline std::string MD5(const std::string &s) +{ + unsigned char hash[WC_MD5_DIGEST_SIZE]; + wc_Md5Hash(reinterpret_cast(s.c_str()), static_cast(s.size()), hash); + return hash_to_hex(hash); +} + +inline std::string SHA_256(const std::string &s) +{ + unsigned char hash[WC_SHA256_DIGEST_SIZE]; + wc_Sha256Hash(reinterpret_cast(s.c_str()), static_cast(s.size()), hash); + return hash_to_hex(hash); +} + +inline std::string SHA_512(const std::string &s) +{ + unsigned char hash[WC_SHA512_DIGEST_SIZE]; + wc_Sha512Hash(reinterpret_cast(s.c_str()), static_cast(s.size()), hash); + return hash_to_hex(hash); +} +#endif + +template +inline bool process_server_socket_ssl(const std::atomic &svr_sock, + tls::session_t session, + socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + T callback) +{ + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); + // See the non-TLS path in process_server_socket(). + strm.set_readable_hint(); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool process_client_socket_ssl(tls::session_t session, + socket_t sock, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, + T callback) +{ + SSLSocketStream strm( + sock, session, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec, max_timeout_msec, start_time); + return callback(strm); +} + +inline std::pair make_digest_authentication_header(const Request &req, + const std::map &auth, + size_t cnonce_count, + const std::string &cnonce, + const std::string &username, + const std::string &password, + bool is_proxy = false) +{ + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } + else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } + else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { + algo = auth.at("algorithm"); + } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 : algo == "SHA-512" ? detail::SHA_512 : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { + A2 += ":" + H(req.body); + } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } + else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + cnonce + "\", response=\"") + response + + "\"" + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} + +inline bool match_hostname(const std::string &pattern, const std::string &hostname) +{ + // Exact match (case-insensitive) + if (detail::case_ignore::equal(hostname, pattern)) { + return true; + } + + // Split both pattern and hostname into components by '.' + std::vector pattern_components; + if (!pattern.empty()) { + split( + pattern.data(), pattern.data() + pattern.size(), '.', [&](const char *b, const char *e) { pattern_components.emplace_back(b, e); }); + } + + std::vector host_components; + if (!hostname.empty()) { + split( + hostname.data(), hostname.data() + hostname.size(), '.', [&](const char *b, const char *e) { host_components.emplace_back(b, e); }); + } + + // Component count must match + if (host_components.size() != pattern_components.size()) { + return false; + } + + // Compare each component with wildcard support + // Supports: "*" (full wildcard), "prefix*" (partial wildcard) + // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 + auto itr = pattern_components.begin(); + for (const auto &h : host_components) { + auto &p = *itr; + if (!detail::case_ignore::equal(p, h) && p != "*") { + bool partial_match = false; + if (!p.empty() && p[p.size() - 1] == '*') { + const auto prefix_length = p.size() - 1; + if (prefix_length == 0) { + partial_match = true; + } + else if (h.size() >= prefix_length) { + partial_match = std::equal( + p.begin(), p.begin() + static_cast(prefix_length), h.begin(), [](const char ca, const char cb) { + return detail::case_ignore::to_lower(ca) == detail::case_ignore::to_lower(cb); + }); + } + } + if (!partial_match) { + return false; + } + } + ++itr; + } + + return true; +} + +#ifdef _WIN32 +// Verify certificate using Windows CertGetCertificateChain API. +// This provides real-time certificate validation with Windows Update +// integration, independent of the TLS backend (OpenSSL or MbedTLS). +inline bool verify_cert_with_windows_schannel(const std::vector &der_cert, + const std::string &hostname, + bool verify_hostname, + uint64_t &out_error) +{ + if (der_cert.empty()) { + return false; + } + + out_error = 0; + + // Create Windows certificate context from DER data + auto cert_context = + CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der_cert.data(), static_cast(der_cert.size())); + + if (!cert_context) { + out_error = GetLastError(); + return false; + } + + auto cert_guard = scope_exit([&] { CertFreeCertificateContext(cert_context); }); + + // Setup chain parameters + CERT_CHAIN_PARA chain_para = {}; + chain_para.cbSize = sizeof(chain_para); + + // Build certificate chain with revocation checking + PCCERT_CHAIN_CONTEXT chain_context = nullptr; + auto chain_result = + CertGetCertificateChain(nullptr, + cert_context, + nullptr, + cert_context->hCertStore, + &chain_para, + CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_END_CERT | CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT, + nullptr, + &chain_context); + + if (!chain_result || !chain_context) { + out_error = GetLastError(); + return false; + } + + auto chain_guard = scope_exit([&] { CertFreeCertificateChain(chain_context); }); + + // Check if chain has errors + if (chain_context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) { + out_error = chain_context->TrustStatus.dwErrorStatus; + return false; + } + + // Verify SSL policy + SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para = {}; + extra_policy_para.cbSize = sizeof(extra_policy_para); +#ifdef AUTHTYPE_SERVER + extra_policy_para.dwAuthType = AUTHTYPE_SERVER; +#endif + + std::wstring whost; + if (verify_hostname) { + whost = u8string_to_wstring(hostname.c_str()); + extra_policy_para.pwszServerName = const_cast(whost.c_str()); + } + + CERT_CHAIN_POLICY_PARA policy_para = {}; + policy_para.cbSize = sizeof(policy_para); +#ifdef CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS + policy_para.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS; +#else + policy_para.dwFlags = 0; +#endif + policy_para.pvExtraPolicyPara = &extra_policy_para; + + CERT_CHAIN_POLICY_STATUS policy_status = {}; + policy_status.cbSize = sizeof(policy_status); + + if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chain_context, &policy_para, &policy_status)) { + out_error = GetLastError(); + return false; + } + + if (policy_status.dwError != 0) { + out_error = policy_status.dwError; + return false; + } + + return true; +} +#endif // _WIN32 + +// Loads CA file/dir configuration and applies the system CA policy to a +// client TLS context. PEM data and native stores are applied to the context +// directly at set time; has_custom_store reflects them for the Auto policy +// decision. +inline bool load_client_ca_config(tls::ctx_t ctx, + const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path, + bool has_custom_store, + SystemCAMode mode, + uint64_t &backend_error) +{ + auto ret = true; + + if (!ca_cert_file_path.empty()) { + if (!tls::load_ca_file(ctx, ca_cert_file_path.c_str())) { + backend_error = tls::get_error(); + ret = false; + } + } + else if (!ca_cert_dir_path.empty()) { + if (!tls::load_ca_dir(ctx, ca_cert_dir_path.c_str())) { + backend_error = tls::get_error(); + ret = false; + } + } + + auto has_custom_ca = !ca_cert_file_path.empty() || !ca_cert_dir_path.empty() || has_custom_store; + if (mode == SystemCAMode::Enabled || (mode == SystemCAMode::Auto && !has_custom_ca)) { + if (!tls::load_system_certs(ctx)) { + backend_error = tls::get_error(); + } + } + + return ret; +} + +inline bool setup_client_tls_session(const std::string &host, + tls::ctx_t ctx, + tls::session_t &session, + socket_t sock, + bool server_certificate_verification, + time_t timeout_sec, + time_t timeout_usec) +{ + using namespace tls; + + if (!ctx) { + return false; + } + + bool is_ip = is_ip_address(host); + +#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT) + // Chain verification happens during the handshake even for IP hosts; the + // certificate identity is verified post-handshake via verify_hostname() + set_verify_client(ctx, server_certificate_verification); +#endif + + session = create_session(ctx, sock); + if (!session) { + return false; + } + + // RFC 6066: SNI must not be set for IP addresses. On Mbed TLS and wolfSSL + // set_hostname also sets SNI, so it must be skipped for IP hosts as well; + // their identity is checked post-handshake below instead. + if (!is_ip) { + if (server_certificate_verification) { + set_hostname(session, host.c_str()); + } + else { + set_sni(session, host.c_str()); + } + } + + if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec, nullptr)) { + return false; + } + + if (server_certificate_verification) { + if (get_verify_result(session) != 0) { + return false; + } + + // Identity check against the peer certificate, post-handshake for all + // backends (same as SSLClient). For IP hosts this is the only identity + // verification since no hostname is bound during the handshake. + auto server_cert = get_peer_cert(session); + if (!server_cert) { + return false; + } + auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); }); + if (!verify_hostname(server_cert, host.c_str())) { + return false; + } + } + + return true; +} + +} // namespace detail +#endif // CPPHTTPLIB_SSL_ENABLED + +/* + * Group 3: httplib namespace - Non-SSL public API implementations + */ + +inline void default_socket_options(socket_t sock) +{ + set_socket_opt(sock, + SOL_SOCKET, +#ifdef SO_REUSEPORT + SO_REUSEPORT, +#else + SO_REUSEADDR, +#endif + 1); +} + +inline bool set_socket_opt(socket_t sock, int level, int optname, int optval) +{ + return detail::set_socket_opt_impl(sock, level, optname, &optval, sizeof(optval)); +} + +inline std::string get_bearer_token_auth(const Request &req) +{ + if (req.has_header("Authorization")) { + constexpr auto bearer_header_prefix_len = detail::str_len("Bearer "); + return req.get_header_value("Authorization").substr(bearer_header_prefix_len); + } + return ""; +} + +inline const char *status_message(int status) +{ + switch (status) { + case StatusCode::Continue_100: + return "Continue"; + case StatusCode::SwitchingProtocol_101: + return "Switching Protocol"; + case StatusCode::Processing_102: + return "Processing"; + case StatusCode::EarlyHints_103: + return "Early Hints"; + case StatusCode::OK_200: + return "OK"; + case StatusCode::Created_201: + return "Created"; + case StatusCode::Accepted_202: + return "Accepted"; + case StatusCode::NonAuthoritativeInformation_203: + return "Non-Authoritative Information"; + case StatusCode::NoContent_204: + return "No Content"; + case StatusCode::ResetContent_205: + return "Reset Content"; + case StatusCode::PartialContent_206: + return "Partial Content"; + case StatusCode::MultiStatus_207: + return "Multi-Status"; + case StatusCode::AlreadyReported_208: + return "Already Reported"; + case StatusCode::IMUsed_226: + return "IM Used"; + case StatusCode::MultipleChoices_300: + return "Multiple Choices"; + case StatusCode::MovedPermanently_301: + return "Moved Permanently"; + case StatusCode::Found_302: + return "Found"; + case StatusCode::SeeOther_303: + return "See Other"; + case StatusCode::NotModified_304: + return "Not Modified"; + case StatusCode::UseProxy_305: + return "Use Proxy"; + case StatusCode::unused_306: + return "unused"; + case StatusCode::TemporaryRedirect_307: + return "Temporary Redirect"; + case StatusCode::PermanentRedirect_308: + return "Permanent Redirect"; + case StatusCode::BadRequest_400: + return "Bad Request"; + case StatusCode::Unauthorized_401: + return "Unauthorized"; + case StatusCode::PaymentRequired_402: + return "Payment Required"; + case StatusCode::Forbidden_403: + return "Forbidden"; + case StatusCode::NotFound_404: + return "Not Found"; + case StatusCode::MethodNotAllowed_405: + return "Method Not Allowed"; + case StatusCode::NotAcceptable_406: + return "Not Acceptable"; + case StatusCode::ProxyAuthenticationRequired_407: + return "Proxy Authentication Required"; + case StatusCode::RequestTimeout_408: + return "Request Timeout"; + case StatusCode::Conflict_409: + return "Conflict"; + case StatusCode::Gone_410: + return "Gone"; + case StatusCode::LengthRequired_411: + return "Length Required"; + case StatusCode::PreconditionFailed_412: + return "Precondition Failed"; + case StatusCode::PayloadTooLarge_413: + return "Payload Too Large"; + case StatusCode::UriTooLong_414: + return "URI Too Long"; + case StatusCode::UnsupportedMediaType_415: + return "Unsupported Media Type"; + case StatusCode::RangeNotSatisfiable_416: + return "Range Not Satisfiable"; + case StatusCode::ExpectationFailed_417: + return "Expectation Failed"; + case StatusCode::ImATeapot_418: + return "I'm a teapot"; + case StatusCode::MisdirectedRequest_421: + return "Misdirected Request"; + case StatusCode::UnprocessableContent_422: + return "Unprocessable Content"; + case StatusCode::Locked_423: + return "Locked"; + case StatusCode::FailedDependency_424: + return "Failed Dependency"; + case StatusCode::TooEarly_425: + return "Too Early"; + case StatusCode::UpgradeRequired_426: + return "Upgrade Required"; + case StatusCode::PreconditionRequired_428: + return "Precondition Required"; + case StatusCode::TooManyRequests_429: + return "Too Many Requests"; + case StatusCode::RequestHeaderFieldsTooLarge_431: + return "Request Header Fields Too Large"; + case StatusCode::UnavailableForLegalReasons_451: + return "Unavailable For Legal Reasons"; + case StatusCode::NotImplemented_501: + return "Not Implemented"; + case StatusCode::BadGateway_502: + return "Bad Gateway"; + case StatusCode::ServiceUnavailable_503: + return "Service Unavailable"; + case StatusCode::GatewayTimeout_504: + return "Gateway Timeout"; + case StatusCode::HttpVersionNotSupported_505: + return "HTTP Version Not Supported"; + case StatusCode::VariantAlsoNegotiates_506: + return "Variant Also Negotiates"; + case StatusCode::InsufficientStorage_507: + return "Insufficient Storage"; + case StatusCode::LoopDetected_508: + return "Loop Detected"; + case StatusCode::NotExtended_510: + return "Not Extended"; + case StatusCode::NetworkAuthenticationRequired_511: + return "Network Authentication Required"; + + default: + case StatusCode::InternalServerError_500: + return "Internal Server Error"; + } +} + +inline std::string to_string(const Error error) +{ + switch (error) { + case Error::Success: + return "Success (no error)"; + case Error::Unknown: + return "Unknown"; + case Error::Connection: + return "Could not establish connection"; + case Error::BindIPAddress: + return "Failed to bind IP address"; + case Error::Read: + return "Failed to read connection"; + case Error::Write: + return "Failed to write connection"; + case Error::ExceedRedirectCount: + return "Maximum redirect count exceeded"; + case Error::Canceled: + return "Connection handling canceled"; + case Error::SSLConnection: + return "SSL connection failed"; + case Error::SSLLoadingCerts: + return "SSL certificate loading failed"; + case Error::SSLServerVerification: + return "SSL server verification failed"; + case Error::SSLServerHostnameVerification: + return "SSL server hostname verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: + return "Compression failed"; + case Error::ConnectionTimeout: + return "Connection timed out"; + case Error::ProxyConnection: + return "Proxy connection failed"; + case Error::ConnectionClosed: + return "Connection closed by server"; + case Error::Timeout: + return "Read timeout"; + case Error::ResourceExhaustion: + return "Resource exhaustion"; + case Error::TooManyFormDataFiles: + return "Too many form data files"; + case Error::ExceedMaxPayloadSize: + return "Exceeded maximum payload size"; + case Error::ExceedUriMaxLength: + return "Exceeded maximum URI length"; + case Error::ExceedMaxSocketDescriptorCount: + return "Exceeded maximum socket descriptor count"; + case Error::InvalidRequestLine: + return "Invalid request line"; + case Error::InvalidHTTPMethod: + return "Invalid HTTP method"; + case Error::InvalidHTTPVersion: + return "Invalid HTTP version"; + case Error::InvalidHeaders: + return "Invalid headers"; + case Error::MultipartParsing: + return "Multipart parsing failed"; + case Error::OpenFile: + return "Failed to open file"; + case Error::Listen: + return "Failed to listen on socket"; + case Error::GetSockName: + return "Failed to get socket name"; + case Error::UnsupportedAddressFamily: + return "Unsupported address family"; + case Error::HTTPParsing: + return "HTTP parsing failed"; + case Error::InvalidRangeHeader: + return "Invalid Range header"; + case Error::UnsupportedContentEncoding: + return "Unsupported Content-Encoding"; + default: + break; + } + + return "Invalid"; +} + +inline std::ostream &operator<<(std::ostream &os, const Error &obj) +{ + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; +} + +inline std::string hosted_at(const std::string &hostname) +{ + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { + return std::string(); + } + return addrs[0]; +} + +inline void hosted_at(const std::string &hostname, std::vector &addrs) +{ + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (detail::getaddrinfo_with_timeout(hostname.c_str(), nullptr, &hints, &result, 0)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &addr = *reinterpret_cast(rp->ai_addr); + std::string ip; + auto dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, dummy)) { + addrs.emplace_back(std::move(ip)); + } + } +} + +inline std::string encode_uri_component(const std::string &value) +{ + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (detail::is_ascii_alnum(c) || c == '-' || c == '_' || c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } + else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string encode_uri(const std::string &value) +{ + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (detail::is_ascii_alnum(c) || c == '-' || c == '_' || c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')' || c == ';' || c == '/' || c == '?' || c == ':' || c == '@' || c == '&' || c == '=' || c == '+' || c == '$' || c == ',' || + c == '#') { + escaped << c; + } + else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string decode_uri_component(const std::string &value) +{ + std::string result; + + for (size_t i = 0; i < value.size(); i++) { + if (value[i] == '%' && i + 2 < value.size()) { + auto val = 0; + if (detail::from_hex_to_i(value, i + 1, 2, val)) { + result += static_cast(val); + i += 2; + } + else { + result += value[i]; + } + } + else { + result += value[i]; + } + } + + return result; +} + +inline std::string decode_uri(const std::string &value) +{ + std::string result; + + for (size_t i = 0; i < value.size(); i++) { + if (value[i] == '%' && i + 2 < value.size()) { + auto val = 0; + if (detail::from_hex_to_i(value, i + 1, 2, val)) { + result += static_cast(val); + i += 2; + } + else { + result += value[i]; + } + } + else { + result += value[i]; + } + } + + return result; +} + +inline std::string encode_path_component(const std::string &component) +{ + std::string result; + result.reserve(component.size() * 3); + + for (size_t i = 0; i < component.size(); i++) { + auto c = static_cast(component[i]); + + // Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~" + if (detail::is_ascii_alnum(static_cast(c)) || c == '-' || c == '.' || c == '_' || c == '~') { + result += static_cast(c); + } + // Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / + // "," / ";" / "=" + else if (c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' || c == '+' || c == ',' || c == ';' || + c == '=') { + result += static_cast(c); + } + // Colon is allowed in path segments except first segment + else if (c == ':') { + result += static_cast(c); + } + // @ is allowed in path + else if (c == '@') { + result += static_cast(c); + } + else { + result += '%'; + char hex[3]; + snprintf(hex, sizeof(hex), "%02X", c); + result.append(hex, 2); + } + } + return result; +} + +inline std::string decode_path_component(const std::string &component) +{ + std::string result; + result.reserve(component.size()); + + for (size_t i = 0; i < component.size(); i++) { + if (component[i] == '%' && i + 1 < component.size()) { + if (component[i + 1] == 'u') { + // Unicode %uXXXX encoding + auto val = 0; + if (detail::from_hex_to_i(component, i + 2, 4, val)) { + // 4 digits Unicode codes: val is 0x0000-0xFFFF (from 4 hex digits), + // so to_utf8 writes at most 3 bytes. buff[4] is safe. + char buff[4]; + size_t len = detail::to_utf8(val, buff); + if (len > 0) { + result.append(buff, len); + } + i += 5; // 'u0000' + } + else { + result += component[i]; + } + } + else { + // Standard %XX encoding + auto val = 0; + if (detail::from_hex_to_i(component, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast(val); + i += 2; // 'XX' + } + else { + result += component[i]; + } + } + } + else { + result += component[i]; + } + } + return result; +} + +inline std::string encode_query_component(const std::string &component, bool space_as_plus) +{ + std::string result; + result.reserve(component.size() * 3); + + for (size_t i = 0; i < component.size(); i++) { + auto c = static_cast(component[i]); + + // Unreserved characters per RFC 3986 + if (detail::is_ascii_alnum(static_cast(c)) || c == '-' || c == '.' || c == '_' || c == '~') { + result += static_cast(c); + } + // Space handling + else if (c == ' ') { + if (space_as_plus) { + result += '+'; + } + else { + result += "%20"; + } + } + // Plus sign handling + else if (c == '+') { + if (space_as_plus) { + result += "%2B"; + } + else { + result += static_cast(c); + } + } + // Query-safe sub-delimiters (excluding & and = which are query delimiters) + else if (c == '!' || c == '$' || c == '\'' || c == '(' || c == ')' || c == '*' || c == ',' || c == ';') { + result += static_cast(c); + } + // Colon and @ are allowed in query + else if (c == ':' || c == '@') { + result += static_cast(c); + } + // Forward slash is allowed in query values + else if (c == '/') { + result += static_cast(c); + } + // Question mark is allowed in query values (after first ?) + else if (c == '?') { + result += static_cast(c); + } + else { + result += '%'; + char hex[3]; + snprintf(hex, sizeof(hex), "%02X", c); + result.append(hex, 2); + } + } + return result; +} + +inline std::string decode_query_component(const std::string &component, bool plus_as_space) +{ + std::string result; + result.reserve(component.size()); + + for (size_t i = 0; i < component.size(); i++) { + if (component[i] == '%' && i + 2 < component.size()) { + auto val = 0; + if (detail::from_hex_to_i(component, i + 1, 2, val)) { + result += static_cast(val); + i += 2; + } + else { + result += component[i]; + } + } + else if (component[i] == '+' && plus_as_space) { + result += ' '; // + becomes space in form-urlencoded + } + else { + result += component[i]; + } + } + return result; +} + +inline std::string sanitize_filename(const std::string &filename) +{ + // Extract basename: find the last path separator (/ or \) + auto pos = filename.find_last_of("/\\"); + auto result = (pos != std::string::npos) ? filename.substr(pos + 1) : filename; + + // Strip null bytes + result.erase(std::remove(result.begin(), result.end(), '\0'), result.end()); + + // Trim whitespace + { + auto start = result.find_first_not_of(" \t"); + auto end = result.find_last_not_of(" \t"); + result = (start == std::string::npos) ? "" : result.substr(start, end - start + 1); + } + + // Reject . and .. + if (result == "." || result == "..") { + return ""; + } + + return result; +} + +inline std::string append_query_params(const std::string &path, const Params ¶ms) +{ + std::string path_with_query = path; + thread_local const std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + +// Header utilities +inline std::pair make_range_header(const Ranges &ranges) +{ + std::string field = "bytes="; + auto i = 0; + for (const auto &r : ranges) { + if (i != 0) { + field += ", "; + } + if (r.first != -1) { + field += std::to_string(r.first); + } + field += '-'; + if (r.second != -1) { + field += std::to_string(r.second); + } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy) +{ + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +inline std::pair make_bearer_token_authentication_header(const std::string &token, bool is_proxy = false) +{ + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +// Request implementation +inline size_t Request::get_header_value_u64(const std::string &key, size_t def, size_t id) const +{ + return detail::get_header_value_u64(headers, key, def, id); +} + +inline bool Request::has_header(const std::string &key) const +{ + return detail::has_header(headers, key); +} + +inline std::string Request::get_header_value(const std::string &key, const char *def, size_t id) const +{ + return detail::get_header_value(headers, key, def, id); +} + +inline size_t Request::get_header_value_count(const std::string &key) const +{ + return detail::get_header_value_count(headers, key); +} + +inline void Request::set_header(const std::string &key, const std::string &val) +{ + detail::set_header(headers, key, val); +} + +inline bool Request::has_trailer(const std::string &key) const +{ + return trailers.find(key) != trailers.end(); +} + +inline std::string Request::get_trailer_value(const std::string &key, size_t id) const +{ + return detail::get_multimap_value(trailers, key, id); +} + +inline size_t Request::get_trailer_value_count(const std::string &key) const +{ + return trailers.count(key); +} + +inline bool Request::has_param(const std::string &key) const +{ + return params.find(key) != params.end(); +} + +inline std::string Request::get_param_value(const std::string &key, size_t id) const +{ + return detail::get_multimap_value(params, key, id); +} + +inline std::vector Request::get_param_values(const std::string &key) const +{ + auto rng = params.equal_range(key); + std::vector values; + values.reserve(static_cast(std::distance(rng.first, rng.second))); + for (auto it = rng.first; it != rng.second; ++it) { + values.push_back(it->second); + } + return values; +} + +inline size_t Request::get_param_value_count(const std::string &key) const +{ + return params.count(key); +} + +inline bool Request::is_multipart_form_data() const +{ + const auto &content_type = get_header_value("Content-Type"); + return detail::extract_media_type(content_type) == "multipart/form-data"; +} + +// Multipart FormData implementation +inline std::string MultipartFormData::get_field(const std::string &key, size_t id) const +{ + auto rng = fields.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return it->second.content; + } + return std::string(); +} + +inline std::vector MultipartFormData::get_fields(const std::string &key) const +{ + std::vector values; + auto rng = fields.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second.content); + } + return values; +} + +inline bool MultipartFormData::has_field(const std::string &key) const +{ + return fields.find(key) != fields.end(); +} + +inline size_t MultipartFormData::get_field_count(const std::string &key) const +{ + return fields.count(key); +} + +inline FormData MultipartFormData::get_file(const std::string &key, size_t id) const +{ + return detail::get_multimap_value(files, key, id); +} + +inline std::vector MultipartFormData::get_files(const std::string &key) const +{ + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; +} + +inline bool MultipartFormData::has_file(const std::string &key) const +{ + return files.find(key) != files.end(); +} + +inline size_t MultipartFormData::get_file_count(const std::string &key) const +{ + return files.count(key); +} + +// Multipart FormData writer implementation +inline bool is_valid_multipart_boundary(const std::string &boundary) +{ + return detail::is_multipart_boundary_chars_valid(boundary); +} + +inline MultipartFormDataWriter::MultipartFormDataWriter() + : boundary_(detail::make_multipart_data_boundary()) +{ +} + +inline MultipartFormDataWriter::MultipartFormDataWriter(std::string boundary) + : boundary_(std::move(boundary)) +{ +} + +inline const std::string &MultipartFormDataWriter::boundary() const +{ + return boundary_; +} + +inline std::string MultipartFormDataWriter::content_type() const +{ + return detail::serialize_multipart_formdata_get_content_type(boundary_); +} + +inline std::string MultipartFormDataWriter::serialize(const UploadFormDataItems &items) const +{ + return detail::serialize_multipart_formdata(items, boundary_); +} + +inline size_t MultipartFormDataWriter::content_length(const UploadFormDataItems &items) const +{ + return detail::get_multipart_content_length(items, boundary_); +} + +inline std::string MultipartFormDataWriter::item_begin(const UploadFormData &item) const +{ + return detail::serialize_multipart_formdata_item_begin(item, boundary_); +} + +inline std::string MultipartFormDataWriter::item_end() +{ + return detail::serialize_multipart_formdata_item_end(); +} + +inline std::string MultipartFormDataWriter::finish() const +{ + return detail::serialize_multipart_formdata_finish(boundary_); +} + +// Response implementation +inline size_t Response::get_header_value_u64(const std::string &key, size_t def, size_t id) const +{ + return detail::get_header_value_u64(headers, key, def, id); +} + +inline bool Response::has_header(const std::string &key) const +{ + return headers.find(key) != headers.end(); +} + +inline std::string Response::get_header_value(const std::string &key, const char *def, size_t id) const +{ + return detail::get_header_value(headers, key, def, id); +} + +inline size_t Response::get_header_value_count(const std::string &key) const +{ + return detail::get_header_value_count(headers, key); +} + +inline void Response::set_header(const std::string &key, const std::string &val) +{ + detail::set_header(headers, key, val); +} +inline bool Response::has_trailer(const std::string &key) const +{ + return trailers.find(key) != trailers.end(); +} + +inline std::string Response::get_trailer_value(const std::string &key, size_t id) const +{ + return detail::get_multimap_value(trailers, key, id); +} + +inline size_t Response::get_trailer_value_count(const std::string &key) const +{ + return trailers.count(key); +} + +inline void Response::set_redirect(const std::string &url, int stat) +{ + if (detail::fields::is_field_value(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } + else { + this->status = StatusCode::Found_302; + } + } +} + +inline void Response::set_content(const char *s, size_t n, const std::string &content_type) +{ + body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content(const std::string &s, const std::string &content_type) +{ + set_content(s.data(), s.size(), content_type); +} + +inline void Response::set_content(std::string &&s, const std::string &content_type) +{ + body = std::move(s); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content_provider(size_t in_length, + const std::string &content_type, + ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) +{ + set_header("Content-Type", content_type); + content_length_ = in_length; + if (in_length > 0) { + content_provider_ = std::move(provider); + } + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = false; +} + +inline void Response::set_content_provider(const std::string &content_type, + ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) +{ + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = false; +} + +inline void Response::set_chunked_content_provider(const std::string &content_type, + ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) +{ + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = true; +} + +inline void Response::set_file_content(const std::string &path, const std::string &content_type) +{ + file_content_path_ = path; + file_content_content_type_ = content_type; +} + +inline void Response::set_file_content(const std::string &path) +{ + file_content_path_ = path; +} + +// Result implementation +inline size_t Result::get_request_header_value_u64(const std::string &key, size_t def, size_t id) const +{ + return detail::get_header_value_u64(request_headers_, key, def, id); +} + +inline bool Result::has_request_header(const std::string &key) const +{ + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const std::string &key, const char *def, size_t id) const +{ + return detail::get_header_value(request_headers_, key, def, id); +} + +inline size_t Result::get_request_header_value_count(const std::string &key) const +{ + return request_headers_.count(key); +} + +// Stream implementation +inline ssize_t Stream::write(const char *ptr) +{ + return write(ptr, strlen(ptr)); +} + +inline ssize_t Stream::write(const std::string &s) +{ + return write(s.data(), s.size()); +} + +// BodyReader implementation +inline ssize_t detail::BodyReader::read(char *buf, size_t len) +{ + if (!stream) { + last_error = Error::Connection; + return -1; + } + if (eof) { + return 0; + } + + if (!chunked) { + // Content-Length based reading + if (has_content_length && bytes_read >= content_length) { + eof = true; + return 0; + } + + auto to_read = len; + if (has_content_length) { + auto remaining = content_length - bytes_read; + to_read = (std::min)(len, remaining); + } + auto n = stream->read(buf, to_read); + + if (n < 0) { + last_error = stream->get_error(); + if (last_error == Error::Success) { + last_error = Error::Read; + } + eof = true; + return n; + } + if (n == 0) { + // Unexpected EOF before content_length + last_error = stream->get_error(); + if (last_error == Error::Success) { + last_error = Error::Read; + } + eof = true; + return 0; + } + + bytes_read += static_cast(n); + if (has_content_length && bytes_read >= content_length) { + eof = true; + } + if (payload_max_length > 0 && bytes_read > payload_max_length) { + last_error = Error::ExceedMaxPayloadSize; + eof = true; + return -1; + } + return n; + } + + // Chunked transfer encoding: delegate to shared decoder instance. + if (!chunked_decoder) { + chunked_decoder.reset(new ChunkedDecoder(*stream)); + } + + size_t chunk_offset = 0; + size_t chunk_total = 0; + auto n = chunked_decoder->read_payload(buf, len, chunk_offset, chunk_total); + if (n < 0) { + last_error = stream->get_error(); + if (last_error == Error::Success) { + last_error = Error::Read; + } + eof = true; + return n; + } + + if (n == 0) { + // Final chunk observed. Leave trailer parsing to the caller (StreamHandle). + eof = true; + return 0; + } + + bytes_read += static_cast(n); + if (payload_max_length > 0 && bytes_read > payload_max_length) { + last_error = Error::ExceedMaxPayloadSize; + eof = true; + return -1; + } + return n; +} + +// ThreadPool implementation +inline ThreadPool::ThreadPool(size_t n, size_t max_n, size_t mqr, time_t idle_timeout_sec) + : base_thread_count_(n) + , max_queued_requests_(mqr) + , idle_timeout_sec_(idle_timeout_sec) + , idle_thread_count_(0) + , shutdown_(false) +{ +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + if (max_n != 0 && max_n < n) { + std::string msg = "max_threads must be >= base_threads"; + throw std::invalid_argument(msg); + } +#endif + max_thread_count_ = max_n == 0 ? n : max_n; + threads_.reserve(base_thread_count_); +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + try { +#endif + for (size_t i = 0; i < base_thread_count_; i++) { + threads_.emplace_back(std::thread([this]() { worker(false); })); + } +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + } + catch (...) { + // If thread creation fails partway (e.g., pthread_create returns EAGAIN), + // signal the workers we already spawned to exit and join them so the + // vector destructor does not see joinable threads (which would call + // std::terminate). Then rethrow so the caller learns of the failure. + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + cond_.notify_all(); + for (auto &t : threads_) { + if (t.joinable()) { + t.join(); + } + } + throw; + } +#endif +} + +inline bool ThreadPool::enqueue(std::function fn) +{ + { + std::unique_lock lock(mutex_); + if (shutdown_) { + return false; + } + if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) { + return false; + } + jobs_.push_back(std::move(fn)); + + // Spawn a dynamic thread if no idle threads and under max + if (idle_thread_count_ == 0 && threads_.size() + dynamic_threads_.size() < max_thread_count_) { + cleanup_finished_threads(); + dynamic_threads_.emplace_back(std::thread([this]() { worker(true); })); + } + } + + cond_.notify_one(); + return true; +} + +inline void ThreadPool::shutdown() +{ + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + + cond_.notify_all(); + + for (auto &t : threads_) { + if (t.joinable()) { + t.join(); + } + } + + // Move dynamic_threads_ to a local list under the lock to avoid racing + // with worker threads that call move_to_finished() concurrently. + std::list remaining_dynamic; + { + std::unique_lock lock(mutex_); + remaining_dynamic = std::move(dynamic_threads_); + } + for (auto &t : remaining_dynamic) { + if (t.joinable()) { + t.join(); + } + } + + std::unique_lock lock(mutex_); + cleanup_finished_threads(); +} + +inline void ThreadPool::move_to_finished(std::thread::id id) +{ + // Must be called with mutex_ held + for (auto it = dynamic_threads_.begin(); it != dynamic_threads_.end(); ++it) { + if (it->get_id() == id) { + finished_threads_.push_back(std::move(*it)); + dynamic_threads_.erase(it); + return; + } + } +} + +inline void ThreadPool::cleanup_finished_threads() +{ + // Must be called with mutex_ held + for (auto &t : finished_threads_) { + if (t.joinable()) { + t.join(); + } + } + finished_threads_.clear(); +} + +inline void ThreadPool::worker(bool is_dynamic) +{ + for (;;) { + std::function fn; + { + std::unique_lock lock(mutex_); + idle_thread_count_++; + + if (is_dynamic) { + auto has_work = cond_.wait_for(lock, std::chrono::seconds(idle_timeout_sec_), [&] { return !jobs_.empty() || shutdown_; }); + if (!has_work) { + // Timed out with no work - exit this dynamic thread + idle_thread_count_--; + move_to_finished(std::this_thread::get_id()); + break; + } + } + else { + cond_.wait(lock, [&] { return !jobs_.empty() || shutdown_; }); + } + + idle_thread_count_--; + + if (shutdown_ && jobs_.empty()) { + break; + } + + fn = std::move(jobs_.front()); + jobs_.pop_front(); + } + + assert(true == static_cast(fn)); + fn(); + } + +#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && !defined(LIBRESSL_VERSION_NUMBER) + OPENSSL_thread_stop(); +#endif +} + +/* + * Group 1 (continued): detail namespace - Stream implementations + */ + +namespace detail { + +inline void calc_actual_timeout(time_t max_timeout_msec, + time_t duration_msec, + time_t timeout_sec, + time_t timeout_usec, + time_t &actual_timeout_sec, + time_t &actual_timeout_usec) +{ + auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000); + + auto actual_timeout_msec = (std::min)(max_timeout_msec - duration_msec, timeout_msec); + + if (actual_timeout_msec < 0) { + actual_timeout_msec = 0; + } + + actual_timeout_sec = actual_timeout_msec / 1000; + actual_timeout_usec = (actual_timeout_msec % 1000) * 1000; +} + +// Socket stream implementation +inline SocketStream::SocketStream(socket_t sock, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time) + : sock_(sock) + , read_timeout_sec_(read_timeout_sec) + , read_timeout_usec_(read_timeout_usec) + , write_timeout_sec_(write_timeout_sec) + , write_timeout_usec_(write_timeout_usec) + , max_timeout_msec_(max_timeout_msec) + , start_time_(start_time) + , read_buff_(read_buff_size_, 0) +{ +} + +inline SocketStream::~SocketStream() = default; + +inline bool SocketStream::is_readable() const +{ + return read_buff_off_ < read_buff_content_size_; +} + +inline bool SocketStream::wait_readable() const +{ + if (max_timeout_msec_ <= 0) { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + } + + time_t read_timeout_sec; + time_t read_timeout_usec; + calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_, read_timeout_usec_, read_timeout_sec, read_timeout_usec); + + return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0; +} + +inline bool SocketStream::wait_writable() const +{ + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0; +} + +inline bool SocketStream::ensure_readable() +{ + if (readable_hint_) { + readable_hint_ = false; + return true; + } + return wait_readable(); +} + +inline const char *SocketStream::buffered_data(size_t &size) const +{ + size = read_buff_content_size_ - read_buff_off_; + return size ? read_buff_.data() + read_buff_off_ : nullptr; +} + +inline void SocketStream::consume_buffered(size_t size) +{ + assert(size <= read_buff_content_size_ - read_buff_off_); + read_buff_off_ += size; +} + +inline bool SocketStream::is_peer_alive() const +{ + return detail::is_socket_alive(sock_); +} + +inline ssize_t SocketStream::read(char *ptr, size_t size) +{ +#ifdef _WIN32 + size = (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } + else { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + + if (!ensure_readable()) { + error_ = Error::Timeout; + return -1; + } + + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + if (n == 0) { + error_ = Error::ConnectionClosed; + } + else { + error_ = Error::Read; + } + return n; + } + else if (n <= static_cast(size)) { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } + else { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } + else { + auto n = read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + if (n == 0) { + error_ = Error::ConnectionClosed; + } + else { + error_ = Error::Read; + } + } + return n; + } +} + +inline ssize_t SocketStream::write(const char *ptr, size_t size) +{ + if (!wait_writable()) { + return -1; + } + +#if defined(_WIN32) && !defined(_WIN64) + size = (std::min)(size, static_cast((std::numeric_limits::max)())); +#endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); +} + +inline void SocketStream::get_remote_ip_and_port(std::string &ip, int &port) const +{ + return detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SocketStream::get_local_ip_and_port(std::string &ip, int &port) const +{ + return detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const +{ + return sock_; +} + +inline time_t SocketStream::duration() const +{ + return std::chrono::duration_cast(std::chrono::steady_clock::now() - start_time_).count(); +} + +inline void SocketStream::set_read_timeout(time_t sec, time_t usec) +{ + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +// Buffer stream implementation +inline bool BufferStream::is_readable() const +{ + return true; +} + +inline bool BufferStream::wait_readable() const +{ + return true; +} + +inline bool BufferStream::wait_writable() const +{ + return true; +} + +inline ssize_t BufferStream::read(char *ptr, size_t size) +{ +#if defined(_MSC_VER) && _MSC_VER < 1910 + auto len_read = buffer._Copy_s(ptr, size, size, position); +#else + auto len_read = buffer.copy(ptr, size, position); +#endif + position += static_cast(len_read); + return static_cast(len_read); +} + +inline ssize_t BufferStream::write(const char *ptr, size_t size) +{ + buffer.append(ptr, size); + return static_cast(size); +} + +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, int & /*port*/) const {} + +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, int & /*port*/) const {} + +inline socket_t BufferStream::socket() const +{ + return 0; +} + +inline time_t BufferStream::duration() const +{ + return 0; +} + +inline const std::string &BufferStream::get_buffer() const +{ + return buffer; +} + +inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) + : MatcherBase(pattern) +{ + constexpr const char marker[] = "/:"; + + // One past the last ending position of a path param substring + std::size_t last_param_end = 0; + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + // Needed to ensure that parameter names are unique during matcher + // construction + // If exceptions are disabled, only last duplicate path + // parameter will be set + std::unordered_set param_name_set; +#endif + + while (true) { + const auto marker_pos = pattern.find(marker, last_param_end == 0 ? last_param_end : last_param_end - 1); + if (marker_pos == std::string::npos) { + break; + } + + static_fragments_.push_back(pattern.substr(last_param_end, marker_pos - last_param_end + 1)); + + const auto param_name_start = marker_pos + str_len(marker); + + auto sep_pos = pattern.find(separator, param_name_start); + if (sep_pos == std::string::npos) { + sep_pos = pattern.length(); + } + + auto param_name = pattern.substr(param_name_start, sep_pos - param_name_start); + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + if (param_name_set.find(param_name) != param_name_set.cend()) { + std::string msg = "Encountered path parameter '" + param_name + "' multiple times in route pattern '" + pattern + "'."; + throw std::invalid_argument(msg); + } +#endif + + param_names_.push_back(std::move(param_name)); + + last_param_end = sep_pos + 1; + } + + if (last_param_end < pattern.length()) { + static_fragments_.push_back(pattern.substr(last_param_end)); + } +} + +inline bool PathParamsMatcher::match(Request &request) const +{ + request.matches = std::smatch(); + request.path_params.clear(); + request.path_params.reserve(param_names_.size()); + + // One past the position at which the path matched the pattern last time + std::size_t starting_pos = 0; + for (size_t i = 0; i < static_fragments_.size(); ++i) { + const auto &fragment = static_fragments_[i]; + + if (starting_pos + fragment.length() > request.path.length()) { + return false; + } + + // Avoid unnecessary allocation by using strncmp instead of substr + + // comparison + if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(), fragment.length()) != 0) { + return false; + } + + starting_pos += fragment.length(); + + // Should only happen when we have a static fragment after a param + // Example: '/users/:id/subscriptions' + // The 'subscriptions' fragment here does not have a corresponding param + if (i >= param_names_.size()) { + continue; + } + + auto sep_pos = request.path.find(separator, starting_pos); + if (sep_pos == std::string::npos) { + sep_pos = request.path.length(); + } + + const auto ¶m_name = param_names_[i]; + + request.path_params.emplace(param_name, request.path.substr(starting_pos, sep_pos - starting_pos)); + + // Mark everything up to '/' as matched + starting_pos = sep_pos + 1; + } + // Returns false if the path is longer than the pattern + return starting_pos >= request.path.length(); +} + +inline bool RegexMatcher::match(Request &request) const +{ + request.path_params.clear(); + return std::regex_match(request.path, request.matches, regex_); +} + +// Enclose IPv6 address in brackets if needed +inline std::string prepare_host_string(const std::string &host) +{ + // Enclose IPv6 address in brackets (but not if already enclosed) + if (host.find(':') == std::string::npos || (!host.empty() && host[0] == '[')) { + // IPv4, hostname, or already bracketed IPv6 + return host; + } + else { + // IPv6 address without brackets + return "[" + host + "]"; + } +} + +inline std::string make_host_and_port_string(const std::string &host, int port, bool is_ssl) +{ + auto result = prepare_host_string(host); + + // Append port if not default + if ((!is_ssl && port == 80) || (is_ssl && port == 443)) { + ; // do nothing + } + else { + result += ":" + std::to_string(port); + } + + return result; +} + +// Create "host:port" string always including port number (for CONNECT method) +inline std::string make_host_and_port_string_always_port(const std::string &host, int port) +{ + return prepare_host_string(host) + ":" + std::to_string(port); +} + +bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out); +NormalizedTarget normalize_target(const std::string &host); +bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits); +bool host_matches_no_proxy(const NormalizedTarget &target, const std::vector &entries); + +inline bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits) +{ + if (prefix_bits < 0 || prefix_bits > 128) { + return false; + } + if (prefix_bits == 0) { + return true; + } + int full_bytes = prefix_bits / 8; + int rem_bits = prefix_bits % 8; + if (full_bytes > 0 && std::memcmp(ip.data(), net.data(), static_cast(full_bytes)) != 0) { + return false; + } + if (rem_bits == 0) { + return true; + } + auto i = static_cast(full_bytes); + auto mask = static_cast(0xFFu << (8 - rem_bits)); + return (ip[i] & mask) == (net[i] & mask); +} + +inline bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out) +{ + if (token.empty()) { + return false; + } + + if (token == "*") { + out.kind = NoProxyKind::Wildcard; + return true; + } + + auto slash = token.find('/'); + std::string addr_part = (slash == std::string::npos) ? token : token.substr(0, slash); + std::string prefix_part = (slash == std::string::npos) ? std::string() : token.substr(slash + 1); + + // A bare slash or trailing-slash CIDR like "10.0.0.0/" is malformed; + // don't silently treat it as a /32 (or /128). + if (slash != std::string::npos && prefix_part.empty()) { + return false; + } + + // Accept the bracketed IPv6 form ("[::1]", "[fe80::]/10") as well as the + // bare form. Brackets have no meaning for IPv4, so skip the IPv4 attempt + // when brackets are present. + bool bracketed = addr_part.size() >= 2 && addr_part.front() == '[' && addr_part.back() == ']'; + if (bracketed) { + addr_part = addr_part.substr(1, addr_part.size() - 2); + } + + if (!bracketed) { + struct in_addr v4; + if (inet_pton(AF_INET, addr_part.c_str(), &v4) == 1) { + int prefix = 32; + if (!prefix_part.empty()) { + auto r = from_chars(prefix_part.data(), prefix_part.data() + prefix_part.size(), prefix); + if (r.ec != std::errc{} || r.ptr != prefix_part.data() + prefix_part.size()) { + return false; + } + if (prefix < 0 || prefix > 32) { + return false; + } + } + out.kind = NoProxyKind::IPv4Cidr; + std::memcpy(out.net.data(), &v4, sizeof(v4)); + out.prefix_bits = prefix; + return true; + } + } + + struct in6_addr v6; + if (inet_pton(AF_INET6, addr_part.c_str(), &v6) == 1) { + int prefix = 128; + if (!prefix_part.empty()) { + auto r = from_chars(prefix_part.data(), prefix_part.data() + prefix_part.size(), prefix); + if (r.ec != std::errc{} || r.ptr != prefix_part.data() + prefix_part.size()) { + return false; + } + if (prefix < 0 || prefix > 128) { + return false; + } + } + out.kind = NoProxyKind::IPv6Cidr; + std::memcpy(out.net.data(), &v6, sizeof(v6)); + out.prefix_bits = prefix; + return true; + } + + // Bracketed entries can only be IPv6. If the IPv6 parse above failed, + // the entry is malformed — don't fall through to the hostname branch. + if (bracketed) { + return false; + } + + // A '/' on a non-IP token means a CIDR prefix without an address. Reject. + if (slash != std::string::npos) { + return false; + } + // Port-specific entries (host:port) are not supported. + if (token.find(':') != std::string::npos) { + return false; + } + + std::string hostname = case_ignore::to_lower(token); + while (!hostname.empty() && hostname.front() == '.') { + hostname.erase(hostname.begin()); + } + while (!hostname.empty() && hostname.back() == '.') { + hostname.pop_back(); + } + if (hostname.empty()) { + return false; + } + + out.kind = NoProxyKind::HostnameSuffix; + out.hostname_pattern = std::move(hostname); + return true; +} + +inline NormalizedTarget normalize_target(const std::string &host) +{ + NormalizedTarget t; + std::string h = host; + + if (h.size() >= 2 && h.front() == '[' && h.back() == ']') { + h = h.substr(1, h.size() - 2); + } + + // Strip a single trailing dot so "example.com." canonicalizes to + // "example.com". + if (!h.empty() && h.back() == '.') { + h.pop_back(); + } + + t.hostname = case_ignore::to_lower(h); + + if (!t.hostname.empty()) { + struct in_addr v4; + struct in6_addr v6; + if (inet_pton(AF_INET, t.hostname.c_str(), &v4) == 1) { + t.is_ipv4 = true; + std::memcpy(t.ip.data(), &v4, sizeof(v4)); + } + else if (inet_pton(AF_INET6, t.hostname.c_str(), &v6) == 1) { + t.is_ipv6 = true; + std::memcpy(t.ip.data(), &v6, sizeof(v6)); + } + } + return t; +} + +inline bool host_matches_no_proxy(const NormalizedTarget &target, const std::vector &entries) +{ + if (target.hostname.empty()) { + return false; + } + for (const auto &e : entries) { + switch (e.kind) { + case NoProxyKind::Wildcard: + return true; + case NoProxyKind::IPv4Cidr: + if (target.is_ipv4 && ip_in_cidr(target.ip, e.net, e.prefix_bits)) { + return true; + } + break; + case NoProxyKind::IPv6Cidr: + if (target.is_ipv6 && ip_in_cidr(target.ip, e.net, e.prefix_bits)) { + return true; + } + break; + case NoProxyKind::HostnameSuffix: + if (target.is_ipv4 || target.is_ipv6) { + break; + } + if (target.hostname == e.hostname_pattern) { + return true; + } + // Dot-boundary suffix match: prevents "evilexample.com" from matching + // an entry of "example.com". + if (target.hostname.size() > e.hostname_pattern.size() + 1) { + auto offset = target.hostname.size() - e.hostname_pattern.size(); + if (target.hostname[offset - 1] == '.' && target.hostname.compare(offset, e.hostname_pattern.size(), e.hostname_pattern) == 0) { + return true; + } + } + break; + } + } + return false; +} + +template +inline bool check_and_write_headers(Stream &strm, Headers &headers, T header_writer, Error &error) +{ + for (const auto &h : headers) { + if (!detail::fields::is_field_valid(h.first, h.second)) { + error = Error::InvalidHeaders; + return false; + } + } + if (header_writer(strm, headers) <= 0) { + error = Error::Write; + return false; + } + return true; +} + +} // namespace detail + +/* + * Group 2 (continued): detail namespace - SSLSocketStream implementation + */ + +#ifdef CPPHTTPLIB_SSL_ENABLED +namespace detail { + +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream(socket_t sock, + tls::session_t session, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time) + : sock_(sock) + , session_(session) + , read_timeout_sec_(read_timeout_sec) + , read_timeout_usec_(read_timeout_usec) + , write_timeout_sec_(write_timeout_sec) + , write_timeout_usec_(write_timeout_usec) + , max_timeout_msec_(max_timeout_msec) + , start_time_(start_time) +{ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // Clear AUTO_RETRY for proper non-blocking I/O timeout handling + // Note: create_session() also clears this, but SSLClient currently + // uses ssl_new() which does not. Until full TLS API migration is complete, + // we need to ensure AUTO_RETRY is cleared here regardless of how the + // SSL session was created. + SSL_clear_mode(static_cast(session), SSL_MODE_AUTO_RETRY); +#endif +} + +inline SSLSocketStream::~SSLSocketStream() = default; + +inline bool SSLSocketStream::is_readable() const +{ + return tls::pending(session_) > 0; +} + +inline bool SSLSocketStream::wait_readable() const +{ + if (max_timeout_msec_ <= 0) { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + } + + time_t read_timeout_sec; + time_t read_timeout_usec; + calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_, read_timeout_usec_, read_timeout_sec, read_timeout_usec); + + return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0; +} + +inline bool SSLSocketStream::wait_writable() const +{ + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && !tls::is_peer_closed(session_, sock_); +} + +inline bool SSLSocketStream::ensure_readable() +{ + if (readable_hint_) { + readable_hint_ = false; + return true; + } + return wait_readable(); +} + +inline bool SSLSocketStream::is_peer_alive() const +{ + return !tls::is_peer_closed(session_, sock_); +} + +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) +{ + if (tls::pending(session_) > 0) { + tls::TlsError err; + auto ret = tls::read(session_, ptr, size, err); + if (ret == 0 || err.code == tls::ErrorCode::PeerClosed) { + error_ = Error::ConnectionClosed; + } + return ret; + } + else if (ensure_readable()) { + tls::TlsError err; + auto ret = tls::read(session_, ptr, size, err); + if (ret < 0) { + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && + (err.code == tls::ErrorCode::WantRead || (err.code == tls::ErrorCode::SyscallError && WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err.code == tls::ErrorCode::WantRead) { +#endif + if (tls::pending(session_) > 0) { + return tls::read(session_, ptr, size, err); + } + else if (wait_readable()) { + std::this_thread::sleep_for(std::chrono::microseconds{10}); + ret = tls::read(session_, ptr, size, err); + if (ret >= 0) { + return ret; + } + } + else { + break; + } + } + assert(ret < 0); + } + else if (ret == 0 || err.code == tls::ErrorCode::PeerClosed) { + error_ = Error::ConnectionClosed; + } + return ret; + } + else { + error_ = Error::Timeout; + return -1; + } +} + +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) +{ + if (wait_writable()) { + auto handle_size = std::min(size, (std::numeric_limits::max)()); + + tls::TlsError err; + auto ret = tls::write(session_, ptr, handle_size, err); + if (ret < 0) { + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && + (err.code == tls::ErrorCode::WantWrite || (err.code == tls::ErrorCode::SyscallError && WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err.code == tls::ErrorCode::WantWrite) { +#endif + if (wait_writable()) { + std::this_thread::sleep_for(std::chrono::microseconds{10}); + ret = tls::write(session_, ptr, handle_size, err); + if (ret >= 0) { + return ret; + } + } + else { + break; + } + } + assert(ret < 0); + } + return ret; + } + return -1; +} + +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, int &port) const +{ + detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, int &port) const +{ + detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const +{ + return sock_; +} + +inline time_t SSLSocketStream::duration() const +{ + return std::chrono::duration_cast(std::chrono::steady_clock::now() - start_time_).count(); +} + +inline void SSLSocketStream::set_read_timeout(time_t sec, time_t usec) +{ + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +} // namespace detail +#endif // CPPHTTPLIB_SSL_ENABLED + +/* + * Group 4: Server implementation + */ + +// HTTP server implementation +inline Server::Server() + : new_task_queue([] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT, CPPHTTPLIB_THREAD_POOL_MAX_COUNT); }) +{ +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif +} + +inline Server::~Server() = default; + +inline std::unique_ptr Server::make_matcher(const std::string &pattern) +{ + if (pattern.find("/:") != std::string::npos) { + return detail::make_unique(pattern); + } + else { + return detail::make_unique(pattern); + } +} + +inline Server &Server::Get(const std::string &pattern, Handler handler) +{ + return add_handler(get_handlers_, pattern, std::move(handler)); +} + +inline Server &Server::Post(const std::string &pattern, Handler handler) +{ + return add_handler(post_handlers_, pattern, std::move(handler)); +} + +inline Server &Server::Post(const std::string &pattern, HandlerWithContentReader handler) +{ + return add_handler(post_handlers_for_content_reader_, pattern, std::move(handler)); +} + +inline Server &Server::Put(const std::string &pattern, Handler handler) +{ + return add_handler(put_handlers_, pattern, std::move(handler)); +} + +inline Server &Server::Put(const std::string &pattern, HandlerWithContentReader handler) +{ + return add_handler(put_handlers_for_content_reader_, pattern, std::move(handler)); +} + +inline Server &Server::Patch(const std::string &pattern, Handler handler) +{ + return add_handler(patch_handlers_, pattern, std::move(handler)); +} + +inline Server &Server::Patch(const std::string &pattern, HandlerWithContentReader handler) +{ + return add_handler(patch_handlers_for_content_reader_, pattern, std::move(handler)); +} + +inline Server &Server::Delete(const std::string &pattern, Handler handler) +{ + return add_handler(delete_handlers_, pattern, std::move(handler)); +} + +inline Server &Server::Delete(const std::string &pattern, HandlerWithContentReader handler) +{ + return add_handler(delete_handlers_for_content_reader_, pattern, std::move(handler)); +} + +inline Server &Server::Options(const std::string &pattern, Handler handler) +{ + return add_handler(options_handlers_, pattern, std::move(handler)); +} + +inline Server &Server::WebSocket(const std::string &pattern, WebSocketHandler handler) +{ + websocket_handlers_.push_back({make_matcher(pattern), std::move(handler), nullptr}); + return *this; +} + +inline Server &Server::WebSocket(const std::string &pattern, WebSocketHandler handler, SubProtocolSelector sub_protocol_selector) +{ + websocket_handlers_.push_back({make_matcher(pattern), std::move(handler), std::move(sub_protocol_selector)}); + return *this; +} + +inline bool Server::set_base_dir(const std::string &dir, const std::string &mount_point) +{ + return set_mount_point(mount_point, dir); +} + +inline bool Server::set_mount_point(const std::string &mount_point, const std::string &dir, Headers headers) +{ + detail::FileStat stat(dir); + if (stat.is_dir()) { + std::string mnt = !mount_point.empty() ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') { + std::string resolved_base; + if (detail::canonicalize_path(dir.c_str(), resolved_base)) { +#if defined(_WIN32) + if (resolved_base.back() != '\\' && resolved_base.back() != '/') { + resolved_base += '\\'; + } +#else + if (resolved_base.back() != '/') { + resolved_base += '/'; + } +#endif + } + base_dirs_.push_back({std::move(mnt), dir, std::move(resolved_base), std::move(headers)}); + return true; + } + } + return false; +} + +inline bool Server::remove_mount_point(const std::string &mount_point) +{ + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { + if (it->mount_point == mount_point) { + base_dirs_.erase(it); + return true; + } + } + return false; +} + +inline Server &Server::set_file_extension_and_mimetype_mapping(const std::string &ext, const std::string &mime) +{ + file_extension_and_mimetype_map_[ext] = mime; + return *this; +} + +inline Server &Server::set_default_file_mimetype(const std::string &mime) +{ + default_file_mimetype_ = mime; + return *this; +} + +inline Server &Server::set_file_request_handler(Handler handler) +{ + file_request_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler_core(HandlerWithResponse handler, std::true_type) +{ + error_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler_core(Handler handler, std::false_type) +{ + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; +} + +inline Server &Server::set_exception_handler(ExceptionHandler handler) +{ + exception_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) +{ + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) +{ + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_request_handler(HandlerWithResponse handler) +{ + pre_request_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) +{ + logger_ = std::move(logger); + return *this; +} + +inline Server &Server::set_error_logger(ErrorLogger error_logger) +{ + error_logger_ = std::move(error_logger); + return *this; +} + +inline Server &Server::set_pre_compression_logger(Logger logger) +{ + pre_compression_logger_ = std::move(logger); + return *this; +} + +inline Server &Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) +{ + expect_100_continue_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_start_handler(StartHandler handler) +{ + start_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_address_family(int family) +{ + address_family_ = family; + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) +{ + tcp_nodelay_ = on; + return *this; +} + +inline Server &Server::set_ipv6_v6only(bool on) +{ + ipv6_v6only_ = on; + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) +{ + socket_options_ = std::move(socket_options); + return *this; +} + +inline Server &Server::set_default_headers(Headers headers) +{ + default_headers_ = std::move(headers); + return *this; +} + +inline Server &Server::set_header_writer(std::function const &writer) +{ + header_writer_ = writer; + return *this; +} + +inline Server &Server::set_trusted_proxies(const std::vector &proxies) +{ + trusted_proxies_ = proxies; + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) +{ + keep_alive_max_count_ = count; + return *this; +} + +inline Server &Server::set_keep_alive_timeout(time_t sec) +{ + keep_alive_timeout_sec_ = sec; + return *this; +} + +template +inline Server &Server::set_keep_alive_timeout(const std::chrono::duration &duration) +{ + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t /*usec*/) { set_keep_alive_timeout(sec); }); + return *this; +} + +inline Server &Server::set_read_timeout(time_t sec, time_t usec) +{ + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_write_timeout(time_t sec, time_t usec) +{ + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_idle_interval(time_t sec, time_t usec) +{ + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + return *this; +} + +inline Server &Server::set_payload_max_length(size_t length) +{ + payload_max_length_ = length; + return *this; +} + +inline Server &Server::set_websocket_max_missed_pongs(int count) +{ + websocket_max_missed_pongs_ = count; + return *this; +} + +inline Server &Server::set_websocket_ping_interval(time_t sec) +{ + websocket_ping_interval_sec_ = sec; + return *this; +} + +template +inline Server &Server::set_websocket_ping_interval(const std::chrono::duration &duration) +{ + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t /*usec*/) { set_websocket_ping_interval(sec); }); + return *this; +} + +inline bool Server::bind_to_port(const std::string &host, int port, int socket_flags) +{ + auto ret = bind_internal(host, port, socket_flags); + if (ret == -1) { + is_decommissioned = true; + } + return ret >= 0; +} +inline int Server::bind_to_any_port(const std::string &host, int socket_flags) +{ + auto ret = bind_internal(host, 0, socket_flags); + if (ret == -1) { + is_decommissioned = true; + } + return ret; +} + +inline bool Server::listen_after_bind() +{ + return listen_internal(); +} + +inline bool Server::listen(const std::string &host, int port, int socket_flags) +{ + return bind_to_port(host, port, socket_flags) && listen_internal(); +} + +inline bool Server::is_running() const +{ + return is_running_; +} + +inline void Server::wait_until_ready() const +{ + while (!is_running_ && !is_decommissioned) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + +inline void Server::stop() noexcept +{ + // Release the listening socket whether or not the accept loop is running: + // bind_to_port() without listen_after_bind() still owns the descriptor. The + // exchange is what makes this safe to call concurrently with the accept loop. + socket_t sock = svr_sock_.exchange(INVALID_SOCKET); + if (sock != INVALID_SOCKET) { + detail::shutdown_socket(sock); + detail::close_socket(sock); + } + is_decommissioned = false; +} + +inline void Server::decommission() +{ + is_decommissioned = true; +} + +inline bool Server::parse_request_line(const char *s, Request &req) const +{ + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { + return false; + } + len -= 2; + + { + size_t count = 0; + + detail::split(s, s + len, ' ', [&](const char *b, const char *e) { + switch (count) { + case 0: + req.method = std::string(b, e); + break; + case 1: + req.target = std::string(b, e); + break; + case 2: + req.version = std::string(b, e); + break; + default: + break; + } + count++; + }); + + if (count != 3) { + return false; + } + } + + thread_local const std::set methods{"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { + output_error_log(Error::InvalidHTTPMethod, &req); + return false; + } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { + output_error_log(Error::InvalidHTTPVersion, &req); + return false; + } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) { + if (req.target[i] == '#') { + req.target.erase(i); + break; + } + } + + detail::divide(req.target, '?', [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data, std::size_t rhs_size) { + req.path = decode_path_component(std::string(lhs_data, lhs_size)); + detail::parse_query_text(rhs_data, rhs_size, req.params); + }); + } + + return true; +} + +inline bool Server::write_response(Stream &strm, bool close_connection, Request &req, Response &res) +{ + // NOTE: `req.ranges` should be empty, otherwise it will be applied + // incorrectly to the error content. + req.ranges.clear(); + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, bool close_connection, const Request &req, Response &res) +{ + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, const Request &req, Response &res, bool need_apply_ranges) +{ + assert(res.status != -1); + + if (400 <= res.status && error_handler_ && error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; + } + + std::string content_type; + std::string boundary; + if (need_apply_ranges) { + apply_ranges(req, res, content_type, boundary); + } + + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close" || + 400 <= res.status) { // Don't leave connections open after errors + res.set_header("Connection", "close"); + } + else { + std::string s = "timeout="; + s += std::to_string(keep_alive_timeout_sec_); + s += ", max="; + s += std::to_string(keep_alive_max_count_); + res.set_header("Keep-Alive", s); + } + + if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) && !res.has_header("Content-Type")) { + res.set_header("Content-Type", "text/plain"); + } + + if (res.body.empty() && !res.content_length_ && !res.content_provider_ && !res.has_header("Content-Length")) { + res.set_header("Content-Length", "0"); + } + + if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { + post_routing_handler_(req, res); + } + + // Response line and headers + detail::BufferStream bstrm; + if (!detail::write_response_line(bstrm, res.status)) { + return false; + } + if (header_writer_(bstrm, res.headers) <= 0) { + return false; + } + + // Combine small body with headers to reduce write syscalls + if (req.method != "HEAD" && !res.body.empty() && !res.content_provider_) { + bstrm.write(res.body.data(), res.body.size()); + } + + // Log before writing to avoid race condition with client-side code that + // accesses logger-captured data immediately after receiving the response. + output_log(req, res); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + return false; + } + + // Streaming body + auto ret = true; + if (req.method != "HEAD" && res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } + else { + ret = false; + } + } + + return ret; +} + +inline bool Server::write_content_with_provider( + Stream &strm, const Request &req, Response &res, const std::string &boundary, const std::string &content_type) +{ + auto is_shutting_down = [this]() { return this->svr_sock_ == INVALID_SOCKET; }; + + if (res.content_length_ > 0) { + // Only a 206 response is served as a partial representation, matching the + // condition `apply_ranges()` used to decide the Content-Length and the + // multipart boundary. Since `detail::range_error()` validates `req.ranges` + // only for a 2xx status, slicing under any other status would write a body + // that disagrees with the header already sent, from an unchecked offset. + auto is_partial = !req.ranges.empty() && res.status == StatusCode::PartialContent_206; + + if (!is_partial) { + return detail::write_content(strm, res.content_provider_, 0, res.content_length_, is_shutting_down); + } + else if (req.ranges.size() == 1) { + auto offset_and_length = detail::get_range_offset_and_length(req.ranges[0], res.content_length_); + + return detail::write_content(strm, res.content_provider_, offset_and_length.first, offset_and_length.second, is_shutting_down); + } + else { + return detail::write_multipart_ranges_data(strm, req, res, boundary, content_type, res.content_length_, is_shutting_down); + } + } + else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + auto compressor = detail::make_compressor(type); + if (!compressor) { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, res.content_provider_, is_shutting_down, *compressor); + } + else { + return detail::write_content_without_length(strm, res.content_provider_, is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) +{ + FormFields::iterator cur_field; + FormFiles::iterator cur_file; + auto is_text_field = false; + size_t count = 0; + if (read_content_core( + strm, + req, + res, + // Regular + [&](const char *buf, size_t n) { + // Prevent arithmetic overflow when checking sizes. + // Avoid computing (req.body.size() + n) directly because + // adding two unsigned `size_t` values can wrap around and + // produce a small result instead of indicating overflow. + // Instead, check using subtraction: ensure `n` does not + // exceed the remaining capacity `max_size() - size()`. + if (req.body.size() >= req.body.max_size() || n > req.body.max_size() - req.body.size()) { + return false; + } + + // Limit decompressed body size to payload_max_length_ to protect + // against "zip bomb" attacks where a small compressed payload + // decompresses to a massive size. + if (payload_max_length_ > 0 && (req.body.size() >= payload_max_length_ || n > payload_max_length_ - req.body.size())) { + return false; + } + + req.body.append(buf, n); + return true; + }, + // Multipart FormData + [&](const FormData &file) { + if (count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + output_error_log(Error::TooManyFormDataFiles, &req); + return false; + } + + if (file.filename.empty()) { + cur_field = req.form.fields.emplace(file.name, FormField{file.name, file.content, file.headers}); + is_text_field = true; + } + else { + cur_file = req.form.files.emplace(file.name, file); + is_text_field = false; + } + return true; + }, + [&](const char *buf, size_t n) { + if (is_text_field) { + auto &content = cur_field->second.content; + if (content.size() + n > content.max_size()) { + return false; + } + content.append(buf, n); + } + else { + auto &content = cur_file->second.content; + if (content.size() + n > content.max_size()) { + return false; + } + content.append(buf, n); + } + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (detail::extract_media_type(content_type) == "application/x-www-form-urlencoded") { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { + res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414? + output_error_log(Error::ExceedMaxPayloadSize, &req); + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, FormDataHeader multipart_header, ContentReceiver multipart_receiver) +{ + return read_content_core(strm, req, res, std::move(receiver), std::move(multipart_header), std::move(multipart_receiver)); +} + +inline bool Server::read_content_core(Stream &strm, + Request &req, + Response &res, + ContentReceiver receiver, + FormDataHeader multipart_header, + ContentReceiver multipart_receiver) const +{ + detail::FormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = StatusCode::BadRequest_400; + output_error_log(Error::MultipartParsing, &req); + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, size_t /*off*/, size_t /*len*/) { + return multipart_form_data_parser.parse(buf, n, multipart_header, multipart_receiver); + }; + } + else { + out = [receiver](const char *buf, size_t n, size_t /*off*/, size_t /*len*/) { return receiver(buf, n); }; + } + + // RFC 9112 §6: no Transfer-Encoding and no Content-Length means no body. + // For non-SSL builds we still scan non-persistent connections for stray + // body bytes so the payload limit is enforced (413). On keep-alive, + // pending bytes may be the next request (issue #2450), so skip. +#if !defined(CPPHTTPLIB_SSL_ENABLED) + if (!req.has_header("Content-Length") && !detail::is_chunked_transfer_encoding(req.headers)) { + if (!detail::is_connection_persistent(req) && payload_max_length_ > 0 && payload_max_length_ < (std::numeric_limits::max)()) { + auto has_data = strm.is_readable(); + if (!has_data) { + auto s = strm.socket(); + if (s != INVALID_SOCKET) { + has_data = detail::select_read(s, 0, 0) > 0; + } + } + if (has_data) { + auto result = detail::read_content_without_length(strm, payload_max_length_, out); + if (result == detail::ReadContentResult::PayloadTooLarge) { + res.status = StatusCode::PayloadTooLarge_413; + return false; + } + else if (result != detail::ReadContentResult::Success) { + return false; + } + return true; + } + } + return true; + } +#else + if (!req.has_header("Content-Length") && !detail::is_chunked_transfer_encoding(req.headers)) { + return true; + } +#endif + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, out, true)) { + return false; + } + + req.body_consumed_ = true; + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = StatusCode::BadRequest_400; + output_error_log(Error::MultipartParsing, &req); + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(Request &req, Response &res) +{ + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { + path += "index.html"; + } + + // Defense-in-depth: is_valid_path blocks ".." traversal in the URL, + // but symlinks/junctions can still escape the base directory. + if (!entry.resolved_base_dir.empty()) { + std::string resolved_path; + if (detail::canonicalize_path(path.c_str(), resolved_path) && + !detail::is_path_within_base(resolved_path, entry.resolved_base_dir)) { + res.status = StatusCode::Forbidden_403; + return true; + } + } + + detail::FileStat stat(path); + + if (stat.is_dir()) { + res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301); + return true; + } + + if (stat.is_file()) { + for (const auto &kv : entry.headers) { + res.set_header(kv.first, kv.second); + } + + auto etag = detail::compute_etag(stat); + if (!etag.empty()) { + res.set_header("ETag", etag); + } + + auto mtime = stat.mtime(); + + auto last_modified = detail::file_mtime_to_http_date(mtime); + if (!last_modified.empty()) { + res.set_header("Last-Modified", last_modified); + } + + if (check_if_not_modified(req, res, etag, mtime)) { + return true; + } + + check_if_range(req, etag, mtime); + + auto mm = std::make_shared(path.c_str()); + if (!mm->is_open()) { + output_error_log(Error::OpenFile, &req); + return false; + } + + res.set_content_provider(mm->size(), + detail::find_content_type(path, file_extension_and_mimetype_map_, default_file_mimetype_), + [mm](size_t offset, size_t length, DataSink &sink) -> bool { + sink.write(mm->data() + offset, length); + return true; + }); + + if (req.method != "HEAD" && file_request_handler_) { + file_request_handler_(req, res); + } + + return true; + } + else { + output_error_log(Error::OpenFile, &req); + } + } + } + } + return false; +} + +inline bool Server::check_if_not_modified(const Request &req, Response &res, const std::string &etag, time_t mtime) const +{ + // Handle conditional GET: + // 1. If-None-Match takes precedence (RFC 9110 Section 13.1.2) + // 2. If-Modified-Since is checked only when If-None-Match is absent + if (req.has_header("If-None-Match")) { + if (!etag.empty()) { + auto val = req.get_header_value("If-None-Match"); + + // NOTE: We use exact string matching here. This works correctly + // because our server always generates weak ETags (W/"..."), and + // clients typically send back the same ETag they received. + // RFC 9110 Section 8.8.3.2 allows weak comparison for + // If-None-Match, where W/"x" and "x" would match, but this + // simplified implementation requires exact matches. + auto ret = detail::split_find(val.data(), val.data() + val.size(), ',', [&](const char *b, const char *e) { + auto seg_len = static_cast(e - b); + return (seg_len == 1 && *b == '*') || (seg_len == etag.size() && std::equal(b, e, etag.begin())); + }); + + if (ret) { + res.status = StatusCode::NotModified_304; + return true; + } + } + } + else if (req.has_header("If-Modified-Since")) { + auto val = req.get_header_value("If-Modified-Since"); + auto t = detail::parse_http_date(val); + + if (t != static_cast(-1) && mtime <= t) { + res.status = StatusCode::NotModified_304; + return true; + } + } + return false; +} + +inline bool Server::check_if_range(Request &req, const std::string &etag, time_t mtime) const +{ + // Handle If-Range for partial content requests (RFC 9110 + // Section 13.1.5). If-Range is only evaluated when Range header is + // present. If the validator matches, serve partial content; otherwise + // serve full content. + if (!req.ranges.empty() && req.has_header("If-Range")) { + auto val = req.get_header_value("If-Range"); + + auto is_valid_range = [&]() { + if (detail::is_strong_etag(val)) { + // RFC 9110 Section 13.1.5: If-Range requires strong ETag + // comparison. + return (!etag.empty() && val == etag); + } + else if (detail::is_weak_etag(val)) { + // Weak ETags are not valid for If-Range (RFC 9110 Section 13.1.5) + return false; + } + else { + // HTTP-date comparison + auto t = detail::parse_http_date(val); + return (t != static_cast(-1) && mtime <= t); + } + }; + + if (!is_valid_range()) { + // Validator doesn't match: ignore Range and serve full content + req.ranges.clear(); + return false; + } + } + + return true; +} + +inline socket_t Server::create_server_socket(const std::string &host, int port, int socket_flags, SocketOptions socket_options) const +{ + return detail::create_socket(host, + std::string(), + port, + address_family_, + socket_flags, + tcp_nodelay_, + ipv6_v6only_, + std::move(socket_options), + [&](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + output_error_log(Error::BindIPAddress, nullptr); + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { + output_error_log(Error::Listen, nullptr); + return false; + } + return true; + }); +} + +inline int Server::bind_internal(const std::string &host, int port, int socket_flags) +{ + if (is_decommissioned) { + return -1; + } + + if (!is_valid()) { + return -1; + } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { + return -1; + } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), &addr_len) == -1) { + output_error_log(Error::GetSockName, nullptr); + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } + else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } + else { + output_error_log(Error::UnsupportedAddressFamily, nullptr); + return -1; + } + } + else { + return port; + } +} + +inline bool Server::listen_internal() +{ + // A stop() between bind and listen leaves nothing to accept on. Report + // failure instead of returning success without ever serving, and mark the + // server decommissioned the way any failed listen does so that a concurrent + // wait_until_ready() wakes up instead of spinning forever. + if (is_decommissioned || svr_sock_ == INVALID_SOCKET) { + is_decommissioned = true; + return false; + } + + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + if (start_handler_) { + start_handler_(); + } + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + +#if defined _WIN32 + // sockets connected via WASAccept inherit flags NO_HANDLE_INHERIT, + // OVERLAPPED + socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0); +#elif defined SOCK_CLOEXEC + socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC); +#else + socket_t sock = accept(svr_sock_, nullptr, nullptr); +#endif + + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::microseconds{1}); + continue; + } + else if (errno == EINTR || errno == EAGAIN) { + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + output_error_log(Error::Connection, nullptr); + } + else { + ; // The server socket was closed by user. + } + break; + } + + detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO, read_timeout_sec_, read_timeout_usec_); + detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec_, write_timeout_usec_); + + if (tcp_nodelay_) { + set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); + } + + if (!task_queue->enqueue([this, sock]() { process_and_close_socket(sock); })) { + output_error_log(Error::ResourceExhaustion, nullptr); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } + } + + task_queue->shutdown(); + } + + is_decommissioned = !ret; + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) +{ + if (pre_routing_handler_ && pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // File handler + if ((req.method == "GET" || req.method == "HEAD") && handle_file_request(req, res)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + // Track whether the ContentReader was aborted due to the decompressed + // payload exceeding `payload_max_length_`. + // The user handler runs after the lambda returns, so we must restore the + // 413 status if the handler overwrites it. + bool content_reader_payload_too_large = false; + + ContentReader reader( + [&](ContentReceiver receiver) { + auto result = read_content_with_content_receiver(strm, req, res, std::move(receiver), nullptr, nullptr); + if (!result) { + output_error_log(Error::Read, &req); + if (res.status == StatusCode::PayloadTooLarge_413) { + content_reader_payload_too_large = true; + } + } + return result; + }, + [&](FormDataHeader header, ContentReceiver receiver) { + auto result = read_content_with_content_receiver(strm, req, res, nullptr, std::move(header), std::move(receiver)); + if (!result) { + output_error_log(Error::Read, &req); + if (res.status == StatusCode::PayloadTooLarge_413) { + content_reader_payload_too_large = true; + } + } + return result; + }); + + bool dispatched = false; + if (req.method == "POST") { + dispatched = dispatch_request_for_content_reader(req, res, std::move(reader), post_handlers_for_content_reader_); + } + else if (req.method == "PUT") { + dispatched = dispatch_request_for_content_reader(req, res, std::move(reader), put_handlers_for_content_reader_); + } + else if (req.method == "PATCH") { + dispatched = dispatch_request_for_content_reader(req, res, std::move(reader), patch_handlers_for_content_reader_); + } + else if (req.method == "DELETE") { + dispatched = dispatch_request_for_content_reader(req, res, std::move(reader), delete_handlers_for_content_reader_); + } + + if (dispatched) { + if (content_reader_payload_too_large) { + // Enforce the limit: override any status the handler may have set + // and return false so the error path sends a plain 413 response. + res.status = StatusCode::PayloadTooLarge_413; + res.body.clear(); + res.content_length_ = 0; + res.content_provider_ = nullptr; + return false; + } + return true; + } + } + + // NOTE: `req.body` is not read here. For a regular handler the body is + // read inside dispatch_request(), after the route has matched and the + // pre-request handler has approved the request, so that a rejected + // request (e.g. failed authentication) never forces us to buffer a + // potentially large body. + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_, strm); + } + else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_, strm); + } + else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_, strm); + } + else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_, strm); + } + else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_, strm); + } + else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_, strm); + } + + res.status = StatusCode::BadRequest_400; + return false; +} + +inline bool Server::dispatch_request(Request &req, Response &res, const Handlers &handlers, Stream &strm) +{ + for (const auto &x : handlers) { + const auto &matcher = x.first; + const auto &handler = x.second; + + if (matcher->match(req)) { + req.matched_route = matcher->pattern(); + + // Run the pre-request handler before reading the body so a rejected + // request (e.g. failed authentication) never forces us to buffer a + // potentially large body. `req.matched_route` is available here. + if (pre_request_handler_ && pre_request_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // The route matched and the request was approved; read the body now. + if (detail::expect_content(req) && !read_content(strm, req, res)) { + output_error_log(Error::Read, &req); + return false; + } + + handler(req, res); + return true; + } + } + return false; +} + +inline void Server::apply_ranges(const Request &req, Response &res, std::string &content_type, std::string &boundary) const +{ + if (req.ranges.size() > 1 && res.status == StatusCode::PartialContent_206) { + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) { + content_type = it->second; + res.headers.erase(it); + } + + boundary = detail::make_multipart_data_boundary(); + + res.set_header("Content-Type", "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) { + if (res.content_length_ > 0) { + size_t length = 0; + if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) { + length = res.content_length_; + } + else if (req.ranges.size() == 1) { + auto offset_and_length = detail::get_range_offset_and_length(req.ranges[0], res.content_length_); + + length = offset_and_length.second; + + auto content_range = detail::make_content_range_header_field(offset_and_length, res.content_length_); + res.set_header("Content-Range", content_range); + } + else { + length = detail::get_multipart_ranges_data_length(req, boundary, content_type, res.content_length_); + } + res.set_header("Content-Length", std::to_string(length)); + } + else { + if (res.content_provider_) { + if (res.is_chunked_content_provider_) { + res.set_header("Transfer-Encoding", "chunked"); + if (type != detail::EncodingType::None) { + res.set_header("Content-Encoding", detail::encoding_name(type)); + res.set_header("Vary", "Accept-Encoding"); + } + } + } + } + } + else { + if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) { + ; + } + else if (req.ranges.size() == 1) { + auto offset_and_length = detail::get_range_offset_and_length(req.ranges[0], res.body.size()); + auto offset = offset_and_length.first; + auto length = offset_and_length.second; + + auto content_range = detail::make_content_range_header_field(offset_and_length, res.body.size()); + res.set_header("Content-Range", content_range); + + assert(offset + length <= res.body.size()); + res.body = res.body.substr(offset, length); + } + else { + std::string data; + detail::make_multipart_ranges_data(req, res, boundary, content_type, res.body.size(), data); + res.body.swap(data); + } + + if (type != detail::EncodingType::None) { + output_pre_compression_log(req, res); + + if (auto compressor = detail::make_compressor(type)) { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", detail::encoding_name(type)); + res.set_header("Vary", "Accept-Encoding"); + } + } + } + + res.content_length_ = res.body.size(); + res.set_header("Content-Length", std::to_string(res.content_length_)); + } +} + +inline bool Server::dispatch_request_for_content_reader(Request &req, + Response &res, + ContentReader content_reader, + const HandlersForContentReader &handlers) const +{ + for (const auto &x : handlers) { + const auto &matcher = x.first; + const auto &handler = x.second; + + if (matcher->match(req)) { + req.matched_route = matcher->pattern(); + if (!pre_request_handler_ || pre_request_handler_(req, res) != HandlerResponse::Handled) { + handler(req, res, content_reader); + } + return true; + } + } + return false; +} + +inline std::string get_client_ip(const std::string &x_forwarded_for, const std::vector &trusted_proxies) +{ + // X-Forwarded-For is a comma-separated list per RFC 7239 + std::vector ip_list; + detail::split(x_forwarded_for.data(), x_forwarded_for.data() + x_forwarded_for.size(), ',', [&](const char *b, const char *e) { + auto r = detail::trim(b, e, 0, static_cast(e - b)); + ip_list.emplace_back(std::string(b + r.first, b + r.second)); + }); + + // A malformed X-Forwarded-For (empty, comma-only, whitespace-only) yields + // no segments. Signal "no client IP derived" with an empty string so the + // caller can fall back to the connection-level remote address. + if (ip_list.empty()) { + return std::string(); + } + + // Each hop appends the address it received the request from, so the rightmost + // entries are the ones written by our own infrastructure while the leftmost + // are whatever the original client chose to send. Walk from the right and + // skip trusted proxies; the first address that is not a trusted proxy is the + // furthest point still attributable to a real hop, i.e. the client. Scanning + // from the left instead lets a client forge an arbitrary address by following + // it with a trusted proxy's address, which the left-to-right scan then + // returned as the client. + for (size_t i = ip_list.size(); i-- > 0;) { + const auto &ip = ip_list[i]; + + auto is_trusted_proxy = + std::any_of(trusted_proxies.begin(), trusted_proxies.end(), [&](const std::string &proxy) { return ip == proxy; }); + + if (!is_trusted_proxy) { + return ip; + } + } + + // Every hop was a trusted proxy; fall back to the first entry. + return ip_list.front(); +} + +inline bool Server::process_request(Stream &strm, + const std::string &remote_addr, + int remote_port, + const std::string &local_addr, + int local_port, + bool close_connection, + bool &connection_closed, + const std::function &setup_request, + bool *websocket_upgraded) +{ + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + // Connection has been closed on client + if (!line_reader.getline()) { + return false; + } + + Request req; + req.start_time_ = std::chrono::steady_clock::now(); + req.remote_addr = remote_addr; + req.remote_port = remote_port; + req.local_addr = local_addr; + req.local_port = local_port; + + Response res; + res.version = "HTTP/1.1"; + res.headers = default_headers_; + + // Request line and headers + if (!parse_request_line(line_reader.ptr(), req)) { + res.status = StatusCode::BadRequest_400; + output_error_log(Error::InvalidRequestLine, &req); + return write_response(strm, close_connection, req, res); + } + + // Request headers + if (!detail::read_headers(strm, req.headers)) { + res.status = StatusCode::BadRequest_400; + output_error_log(Error::InvalidHeaders, &req); + return write_response(strm, close_connection, req, res); + } + + // RFC 9112 §6.3: Reject requests whose framing is ambiguous, which would + // otherwise let an intermediary and this parser disagree on where the body + // ends and enable request smuggling. Two cases: a non-zero Content-Length + // alongside any Transfer-Encoding (Content-Length: 0 is tolerated for + // compatibility with existing clients), and a Transfer-Encoding whose final + // coding is not chunked, which leaves the body length undeterminable. The + // latter must not fall through to the "no body" path, or the body bytes are + // parsed as the next request on a persistent connection. + if (req.has_header("Transfer-Encoding") && + (req.get_header_value_u64("Content-Length") > 0 || !detail::is_chunked_transfer_encoding(req.headers))) { + connection_closed = true; + res.status = StatusCode::BadRequest_400; + return write_response(strm, close_connection, req, res); + } + + // Check if the request URI doesn't exceed the limit + if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + connection_closed = true; + res.status = StatusCode::UriTooLong_414; + output_error_log(Error::ExceedUriMaxLength, &req); + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") { + connection_closed = true; + } + + if (req.version == "HTTP/1.0" && req.get_header_value("Connection") != "Keep-Alive") { + connection_closed = true; + } + + // Only honor X-Forwarded-For if the peer on the actual TCP connection is + // itself a trusted proxy. Otherwise any direct client could spoof + // remote_addr simply by sending an arbitrary X-Forwarded-For header. + auto is_trusted_peer = + std::any_of(trusted_proxies_.begin(), trusted_proxies_.end(), [&](const std::string &proxy) { return proxy == remote_addr; }); + + if (is_trusted_peer && req.has_header("X-Forwarded-For")) { + auto x_forwarded_for = req.get_header_value("X-Forwarded-For"); + auto derived = get_client_ip(x_forwarded_for, trusted_proxies_); + req.remote_addr = derived.empty() ? remote_addr : derived; + } + else { + req.remote_addr = remote_addr; + } + req.remote_port = remote_port; + + req.local_addr = local_addr; + req.local_port = local_port; + + if (req.has_header("Accept")) { + const auto &accept_header = req.get_header_value("Accept"); + if (!detail::parse_accept_header(accept_header, req.accept_content_types)) { + connection_closed = true; + res.status = StatusCode::BadRequest_400; + output_error_log(Error::HTTPParsing, &req); + return write_response(strm, close_connection, req, res); + } + } + + if (req.has_header("Range")) { + const auto &range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) { + connection_closed = true; + res.status = StatusCode::RangeNotSatisfiable_416; + output_error_log(Error::InvalidRangeHeader, &req); + return write_response(strm, close_connection, req, res); + } + } + + if (setup_request) { + setup_request(req); + } + + if (req.get_header_value("Expect") == "100-continue") { + int status = StatusCode::Continue_100; + if (expect_100_continue_handler_) { + status = expect_100_continue_handler_(req, res); + } + switch (status) { + case StatusCode::Continue_100: + case StatusCode::ExpectationFailed_417: + detail::write_response_line(strm, status); + strm.write("\r\n"); + break; + default: + connection_closed = true; + return write_response(strm, true, req, res); + } + } + + // Setup `is_connection_closed` method + auto sock = strm.socket(); + req.is_connection_closed = [sock]() { return !detail::is_socket_alive(sock); }; + + // WebSocket upgrade + // Check pre_routing_handler_ before upgrading so that authentication + // and other middleware can reject the request with an HTTP response + // (e.g., 401) before the protocol switches. + if (detail::is_websocket_upgrade(req)) { + if (pre_routing_handler_ && pre_routing_handler_(req, res) == HandlerResponse::Handled) { + if (res.status == -1) { + res.status = StatusCode::OK_200; + } + return write_response(strm, close_connection, req, res); + } + // Find matching WebSocket handler + for (const auto &entry : websocket_handlers_) { + if (entry.matcher->match(req)) { + // Compute accept key + auto client_key = req.get_header_value("Sec-WebSocket-Key"); + auto accept_key = detail::websocket_accept_key(client_key); + + // Negotiate subprotocol + std::string selected_subprotocol; + if (entry.sub_protocol_selector) { + auto protocol_header = req.get_header_value("Sec-WebSocket-Protocol"); + if (!protocol_header.empty()) { + std::vector protocols; + std::istringstream iss(protocol_header); + std::string token; + while (std::getline(iss, token, ',')) { + // Trim whitespace + auto start = token.find_first_not_of(' '); + auto end = token.find_last_not_of(' '); + if (start != std::string::npos) { + protocols.push_back(token.substr(start, end - start + 1)); + } + } + selected_subprotocol = entry.sub_protocol_selector(protocols); + } + } + + // Send 101 Switching Protocols + std::string handshake_response = + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: " + + accept_key + "\r\n"; + if (!selected_subprotocol.empty()) { + if (!detail::fields::is_field_value(selected_subprotocol)) { + return false; + } + handshake_response += "Sec-WebSocket-Protocol: " + selected_subprotocol + "\r\n"; + } + handshake_response += "\r\n"; + if (strm.write(handshake_response.data(), handshake_response.size()) < 0) { + return false; + } + + connection_closed = true; + if (websocket_upgraded) { + *websocket_upgraded = true; + } + + { + // Use WebSocket-specific read timeout instead of HTTP timeout + strm.set_read_timeout(CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND, 0); + ws::WebSocket ws(strm, req, true, websocket_ping_interval_sec_, websocket_max_missed_pongs_); + entry.handler(req, ws); + } + return true; + } + } + // No matching handler - fall through to 404 + } + + // Routing + auto routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try { + routed = routing(req, res, strm); + } + catch (std::exception &) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } + else { + res.status = StatusCode::InternalServerError_500; + } + } + catch (...) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } + else { + res.status = StatusCode::InternalServerError_500; + } + } +#endif + auto ret = false; + if (routed) { + if (res.status == -1) { + res.status = req.ranges.empty() ? StatusCode::OK_200 : StatusCode::PartialContent_206; + } + + // Serve file content by using a content provider + auto file_open_error = false; + if (!res.file_content_path_.empty()) { + const auto &path = res.file_content_path_; + auto mm = std::make_shared(path.c_str()); + if (!mm->is_open()) { + res.body.clear(); + res.content_length_ = 0; + res.content_provider_ = nullptr; + res.status = StatusCode::NotFound_404; + output_error_log(Error::OpenFile, &req); + file_open_error = true; + } + else { + auto content_type = res.file_content_content_type_; + if (content_type.empty()) { + content_type = detail::find_content_type(path, file_extension_and_mimetype_map_, default_file_mimetype_); + } + + res.set_content_provider(mm->size(), content_type, [mm](size_t offset, size_t length, DataSink &sink) -> bool { + sink.write(mm->data() + offset, length); + return true; + }); + } + } + + if (file_open_error) { + ret = write_response(strm, close_connection, req, res); + } + else if (detail::range_error(req, res)) { + res.body.clear(); + res.content_length_ = 0; + res.content_provider_ = nullptr; + res.status = StatusCode::RangeNotSatisfiable_416; + ret = write_response(strm, close_connection, req, res); + } + else { + ret = write_response_with_content(strm, close_connection, req, res); + } + } + else { + if (res.status == -1) { + res.status = StatusCode::NotFound_404; + } + ret = write_response(strm, close_connection, req, res); + } + + // Drain any unconsumed framed body to prevent request smuggling on + // keep-alive. Without framing there is no body to drain — reading would + // consume the next request (issue #2450). If the response has committed the + // connection to close, there is no next request to protect. + if (!req.body_consumed_ && detail::has_framed_body(req)) { + if (res.get_header_value("Connection") == "close") { + connection_closed = true; + } + else { + int dummy_status; + if (!detail::read_content( + strm, req, payload_max_length_, dummy_status, nullptr, [](const char *, size_t, size_t, size_t) { return true; }, false)) { + connection_closed = true; + } + } + } + + return ret; +} + +inline bool Server::is_valid() const +{ + return true; +} + +inline bool Server::process_and_close_socket(socket_t sock) +{ + std::string remote_addr; + int remote_port = 0; + detail::get_remote_ip_and_port(sock, remote_addr, remote_port); + + std::string local_addr; + int local_port = 0; + detail::get_local_ip_and_port(sock, local_addr, local_port); + + bool websocket_upgraded = false; + auto ret = detail::process_server_socket( + svr_sock_, + sock, + keep_alive_max_count_, + keep_alive_timeout_sec_, + read_timeout_sec_, + read_timeout_usec_, + write_timeout_sec_, + write_timeout_usec_, + [&](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request( + strm, remote_addr, remote_port, local_addr, local_port, close_connection, connection_closed, nullptr, &websocket_upgraded); + }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +inline void Server::output_log(const Request &req, const Response &res) const +{ + if (logger_) { + std::lock_guard guard(logger_mutex_); + logger_(req, res); + } +} + +inline void Server::output_pre_compression_log(const Request &req, const Response &res) const +{ + if (pre_compression_logger_) { + std::lock_guard guard(logger_mutex_); + pre_compression_logger_(req, res); + } +} + +inline void Server::output_error_log(const Error &err, const Request *req) const +{ + if (error_logger_) { + std::lock_guard guard(logger_mutex_); + error_logger_(err, req); + } +} + +/* + * Group 5: ClientImpl and Client (Universal) implementation + */ +// HTTP client implementation +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) +{ +} + +inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) +{ +} + +inline ClientImpl::ClientImpl(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path) + : host_(detail::escape_abstract_namespace_unix_domain(host)) + , port_(port) + , client_cert_path_(client_cert_path) + , client_key_path_(client_key_path) +{ +} + +inline ClientImpl::~ClientImpl() +{ + // Wait until all the requests in flight are handled. + size_t retry_count = 10; + while (retry_count-- > 0) { + { + std::lock_guard guard(socket_mutex_); + if (socket_requests_in_flight_ == 0) { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } + + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline bool ClientImpl::is_valid() const +{ + return true; +} + +inline void ClientImpl::copy_settings(const ClientImpl &rhs) +{ + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + max_timeout_msec_ = rhs.max_timeout_msec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + path_encode_ = rhs.path_encode_; + address_family_ = rhs.address_family_; + tcp_nodelay_ = rhs.tcp_nodelay_; + ipv6_v6only_ = rhs.ipv6_v6only_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + payload_max_length_ = rhs.payload_max_length_; + has_payload_max_length_ = rhs.has_payload_max_length_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; + no_proxy_entries_ = rhs.no_proxy_entries_; + logger_ = rhs.logger_; + error_logger_ = rhs.error_logger_; + +#ifdef CPPHTTPLIB_SSL_ENABLED + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + server_certificate_verification_ = rhs.server_certificate_verification_; + server_hostname_verification_ = rhs.server_hostname_verification_; + system_ca_mode_ = rhs.system_ca_mode_; +#endif +} + +inline bool ClientImpl::is_proxy_enabled_for_host(const std::string &host) const +{ + if (proxy_host_.empty() || proxy_port_ == -1) { + return false; + } + if (no_proxy_entries_.empty()) { + return true; + } + // host_ is const so its normalized form is invariant; cache it. The + // cross-host path (setup_redirect_client passing next_host) re-normalizes. + if (host == host_) { + if (!host_normalized_valid_) { + host_normalized_ = detail::normalize_target(host_); + host_normalized_valid_ = true; + } + return !detail::host_matches_no_proxy(host_normalized_, no_proxy_entries_); + } + auto target = detail::normalize_target(host); + return !detail::host_matches_no_proxy(target, no_proxy_entries_); +} + +inline socket_t ClientImpl::create_client_socket(Error &error) const +{ + if (is_proxy_enabled_for_host(host_)) { + return detail::create_client_socket(proxy_host_, + std::string(), + proxy_port_, + address_family_, + tcp_nodelay_, + ipv6_v6only_, + socket_options_, + connection_timeout_sec_, + connection_timeout_usec_, + read_timeout_sec_, + read_timeout_usec_, + write_timeout_sec_, + write_timeout_usec_, + interface_, + error); + } + + // Check is custom IP or hostname specified for host_ + std::string connect_host; + std::string ip; + detail::apply_addr_map(addr_map_, host_, connect_host, ip); + + return detail::create_client_socket(connect_host, + ip, + port_, + address_family_, + tcp_nodelay_, + ipv6_v6only_, + socket_options_, + connection_timeout_sec_, + connection_timeout_usec_, + read_timeout_sec_, + read_timeout_usec_, + write_timeout_sec_, + write_timeout_usec_, + interface_, + error); +} + +inline bool ClientImpl::create_and_connect_socket(Socket &socket, Error &error) +{ + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { + return false; + } + socket.sock = sock; + return true; +} + +inline bool ClientImpl::ensure_socket_connection(Socket &socket, Error &error) +{ + return create_and_connect_socket(socket, error); +} + +inline bool ClientImpl::setup_proxy_connection(Socket & /*socket*/, + std::chrono::time_point /*start_time*/, + Response & /*res*/, + bool & /*success*/, + Error & /*error*/) +{ + return true; +} + +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, bool /*shutdown_gracefully*/) +{ + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || socket_requests_are_from_thread_ == std::this_thread::get_id()); +} + +inline void ClientImpl::shutdown_socket(Socket &socket) const +{ + if (socket.sock == INVALID_SOCKET) { + return; + } + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) +{ + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_SSL_ENABLED + assert(socket.ssl == nullptr); +#endif + + if (socket.sock == INVALID_SOCKET) { + return; + } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline void ClientImpl::disconnect(bool gracefully) +{ + shutdown_ssl(socket_, gracefully); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, Response &res, bool skip_100_continue) const +{ + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { + return false; + } + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#else + thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#endif + + std::cmatch m; + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' (only when not using Expect: 100-continue explicitly) + while (skip_100_continue && res.status == StatusCode::Continue_100) { + if (!line_reader.getline()) { + return false; + } // CRLF + if (!line_reader.getline()) { + return false; + } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { + return false; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + } + + return true; +} + +inline bool ClientImpl::send(Request &req, Response &res, Error &error) +{ + std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + // If still failing with SSLPeerCouldBeClosed_, convert to Read error + if (error == Error::SSLPeerCouldBeClosed_) { + error = Error::Read; + } + } + return ret; +} + +inline bool ClientImpl::send_(Request &req, Response &res, Error &error) +{ + { + std::lock_guard guard(socket_mutex_); + + // Set this to false immediately - if it ever gets set to true by the end + // of the request, we know another thread instructed us to close the + // socket. + socket_should_be_closed_when_request_is_done_ = false; + + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::is_socket_alive(socket_.sock); + +#ifdef CPPHTTPLIB_SSL_ENABLED + if (is_alive && is_ssl()) { + if (tls::is_peer_closed(socket_.ssl, socket_.sock)) { + is_alive = false; + } + } +#endif + + if (!is_alive) { + // Peer seems gone — non-graceful shutdown to avoid SIGPIPE. + disconnect(/*gracefully=*/false); + } + } + + if (!is_alive) { + if (!ensure_socket_connection(socket_, error)) { + output_error_log(error, &req); + return false; + } + + { + auto success = true; + if (!setup_proxy_connection(socket_, req.start_time_, res, success, error)) { + if (!success) { + output_error_log(error, &req); + } + return success; + } + } + } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto ret = false; + auto close_connection = !keep_alive_; + + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || !ret) { + disconnect(/*gracefully=*/true); + } + }); + + ret = process_socket(socket_, req.start_time_, [&](Stream &strm) { return handle_request(strm, req, res, close_connection, error); }); + + if (!ret) { + if (error == Error::Success) { + error = Error::Unknown; + output_error_log(error, &req); + } + } + + return ret; +} + +inline Result ClientImpl::send(const Request &req) +{ + auto req2 = req; + return send_(std::move(req2)); +} + +inline Result ClientImpl::send_(Request &&req) +{ + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); +#ifdef CPPHTTPLIB_SSL_ENABLED + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers), last_ssl_error_, last_backend_error_}; +#else + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +#endif +} + +inline void ClientImpl::prepare_default_headers(Request &r, bool for_stream, const std::string &ct) +{ + (void)for_stream; + for (const auto &header : default_headers_) { + if (!r.has_header(header.first)) { + r.headers.insert(header); + } + } + + // RFC 9110 5.3 recommends sending control data such as Host first, so + // prepend it rather than appending it after the caller's own fields. + if (!r.has_header("Host")) { + if (address_family_ == AF_UNIX) { + r.headers.emplace_front("Host", "localhost"); + } + else { + r.headers.emplace_front("Host", detail::make_host_and_port_string(host_, port_, is_ssl())); + } + } + + if (!r.has_header("Accept")) { + r.headers.emplace("Accept", "*/*"); + } + + if (!r.content_receiver) { + if (!r.has_header("Accept-Encoding")) { + std::string accept_encoding; +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + accept_encoding = "br"; +#endif +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (!accept_encoding.empty()) { + accept_encoding += ", "; + } + accept_encoding += "gzip, deflate"; +#endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + if (!accept_encoding.empty()) { + accept_encoding += ", "; + } + accept_encoding += "zstd"; +#endif + r.set_header("Accept-Encoding", accept_encoding); + } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!r.has_header("User-Agent")) { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + r.set_header("User-Agent", agent); + } +#endif + } + + if (!r.body.empty()) { + if (!ct.empty() && !r.has_header("Content-Type")) { + r.headers.emplace("Content-Type", ct); + } + if (!r.has_header("Content-Length")) { + r.headers.emplace("Content-Length", std::to_string(r.body.size())); + } + } +} + +inline ClientImpl::StreamHandle ClientImpl::open_stream(const std::string &method, + const std::string &path, + const Params ¶ms, + const Headers &headers, + const std::string &body, + const std::string &content_type) +{ + StreamHandle handle; + handle.response = detail::make_unique(); + handle.error = Error::Success; + + // Encode the target exactly like the buffered send path does, so that the + // same `path` produces the same request line through either API. + auto raw_query_path = params.empty() ? path : append_query_params(path, params); + auto query_path = detail::encode_request_target(raw_query_path, path_encode_); + + handle.connection_ = detail::make_unique(); + + { + std::lock_guard guard(socket_mutex_); + + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::is_socket_alive(socket_.sock); +#ifdef CPPHTTPLIB_SSL_ENABLED + if (is_alive && is_ssl()) { + if (tls::is_peer_closed(socket_.ssl, socket_.sock)) { + is_alive = false; + } + } +#endif + if (!is_alive) { + disconnect(/*gracefully=*/false); + } + } + + if (!is_alive) { + if (!ensure_socket_connection(socket_, handle.error)) { + handle.response.reset(); + return handle; + } + + { + auto success = true; + auto start_time = std::chrono::steady_clock::now(); + if (!setup_proxy_connection(socket_, start_time, *handle.response, success, handle.error)) { + if (!success) { + handle.response.reset(); + } + return handle; + } + } + } + + transfer_socket_ownership_to_handle(handle); + } + +#ifdef CPPHTTPLIB_SSL_ENABLED + if (is_ssl() && handle.connection_->session) { + handle.socket_stream_ = detail::make_unique(handle.connection_->sock, + handle.connection_->session, + read_timeout_sec_, + read_timeout_usec_, + write_timeout_sec_, + write_timeout_usec_); + } + else { + handle.socket_stream_ = detail::make_unique( + handle.connection_->sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_); + } +#else + handle.socket_stream_ = detail::make_unique( + handle.connection_->sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_); +#endif + handle.stream_ = handle.socket_stream_.get(); + + Request req; + req.method = method; + req.path = query_path; + req.headers = headers; + req.body = body; + + prepare_default_headers(req, true, content_type); + + auto &strm = *handle.stream_; + if (detail::write_request_line(strm, req.method, req.path) < 0) { + handle.error = Error::Write; + handle.response.reset(); + return handle; + } + + if (!detail::check_and_write_headers(strm, req.headers, header_writer_, handle.error)) { + handle.response.reset(); + return handle; + } + + if (!body.empty()) { + if (strm.write(body.data(), body.size()) < 0) { + handle.error = Error::Write; + handle.response.reset(); + return handle; + } + } + + if (!read_response_line(strm, req, *handle.response) || !detail::read_headers(strm, handle.response->headers)) { + handle.error = Error::Read; + handle.response.reset(); + return handle; + } + + handle.body_reader_.stream = handle.stream_; + handle.body_reader_.payload_max_length = payload_max_length_; + + if (handle.response->has_header("Content-Length")) { + bool is_invalid = false; + auto content_length = detail::get_header_value_u64(handle.response->headers, "Content-Length", 0, 0, is_invalid); + if (is_invalid) { + handle.error = Error::Read; + handle.response.reset(); + return handle; + } + handle.body_reader_.has_content_length = true; + handle.body_reader_.content_length = content_length; + } + + handle.body_reader_.chunked = detail::is_chunked_transfer_encoding(handle.response->headers); + + auto content_encoding = handle.response->get_header_value("Content-Encoding"); + if (!content_encoding.empty()) { + // Same policy as prepare_content_receiver(): reject a coding we know about + // but were not built with, pass an unrecognized one through as-is. + handle.decompressor_ = detail::create_decompressor(content_encoding); + if (!handle.decompressor_) { + if (detail::is_known_content_encoding(content_encoding)) { + handle.error = Error::UnsupportedContentEncoding; + handle.response.reset(); + return handle; + } + } + else if (!handle.decompressor_->is_valid()) { + handle.error = Error::Compression; + handle.response.reset(); + return handle; + } + } + + return handle; +} + +inline ssize_t ClientImpl::StreamHandle::read(char *buf, size_t len) +{ + if (!is_valid() || !response) { + return -1; + } + + if (decompressor_) { + return read_with_decompression(buf, len); + } + auto n = detail::read_body_content(stream_, body_reader_, buf, len); + + if (n <= 0 && body_reader_.chunked && !trailers_parsed_ && stream_) { + trailers_parsed_ = true; + if (body_reader_.chunked_decoder) { + if (!body_reader_.chunked_decoder->parse_trailers_into(response->trailers, response->headers)) { + return n; + } + } + else { + detail::ChunkedDecoder dec(*stream_); + if (!dec.parse_trailers_into(response->trailers, response->headers)) { + return n; + } + } + } + + return n; +} + +inline ssize_t ClientImpl::StreamHandle::read_with_decompression(char *buf, size_t len) +{ + if (decompress_offset_ < decompress_buffer_.size()) { + auto available = decompress_buffer_.size() - decompress_offset_; + auto to_copy = (std::min)(len, available); + std::memcpy(buf, decompress_buffer_.data() + decompress_offset_, to_copy); + decompress_offset_ += to_copy; + decompressed_bytes_read_ += to_copy; + return static_cast(to_copy); + } + + decompress_buffer_.clear(); + decompress_offset_ = 0; + + constexpr size_t kDecompressionBufferSize = 8192; + char compressed_buf[kDecompressionBufferSize]; + + while (true) { + auto n = detail::read_body_content(stream_, body_reader_, compressed_buf, sizeof(compressed_buf)); + + if (n <= 0) { + return n; + } + + bool decompress_ok = decompressor_->decompress(compressed_buf, static_cast(n), [this](const char *data, size_t data_len) { + decompress_buffer_.append(data, data_len); + auto limit = body_reader_.payload_max_length; + if (decompressed_bytes_read_ + decompress_buffer_.size() > limit) { + return false; + } + return true; + }); + + if (!decompress_ok) { + body_reader_.last_error = Error::Read; + return -1; + } + + if (!decompress_buffer_.empty()) { + break; + } + } + + auto to_copy = (std::min)(len, decompress_buffer_.size()); + std::memcpy(buf, decompress_buffer_.data(), to_copy); + decompress_offset_ = to_copy; + decompressed_bytes_read_ += to_copy; + return static_cast(to_copy); +} + +inline void ClientImpl::StreamHandle::parse_trailers_if_needed() +{ + if (!response || !stream_ || !body_reader_.chunked || trailers_parsed_) { + return; + } + + trailers_parsed_ = true; + + const auto bufsiz = 128; + char line_buf[bufsiz]; + detail::stream_line_reader line_reader(*stream_, line_buf, bufsiz); + + if (!line_reader.getline()) { + return; + } + + if (!detail::parse_trailers(line_reader, response->trailers, response->headers)) { + return; + } +} + +namespace detail { + +inline ChunkedDecoder::ChunkedDecoder(Stream &s) + : strm(s) +{ +} + +inline ssize_t ChunkedDecoder::read_payload(char *buf, size_t len, size_t &out_chunk_offset, size_t &out_chunk_total) +{ + if (finished) { + return 0; + } + + if (chunk_remaining == 0) { + stream_line_reader lr(strm, line_buf, sizeof(line_buf)); + if (!lr.getline()) { + return -1; + } + + // RFC 9112 §7.1: chunk-size = 1*HEXDIG + const char *p = lr.ptr(); + int v = 0; + if (!is_hex(*p, v)) { + return -1; + } + + size_t chunk_len = 0; + constexpr size_t chunk_len_max = (std::numeric_limits::max)(); + for (; is_hex(*p, v); ++p) { + if (chunk_len > (chunk_len_max >> 4)) { + return -1; + } + chunk_len = (chunk_len << 4) | static_cast(v); + } + + while (is_space_or_tab(*p)) { + ++p; + } + if (*p != '\0' && *p != ';' && *p != '\r' && *p != '\n') { + return -1; + } + + if (chunk_len == 0) { + chunk_remaining = 0; + finished = true; + out_chunk_offset = 0; + out_chunk_total = 0; + return 0; + } + + chunk_remaining = chunk_len; + last_chunk_total = chunk_remaining; + last_chunk_offset = 0; + } + + auto to_read = (std::min)(chunk_remaining, len); + auto n = strm.read(buf, to_read); + if (n <= 0) { + return -1; + } + + auto offset_before = last_chunk_offset; + last_chunk_offset += static_cast(n); + chunk_remaining -= static_cast(n); + + out_chunk_offset = offset_before; + out_chunk_total = last_chunk_total; + + if (chunk_remaining == 0) { + stream_line_reader lr(strm, line_buf, sizeof(line_buf)); + if (!lr.getline()) { + return -1; + } + if (std::strcmp(lr.ptr(), "\r\n") != 0) { + return -1; + } + } + + return n; +} + +inline bool ChunkedDecoder::parse_trailers_into(Headers &dest, const Headers &src_headers) +{ + stream_line_reader lr(strm, line_buf, sizeof(line_buf)); + if (!lr.getline()) { + return false; + } + return parse_trailers(lr, dest, src_headers); +} + +} // namespace detail + +inline void ClientImpl::transfer_socket_ownership_to_handle(StreamHandle &handle) +{ + handle.connection_->sock = socket_.sock; +#ifdef CPPHTTPLIB_SSL_ENABLED + handle.connection_->session = socket_.ssl; + socket_.ssl = nullptr; +#endif + socket_.sock = INVALID_SOCKET; +} + +inline bool ClientImpl::handle_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error) +{ + if (req.path.empty()) { + error = Error::Connection; + output_error_log(error, &req); + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && is_proxy_enabled_for_host(host_)) { + auto req2 = req; + req2.path = "http://" + detail::make_host_and_port_string(host_, port_, false) + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = std::move(req2); + req.path = req_save.path; + } + else { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { + return false; + } + + if (res.get_header_value("Connection") == "close" || (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // NOTE: this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. + + // This is safe to call because handle_request is only called by send_ + // which locks the request mutex during the process. It would be a bug + // to call it from a different thread since it's a thread-safety issue + // to do these things to the socket if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + disconnect(/*gracefully=*/true); + } + + if (300 < res.status && res.status < 400 && follow_location_) { + req = std::move(req_save); + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_SSL_ENABLED + if ((res.status == StatusCode::Unauthorized_401 || res.status == StatusCode::ProxyAuthenticationRequired_407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407; + + // Only retry when the 407 actually came from a proxy hop: plain HTTP + // through an enabled proxy. HTTPS via CONNECT tunnels the 407 from the + // origin (#2457); direct/bypassed origins have no proxy hop at all. + if (is_proxy && !(!is_ssl() && is_proxy_enabled_for_host(host_))) { + return ret; + } + + const auto &username = is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + new_req.headers.erase(is_proxy ? "Proxy-Authorization" : "Authorization"); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { + res = std::move(new_res); + } + } + } + } +#endif + + return ret; +} + +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) +{ + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; + output_error_log(error, &req); + return false; + } + + auto location = res.get_header_value("location"); + if (location.empty()) { + return false; + } + + detail::UrlComponents uc; + if (!detail::parse_url(location, uc)) { + return false; + } + + // Only follow http/https redirects + if (!uc.scheme.empty() && uc.scheme != "http" && uc.scheme != "https") { + return false; + } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = std::move(uc.scheme); + auto next_host = std::move(uc.host); + auto port_str = std::move(uc.port); + auto next_path = std::move(uc.path); + auto next_query = std::move(uc.query); + + auto next_port = port_; + if (!port_str.empty()) { + if (!detail::parse_port(port_str, next_port)) { + return false; + } + } + else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { + next_scheme = scheme; + } + if (next_host.empty()) { + next_host = host_; + } + if (next_path.empty()) { + next_path = "/"; + } + + auto path = decode_path_component(next_path) + next_query; + + // Same host redirect - use current client + if (next_scheme == scheme && next_host == host_ && next_port == port_) { + return detail::redirect(*this, req, res, path, location, error); + } + + // Cross-host/scheme redirect - create new client with robust setup + return create_redirect_client(next_scheme, next_host, next_port, req, res, path, location, error); +} + +// New method for robust redirect client creation +inline bool ClientImpl::create_redirect_client(const std::string &scheme, + const std::string &host, + int port, + Request &req, + Response &res, + const std::string &path, + const std::string &location, + Error &error) +{ + // Determine if we need SSL + auto need_ssl = (scheme == "https"); + + // Clean up request headers that are host/client specific + // Remove headers that should not be carried over to new host + auto headers_to_remove = std::vector{"Host", "Proxy-Authorization", "Authorization", "Cookie", "Cookie2"}; + + for (const auto &header_name : headers_to_remove) { + auto it = req.headers.find(header_name); + while (it != req.headers.end()) { + it = req.headers.erase(it); + it = req.headers.find(header_name); + } + } + + // Create appropriate client type and handle redirect + if (need_ssl) { +#ifdef CPPHTTPLIB_SSL_ENABLED + // Create SSL client for HTTPS redirect + SSLClient redirect_client(host, port); + + // Setup basic client configuration first + setup_redirect_client(redirect_client); + + redirect_client.enable_server_certificate_verification(server_certificate_verification_); + redirect_client.enable_server_hostname_verification(server_hostname_verification_); + redirect_client.system_ca_mode_ = system_ca_mode_; + + // Transfer CA certificate to redirect client + if (!ca_cert_pem_.empty()) { + redirect_client.load_ca_cert_store(ca_cert_pem_.c_str(), ca_cert_pem_.size()); + } + if (!ca_cert_file_path_.empty()) { + redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_); + } + + // Client certificates are set through constructor for SSLClient + // NOTE: SSLClient constructor already takes client_cert_path and + // client_key_path so we need to create it properly if client certs are + // needed + + // Execute the redirect + return detail::redirect(redirect_client, req, res, path, location, error); +#else + // SSL not supported - set appropriate error + error = Error::SSLConnection; + output_error_log(error, &req); + return false; +#endif + } + else { + // HTTP redirect + ClientImpl redirect_client(host, port); + + // Setup client with robust configuration + setup_redirect_client(redirect_client); + + // Execute the redirect + return detail::redirect(redirect_client, req, res, path, location, error); + } +} + +// New method for robust client setup (based on basic_manual_redirect.cpp +// logic) +template +inline void ClientImpl::setup_redirect_client(ClientType &client) +{ + // Copy basic settings first + client.set_connection_timeout(connection_timeout_sec_); + client.set_read_timeout(read_timeout_sec_, read_timeout_usec_); + client.set_write_timeout(write_timeout_sec_, write_timeout_usec_); + client.set_keep_alive(keep_alive_); + client.set_follow_location(true); // Enable redirects to handle multi-step redirects + client.set_path_encode(path_encode_); + client.set_compress(compress_); + client.set_decompress(decompress_); + + // NOTE: Authentication credentials (basic auth, bearer token, digest auth) + // are intentionally NOT copied to the redirect client. Per RFC 9110 Section + // 15.4, credentials must not be forwarded when redirecting to a different + // host. This function is only called for cross-host redirects; same-host + // redirects are handled directly in ClientImpl::redirect(). + + // Copy the proxy configuration unconditionally; the per-target bypass is + // re-evaluated at send time, so a later hop to a non-bypassed host can + // still use the proxy. + client.no_proxy_entries_ = no_proxy_entries_; + if (!proxy_host_.empty() && proxy_port_ != -1) { + client.set_proxy(proxy_host_, proxy_port_); + + if (!proxy_basic_auth_username_.empty()) { + client.set_proxy_basic_auth(proxy_basic_auth_username_, proxy_basic_auth_password_); + } + if (!proxy_bearer_token_auth_token_.empty()) { + client.set_proxy_bearer_token_auth(proxy_bearer_token_auth_token_); + } +#ifdef CPPHTTPLIB_SSL_ENABLED + if (!proxy_digest_auth_username_.empty()) { + client.set_proxy_digest_auth(proxy_digest_auth_username_, proxy_digest_auth_password_); + } +#endif + } + + // Copy network and socket settings + client.set_address_family(address_family_); + client.set_tcp_nodelay(tcp_nodelay_); + client.set_ipv6_v6only(ipv6_v6only_); + if (socket_options_) { + client.set_socket_options(socket_options_); + } + if (!interface_.empty()) { + client.set_interface(interface_); + } + + // Copy logging and headers + if (logger_) { + client.set_logger(logger_); + } + if (error_logger_) { + client.set_error_logger(error_logger_); + } + + // NOTE: DO NOT copy default_headers_ as they may contain stale Host headers + // Each new client should generate its own headers based on its target host +} + +inline bool ClientImpl::write_content_with_provider(Stream &strm, const Request &req, Error &error) const +{ + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) { + auto compressor = compress_ ? detail::create_compressor().first : std::unique_ptr(); + if (!compressor) { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, req.content_provider_, is_shutting_down, *compressor, error); + } + else { + return detail::write_content_with_progress( + strm, req.content_provider_, 0, req.content_length_, is_shutting_down, req.upload_progress, error); + } +} + +inline bool ClientImpl::write_request(Stream &strm, Request &req, bool close_connection, Error &error, bool skip_body) +{ + // Prepare additional headers + if (close_connection) { + if (!req.has_header("Connection")) { + req.set_header("Connection", "close"); + } + } + + std::string ct_for_defaults; + if (!req.has_header("Content-Type") && !req.body.empty()) { + ct_for_defaults = "text/plain"; + } + prepare_default_headers(req, false, ct_for_defaults); + + if (req.body.empty()) { + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.content_length_); + req.set_header("Content-Length", length); + } + } + } + else { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH") { + req.set_header("Content-Length", "0"); + } + } + } + + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_basic_authentication_header(basic_auth_username_, basic_auth_password_, false)); + } + } + + if (!bearer_token_auth_token_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_bearer_token_authentication_header(bearer_token_auth_token_, false)); + } + } + + // Proxy-Authorization is only sent when the proxy is actually used for + // this target — otherwise NO_PROXY-matched requests would leak proxy + // credentials directly to the destination server. + if (is_proxy_enabled_for_host(host_)) { + if (!proxy_basic_auth_username_.empty() && !proxy_basic_auth_password_.empty() && !req.has_header("Proxy-Authorization")) { + req.headers.insert(make_basic_authentication_header(proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + if (!proxy_bearer_token_auth_token_.empty() && !req.has_header("Proxy-Authorization")) { + req.headers.insert(make_bearer_token_authentication_header(proxy_bearer_token_auth_token_, true)); + } + } + + // Request line and headers + { + detail::BufferStream bstrm; + + // Extract the query from req.path. The encoding itself is delegated to + // `encode_request_target`; the raw query is still needed here to decide + // between populating `req.params` from it and falling back to building a + // query out of caller-supplied `req.params`. + auto query_pos = req.path.find('?'); + auto query_part = query_pos == std::string::npos ? std::string() : req.path.substr(query_pos + 1); + + auto path_with_query = detail::encode_request_target(req.path, path_encode_); + + if (!query_part.empty()) { + // The query already came in through `req.path`; still populate + // `req.params` for handlers/users who read them. + detail::parse_query_text(query_part, req.params); + } + else if (!req.params.empty()) { + // No query in `req.path`; build one from `req.params` so existing + // callers that pass `Params` separately continue to work. + path_with_query = append_query_params(path_with_query, req.params); + } + + // Write request line and headers + if (detail::write_request_line(bstrm, req.method, path_with_query) < 0) { + // A rejected target (e.g. CR/LF smuggled in via a decoded redirect + // Location under set_path_encode(false)) must fail the request cleanly + // instead of emitting a request-line-less, header-injecting request. + error = Error::Write; + output_error_log(error, &req); + return false; + } + if (!detail::check_and_write_headers(bstrm, req.headers, header_writer_, error)) { + output_error_log(error, &req); + return false; + } + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + output_error_log(error, &req); + return false; + } + } + + // After sending request line and headers, wait briefly for an early server + // response (e.g. 4xx) and avoid sending a potentially large request body + // unnecessarily. This workaround is only enabled on Windows because Unix + // platforms surface write errors (EPIPE) earlier; on Windows kernel send + // buffering can accept large writes even when the peer already responded. + // Check the stream first (which covers SSL via `is_readable()`), then + // fall back to select on the socket. Only perform the wait for very large + // request bodies to avoid interfering with normal small requests and + // reduce side-effects. Poll briefly (up to 50ms as default) for an early + // response. Skip this check when using Expect: 100-continue, as the protocol + // handles early responses properly. +#if defined(_WIN32) + if (!skip_body && req.body.size() > CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD && + req.path.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + auto start = std::chrono::high_resolution_clock::now(); + + for (;;) { + // Prefer socket-level readiness to avoid SSL_pending() false-positives + // from SSL internals. If the underlying socket is readable, assume an + // early response may be present. + auto sock = strm.socket(); + if (sock != INVALID_SOCKET && detail::select_read(sock, 0, 0) > 0) { + return false; + } + + // Fallback to stream-level check for non-socket streams or when the + // socket isn't reporting readable. Avoid using `is_readable()` for + // SSL, since `SSL_pending()` may report buffered records that do not + // indicate a complete application-level response yet. + if (!is_ssl() && strm.is_readable()) { + return false; + } + + auto now = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(now - start).count(); + if (elapsed >= CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND) { + break; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } +#endif + + // Body + if (skip_body) { + return true; + } + + return write_request_body(strm, req, error); +} + +inline bool ClientImpl::write_request_body(Stream &strm, Request &req, Error &error) +{ + if (req.body.empty()) { + return write_content_with_provider(strm, req, error); + } + + if (req.upload_progress) { + auto body_size = req.body.size(); + size_t written = 0; + auto data = req.body.data(); + + while (written < body_size) { + size_t to_write = (std::min)(CPPHTTPLIB_SEND_BUFSIZ, body_size - written); + if (!detail::write_data(strm, data + written, to_write)) { + error = Error::Write; + output_error_log(error, &req); + return false; + } + written += to_write; + + if (!req.upload_progress(written, body_size)) { + error = Error::Canceled; + output_error_log(error, &req); + return false; + } + } + } + else { + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + output_error_log(error, &req); + return false; + } + } + + return true; +} + +inline std::unique_ptr ClientImpl::send_with_content_provider_and_receiver( + Request &req, + const char *body, + size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, + ContentReceiver content_receiver, + Error &error) +{ + if (!content_type.empty()) { + req.set_header("Content-Type", content_type); + } + + auto enc = compress_ ? detail::create_compressor() : std::pair, const char *>(nullptr, nullptr); + + if (enc.second) { + req.set_header("Content-Encoding", enc.second); + } + + if (enc.first && !content_provider_without_length) { + auto &compressor = enc.first; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) -> bool { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor->compress(data, data_len, last, [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); + return true; + }); + + if (ret) { + offset += data_len; + } + else { + ok = false; + } + } + return ok; + }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + output_error_log(error, &req); + return nullptr; + } + } + } + else { + if (!compressor->compress(body, content_length, true, [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + output_error_log(error, &req); + return nullptr; + } + } + } + else { + if (content_provider) { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } + else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter(std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.set_header("Transfer-Encoding", "chunked"); + } + else { + req.body.assign(body, content_length); + } + } + + if (content_receiver) { + req.content_receiver = [content_receiver](const char *data, size_t data_length, size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + } + + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; +} + +inline Result ClientImpl::send_with_content_provider_and_receiver(const std::string &method, + const std::string &path, + const Headers &headers, + const char *body, + size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + Request req; + req.method = method; + req.headers = headers; + req.path = path; + req.upload_progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + auto error = Error::Success; + + auto res = send_with_content_provider_and_receiver(req, + body, + content_length, + std::move(content_provider), + std::move(content_provider_without_length), + content_type, + std::move(content_receiver), + error); + +#ifdef CPPHTTPLIB_SSL_ENABLED + return Result{std::move(res), error, std::move(req.headers), last_ssl_error_, last_backend_error_}; +#else + return Result{std::move(res), error, std::move(req.headers)}; +#endif +} + +inline void ClientImpl::output_log(const Request &req, const Response &res) const +{ + if (logger_) { + std::lock_guard guard(logger_mutex_); + logger_(req, res); + } +} + +inline void ClientImpl::output_error_log(const Error &err, const Request *req) const +{ + if (error_logger_) { + std::lock_guard guard(logger_mutex_); + error_logger_(err, req); + } +} + +inline bool ClientImpl::process_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error) +{ + // Auto-add Expect: 100-continue for large bodies + if (CPPHTTPLIB_EXPECT_100_THRESHOLD > 0 && !req.has_header("Expect")) { + auto body_size = req.body.empty() ? req.content_length_ : req.body.size(); + if (body_size >= CPPHTTPLIB_EXPECT_100_THRESHOLD) { + req.set_header("Expect", "100-continue"); + } + } + + // Check for Expect: 100-continue + auto expect_100_continue = req.get_header_value("Expect") == "100-continue"; + + // Send request (skip body if using Expect: 100-continue) + auto write_request_success = write_request(strm, req, close_connection, error, expect_100_continue); + +#ifdef CPPHTTPLIB_SSL_ENABLED + if (is_ssl() && !expect_100_continue) { + auto is_proxy_enabled = is_proxy_enabled_for_host(host_); + if (!is_proxy_enabled) { + if (tls::is_peer_closed(socket_.ssl, socket_.sock)) { + error = Error::SSLPeerCouldBeClosed_; + output_error_log(error, &req); + return false; + } + } + } +#endif + + // Handle Expect: 100-continue. + // + // Wait for an interim/early response by attempting to read the status line + // under a short timeout, instead of trusting raw socket readability. Over + // TLS, post-handshake records (e.g. session tickets) make the socket + // readable without any HTTP response being available; relying on + // `select_read` there caused the body to be withheld forever and the + // request to fail with `Read` (#2458). If no status line arrives within the + // timeout, send the body anyway (matching curl's behavior). + auto status_line_read = false; + if (expect_100_continue && write_request_success) { + if (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND > 0) { + time_t sec = CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND / 1000; + time_t usec = (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND % 1000) * 1000; + strm.set_read_timeout(sec, usec); + status_line_read = read_response_line(strm, req, res, false); + strm.set_read_timeout(read_timeout_sec_, read_timeout_usec_); + } + + if (!status_line_read) { + // No interim response within the timeout: send the body and handle the + // response as usual. + if (!write_request_body(strm, req, error)) { + return false; + } + expect_100_continue = false; // Switch to normal response handling + } + } + + // Receive response and headers + // When using Expect: 100-continue, don't auto-skip `100 Continue` response + if ((!status_line_read && !read_response_line(strm, req, res, !expect_100_continue)) || !detail::read_headers(strm, res.headers)) { + if (write_request_success) { + error = Error::Read; + } + output_error_log(error, &req); + return false; + } + + if (!write_request_success) { + return false; + } + + // Handle Expect: 100-continue response + if (expect_100_continue) { + if (res.status == StatusCode::Continue_100) { + // Server accepted, send the body + if (!write_request_body(strm, req, error)) { + return false; + } + + // Read the actual response + res.headers.clear(); + res.body.clear(); + if (!read_response_line(strm, req, res) || !detail::read_headers(strm, res.headers)) { + error = Error::Read; + output_error_log(error, &req); + return false; + } + } + // If not 100 Continue, server returned an error; proceed with that response + } + + // Body + if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" && req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && res.status != StatusCode::NotModified_304 && follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + output_error_log(error, &req); + return false; + } + } + + auto out = req.content_receiver + ? static_cast([&](const char *buf, size_t n, size_t off, size_t len) { + if (redirect) { + return true; + } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { + error = Error::Canceled; + output_error_log(error, &req); + } + return ret; + }) + : static_cast([&](const char *buf, size_t n, size_t /*off*/, size_t /*len*/) { + assert(res.body.size() + n <= res.body.max_size()); + if (payload_max_length_ > 0 && (res.body.size() >= payload_max_length_ || n > payload_max_length_ - res.body.size())) { + return false; + } + res.body.append(buf, n); + return true; + }); + + auto progress = [&](size_t current, size_t total) { + if (!req.download_progress || redirect) { + return true; + } + auto ret = req.download_progress(current, total); + if (!ret) { + error = Error::Canceled; + output_error_log(error, &req); + } + return ret; + }; + + if (res.has_header("Content-Length")) { + if (!req.content_receiver) { + auto len = res.get_header_value_u64("Content-Length"); + if (len > res.body.max_size()) { + error = Error::Read; + output_error_log(error, &req); + return false; + } + // Cap the reservation by payload_max_length_ to avoid OOM when a + // hostile or malformed server sends an enormous Content-Length. + // The actual body read below is bounded by payload_max_length_, + // so reserving more than that is never useful. + auto reserve_len = static_cast(len); + if (payload_max_length_ > 0 && reserve_len > payload_max_length_) { + reserve_len = payload_max_length_; + } + res.body.reserve(reserve_len); + } + } + + if (res.status != StatusCode::NotModified_304) { + auto content_status = 0; + auto max_length = (!has_payload_max_length_ && req.content_receiver) ? (std::numeric_limits::max)() : payload_max_length_; + if (!detail::read_content(strm, res, max_length, content_status, std::move(progress), std::move(out), decompress_)) { + if (error != Error::Canceled) { + // Tell the caller apart from a plain read failure when the body could + // not be decoded because of its Content-Encoding. + switch (content_status) { + case StatusCode::UnsupportedMediaType_415: + error = Error::UnsupportedContentEncoding; + break; + case StatusCode::InternalServerError_500: + error = Error::Compression; + break; + default: + error = Error::Read; + break; + } + } + output_error_log(error, &req); + return false; + } + } + } + + // Log + output_log(req, res); + + return true; +} + +inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(const std::string &boundary, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items) const +{ + size_t cur_item = 0; + size_t cur_start = 0; + // cur_item and cur_start are copied to within the std::function and + // maintain state between successive calls + return [&, cur_item, cur_start](size_t offset, DataSink &sink) mutable -> bool { + if (!offset && !items.empty()) { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } + else if (cur_item < provider_items.size()) { + if (!cur_start) { + const auto &begin = detail::serialize_multipart_formdata_item_begin(provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + auto has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) { + return false; + } + + if (!has_data) { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } + else { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; + } + }; +} + +inline bool ClientImpl::process_socket(const Socket &socket, + std::chrono::time_point start_time, + std::function callback) +{ + return detail::process_client_socket(socket.sock, + read_timeout_sec_, + read_timeout_usec_, + write_timeout_sec_, + write_timeout_usec_, + max_timeout_msec_, + start_time, + std::move(callback)); +} + +inline bool ClientImpl::is_ssl() const +{ + return false; +} + +inline Result ClientImpl::Get(const std::string &path, DownloadProgress progress) +{ + return Get(path, Headers(), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, DownloadProgress progress) +{ + return Get(path, params, Headers(), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, const Headers &headers, DownloadProgress progress) +{ + if (params.empty()) { + return Get(path, headers); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, DownloadProgress progress) +{ + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.download_progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress) +{ + return Get(path, Headers(), nullptr, std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress) +{ + return Get(path, headers, nullptr, std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + return Get(path, Headers(), std::move(response_handler), std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = [content_receiver](const char *data, size_t data_length, size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get( + const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress) +{ + return Get(path, params, headers, nullptr, std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, + const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + if (params.empty()) { + return Get(path, headers, std::move(response_handler), std::move(content_receiver), std::move(progress)); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(response_handler), std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Head(const std::string &path) +{ + return Head(path, Headers()); +} + +inline Result ClientImpl::Head(const std::string &path, const Headers &headers) +{ + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + return send_(std::move(req)); +} + +inline Result ClientImpl::Post(const std::string &path) +{ + return Post(path, std::string(), std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers) +{ + return Post(path, headers, nullptr, 0, std::string()); +} + +inline Result ClientImpl::Post( + const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress) +{ + return Post(path, Headers(), body, content_length, content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress) +{ + return Post(path, Headers(), body, content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) +{ + return Post(path, Headers(), params); +} + +inline Result ClientImpl::Post(const std::string &path, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return Post(path, Headers(), content_length, std::move(content_provider), content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return Post(path, Headers(), content_length, std::move(content_provider), content_type, std::move(content_receiver), progress); +} + +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return Post(path, Headers(), std::move(content_provider), content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return Post(path, Headers(), std::move(content_provider), content_type, std::move(content_receiver), progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const Params ¶ms) +{ + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress) +{ + return Post(path, Headers(), items, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress) +{ + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); + auto content_length = detail::get_multipart_content_length(items, boundary); + return Post(path, headers, content_length, detail::make_multipart_content_provider(items, boundary), content_type, progress); +} + +inline Result ClientImpl::Post( + const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress) +{ + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); + auto content_length = detail::get_multipart_content_length(items, boundary); + return Post(path, headers, content_length, detail::make_multipart_content_provider(items, boundary), content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers, + const char *body, + size_t content_length, + const std::string &content_type, + UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "POST", path, headers, body, content_length, nullptr, nullptr, content_type, nullptr, progress); +} + +inline Result ClientImpl::Post( + const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "POST", path, headers, body.data(), body.size(), nullptr, nullptr, content_type, nullptr, progress); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "POST", path, headers, nullptr, content_length, std::move(content_provider), nullptr, content_type, nullptr, progress); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + return send_with_content_provider_and_receiver("POST", + path, + headers, + nullptr, + content_length, + std::move(content_provider), + nullptr, + content_type, + std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "POST", path, headers, nullptr, 0, nullptr, std::move(content_provider), content_type, nullptr, progress); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + return send_with_content_provider_and_receiver("POST", + path, + headers, + nullptr, + 0, + nullptr, + std::move(content_provider), + content_type, + std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) +{ + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider_and_receiver("POST", + path, + headers, + nullptr, + 0, + nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, + nullptr, + progress); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + Request req; + req.method = "POST"; + req.path = path; + req.headers = headers; + req.body = body; + req.content_receiver = [content_receiver](const char *data, size_t data_length, size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { + req.set_header("Content-Type", content_type); + } + + return send_(std::move(req)); +} + +inline Result ClientImpl::Put(const std::string &path) +{ + return Put(path, std::string(), std::string()); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers) +{ + return Put(path, headers, nullptr, 0, std::string()); +} + +inline Result ClientImpl::Put( + const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress) +{ + return Put(path, Headers(), body, content_length, content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress) +{ + return Put(path, Headers(), body, content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) +{ + return Put(path, Headers(), params); +} + +inline Result ClientImpl::Put(const std::string &path, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return Put(path, Headers(), content_length, std::move(content_provider), content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return Put(path, Headers(), content_length, std::move(content_provider), content_type, std::move(content_receiver), progress); +} + +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return Put(path, Headers(), std::move(content_provider), content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return Put(path, Headers(), std::move(content_provider), content_type, std::move(content_receiver), progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const Params ¶ms) +{ + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress) +{ + return Put(path, Headers(), items, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress) +{ + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); + auto content_length = detail::get_multipart_content_length(items, boundary); + return Put(path, headers, content_length, detail::make_multipart_content_provider(items, boundary), content_type, progress); +} + +inline Result ClientImpl::Put( + const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress) +{ + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); + auto content_length = detail::get_multipart_content_length(items, boundary); + return Put(path, headers, content_length, detail::make_multipart_content_provider(items, boundary), content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, + const Headers &headers, + const char *body, + size_t content_length, + const std::string &content_type, + UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "PUT", path, headers, body, content_length, nullptr, nullptr, content_type, nullptr, progress); +} + +inline Result ClientImpl::Put( + const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "PUT", path, headers, body.data(), body.size(), nullptr, nullptr, content_type, nullptr, progress); +} + +inline Result ClientImpl::Put(const std::string &path, + const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "PUT", path, headers, nullptr, content_length, std::move(content_provider), nullptr, content_type, nullptr, progress); +} + +inline Result ClientImpl::Put(const std::string &path, + const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return send_with_content_provider_and_receiver("PUT", + path, + headers, + nullptr, + content_length, + std::move(content_provider), + nullptr, + content_type, + std::move(content_receiver), + progress); +} + +inline Result ClientImpl::Put(const std::string &path, + const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider), content_type, nullptr, progress); +} + +inline Result ClientImpl::Put(const std::string &path, + const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider), content_type, std::move(content_receiver), progress); +} + +inline Result ClientImpl::Put(const std::string &path, + const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) +{ + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider_and_receiver("PUT", + path, + headers, + nullptr, + 0, + nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, + nullptr, + progress); +} + +inline Result ClientImpl::Put(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + Request req; + req.method = "PUT"; + req.path = path; + req.headers = headers; + req.body = body; + req.content_receiver = [content_receiver](const char *data, size_t data_length, size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { + req.set_header("Content-Type", content_type); + } + + return send_(std::move(req)); +} + +inline Result ClientImpl::Patch(const std::string &path) +{ + return Patch(path, std::string(), std::string()); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, UploadProgress progress) +{ + return Patch(path, headers, nullptr, 0, std::string(), progress); +} + +inline Result ClientImpl::Patch( + const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress) +{ + return Patch(path, Headers(), body, content_length, content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress) +{ + return Patch(path, Headers(), body, content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, const Params ¶ms) +{ + return Patch(path, Headers(), params); +} + +inline Result ClientImpl::Patch(const std::string &path, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return Patch(path, Headers(), content_length, std::move(content_provider), content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return Patch(path, Headers(), content_length, std::move(content_provider), content_type, std::move(content_receiver), progress); +} + +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return Patch(path, Headers(), std::move(content_provider), content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return Patch(path, Headers(), std::move(content_provider), content_type, std::move(content_receiver), progress); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, const Params ¶ms) +{ + auto query = detail::params_to_query_str(params); + return Patch(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress) +{ + return Patch(path, Headers(), items, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress) +{ + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); + auto content_length = detail::get_multipart_content_length(items, boundary); + return Patch(path, headers, content_length, detail::make_multipart_content_provider(items, boundary), content_type, progress); +} + +inline Result ClientImpl::Patch( + const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress) +{ + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); + auto content_length = detail::get_multipart_content_length(items, boundary); + return Patch(path, headers, content_length, detail::make_multipart_content_provider(items, boundary), content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, + const Headers &headers, + const char *body, + size_t content_length, + const std::string &content_type, + UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "PATCH", path, headers, body, content_length, nullptr, nullptr, content_type, nullptr, progress); +} + +inline Result ClientImpl::Patch( + const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "PATCH", path, headers, body.data(), body.size(), nullptr, nullptr, content_type, nullptr, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, + const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "PATCH", path, headers, nullptr, content_length, std::move(content_provider), nullptr, content_type, nullptr, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, + const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return send_with_content_provider_and_receiver("PATCH", + path, + headers, + nullptr, + content_length, + std::move(content_provider), + nullptr, + content_type, + std::move(content_receiver), + progress); +} + +inline Result ClientImpl::Patch(const std::string &path, + const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider), content_type, nullptr, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, + const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return send_with_content_provider_and_receiver( + "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider), content_type, std::move(content_receiver), progress); +} + +inline Result ClientImpl::Patch(const std::string &path, + const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) +{ + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider_and_receiver("PATCH", + path, + headers, + nullptr, + 0, + nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, + nullptr, + progress); +} + +inline Result ClientImpl::Patch(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + Request req; + req.method = "PATCH"; + req.path = path; + req.headers = headers; + req.body = body; + req.content_receiver = [content_receiver](const char *data, size_t data_length, size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { + req.set_header("Content-Type", content_type); + } + + return send_(std::move(req)); +} + +inline Result ClientImpl::Delete(const std::string &path, DownloadProgress progress) +{ + return Delete(path, Headers(), std::string(), std::string(), progress); +} + +inline Result ClientImpl::Delete(const std::string &path, const Headers &headers, DownloadProgress progress) +{ + return Delete(path, headers, std::string(), std::string(), progress); +} + +inline Result ClientImpl::Delete( + const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress) +{ + return Delete(path, Headers(), body, content_length, content_type, progress); +} + +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type, + DownloadProgress progress) +{ + return Delete(path, Headers(), body.data(), body.size(), content_type, progress); +} + +inline Result ClientImpl::Delete( + const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress) +{ + return Delete(path, headers, body.data(), body.size(), content_type, progress); +} + +inline Result ClientImpl::Delete(const std::string &path, const Params ¶ms, DownloadProgress progress) +{ + return Delete(path, Headers(), params, progress); +} + +inline Result ClientImpl::Delete(const std::string &path, const Headers &headers, const Params ¶ms, DownloadProgress progress) +{ + auto query = detail::params_to_query_str(params); + return Delete(path, headers, query, "application/x-www-form-urlencoded", progress); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const char *body, + size_t content_length, + const std::string &content_type, + DownloadProgress progress) +{ + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + req.download_progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { + req.set_header("Content-Type", content_type); + } + req.body.assign(body, content_length); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Options(const std::string &path) +{ + return Options(path, Headers()); +} + +inline Result ClientImpl::Options(const std::string &path, const Headers &headers) +{ + Request req; + req.method = "OPTIONS"; + req.headers = headers; + req.path = path; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + return send_(std::move(req)); +} + +inline void ClientImpl::stop() +{ + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions + // are not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + disconnect(/*gracefully=*/true); +} + +inline std::string ClientImpl::host() const +{ + return host_; +} + +inline int ClientImpl::port() const +{ + return port_; +} + +inline size_t ClientImpl::is_socket_open() const +{ + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); +} + +inline socket_t ClientImpl::socket() const +{ + return socket_.sock; +} + +inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) +{ + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) +{ + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) +{ + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; +} + +inline void ClientImpl::set_max_timeout(time_t msec) +{ + max_timeout_msec_ = msec; +} + +inline void ClientImpl::set_basic_auth(const std::string &username, const std::string &password) +{ + basic_auth_username_ = username; + basic_auth_password_ = password; +} + +inline void ClientImpl::set_bearer_token_auth(const std::string &token) +{ + bearer_token_auth_token_ = token; +} + +inline void ClientImpl::set_keep_alive(bool on) +{ + keep_alive_ = on; +} + +inline void ClientImpl::set_follow_location(bool on) +{ + follow_location_ = on; +} + +inline void ClientImpl::set_path_encode(bool on) +{ + path_encode_ = on; +} + +inline void ClientImpl::set_hostname_addr_map(std::map addr_map) +{ + addr_map_ = std::move(addr_map); +} + +inline void ClientImpl::set_default_headers(Headers headers) +{ + default_headers_ = std::move(headers); +} + +inline void ClientImpl::set_header_writer(std::function const &writer) +{ + header_writer_ = writer; +} + +inline void ClientImpl::set_address_family(int family) +{ + address_family_ = family; +} + +inline void ClientImpl::set_tcp_nodelay(bool on) +{ + tcp_nodelay_ = on; +} + +inline void ClientImpl::set_ipv6_v6only(bool on) +{ + ipv6_v6only_ = on; +} + +inline void ClientImpl::set_socket_options(SocketOptions socket_options) +{ + socket_options_ = std::move(socket_options); +} + +inline void ClientImpl::set_compress(bool on) +{ + compress_ = on; +} + +inline void ClientImpl::set_decompress(bool on) +{ + decompress_ = on; +} + +inline void ClientImpl::set_payload_max_length(size_t length) +{ + payload_max_length_ = length; + has_payload_max_length_ = true; +} + +inline void ClientImpl::set_interface(const std::string &intf) +{ + interface_ = intf; +} + +inline void ClientImpl::set_proxy(const std::string &host, int port) +{ + proxy_host_ = host; + proxy_port_ = port; + std::lock_guard guard(socket_mutex_); + disconnect(/*gracefully=*/true); +} + +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, const std::string &password) +{ + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; +} + +inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) +{ + proxy_bearer_token_auth_token_ = token; +} + +inline void ClientImpl::set_no_proxy(const std::vector &patterns) +{ + std::vector parsed; + parsed.reserve(patterns.size()); + for (const auto &p : patterns) { + auto trimmed = detail::trim_copy(p); + if (trimmed.empty()) { + continue; + } + detail::NoProxyEntry entry; + if (detail::parse_no_proxy_entry(trimmed, entry)) { + parsed.push_back(std::move(entry)); + } + } + no_proxy_entries_ = std::move(parsed); + std::lock_guard guard(socket_mutex_); + disconnect(/*gracefully=*/true); +} + +#ifdef CPPHTTPLIB_SSL_ENABLED +inline void ClientImpl::set_digest_auth(const std::string &username, const std::string &password) +{ + digest_auth_username_ = username; + digest_auth_password_ = password; +} + +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path) +{ + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, const std::string &password) +{ + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} + +inline void ClientImpl::enable_server_certificate_verification(bool enabled) +{ + server_certificate_verification_ = enabled; +} + +inline void ClientImpl::enable_server_hostname_verification(bool enabled) +{ + server_hostname_verification_ = enabled; +} + +inline void ClientImpl::enable_system_ca(bool enabled) +{ + system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled; +} +#endif + +inline void ClientImpl::set_logger(Logger logger) +{ + logger_ = std::move(logger); +} + +inline void ClientImpl::set_error_logger(ErrorLogger error_logger) +{ + error_logger_ = std::move(error_logger); +} + +/* + * SSL/TLS Common Implementation + */ + +inline ClientConnection::~ClientConnection() +{ +#ifdef CPPHTTPLIB_SSL_ENABLED + if (session) { + tls::shutdown(session, true); + tls::free_session(session); + session = nullptr; + } +#endif + + if (sock != INVALID_SOCKET) { + detail::close_socket(sock); + sock = INVALID_SOCKET; + } +} + +// Universal client implementation +inline Client::Client(const std::string &scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) +{ +} + +inline Client::Client(const std::string &scheme_host_port, const std::string &client_cert_path, const std::string &client_key_path) +{ + detail::UrlComponents uc; + if (detail::parse_url(scheme_host_port, uc) && !uc.host.empty()) { + auto &scheme = uc.scheme; + +#ifdef CPPHTTPLIB_SSL_ENABLED + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); +#endif + return; + } + + auto is_ssl = scheme == "https"; + + auto host = std::move(uc.host); + + auto port = is_ssl ? 443 : 80; + if (!uc.port.empty() && !detail::parse_port(uc.port, port)) { + return; + } + + if (is_ssl) { +#ifdef CPPHTTPLIB_SSL_ENABLED + cli_ = detail::make_unique(host, port, client_cert_path, client_key_path); + is_ssl_ = is_ssl; +#endif + } + else { + cli_ = detail::make_unique(host, port, client_cert_path, client_key_path); + } + } + else { + // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress) + // if port param below changes. + cli_ = detail::make_unique(scheme_host_port, 80, client_cert_path, client_key_path); + } +} + +inline Client::Client(const std::string &host, int port) + : Client(host, port, std::string(), std::string()) +{ +} + +inline Client::Client(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, client_key_path)) +{ +} + +inline Client::~Client() = default; + +inline bool Client::is_valid() const +{ + return cli_ != nullptr && cli_->is_valid(); +} + +inline Result Client::Get(const std::string &path, DownloadProgress progress) +{ + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, DownloadProgress progress) +{ + return cli_->Get(path, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress) +{ + return cli_->Get(path, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress) +{ + return cli_->Get(path, headers, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + return cli_->Get(path, std::move(response_handler), std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + return cli_->Get(path, headers, std::move(response_handler), std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, DownloadProgress progress) +{ + return cli_->Get(path, params, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, const Headers &headers, DownloadProgress progress) +{ + return cli_->Get(path, params, headers, std::move(progress)); +} +inline Result Client::Get( + const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress) +{ + return cli_->Get(path, params, headers, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, + const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + return cli_->Get(path, params, headers, std::move(response_handler), std::move(content_receiver), std::move(progress)); +} + +inline Result Client::Head(const std::string &path) +{ + return cli_->Head(path); +} +inline Result Client::Head(const std::string &path, const Headers &headers) +{ + return cli_->Head(path, headers); +} + +inline Result Client::Post(const std::string &path) +{ + return cli_->Post(path); +} +inline Result Client::Post(const std::string &path, const Headers &headers) +{ + return cli_->Post(path, headers); +} +inline Result Client::Post( + const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress) +{ + return cli_->Post(path, body, content_length, content_type, progress); +} +inline Result Client::Post(const std::string &path, + const Headers &headers, + const char *body, + size_t content_length, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Post(path, headers, body, content_length, content_type, progress); +} +inline Result Client::Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress) +{ + return cli_->Post(path, body, content_type, progress); +} +inline Result Client::Post( + const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress) +{ + return cli_->Post(path, headers, body, content_type, progress); +} +inline Result Client::Post(const std::string &path, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Post(path, content_length, std::move(content_provider), content_type, progress); +} +inline Result Client::Post(const std::string &path, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return cli_->Post(path, content_length, std::move(content_provider), content_type, std::move(content_receiver), progress); +} +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Post(path, std::move(content_provider), content_type, progress); +} +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return cli_->Post(path, std::move(content_provider), content_type, std::move(content_receiver), progress); +} +inline Result Client::Post(const std::string &path, + const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Post(path, headers, content_length, std::move(content_provider), content_type, progress); +} +inline Result Client::Post(const std::string &path, + const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + return cli_->Post(path, headers, content_length, std::move(content_provider), content_type, std::move(content_receiver), progress); +} +inline Result Client::Post(const std::string &path, + const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Post(path, headers, std::move(content_provider), content_type, progress); +} +inline Result Client::Post(const std::string &path, + const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + return cli_->Post(path, headers, std::move(content_provider), content_type, std::move(content_receiver), progress); +} +inline Result Client::Post(const std::string &path, const Params ¶ms) +{ + return cli_->Post(path, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, const Params ¶ms) +{ + return cli_->Post(path, headers, params); +} +inline Result Client::Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress) +{ + return cli_->Post(path, items, progress); +} +inline Result Client::Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress) +{ + return cli_->Post(path, headers, items, progress); +} +inline Result Client::Post( + const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress) +{ + return cli_->Post(path, headers, items, boundary, progress); +} +inline Result Client::Post(const std::string &path, + const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) +{ + return cli_->Post(path, headers, items, provider_items, progress); +} +inline Result Client::Post(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + return cli_->Post(path, headers, body, content_type, std::move(content_receiver), progress); +} + +inline Result Client::Put(const std::string &path) +{ + return cli_->Put(path); +} +inline Result Client::Put(const std::string &path, const Headers &headers) +{ + return cli_->Put(path, headers); +} +inline Result Client::Put( + const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress) +{ + return cli_->Put(path, body, content_length, content_type, progress); +} +inline Result Client::Put(const std::string &path, + const Headers &headers, + const char *body, + size_t content_length, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Put(path, headers, body, content_length, content_type, progress); +} +inline Result Client::Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress) +{ + return cli_->Put(path, body, content_type, progress); +} +inline Result Client::Put( + const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress) +{ + return cli_->Put(path, headers, body, content_type, progress); +} +inline Result Client::Put(const std::string &path, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Put(path, content_length, std::move(content_provider), content_type, progress); +} +inline Result Client::Put(const std::string &path, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return cli_->Put(path, content_length, std::move(content_provider), content_type, std::move(content_receiver), progress); +} +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Put(path, std::move(content_provider), content_type, progress); +} +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return cli_->Put(path, std::move(content_provider), content_type, std::move(content_receiver), progress); +} +inline Result Client::Put(const std::string &path, + const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Put(path, headers, content_length, std::move(content_provider), content_type, progress); +} +inline Result Client::Put(const std::string &path, + const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return cli_->Put(path, headers, content_length, std::move(content_provider), content_type, std::move(content_receiver), progress); +} +inline Result Client::Put(const std::string &path, + const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Put(path, headers, std::move(content_provider), content_type, progress); +} +inline Result Client::Put(const std::string &path, + const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return cli_->Put(path, headers, std::move(content_provider), content_type, std::move(content_receiver), progress); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) +{ + return cli_->Put(path, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, const Params ¶ms) +{ + return cli_->Put(path, headers, params); +} +inline Result Client::Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress) +{ + return cli_->Put(path, items, progress); +} +inline Result Client::Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress) +{ + return cli_->Put(path, headers, items, progress); +} +inline Result Client::Put( + const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress) +{ + return cli_->Put(path, headers, items, boundary, progress); +} +inline Result Client::Put(const std::string &path, + const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) +{ + return cli_->Put(path, headers, items, provider_items, progress); +} +inline Result Client::Put(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + return cli_->Put(path, headers, body, content_type, content_receiver, progress); +} + +inline Result Client::Patch(const std::string &path) +{ + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const Headers &headers) +{ + return cli_->Patch(path, headers); +} +inline Result Client::Patch( + const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress) +{ + return cli_->Patch(path, body, content_length, content_type, progress); +} +inline Result Client::Patch(const std::string &path, + const Headers &headers, + const char *body, + size_t content_length, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Patch(path, headers, body, content_length, content_type, progress); +} +inline Result Client::Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress) +{ + return cli_->Patch(path, body, content_type, progress); +} +inline Result Client::Patch( + const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress) +{ + return cli_->Patch(path, headers, body, content_type, progress); +} +inline Result Client::Patch(const std::string &path, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Patch(path, content_length, std::move(content_provider), content_type, progress); +} +inline Result Client::Patch(const std::string &path, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return cli_->Patch(path, content_length, std::move(content_provider), content_type, std::move(content_receiver), progress); +} +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Patch(path, std::move(content_provider), content_type, progress); +} +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return cli_->Patch(path, std::move(content_provider), content_type, std::move(content_receiver), progress); +} +inline Result Client::Patch(const std::string &path, + const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Patch(path, headers, content_length, std::move(content_provider), content_type, progress); +} +inline Result Client::Patch(const std::string &path, + const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return cli_->Patch(path, headers, content_length, std::move(content_provider), content_type, std::move(content_receiver), progress); +} +inline Result Client::Patch(const std::string &path, + const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) +{ + return cli_->Patch(path, headers, std::move(content_provider), content_type, progress); +} +inline Result Client::Patch(const std::string &path, + const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + ContentReceiver content_receiver, + UploadProgress progress) +{ + return cli_->Patch(path, headers, std::move(content_provider), content_type, std::move(content_receiver), progress); +} +inline Result Client::Patch(const std::string &path, const Params ¶ms) +{ + return cli_->Patch(path, params); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, const Params ¶ms) +{ + return cli_->Patch(path, headers, params); +} +inline Result Client::Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress) +{ + return cli_->Patch(path, items, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress) +{ + return cli_->Patch(path, headers, items, progress); +} +inline Result Client::Patch( + const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress) +{ + return cli_->Patch(path, headers, items, boundary, progress); +} +inline Result Client::Patch(const std::string &path, + const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) +{ + return cli_->Patch(path, headers, items, provider_items, progress); +} +inline Result Client::Patch(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) +{ + return cli_->Patch(path, headers, body, content_type, content_receiver, progress); +} + +inline Result Client::Delete(const std::string &path, DownloadProgress progress) +{ + return cli_->Delete(path, progress); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, DownloadProgress progress) +{ + return cli_->Delete(path, headers, progress); +} +inline Result Client::Delete( + const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress) +{ + return cli_->Delete(path, body, content_length, content_type, progress); +} +inline Result Client::Delete(const std::string &path, + const Headers &headers, + const char *body, + size_t content_length, + const std::string &content_type, + DownloadProgress progress) +{ + return cli_->Delete(path, headers, body, content_length, content_type, progress); +} +inline Result Client::Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress) +{ + return cli_->Delete(path, body, content_type, progress); +} +inline Result Client::Delete( + const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress) +{ + return cli_->Delete(path, headers, body, content_type, progress); +} +inline Result Client::Delete(const std::string &path, const Params ¶ms, DownloadProgress progress) +{ + return cli_->Delete(path, params, progress); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, const Params ¶ms, DownloadProgress progress) +{ + return cli_->Delete(path, headers, params, progress); +} + +inline Result Client::Options(const std::string &path) +{ + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) +{ + return cli_->Options(path, headers); +} + +inline ClientImpl::StreamHandle Client::open_stream(const std::string &method, + const std::string &path, + const Params ¶ms, + const Headers &headers, + const std::string &body, + const std::string &content_type) +{ + return cli_->open_stream(method, path, params, headers, body, content_type); +} + +inline bool Client::send(Request &req, Response &res, Error &error) +{ + return cli_->send(req, res, error); +} + +inline Result Client::send(const Request &req) +{ + return cli_->send(req); +} + +inline void Client::stop() +{ + cli_->stop(); +} + +inline std::string Client::host() const +{ + return cli_->host(); +} + +inline int Client::port() const +{ + return cli_->port(); +} + +inline size_t Client::is_socket_open() const +{ + return cli_->is_socket_open(); +} + +inline socket_t Client::socket() const +{ + return cli_->socket(); +} + +inline void Client::set_hostname_addr_map(std::map addr_map) +{ + cli_->set_hostname_addr_map(std::move(addr_map)); +} + +inline void Client::set_default_headers(Headers headers) +{ + cli_->set_default_headers(std::move(headers)); +} + +inline void Client::set_header_writer(std::function const &writer) +{ + cli_->set_header_writer(writer); +} + +inline void Client::set_address_family(int family) +{ + cli_->set_address_family(family); +} + +inline void Client::set_tcp_nodelay(bool on) +{ + cli_->set_tcp_nodelay(on); +} + +inline void Client::set_socket_options(SocketOptions socket_options) +{ + cli_->set_socket_options(std::move(socket_options)); +} + +inline void Client::set_connection_timeout(time_t sec, time_t usec) +{ + cli_->set_connection_timeout(sec, usec); +} + +inline void Client::set_read_timeout(time_t sec, time_t usec) +{ + cli_->set_read_timeout(sec, usec); +} + +inline void Client::set_write_timeout(time_t sec, time_t usec) +{ + cli_->set_write_timeout(sec, usec); +} + +inline void Client::set_basic_auth(const std::string &username, const std::string &password) +{ + cli_->set_basic_auth(username, password); +} +inline void Client::set_bearer_token_auth(const std::string &token) +{ + cli_->set_bearer_token_auth(token); +} + +inline void Client::set_keep_alive(bool on) +{ + cli_->set_keep_alive(on); +} +inline void Client::set_follow_location(bool on) +{ + cli_->set_follow_location(on); +} + +inline void Client::set_path_encode(bool on) +{ + cli_->set_path_encode(on); +} + +inline void Client::set_compress(bool on) +{ + cli_->set_compress(on); +} + +inline void Client::set_decompress(bool on) +{ + cli_->set_decompress(on); +} + +inline void Client::set_payload_max_length(size_t length) +{ + cli_->set_payload_max_length(length); +} + +inline void Client::set_interface(const std::string &intf) +{ + cli_->set_interface(intf); +} + +inline void Client::set_proxy(const std::string &host, int port) +{ + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const std::string &username, const std::string &password) +{ + cli_->set_proxy_basic_auth(username, password); +} +inline void Client::set_proxy_bearer_token_auth(const std::string &token) +{ + cli_->set_proxy_bearer_token_auth(token); +} +inline void Client::set_no_proxy(const std::vector &patterns) +{ + cli_->set_no_proxy(patterns); +} + +inline void Client::set_logger(Logger logger) +{ + cli_->set_logger(std::move(logger)); +} + +inline void Client::set_error_logger(ErrorLogger error_logger) +{ + cli_->set_error_logger(std::move(error_logger)); +} + +/* + * Group 6: SSL Server and Client implementation + */ + +#ifdef CPPHTTPLIB_SSL_ENABLED + +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char *cert_path, + const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path, + const char *private_key_password) +{ + using namespace tls; + + ctx_ = create_server_context(); + if (!ctx_) { + return; + } + + // Load server certificate and private key + if (!set_server_cert_file(ctx_, cert_path, private_key_path, private_key_password)) { + last_ssl_error_ = static_cast(get_error()); + free_context(ctx_); + ctx_ = nullptr; + return; + } + + // Load client CA certificates for client authentication + if (client_ca_cert_file_path || client_ca_cert_dir_path) { + if (!set_client_ca_file(ctx_, client_ca_cert_file_path, client_ca_cert_dir_path)) { + last_ssl_error_ = static_cast(get_error()); + free_context(ctx_); + ctx_ = nullptr; + return; + } + // Enable client certificate verification + set_verify_client(ctx_, true); + } +} + +inline SSLServer::SSLServer(const PemMemory &pem) +{ + using namespace tls; + ctx_ = create_server_context(); + if (ctx_) { + if (!set_server_cert_pem(ctx_, pem.cert_pem, pem.key_pem, pem.private_key_password)) { + last_ssl_error_ = static_cast(get_error()); + free_context(ctx_); + ctx_ = nullptr; + } + else if (pem.client_ca_pem && pem.client_ca_pem_len > 0) { + if (!load_ca_pem(ctx_, pem.client_ca_pem, pem.client_ca_pem_len)) { + last_ssl_error_ = static_cast(get_error()); + free_context(ctx_); + ctx_ = nullptr; + } + else { + set_verify_client(ctx_, true); + } + } + } +} + +inline SSLServer::SSLServer(const tls::ContextSetupCallback &setup_callback) +{ + using namespace tls; + ctx_ = create_server_context(); + if (ctx_) { + if (!setup_callback(ctx_)) { + free_context(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLServer::~SSLServer() +{ + if (ctx_) { + tls::free_context(ctx_); + } +} + +inline bool SSLServer::is_valid() const +{ + return ctx_ != nullptr; +} + +inline bool SSLServer::process_and_close_socket(socket_t sock) +{ + using namespace tls; + + // Create TLS session with mutex protection + session_t session = nullptr; + { + std::lock_guard guard(ctx_mutex_); + session = create_session(static_cast(ctx_), sock); + } + + if (!session) { + last_ssl_error_ = static_cast(get_error()); + detail::shutdown_socket(sock); + detail::close_socket(sock); + return false; + } + + // Use scope_exit to ensure cleanup on all paths (including exceptions) + bool handshake_done = false; + bool ret = false; + bool websocket_upgraded = false; + auto cleanup = detail::scope_exit([&] { + if (handshake_done) { + shutdown(session, !websocket_upgraded && ret); + } + free_session(session); + detail::shutdown_socket(sock); + detail::close_socket(sock); + }); + + // Perform TLS accept handshake with timeout + TlsError tls_err; + if (!accept_nonblocking(session, sock, read_timeout_sec_, read_timeout_usec_, &tls_err)) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // Map TlsError to legacy ssl_error for backward compatibility + if (tls_err.code == ErrorCode::WantRead) { + last_ssl_error_ = SSL_ERROR_WANT_READ; + } + else if (tls_err.code == ErrorCode::WantWrite) { + last_ssl_error_ = SSL_ERROR_WANT_WRITE; + } + else { + last_ssl_error_ = SSL_ERROR_SSL; + } +#else + last_ssl_error_ = static_cast(get_error()); +#endif + return false; + } + + handshake_done = true; + + std::string remote_addr; + int remote_port = 0; + detail::get_remote_ip_and_port(sock, remote_addr, remote_port); + + std::string local_addr; + int local_port = 0; + detail::get_local_ip_and_port(sock, local_addr, local_port); + + ret = detail::process_server_socket_ssl(svr_sock_, + session, + sock, + keep_alive_max_count_, + keep_alive_timeout_sec_, + read_timeout_sec_, + read_timeout_usec_, + write_timeout_sec_, + write_timeout_usec_, + [&](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request( + strm, + remote_addr, + remote_port, + local_addr, + local_port, + close_connection, + connection_closed, + [&](Request &req) { req.ssl = session; }, + &websocket_upgraded); + }); + + return ret; +} + +inline bool SSLServer::update_certs_pem(const char *cert_pem, const char *key_pem, const char *client_ca_pem, const char *password) +{ + if (!ctx_) { + return false; + } + std::lock_guard guard(ctx_mutex_); + if (!tls::update_server_cert(ctx_, cert_pem, key_pem, password)) { + return false; + } + if (client_ca_pem) { + return tls::update_server_client_ca(ctx_, client_ca_pem); + } + return true; +} + +// SSL HTTP client implementation +inline SSLClient::~SSLClient() +{ + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + // This must happen before the context is freed below: some backends + // (e.g. mbedTLS) have the SSL session borrow a raw pointer into the + // context, so freeing the context first leaves close_notify reading + // freed memory. + shutdown_ssl_impl(socket_, true); + if (ctx_) { + tls::free_context(ctx_); + ctx_ = nullptr; + } +} + +inline bool SSLClient::is_valid() const +{ + return ctx_ != nullptr; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) +{ + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully) +{ + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + tls::shutdown(socket.ssl, shutdown_gracefully); + { + std::lock_guard guard(ctx_mutex_); + tls::free_session(socket.ssl); + } + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool SSLClient::process_socket(const Socket &socket, + std::chrono::time_point start_time, + std::function callback) +{ + assert(socket.ssl); + return detail::process_client_socket_ssl(socket.ssl, + socket.sock, + read_timeout_sec_, + read_timeout_usec_, + write_timeout_sec_, + write_timeout_usec_, + max_timeout_msec_, + start_time, + std::move(callback)); +} + +inline bool SSLClient::is_ssl() const +{ + return true; +} + +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) +{ + if (!is_valid()) { + error = Error::SSLConnection; + return false; + } + return ClientImpl::create_and_connect_socket(socket, error); +} + +inline bool SSLClient::setup_proxy_connection( + Socket &socket, std::chrono::time_point start_time, Response &res, bool &success, Error &error) +{ + if (!is_proxy_enabled_for_host(host_)) { + return true; + } + + if (!connect_with_proxy(socket, start_time, res, success, error)) { + return false; + } + + if (!initialize_ssl(socket, error)) { + success = false; + return false; + } + + return true; +} + +// Assumes that socket_mutex_ is locked and that there are no requests in +// flight +inline bool SSLClient::connect_with_proxy( + Socket &socket, std::chrono::time_point start_time, Response &res, bool &success, Error &error) +{ + success = true; + Response proxy_res; + if (!detail::process_client_socket(socket.sock, + read_timeout_sec_, + read_timeout_usec_, + write_timeout_sec_, + write_timeout_usec_, + max_timeout_msec_, + start_time, + [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = detail::make_host_and_port_string_always_port(host_, port_); + if (max_timeout_msec_ > 0) { + req2.start_time_ = std::chrono::steady_clock::now(); + } + return process_request(strm, req2, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + + if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) { + if (!proxy_digest_auth_username_.empty() && !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(proxy_res, auth, true)) { + // Close the current socket and create a new one for the authenticated + // request + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + + // Create a new socket for the authenticated CONNECT request + if (!ensure_socket_connection(socket, error)) { + success = false; + output_error_log(error, nullptr); + return false; + } + + proxy_res = Response(); + if (!detail::process_client_socket( + socket.sock, + read_timeout_sec_, + read_timeout_usec_, + write_timeout_sec_, + write_timeout_usec_, + max_timeout_msec_, + start_time, + [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = detail::make_host_and_port_string_always_port(host_, port_); + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), proxy_digest_auth_username_, proxy_digest_auth_password_, true)); + if (max_timeout_msec_ > 0) { + req3.start_time_ = std::chrono::steady_clock::now(); + } + return process_request(strm, req3, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } + } + + // If status code is not 200, proxy request is failed. + // Set error to ProxyConnection and return proxy response + // as the response of the request + if (proxy_res.status != StatusCode::OK_200) { + error = Error::ProxyConnection; + output_error_log(error, nullptr); + res = std::move(proxy_res); + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + return false; + } + + return true; +} + +inline bool SSLClient::ensure_socket_connection(Socket &socket, Error &error) +{ + if (!ClientImpl::ensure_socket_connection(socket, error)) { + return false; + } + + if (is_proxy_enabled_for_host(host_)) { + return true; + } + + if (!initialize_ssl(socket, error)) { + shutdown_socket(socket); + close_socket(socket); + return false; + } + + return true; +} + +// SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) +{ +} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) +{ +} + +inline void SSLClient::init_ctx() +{ + ctx_ = tls::create_client_context(); + if (ctx_) { + tls::set_min_version(ctx_, tls::Version::TLS1_2); + } +} + +inline void SSLClient::reset_ctx_on_error() +{ + last_backend_error_ = tls::get_error(); + tls::free_context(ctx_); + ctx_ = nullptr; +} + +inline SSLClient::SSLClient(const std::string &host, + int port, + const std::string &client_cert_path, + const std::string &client_key_path, + const std::string &private_key_password) + : ClientImpl(host, port, client_cert_path, client_key_path) +{ + init_ctx(); + if (!ctx_) { + return; + } + + if (!client_cert_path.empty() && !client_key_path.empty()) { + const char *password = private_key_password.empty() ? nullptr : private_key_password.c_str(); + if (!tls::set_client_cert_file(ctx_, client_cert_path.c_str(), client_key_path.c_str(), password)) { + reset_ctx_on_error(); + } + } +} + +inline SSLClient::SSLClient(const std::string &host, int port, const PemMemory &pem) + : ClientImpl(host, port) +{ + init_ctx(); + if (!ctx_) { + return; + } + + if (pem.cert_pem && pem.key_pem) { + if (!tls::set_client_cert_pem(ctx_, pem.cert_pem, pem.key_pem, pem.private_key_password)) { + reset_ctx_on_error(); + } + } +} + +inline void SSLClient::set_ca_cert_store(tls::ca_store_t ca_cert_store) +{ + if (ca_cert_store && ctx_) { + // set_ca_store takes ownership of ca_cert_store + tls::set_ca_store(ctx_, ca_cert_store); + ca_cert_store_set_ = true; + } + else if (ca_cert_store) { + tls::free_ca_store(ca_cert_store); + } +} + +inline void SSLClient::set_server_certificate_verifier(tls::VerifyCallback verifier) +{ + if (!ctx_) { + return; + } + tls::set_verify_callback(ctx_, verifier); +} + +inline void SSLClient::set_session_verifier(std::function verifier) +{ + session_verifier_ = std::move(verifier); +} + +#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE +inline void SSLClient::enable_windows_certificate_verification(bool enabled) +{ + enable_windows_cert_verification_ = enabled; +} +#endif + +inline void SSLClient::load_ca_cert_store(const char *ca_cert, std::size_t size) +{ + if (ctx_ && ca_cert && size > 0) { + ca_cert_pem_.assign(ca_cert, size); // Store for redirect transfer + tls::load_ca_pem(ctx_, ca_cert, size); + } +} + +inline bool SSLClient::load_certs() +{ + auto ret = true; + + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + + ret = detail::load_client_ca_config( + ctx_, ca_cert_file_path_, ca_cert_dir_path_, !ca_cert_pem_.empty() || ca_cert_store_set_, system_ca_mode_, last_backend_error_); + }); + + return ret; +} + +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) +{ + using namespace tls; + + // Load CA certificates if server verification is enabled + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + output_error_log(error, nullptr); + return false; + } + } + + bool is_ip = detail::is_ip_address(host_); + +#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT) + // MbedTLS/wolfSSL need explicit verification mode (OpenSSL uses + // SSL_VERIFY_NONE by default and performs all verification post-handshake). + // Chain verification happens during the handshake even for IP hosts; the + // certificate identity is verified post-handshake via verify_hostname(). + set_verify_client(ctx_, server_certificate_verification_); +#endif + + // Create TLS session + session_t session = nullptr; + { + std::lock_guard guard(ctx_mutex_); + session = create_session(ctx_, socket.sock); + } + + if (!session) { + error = Error::SSLConnection; + last_backend_error_ = get_error(); + return false; + } + + // Use scope_exit to ensure session is freed on error paths + bool success = false; + auto session_guard = detail::scope_exit([&] { + if (!success) { + free_session(session); + } + }); + + // Set SNI extension (skip for IP addresses per RFC 6066). + // On MbedTLS, set_sni also enables hostname verification internally. + // On OpenSSL, set_sni only sets SNI; verification is done post-handshake. + if (!is_ip) { + if (!set_sni(session, host_.c_str())) { + error = Error::SSLConnection; + last_backend_error_ = get_error(); + return false; + } + } + + // Perform non-blocking TLS handshake with timeout + TlsError tls_err; + if (!connect_nonblocking(session, socket.sock, connection_timeout_sec_, connection_timeout_usec_, &tls_err)) { + last_ssl_error_ = static_cast(tls_err.code); + last_backend_error_ = tls_err.backend_code; + if (tls_err.code == ErrorCode::CertVerifyFailed) { + error = Error::SSLServerVerification; + } + else if (tls_err.code == ErrorCode::HostnameMismatch) { + error = Error::SSLServerHostnameVerification; + } + else { + error = Error::SSLConnection; + } + output_error_log(error, nullptr); + return false; + } + + // Post-handshake session verifier callback + auto verification_status = SSLVerifierResponse::NoDecisionMade; + if (session_verifier_) { + verification_status = session_verifier_(session); + } + + if (verification_status == SSLVerifierResponse::CertificateRejected) { + last_backend_error_ = get_error(); + error = Error::SSLServerVerification; + output_error_log(error, nullptr); + return false; + } + + // Default server certificate verification + if (verification_status == SSLVerifierResponse::NoDecisionMade && server_certificate_verification_) { + verify_result_ = tls::get_verify_result(session); + if (verify_result_ != 0) { + last_backend_error_ = static_cast(verify_result_); + error = Error::SSLServerVerification; + output_error_log(error, nullptr); + return false; + } + + auto server_cert = get_peer_cert(session); + if (!server_cert) { + last_backend_error_ = get_error(); + error = Error::SSLServerVerification; + output_error_log(error, nullptr); + return false; + } + auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); }); + + // Hostname verification (post-handshake for all cases). + // On OpenSSL, verification is always post-handshake (SSL_VERIFY_NONE). + // On MbedTLS, set_sni already enabled hostname verification during + // handshake for non-IP hosts, but this check is still needed for IP + // addresses where SNI is not set. + if (server_hostname_verification_) { + if (!verify_hostname(server_cert, host_.c_str())) { + last_backend_error_ = hostname_mismatch_code(); + error = Error::SSLServerHostnameVerification; + output_error_log(error, nullptr); + return false; + } + } + +#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE + // Additional Windows Schannel verification. + // This provides real-time certificate validation with Windows Update + // integration, working with both OpenSSL and MbedTLS backends. + // Skip when a custom CA cert is specified, as the Windows certificate + // store would not know about user-provided CA certificates. Also skip + // when system CA trust is explicitly disabled. + if (enable_windows_cert_verification_ && system_ca_mode_ != SystemCAMode::Disabled && ca_cert_file_path_.empty() && + ca_cert_dir_path_.empty() && ca_cert_pem_.empty() && !ca_cert_store_set_) { + std::vector der; + if (get_cert_der(server_cert, der)) { + uint64_t wincrypt_error = 0; + if (!detail::verify_cert_with_windows_schannel(der, host_, server_hostname_verification_, wincrypt_error)) { + last_backend_error_ = wincrypt_error; + error = Error::SSLServerVerification; + output_error_log(error, nullptr); + return false; + } + } + } +#endif + } + + success = true; + socket.ssl = session; + return true; +} + +inline void Client::set_digest_auth(const std::string &username, const std::string &password) +{ + cli_->set_digest_auth(username, password); +} + +inline void Client::set_proxy_digest_auth(const std::string &username, const std::string &password) +{ + cli_->set_proxy_digest_auth(username, password); +} + +inline void Client::enable_server_certificate_verification(bool enabled) +{ + cli_->enable_server_certificate_verification(enabled); +} + +inline void Client::enable_server_hostname_verification(bool enabled) +{ + cli_->enable_server_hostname_verification(enabled); +} + +inline void Client::enable_system_ca(bool enabled) +{ + cli_->enable_system_ca(enabled); +} + +#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE +inline void Client::enable_windows_certificate_verification(bool enabled) +{ + if (is_ssl_) { + static_cast(*cli_).enable_windows_certificate_verification(enabled); + } +} +#endif + +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path) +{ + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); +} + +inline void Client::set_ca_cert_store(tls::ca_store_t ca_cert_store) +{ + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } + else if (ca_cert_store) { + tls::free_ca_store(ca_cert_store); + } +} + +inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) +{ + if (is_ssl_) { + // Use the PEM-based path so the CA data is retained for redirect transfer + static_cast(*cli_).load_ca_cert_store(ca_cert, size); + } +} + +inline void Client::set_server_certificate_verifier(tls::VerifyCallback verifier) +{ + if (is_ssl_) { + static_cast(*cli_).set_server_certificate_verifier(std::move(verifier)); + } +} + +inline void Client::set_session_verifier(std::function verifier) +{ + if (is_ssl_) { + static_cast(*cli_).set_session_verifier(std::move(verifier)); + } +} + +inline tls::ctx_t Client::tls_context() const +{ + if (is_ssl_) { + return static_cast(*cli_).tls_context(); + } + return nullptr; +} + +#endif // CPPHTTPLIB_SSL_ENABLED + +/* + * Group 7: TLS abstraction layer - Common API + */ + +#ifdef CPPHTTPLIB_SSL_ENABLED + +namespace tls { + +// Helper for PeerCert construction +inline PeerCert get_peer_cert_from_session(const_session_t session) +{ + return PeerCert(get_peer_cert(session)); +} + +namespace impl { + +inline VerifyCallback &get_verify_callback() +{ + static thread_local VerifyCallback callback; + return callback; +} + +inline VerifyCallback &get_mbedtls_verify_callback() +{ + static thread_local VerifyCallback callback; + return callback; +} + +// Check if a string is an IPv4 address +inline bool is_ipv4_address(const std::string &str) +{ + int dots = 0; + for (char c : str) { + if (c == '.') { + dots++; + } + else if (!detail::is_ascii_digit(c)) { + return false; + } + } + return dots == 3; +} + +// Parse IPv4 address string to bytes +inline bool parse_ipv4(const std::string &str, unsigned char *out) +{ + const char *p = str.c_str(); + for (int i = 0; i < 4; i++) { + if (i > 0) { + if (*p != '.') { + return false; + } + p++; + } + int val = 0; + int digits = 0; + while (detail::is_ascii_digit(*p)) { + val = val * 10 + (*p - '0'); + if (val > 255) { + return false; + } + p++; + digits++; + } + if (digits == 0) { + return false; + } + // Reject leading zeros (e.g., "01.002.03.04") to prevent ambiguity + if (digits > 1 && *(p - digits) == '0') { + return false; + } + out[i] = static_cast(val); + } + return *p == '\0'; +} + +// Parse an IP literal (IPv4 or IPv6) into raw network-order bytes. +// `out` must have room for at least 16 bytes. Returns the address length +// (4 for IPv4, 16 for IPv6) on success, or 0 if the string is not an IP +// literal. Used to match a host against iPAddress SANs the same way the +// OpenSSL backend does via X509_check_ip. +inline size_t parse_ip_address(const std::string &str, unsigned char *out) +{ + if (is_ipv4_address(str)) { + return parse_ipv4(str, out) ? 4 : 0; + } + struct in6_addr addr6 = {}; + if (inet_pton(AF_INET6, str.c_str(), &addr6) == 1) { + memcpy(out, &addr6, 16); + return 16; + } + return 0; +} + +#ifdef _WIN32 +// Enumerate Windows system certificates and call callback with DER data +template +inline bool enumerate_windows_system_certs(Callback cb) +{ + bool loaded = false; + static const wchar_t *store_names[] = {L"ROOT", L"CA"}; + for (auto store_name : store_names) { + HCERTSTORE hStore = CertOpenSystemStoreW(0, store_name); + if (hStore) { + PCCERT_CONTEXT pContext = nullptr; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != nullptr) { + if (cb(pContext->pbCertEncoded, pContext->cbCertEncoded)) { + loaded = true; + } + } + CertCloseStore(hStore, 0); + } + } + return loaded; +} +#endif + +#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN +// Enumerate macOS Keychain certificates and call callback with DER data +template +inline bool enumerate_macos_keychain_certs(Callback cb) +{ + bool loaded = false; + const SecTrustSettingsDomain domains[] = { + kSecTrustSettingsDomainSystem, + kSecTrustSettingsDomainAdmin, + kSecTrustSettingsDomainUser, + }; + for (auto domain : domains) { + CFArrayRef certs = nullptr; + OSStatus status = SecTrustSettingsCopyCertificates(domain, &certs); + if (status != errSecSuccess || !certs) { + if (certs) + CFRelease(certs); + continue; + } + CFIndex count = CFArrayGetCount(certs); + for (CFIndex i = 0; i < count; i++) { + SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, i); + CFDataRef data = SecCertificateCopyData(cert); + if (data) { + if (cb(CFDataGetBytePtr(data), static_cast(CFDataGetLength(data)))) { + loaded = true; + } + CFRelease(data); + } + } + CFRelease(certs); + } + return loaded; +} +#endif + +#if !defined(_WIN32) && !(defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)) +// Common CA certificate file paths on Linux/Unix +inline const char **system_ca_paths() +{ + static const char *paths[] = {"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu + "/etc/pki/tls/certs/ca-bundle.crt", // RHEL/CentOS + "/etc/ssl/ca-bundle.pem", // OpenSUSE + "/etc/pki/tls/cacert.pem", // OpenELEC + "/etc/ssl/cert.pem", // Alpine, FreeBSD + nullptr}; + return paths; +} + +// Common CA certificate directory paths on Linux/Unix +inline const char **system_ca_dirs() +{ + static const char *dirs[] = {"/etc/ssl/certs", // Debian/Ubuntu + "/etc/pki/tls/certs", // RHEL/CentOS + "/usr/share/ca-certificates", // Other + nullptr}; + return dirs; +} +#endif + +} // namespace impl + +inline bool set_client_ca_file(ctx_t ctx, const char *ca_file, const char *ca_dir) +{ + if (!ctx) { + return false; + } + + bool success = true; + if (ca_file && *ca_file) { + if (!load_ca_file(ctx, ca_file)) { + success = false; + } + } + if (ca_dir && *ca_dir) { + if (!load_ca_dir(ctx, ca_dir)) { + success = false; + } + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // Set CA list for client certificate request (CertificateRequest message) + if (ca_file && *ca_file) { + auto list = SSL_load_client_CA_file(ca_file); + if (list) { + SSL_CTX_set_client_CA_list(static_cast(ctx), list); + } + } +#endif + + return success; +} + +inline bool set_server_cert_pem(ctx_t ctx, const char *cert, const char *key, const char *password) +{ + return set_client_cert_pem(ctx, cert, key, password); +} + +inline bool set_server_cert_file(ctx_t ctx, const char *cert_path, const char *key_path, const char *password) +{ + return set_client_cert_file(ctx, cert_path, key_path, password); +} + +// PeerCert implementation +inline PeerCert::PeerCert() = default; + +inline PeerCert::PeerCert(cert_t cert) + : cert_(cert) +{ +} + +inline PeerCert::PeerCert(PeerCert &&other) noexcept + : cert_(other.cert_) +{ + other.cert_ = nullptr; +} + +inline PeerCert &PeerCert::operator=(PeerCert &&other) noexcept +{ + if (this != &other) { + if (cert_) { + free_cert(cert_); + } + cert_ = other.cert_; + other.cert_ = nullptr; + } + return *this; +} + +inline PeerCert::~PeerCert() +{ + if (cert_) { + free_cert(cert_); + } +} + +inline PeerCert::operator bool() const +{ + return cert_ != nullptr; +} + +inline std::string PeerCert::subject_cn() const +{ + return cert_ ? get_cert_subject_cn(cert_) : std::string(); +} + +inline std::string PeerCert::issuer_name() const +{ + return cert_ ? get_cert_issuer_name(cert_) : std::string(); +} + +inline bool PeerCert::check_hostname(const char *hostname) const +{ + return cert_ ? verify_hostname(cert_, hostname) : false; +} + +inline std::vector PeerCert::sans() const +{ + std::vector result; + if (cert_) { + get_cert_sans(cert_, result); + } + return result; +} + +inline bool PeerCert::validity(time_t ¬_before, time_t ¬_after) const +{ + return cert_ ? get_cert_validity(cert_, not_before, not_after) : false; +} + +inline std::string PeerCert::serial() const +{ + return cert_ ? get_cert_serial(cert_) : std::string(); +} + +// VerifyContext method implementations +inline std::string VerifyContext::subject_cn() const +{ + return cert ? get_cert_subject_cn(cert) : std::string(); +} + +inline std::string VerifyContext::issuer_name() const +{ + return cert ? get_cert_issuer_name(cert) : std::string(); +} + +inline bool VerifyContext::check_hostname(const char *hostname) const +{ + return cert ? verify_hostname(cert, hostname) : false; +} + +inline std::vector VerifyContext::sans() const +{ + std::vector result; + if (cert) { + get_cert_sans(cert, result); + } + return result; +} + +inline bool VerifyContext::validity(time_t ¬_before, time_t ¬_after) const +{ + return cert ? get_cert_validity(cert, not_before, not_after) : false; +} + +inline std::string VerifyContext::serial() const +{ + return cert ? get_cert_serial(cert) : std::string(); +} + +// TlsError static method implementation +inline std::string TlsError::verify_error_to_string(long error_code) +{ + return verify_error_string(error_code); +} + +} // namespace tls + +// Request::peer_cert() implementation +inline tls::PeerCert Request::peer_cert() const +{ + return tls::get_peer_cert_from_session(ssl); +} + +// Request::sni() implementation +inline std::string Request::sni() const +{ + if (!ssl) { + return std::string(); + } + const char *s = tls::get_sni(ssl); + return s ? std::string(s) : std::string(); +} + +#endif // CPPHTTPLIB_SSL_ENABLED + +/* + * Group 8: TLS abstraction layer - OpenSSL backend + */ + +/* + * OpenSSL Backend Implementation + */ + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace tls { + +namespace impl { + +// Helper to map OpenSSL SSL_get_error to ErrorCode +inline ErrorCode map_ssl_error(int ssl_error, int &out_errno) +{ + switch (ssl_error) { + case SSL_ERROR_NONE: + return ErrorCode::Success; + case SSL_ERROR_WANT_READ: + return ErrorCode::WantRead; + case SSL_ERROR_WANT_WRITE: + return ErrorCode::WantWrite; + case SSL_ERROR_ZERO_RETURN: + return ErrorCode::PeerClosed; + case SSL_ERROR_SYSCALL: + out_errno = errno; + return ErrorCode::SyscallError; + case SSL_ERROR_SSL: + default: + return ErrorCode::Fatal; + } +} + +// Helper: Create client CA list from PEM string +// Returns a new STACK_OF(X509_NAME)* or nullptr on failure +// Caller takes ownership of returned list +inline STACK_OF(X509_NAME) * create_client_ca_list_from_pem(const char *ca_pem) +{ + if (!ca_pem) { + return nullptr; + } + + auto ca_list = sk_X509_NAME_new_null(); + if (!ca_list) { + return nullptr; + } + + BIO *bio = BIO_new_mem_buf(ca_pem, -1); + if (!bio) { + sk_X509_NAME_pop_free(ca_list, X509_NAME_free); + return nullptr; + } + + X509 *cert = nullptr; + while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) != nullptr) { + const X509_NAME *name = X509_get_subject_name(cert); + if (name) { + sk_X509_NAME_push(ca_list, X509_NAME_dup(const_cast(name))); + } + X509_free(cert); + } + BIO_free(bio); + + return ca_list; +} + +// OpenSSL verify callback wrapper +inline int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) +{ + auto &callback = get_verify_callback(); + if (!callback) { + return preverify_ok; + } + + // Get SSL object from X509_STORE_CTX + auto ssl = static_cast(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); + if (!ssl) { + return preverify_ok; + } + + // Get current certificate and depth + auto cert = X509_STORE_CTX_get_current_cert(ctx); + int depth = X509_STORE_CTX_get_error_depth(ctx); + int error = X509_STORE_CTX_get_error(ctx); + + // Build context + VerifyContext verify_ctx; + verify_ctx.session = static_cast(ssl); + verify_ctx.cert = static_cast(cert); + verify_ctx.depth = depth; + verify_ctx.preverify_ok = (preverify_ok != 0); + verify_ctx.error_code = error; + verify_ctx.error_string = (error != X509_V_OK) ? X509_verify_cert_error_string(error) : nullptr; + + return callback(verify_ctx) ? 1 : 0; +} + +// X509_STORE_get0_objects is deprecated since OpenSSL 4.0 because it is not +// thread-safe; X509_STORE_get1_objects (OpenSSL 3.3+) returns a snapshot +// that must be released with release_store_objects +#if !defined(OPENSSL_IS_BORINGSSL) && !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30300000L +#define CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS +#endif + +inline STACK_OF(X509_OBJECT) * get_store_objects(X509_STORE *store) +{ +#ifdef CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS + return X509_STORE_get1_objects(store); +#else + return X509_STORE_get0_objects(store); +#endif +} + +inline void release_store_objects(STACK_OF(X509_OBJECT) * objs) +{ +#ifdef CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS + sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free); +#else + (void)objs; // get0 variant returns an internal pointer; nothing to free +#endif +} + +} // namespace impl + +inline ctx_t create_client_context() +{ + SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); + if (ctx) { + // Disable auto-retry to properly handle non-blocking I/O + SSL_CTX_clear_mode(ctx, SSL_MODE_AUTO_RETRY); + // Set minimum TLS version + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + } + return static_cast(ctx); +} + +inline void free_context(ctx_t ctx) +{ + if (ctx) { + SSL_CTX_free(static_cast(ctx)); + } +} + +inline bool set_min_version(ctx_t ctx, Version version) +{ + if (!ctx) + return false; + return SSL_CTX_set_min_proto_version(static_cast(ctx), static_cast(version)) == 1; +} + +inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) +{ + if (!ctx || !pem || len == 0) + return false; + + auto ssl_ctx = static_cast(ctx); + auto store = SSL_CTX_get_cert_store(ssl_ctx); + if (!store) + return false; + + auto bio = BIO_new_mem_buf(pem, static_cast(len)); + if (!bio) + return false; + + bool ok = true; + X509 *cert = nullptr; + while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) != nullptr) { + if (X509_STORE_add_cert(store, cert) != 1) { + // Ignore duplicate errors + auto err = ERR_peek_last_error(); + if (ERR_GET_REASON(err) != X509_R_CERT_ALREADY_IN_HASH_TABLE) { + ok = false; + } + } + X509_free(cert); + if (!ok) + break; + } + BIO_free(bio); + + // Clear any "no more certificates" errors + ERR_clear_error(); + return ok; +} + +inline bool load_ca_file(ctx_t ctx, const char *file_path) +{ + if (!ctx || !file_path) + return false; + return SSL_CTX_load_verify_locations(static_cast(ctx), file_path, nullptr) == 1; +} + +inline bool load_ca_dir(ctx_t ctx, const char *dir_path) +{ + if (!ctx || !dir_path) + return false; + return SSL_CTX_load_verify_locations(static_cast(ctx), nullptr, dir_path) == 1; +} + +inline bool load_system_certs(ctx_t ctx) +{ + if (!ctx) + return false; + auto ssl_ctx = static_cast(ctx); + +#ifdef _WIN32 + // Windows: Load from system certificate store (ROOT and CA) + auto store = SSL_CTX_get_cert_store(ssl_ctx); + if (!store) + return false; + + bool loaded_any = false; + static const wchar_t *store_names[] = {L"ROOT", L"CA"}; + for (auto store_name : store_names) { + auto hStore = CertOpenSystemStoreW(NULL, store_name); + if (!hStore) + continue; + + PCCERT_CONTEXT pContext = nullptr; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != nullptr) { + const unsigned char *data = pContext->pbCertEncoded; + auto x509 = d2i_X509(nullptr, &data, pContext->cbCertEncoded); + if (x509) { + if (X509_STORE_add_cert(store, x509) == 1) { + loaded_any = true; + } + X509_free(x509); + } + } + CertCloseStore(hStore, 0); + } + return loaded_any; + +#elif defined(__APPLE__) +#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN + // macOS: Load from Keychain + auto store = SSL_CTX_get_cert_store(ssl_ctx); + if (!store) + return false; + + bool loaded_any = false; + const SecTrustSettingsDomain domains[] = { + kSecTrustSettingsDomainSystem, + kSecTrustSettingsDomainAdmin, + kSecTrustSettingsDomainUser, + }; + for (auto domain : domains) { + CFArrayRef certs = nullptr; + if (SecTrustSettingsCopyCertificates(domain, &certs) != errSecSuccess || !certs) { + if (certs) + CFRelease(certs); + continue; + } + auto count = CFArrayGetCount(certs); + for (CFIndex i = 0; i < count; i++) { + auto cert = reinterpret_cast(const_cast(CFArrayGetValueAtIndex(certs, i))); + CFDataRef der = SecCertificateCopyData(cert); + if (der) { + const unsigned char *data = CFDataGetBytePtr(der); + auto x509 = d2i_X509(nullptr, &data, CFDataGetLength(der)); + if (x509) { + if (X509_STORE_add_cert(store, x509) == 1) { + loaded_any = true; + } + X509_free(x509); + } + CFRelease(der); + } + } + CFRelease(certs); + } + return loaded_any || SSL_CTX_set_default_verify_paths(ssl_ctx) == 1; +#else + return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1; +#endif + +#else + // Other Unix: use default verify paths + return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1; +#endif +} + +inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key, const char *password) +{ + if (!ctx || !cert || !key) + return false; + + auto ssl_ctx = static_cast(ctx); + + // Load certificate + auto cert_bio = BIO_new_mem_buf(cert, -1); + if (!cert_bio) + return false; + + auto x509 = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr); + BIO_free(cert_bio); + if (!x509) + return false; + + auto cert_ok = SSL_CTX_use_certificate(ssl_ctx, x509) == 1; + X509_free(x509); + if (!cert_ok) + return false; + + // Load private key + auto key_bio = BIO_new_mem_buf(key, -1); + if (!key_bio) + return false; + + auto pkey = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr, password ? const_cast(password) : nullptr); + BIO_free(key_bio); + if (!pkey) + return false; + + auto key_ok = SSL_CTX_use_PrivateKey(ssl_ctx, pkey) == 1; + EVP_PKEY_free(pkey); + + return key_ok && SSL_CTX_check_private_key(ssl_ctx) == 1; +} + +inline bool set_client_cert_file(ctx_t ctx, const char *cert_path, const char *key_path, const char *password) +{ + if (!ctx || !cert_path || !key_path) + return false; + + auto ssl_ctx = static_cast(ctx); + + if (password && password[0] != '\0') { + SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, reinterpret_cast(const_cast(password))); + } + + return SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_path) == 1 && + SSL_CTX_use_PrivateKey_file(ssl_ctx, key_path, SSL_FILETYPE_PEM) == 1; +} + +inline ctx_t create_server_context() +{ + SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); + if (ctx) { + SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + } + return static_cast(ctx); +} + +inline void set_verify_client(ctx_t ctx, bool require) +{ + if (!ctx) + return; + SSL_CTX_set_verify(static_cast(ctx), require ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT) : SSL_VERIFY_NONE, nullptr); +} + +inline session_t create_session(ctx_t ctx, socket_t sock) +{ + if (!ctx || sock == INVALID_SOCKET) + return nullptr; + + auto ssl_ctx = static_cast(ctx); + SSL *ssl = SSL_new(ssl_ctx); + if (!ssl) + return nullptr; + + // Disable auto-retry for proper non-blocking I/O handling + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); + + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + if (!bio) { + SSL_free(ssl); + return nullptr; + } + + SSL_set_bio(ssl, bio, bio); + return static_cast(ssl); +} + +inline void free_session(session_t session) +{ + if (session) { + SSL_free(static_cast(session)); + } +} + +inline bool set_sni(session_t session, const char *hostname) +{ + if (!session || !hostname) + return false; + + auto ssl = static_cast(session); + + // Set SNI (Server Name Indication) only - does not enable verification +#if defined(OPENSSL_IS_BORINGSSL) + return SSL_set_tlsext_host_name(ssl, hostname) == 1; +#else + // Direct call instead of macro to suppress -Wold-style-cast warning + return SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, static_cast(const_cast(hostname))) == 1; +#endif +} + +inline bool set_hostname(session_t session, const char *hostname) +{ + if (!session || !hostname) + return false; + + auto ssl = static_cast(session); + + // Enable hostname verification + auto param = SSL_get0_param(ssl); + if (!param) + return false; + + if (detail::is_ip_address(hostname)) { + // RFC 6066: SNI must not be set for IP addresses; verify against the + // certificate's IP SANs instead of its DNS names + if (X509_VERIFY_PARAM_set1_ip_asc(param, hostname) != 1) { + return false; + } + } + else { + // Set SNI (Server Name Indication) + if (!set_sni(session, hostname)) { + return false; + } + + X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + if (X509_VERIFY_PARAM_set1_host(param, hostname, 0) != 1) { + return false; + } + } + + SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr); + return true; +} + +inline TlsError connect(session_t session) +{ + if (!session) { + return TlsError(); + } + + auto ssl = static_cast(session); + auto ret = SSL_connect(ssl); + + TlsError err; + if (ret == 1) { + err.code = ErrorCode::Success; + } + else { + auto ssl_err = SSL_get_error(ssl, ret); + err.code = impl::map_ssl_error(ssl_err, err.sys_errno); + err.backend_code = ERR_get_error(); + } + return err; +} + +inline TlsError accept(session_t session) +{ + if (!session) { + return TlsError(); + } + + auto ssl = static_cast(session); + auto ret = SSL_accept(ssl); + + TlsError err; + if (ret == 1) { + err.code = ErrorCode::Success; + } + else { + auto ssl_err = SSL_get_error(ssl, ret); + err.code = impl::map_ssl_error(ssl_err, err.sys_errno); + err.backend_code = ERR_get_error(); + } + return err; +} + +inline bool connect_nonblocking(session_t session, socket_t sock, time_t timeout_sec, time_t timeout_usec, TlsError *err) +{ + if (!session) { + if (err) { + err->code = ErrorCode::Fatal; + } + return false; + } + + auto ssl = static_cast(session); + auto bio = SSL_get_rbio(ssl); + + // Set non-blocking mode for handshake + detail::set_nonblocking(sock, true); + if (bio) { + BIO_set_nbio(bio, 1); + } + + auto cleanup = detail::scope_exit([&]() { + // Restore blocking mode after handshake + if (bio) { + BIO_set_nbio(bio, 0); + } + detail::set_nonblocking(sock, false); + }); + + auto res = 0; + while ((res = SSL_connect(ssl)) != 1) { + auto ssl_err = SSL_get_error(ssl, res); + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) { + continue; + } + break; + case SSL_ERROR_WANT_WRITE: + if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) { + continue; + } + break; + default: + break; + } + if (err) { + err->code = impl::map_ssl_error(ssl_err, err->sys_errno); + err->backend_code = ERR_get_error(); + } + return false; + } + if (err) { + err->code = ErrorCode::Success; + } + return true; +} + +inline bool accept_nonblocking(session_t session, socket_t sock, time_t timeout_sec, time_t timeout_usec, TlsError *err) +{ + if (!session) { + if (err) { + err->code = ErrorCode::Fatal; + } + return false; + } + + auto ssl = static_cast(session); + auto bio = SSL_get_rbio(ssl); + + // Set non-blocking mode for handshake + detail::set_nonblocking(sock, true); + if (bio) { + BIO_set_nbio(bio, 1); + } + + auto cleanup = detail::scope_exit([&]() { + // Restore blocking mode after handshake + if (bio) { + BIO_set_nbio(bio, 0); + } + detail::set_nonblocking(sock, false); + }); + + auto res = 0; + while ((res = SSL_accept(ssl)) != 1) { + auto ssl_err = SSL_get_error(ssl, res); + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) { + continue; + } + break; + case SSL_ERROR_WANT_WRITE: + if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) { + continue; + } + break; + default: + break; + } + if (err) { + err->code = impl::map_ssl_error(ssl_err, err->sys_errno); + err->backend_code = ERR_get_error(); + } + return false; + } + if (err) { + err->code = ErrorCode::Success; + } + return true; +} + +inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) +{ + if (!session || !buf) { + err.code = ErrorCode::Fatal; + return -1; + } + + auto ssl = static_cast(session); + constexpr auto max_len = static_cast((std::numeric_limits::max)()); + if (len > max_len) { + len = max_len; + } + auto ret = SSL_read(ssl, buf, static_cast(len)); + + if (ret > 0) { + err.code = ErrorCode::Success; + return ret; + } + + auto ssl_err = SSL_get_error(ssl, ret); + err.code = impl::map_ssl_error(ssl_err, err.sys_errno); + if (err.code == ErrorCode::PeerClosed) { + return 0; + } // Gracefully handle the peer closed state. + if (err.code == ErrorCode::Fatal) { + err.backend_code = ERR_get_error(); + } + return -1; +} + +inline ssize_t write(session_t session, const void *buf, size_t len, TlsError &err) +{ + if (!session || !buf) { + err.code = ErrorCode::Fatal; + return -1; + } + + auto ssl = static_cast(session); + auto ret = SSL_write(ssl, buf, static_cast(len)); + + if (ret > 0) { + err.code = ErrorCode::Success; + return ret; + } + + auto ssl_err = SSL_get_error(ssl, ret); + err.code = impl::map_ssl_error(ssl_err, err.sys_errno); + if (err.code == ErrorCode::Fatal) { + err.backend_code = ERR_get_error(); + } + return -1; +} + +inline int pending(const_session_t session) +{ + if (!session) + return 0; + return SSL_pending(static_cast(const_cast(session))); +} + +inline void shutdown(session_t session, bool graceful) +{ + if (!session) + return; + + auto ssl = static_cast(session); + if (graceful) { + // First call sends close_notify + if (SSL_shutdown(ssl) == 0) { + // Second call waits for peer's close_notify + SSL_shutdown(ssl); + } + } +} + +inline bool is_peer_closed(session_t session, socket_t sock) +{ + if (!session) + return true; + + // Temporarily set socket to non-blocking to avoid blocking on SSL_peek + detail::set_nonblocking(sock, true); + auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); }); + + auto ssl = static_cast(session); + char buf; + auto ret = SSL_peek(ssl, &buf, 1); + if (ret > 0) + return false; + + auto err = SSL_get_error(ssl, ret); + return err == SSL_ERROR_ZERO_RETURN; +} + +inline cert_t get_peer_cert(const_session_t session) +{ + if (!session) + return nullptr; + return static_cast(SSL_get1_peer_certificate(static_cast(const_cast(session)))); +} + +inline void free_cert(cert_t cert) +{ + if (cert) { + X509_free(static_cast(cert)); + } +} + +inline bool verify_hostname(cert_t cert, const char *hostname) +{ + if (!cert || !hostname) + return false; + + auto x509 = static_cast(cert); + + // Use X509_check_ip_asc for IP addresses, X509_check_host for DNS names + if (detail::is_ip_address(hostname)) { + return X509_check_ip_asc(x509, hostname, 0) == 1; + } + return X509_check_host(x509, hostname, strlen(hostname), 0, nullptr) == 1; +} + +inline uint64_t hostname_mismatch_code() +{ + return static_cast(X509_V_ERR_HOSTNAME_MISMATCH); +} + +inline long get_verify_result(const_session_t session) +{ + if (!session) + return X509_V_ERR_UNSPECIFIED; + return SSL_get_verify_result(static_cast(const_cast(session))); +} + +inline std::string get_cert_subject_cn(cert_t cert) +{ + if (!cert) + return ""; + auto x509 = static_cast(cert); + auto subject_name = X509_get_subject_name(x509); + if (!subject_name) + return ""; + + // X509_NAME_get_text_by_NID is deprecated since OpenSSL 4.0 + auto idx = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1); + if (idx < 0) + return ""; + + auto entry = X509_NAME_get_entry(subject_name, idx); + if (!entry) + return ""; + + auto data = X509_NAME_ENTRY_get_data(entry); + if (!data) + return ""; + + return std::string(reinterpret_cast(ASN1_STRING_get0_data(data)), static_cast(ASN1_STRING_length(data))); +} + +inline std::string get_cert_issuer_name(cert_t cert) +{ + if (!cert) + return ""; + auto x509 = static_cast(cert); + auto issuer_name = X509_get_issuer_name(x509); + if (!issuer_name) + return ""; + + char buf[256]; + X509_NAME_oneline(issuer_name, buf, sizeof(buf)); + return std::string(buf); +} + +inline bool get_cert_sans(cert_t cert, std::vector &sans) +{ + sans.clear(); + if (!cert) + return false; + auto x509 = static_cast(cert); + + auto names = static_cast(X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr)); + if (!names) + return true; // No SANs is valid + + auto count = sk_GENERAL_NAME_num(names); + for (decltype(count) i = 0; i < count; i++) { + auto gen = sk_GENERAL_NAME_value(names, i); + if (!gen) + continue; + + SanEntry entry; + switch (gen->type) { + case GEN_DNS: + entry.type = SanType::DNS; + if (gen->d.dNSName) { + entry.value = std::string(reinterpret_cast(ASN1_STRING_get0_data(gen->d.dNSName)), + static_cast(ASN1_STRING_length(gen->d.dNSName))); + } + break; + case GEN_IPADD: + entry.type = SanType::IP; + if (gen->d.iPAddress) { + auto data = ASN1_STRING_get0_data(gen->d.iPAddress); + auto len = ASN1_STRING_length(gen->d.iPAddress); + if (len == 4) { + // IPv4 + char buf[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, data, buf, sizeof(buf)); + entry.value = buf; + } + else if (len == 16) { + // IPv6 + char buf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, data, buf, sizeof(buf)); + entry.value = buf; + } + } + break; + case GEN_EMAIL: + entry.type = SanType::EMAIL; + if (gen->d.rfc822Name) { + entry.value = std::string(reinterpret_cast(ASN1_STRING_get0_data(gen->d.rfc822Name)), + static_cast(ASN1_STRING_length(gen->d.rfc822Name))); + } + break; + case GEN_URI: + entry.type = SanType::URI; + if (gen->d.uniformResourceIdentifier) { + entry.value = std::string(reinterpret_cast(ASN1_STRING_get0_data(gen->d.uniformResourceIdentifier)), + static_cast(ASN1_STRING_length(gen->d.uniformResourceIdentifier))); + } + break; + default: + entry.type = SanType::OTHER; + break; + } + + if (!entry.value.empty()) { + sans.push_back(std::move(entry)); + } + } + + GENERAL_NAMES_free(names); + return true; +} + +inline bool get_cert_validity(cert_t cert, time_t ¬_before, time_t ¬_after) +{ + if (!cert) + return false; + auto x509 = static_cast(cert); + + auto nb = X509_get0_notBefore(x509); + auto na = X509_get0_notAfter(x509); + if (!nb || !na) + return false; + + ASN1_TIME *epoch = ASN1_TIME_new(); + if (!epoch) + return false; + auto se = detail::scope_exit([&] { ASN1_TIME_free(epoch); }); + + if (!ASN1_TIME_set(epoch, 0)) + return false; + + int pday, psec; + + if (!ASN1_TIME_diff(&pday, &psec, epoch, nb)) + return false; + not_before = 86400 * (time_t)pday + psec; + + if (!ASN1_TIME_diff(&pday, &psec, epoch, na)) + return false; + not_after = 86400 * (time_t)pday + psec; + + return true; +} + +inline std::string get_cert_serial(cert_t cert) +{ + if (!cert) + return ""; + auto x509 = static_cast(cert); + + auto serial = X509_get_serialNumber(x509); + if (!serial) + return ""; + + auto bn = ASN1_INTEGER_to_BN(serial, nullptr); + if (!bn) + return ""; + + auto hex = BN_bn2hex(bn); + BN_free(bn); + if (!hex) + return ""; + + std::string result(hex); + OPENSSL_free(hex); + return result; +} + +inline bool get_cert_der(cert_t cert, std::vector &der) +{ + if (!cert) + return false; + auto x509 = static_cast(cert); + auto len = i2d_X509(x509, nullptr); + if (len < 0) + return false; + der.resize(static_cast(len)); + auto p = der.data(); + i2d_X509(x509, &p); + return true; +} + +inline const char *get_sni(const_session_t session) +{ + if (!session) + return nullptr; + auto ssl = static_cast(const_cast(session)); + return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); +} + +inline uint64_t peek_error() +{ + return ERR_peek_last_error(); +} + +inline uint64_t get_error() +{ + return ERR_get_error(); +} + +inline std::string error_string(uint64_t code) +{ + char buf[256]; + ERR_error_string_n(static_cast(code), buf, sizeof(buf)); + return std::string(buf); +} + +inline ca_store_t create_ca_store(const char *pem, size_t len) +{ + auto mem = BIO_new_mem_buf(pem, static_cast(len)); + if (!mem) { + return nullptr; + } + auto mem_guard = detail::scope_exit([&] { BIO_free_all(mem); }); + + auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr); + if (!inf) { + return nullptr; + } + + auto store = X509_STORE_new(); + if (store) { + for (auto i = 0; i < static_cast(sk_X509_INFO_num(inf)); i++) { + auto itmp = sk_X509_INFO_value(inf, i); + if (!itmp) { + continue; + } + if (itmp->x509) { + X509_STORE_add_cert(store, itmp->x509); + } + if (itmp->crl) { + X509_STORE_add_crl(store, itmp->crl); + } + } + } + + sk_X509_INFO_pop_free(inf, X509_INFO_free); + return static_cast(store); +} + +inline void free_ca_store(ca_store_t store) +{ + if (store) { + X509_STORE_free(static_cast(store)); + } +} + +inline bool set_ca_store(ctx_t ctx, ca_store_t store) +{ + if (!ctx || !store) { + return false; + } + auto ssl_ctx = static_cast(ctx); + auto x509_store = static_cast(store); + + // Check if same store is already set + if (SSL_CTX_get_cert_store(ssl_ctx) == x509_store) { + return true; + } + + // SSL_CTX_set_cert_store takes ownership and frees the old store + SSL_CTX_set_cert_store(ssl_ctx, x509_store); + return true; +} + +inline size_t get_ca_certs(ctx_t ctx, std::vector &certs) +{ + certs.clear(); + if (!ctx) { + return 0; + } + auto ssl_ctx = static_cast(ctx); + + auto store = SSL_CTX_get_cert_store(ssl_ctx); + if (!store) { + return 0; + } + + auto objs = impl::get_store_objects(store); + if (!objs) { + return 0; + } + auto se = detail::scope_exit([&] { impl::release_store_objects(objs); }); + + auto count = sk_X509_OBJECT_num(objs); + for (decltype(count) i = 0; i < count; i++) { + auto obj = sk_X509_OBJECT_value(objs, i); + if (!obj) { + continue; + } + if (X509_OBJECT_get_type(obj) == X509_LU_X509) { + auto x509 = X509_OBJECT_get0_X509(obj); + if (x509) { + // Increment reference count so caller can free it + X509_up_ref(x509); + certs.push_back(static_cast(x509)); + } + } + } + return certs.size(); +} + +inline std::vector get_ca_names(ctx_t ctx) +{ + std::vector names; + if (!ctx) { + return names; + } + auto ssl_ctx = static_cast(ctx); + + auto store = SSL_CTX_get_cert_store(ssl_ctx); + if (!store) { + return names; + } + + auto objs = impl::get_store_objects(store); + if (!objs) { + return names; + } + auto se = detail::scope_exit([&] { impl::release_store_objects(objs); }); + + auto count = sk_X509_OBJECT_num(objs); + for (decltype(count) i = 0; i < count; i++) { + auto obj = sk_X509_OBJECT_value(objs, i); + if (!obj) { + continue; + } + if (X509_OBJECT_get_type(obj) == X509_LU_X509) { + auto x509 = X509_OBJECT_get0_X509(obj); + if (x509) { + auto subject = X509_get_subject_name(x509); + if (subject) { + char buf[512]; + X509_NAME_oneline(subject, buf, sizeof(buf)); + names.push_back(buf); + } + } + } + } + return names; +} + +inline bool update_server_cert(ctx_t ctx, const char *cert_pem, const char *key_pem, const char *password) +{ + if (!ctx || !cert_pem || !key_pem) { + return false; + } + auto ssl_ctx = static_cast(ctx); + + // Load certificate from PEM + auto cert_bio = BIO_new_mem_buf(cert_pem, -1); + if (!cert_bio) { + return false; + } + auto cert = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr); + BIO_free(cert_bio); + if (!cert) { + return false; + } + + // Load private key from PEM + auto key_bio = BIO_new_mem_buf(key_pem, -1); + if (!key_bio) { + X509_free(cert); + return false; + } + auto key = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr, password ? const_cast(password) : nullptr); + BIO_free(key_bio); + if (!key) { + X509_free(cert); + return false; + } + + // Update certificate and key + auto ret = SSL_CTX_use_certificate(ssl_ctx, cert) == 1 && SSL_CTX_use_PrivateKey(ssl_ctx, key) == 1; + + X509_free(cert); + EVP_PKEY_free(key); + return ret; +} + +inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) +{ + if (!ctx || !ca_pem) { + return false; + } + auto ssl_ctx = static_cast(ctx); + + // Create new X509_STORE from PEM + auto store = create_ca_store(ca_pem, strlen(ca_pem)); + if (!store) { + return false; + } + + // SSL_CTX_set_cert_store takes ownership + SSL_CTX_set_cert_store(ssl_ctx, static_cast(store)); + + // Set client CA list for client certificate request + auto ca_list = impl::create_client_ca_list_from_pem(ca_pem); + if (ca_list) { + // SSL_CTX_set_client_CA_list takes ownership of ca_list + SSL_CTX_set_client_CA_list(ssl_ctx, ca_list); + } + + return true; +} + +inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) +{ + if (!ctx) { + return false; + } + auto ssl_ctx = static_cast(ctx); + + impl::get_verify_callback() = std::move(callback); + + if (impl::get_verify_callback()) { + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, impl::openssl_verify_callback); + } + else { + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr); + } + return true; +} + +inline long get_verify_error(const_session_t session) +{ + if (!session) { + return -1; + } + auto ssl = static_cast(const_cast(session)); + return SSL_get_verify_result(ssl); +} + +inline std::string verify_error_string(long error_code) +{ + if (error_code == X509_V_OK) { + return ""; + } + const char *str = X509_verify_cert_error_string(static_cast(error_code)); + return str ? str : "unknown error"; +} + +} // namespace tls + +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +/* + * Group 9: TLS abstraction layer - Mbed TLS backend + */ + +/* + * Mbed TLS Backend Implementation + */ + +#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT +namespace tls { + +namespace impl { + +// Mbed TLS session wrapper +struct MbedTlsSession { + mbedtls_ssl_context ssl; + socket_t sock = INVALID_SOCKET; + std::string hostname; // For client: set via set_sni + std::string sni_hostname; // For server: received from client via SNI callback + + // Mbed TLS has no SSL_peek() equivalent, so is_peer_closed() must probe with + // a real 1-byte mbedtls_ssl_read(). If that probe lands on application data + // (e.g. a response that arrived while this side was still in its post-write + // check), the byte is pushed back here and served by the next read(). + unsigned char peeked_byte = 0; + bool has_peeked_byte = false; + + MbedTlsSession() { mbedtls_ssl_init(&ssl); } + + ~MbedTlsSession() { mbedtls_ssl_free(&ssl); } + + MbedTlsSession(const MbedTlsSession &) = delete; + MbedTlsSession &operator=(const MbedTlsSession &) = delete; +}; + +// Thread-local error code accessor for Mbed TLS (since it doesn't have an error +// queue) +inline int &mbedtls_last_error() +{ + static thread_local int err = 0; + return err; +} + +// Helper to map Mbed TLS error to ErrorCode +inline ErrorCode map_mbedtls_error(int ret, int &out_errno) +{ + if (ret == 0) { + return ErrorCode::Success; + } + if (ret == MBEDTLS_ERR_SSL_WANT_READ) { + return ErrorCode::WantRead; + } + if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + return ErrorCode::WantWrite; + } + if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + return ErrorCode::PeerClosed; + } + if (ret == MBEDTLS_ERR_NET_CONN_RESET || ret == MBEDTLS_ERR_NET_SEND_FAILED || ret == MBEDTLS_ERR_NET_RECV_FAILED) { + out_errno = errno; + return ErrorCode::SyscallError; + } + if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) { + return ErrorCode::CertVerifyFailed; + } + return ErrorCode::Fatal; +} + +// A TLS 1.3 NewSessionTicket (signaled by default on Mbed TLS 4.x) is a +// non-fatal notification delivered between records, not an error and not +// application data, so I/O calls that see it should just be retried. Kept in +// one helper so the retry loops keep an intact "do { } while (...)" instead of +// splitting the closing brace across an #if. +inline bool mbedtls_is_session_ticket(int ret) +{ +#if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) + return ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET; +#else + (void)ret; + return false; +#endif +} + +// BIO-like send callback for Mbed TLS +inline int mbedtls_net_send_cb(void *ctx, const unsigned char *buf, size_t len) +{ + auto sock = *static_cast(ctx); +#ifdef _WIN32 + auto ret = send(sock, reinterpret_cast(buf), static_cast(len), 0); + if (ret == SOCKET_ERROR) { + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK) { + return MBEDTLS_ERR_SSL_WANT_WRITE; + } + return MBEDTLS_ERR_NET_SEND_FAILED; + } +#else + auto ret = send(sock, buf, len, 0); + if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return MBEDTLS_ERR_SSL_WANT_WRITE; + } + return MBEDTLS_ERR_NET_SEND_FAILED; + } +#endif + return static_cast(ret); +} + +// BIO-like recv callback for Mbed TLS +inline int mbedtls_net_recv_cb(void *ctx, unsigned char *buf, size_t len) +{ + auto sock = *static_cast(ctx); +#ifdef _WIN32 + auto ret = recv(sock, reinterpret_cast(buf), static_cast(len), 0); + if (ret == SOCKET_ERROR) { + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK) { + return MBEDTLS_ERR_SSL_WANT_READ; + } + return MBEDTLS_ERR_NET_RECV_FAILED; + } +#else + auto ret = recv(sock, buf, len, 0); + if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return MBEDTLS_ERR_SSL_WANT_READ; + } + return MBEDTLS_ERR_NET_RECV_FAILED; + } +#endif + if (ret == 0) { + return MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY; + } + return static_cast(ret); +} + +// MbedTlsContext constructor/destructor implementations +inline MbedTlsContext::MbedTlsContext() +{ + mbedtls_ssl_config_init(&conf); +#ifndef CPPHTTPLIB_MBEDTLS_V4 + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_init(&ctr_drbg); +#endif + mbedtls_x509_crt_init(&ca_chain); + mbedtls_x509_crt_init(&own_cert); + mbedtls_pk_init(&own_key); +} + +inline MbedTlsContext::~MbedTlsContext() +{ + mbedtls_pk_free(&own_key); + mbedtls_x509_crt_free(&own_cert); + mbedtls_x509_crt_free(&ca_chain); +#ifndef CPPHTTPLIB_MBEDTLS_V4 + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); +#endif + mbedtls_ssl_config_free(&conf); +} + +// Thread-local storage for SNI captured during handshake +// This is needed because the SNI callback doesn't have a way to pass +// session-specific data before the session is fully set up +inline std::string &mbedpending_sni() +{ + static thread_local std::string sni; + return sni; +} + +// SNI callback for Mbed TLS server to capture client's SNI hostname +inline int mbedtls_sni_callback(void *p_ctx, mbedtls_ssl_context *ssl, const unsigned char *name, size_t name_len) +{ + (void)p_ctx; + (void)ssl; + + // Store SNI name in thread-local storage + // It will be retrieved and stored in the session after handshake + if (name && name_len > 0) { + mbedpending_sni().assign(reinterpret_cast(name), name_len); + } + else { + mbedpending_sni().clear(); + } + return 0; // Accept any SNI +} + +inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt, int cert_depth, uint32_t *flags); + +// MbedTLS verify callback wrapper +inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt, int cert_depth, uint32_t *flags) +{ + auto &callback = get_verify_callback(); + if (!callback) { + return 0; + } // Continue with default verification + + // data points to the MbedTlsSession + auto *session = static_cast(data); + + // Build context + VerifyContext verify_ctx; + verify_ctx.session = static_cast(session); + verify_ctx.cert = static_cast(crt); + verify_ctx.depth = cert_depth; + verify_ctx.preverify_ok = (*flags == 0); + verify_ctx.error_code = static_cast(*flags); + + // Convert Mbed TLS flags to error string + static thread_local char error_buf[256]; + if (*flags != 0) { + mbedtls_x509_crt_verify_info(error_buf, sizeof(error_buf), "", *flags); + verify_ctx.error_string = error_buf; + } + else { + verify_ctx.error_string = nullptr; + } + + bool accepted = callback(verify_ctx); + + if (accepted) { + *flags = 0; // Clear all error flags + return 0; + } + return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED; +} + +} // namespace impl + +inline ctx_t create_client_context() +{ + auto ctx = new (std::nothrow) impl::MbedTlsContext(); + if (!ctx) { + return nullptr; + } + + ctx->is_server = false; + +#ifdef CPPHTTPLIB_MBEDTLS_V4 + // Mbed TLS 4.x draws randomness from PSA Crypto; just ensure it is ready. + if (!detail::ensure_mbedtls_psa_crypto()) { + delete ctx; + return nullptr; + } + int ret; +#else + // Seed the random number generator + const char *pers = "httplib_client"; + int ret = + mbedtls_ctr_drbg_seed(&ctx->ctr_drbg, mbedtls_entropy_func, &ctx->entropy, reinterpret_cast(pers), strlen(pers)); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + delete ctx; + return nullptr; + } +#endif + + // Set up SSL config for client + ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + delete ctx; + return nullptr; + } + +#ifndef CPPHTTPLIB_MBEDTLS_V4 + // Set random number generator (Mbed TLS 4.x uses the PSA RNG implicitly) + mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg); +#endif + + // Default: verify peer certificate + mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED); + + // Set minimum TLS version to 1.2 +#ifdef CPPHTTPLIB_MBEDTLS_V3 + mbedtls_ssl_conf_min_tls_version(&ctx->conf, MBEDTLS_SSL_VERSION_TLS1_2); +#else + mbedtls_ssl_conf_min_version(&ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); +#endif + + return static_cast(ctx); +} + +inline ctx_t create_server_context() +{ + auto ctx = new (std::nothrow) impl::MbedTlsContext(); + if (!ctx) { + return nullptr; + } + + ctx->is_server = true; + +#ifdef CPPHTTPLIB_MBEDTLS_V4 + // Mbed TLS 4.x draws randomness from PSA Crypto; just ensure it is ready. + if (!detail::ensure_mbedtls_psa_crypto()) { + delete ctx; + return nullptr; + } + int ret; +#else + // Seed the random number generator + const char *pers = "httplib_server"; + int ret = + mbedtls_ctr_drbg_seed(&ctx->ctr_drbg, mbedtls_entropy_func, &ctx->entropy, reinterpret_cast(pers), strlen(pers)); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + delete ctx; + return nullptr; + } +#endif + + // Set up SSL config for server + ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + delete ctx; + return nullptr; + } + +#ifndef CPPHTTPLIB_MBEDTLS_V4 + // Set random number generator (Mbed TLS 4.x uses the PSA RNG implicitly) + mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg); +#endif + + // Default: don't verify client + mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_NONE); + + // Set minimum TLS version to 1.2 +#ifdef CPPHTTPLIB_MBEDTLS_V3 + mbedtls_ssl_conf_min_tls_version(&ctx->conf, MBEDTLS_SSL_VERSION_TLS1_2); +#else + mbedtls_ssl_conf_min_version(&ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); +#endif + + // Set SNI callback to capture client's SNI hostname + mbedtls_ssl_conf_sni(&ctx->conf, impl::mbedtls_sni_callback, nullptr); + + return static_cast(ctx); +} + +inline void free_context(ctx_t ctx) +{ + if (ctx) { + delete static_cast(ctx); + } +} + +inline bool set_min_version(ctx_t ctx, Version version) +{ + if (!ctx) { + return false; + } + auto mctx = static_cast(ctx); + +#ifdef CPPHTTPLIB_MBEDTLS_V3 + // Mbed TLS 3.x uses mbedtls_ssl_protocol_version enum + mbedtls_ssl_protocol_version min_ver = MBEDTLS_SSL_VERSION_TLS1_2; + if (version >= Version::TLS1_3) { +#if defined(MBEDTLS_SSL_PROTO_TLS1_3) + min_ver = MBEDTLS_SSL_VERSION_TLS1_3; +#endif + } + mbedtls_ssl_conf_min_tls_version(&mctx->conf, min_ver); +#else + // Mbed TLS 2.x uses major/minor version numbers + int major = MBEDTLS_SSL_MAJOR_VERSION_3; + int minor = MBEDTLS_SSL_MINOR_VERSION_3; // TLS 1.2 + if (version >= Version::TLS1_3) { +#if defined(MBEDTLS_SSL_PROTO_TLS1_3) + minor = MBEDTLS_SSL_MINOR_VERSION_4; // TLS 1.3 +#else + minor = MBEDTLS_SSL_MINOR_VERSION_3; // Fall back to TLS 1.2 +#endif + } + mbedtls_ssl_conf_min_version(&mctx->conf, major, minor); +#endif + return true; +} + +inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) +{ + if (!ctx || !pem) { + return false; + } + auto mctx = static_cast(ctx); + + // mbedtls_x509_crt_parse expects null-terminated string for PEM + // Add null terminator if not present + std::string pem_str(pem, len); + int ret = mbedtls_x509_crt_parse(&mctx->ca_chain, reinterpret_cast(pem_str.c_str()), pem_str.size() + 1); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } + + mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr); + return true; +} + +inline bool load_ca_file(ctx_t ctx, const char *file_path) +{ + if (!ctx || !file_path) { + return false; + } + auto mctx = static_cast(ctx); + + int ret = mbedtls_x509_crt_parse_file(&mctx->ca_chain, file_path); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } + + mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr); + return true; +} + +inline bool load_ca_dir(ctx_t ctx, const char *dir_path) +{ + if (!ctx || !dir_path) { + return false; + } + auto mctx = static_cast(ctx); + + int ret = mbedtls_x509_crt_parse_path(&mctx->ca_chain, dir_path); + if (ret < 0) { // Returns number of certs on success, negative on error + impl::mbedtls_last_error() = ret; + return false; + } + + mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr); + return true; +} + +inline bool load_system_certs(ctx_t ctx) +{ + if (!ctx) { + return false; + } + auto mctx = static_cast(ctx); + bool loaded = false; + +#ifdef _WIN32 + loaded = impl::enumerate_windows_system_certs( + [&](const unsigned char *data, size_t len) { return mbedtls_x509_crt_parse_der(&mctx->ca_chain, data, len) == 0; }); +#elif defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) + loaded = impl::enumerate_macos_keychain_certs( + [&](const unsigned char *data, size_t len) { return mbedtls_x509_crt_parse_der(&mctx->ca_chain, data, len) == 0; }); +#else + for (auto path = impl::system_ca_paths(); *path; ++path) { + if (mbedtls_x509_crt_parse_file(&mctx->ca_chain, *path) >= 0) { + loaded = true; + break; + } + } + + if (!loaded) { + for (auto dir = impl::system_ca_dirs(); *dir; ++dir) { + if (mbedtls_x509_crt_parse_path(&mctx->ca_chain, *dir) >= 0) { + loaded = true; + break; + } + } + } +#endif + + if (loaded) { + mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr); + } + return loaded; +} + +inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key, const char *password) +{ + if (!ctx || !cert || !key) { + return false; + } + auto mctx = static_cast(ctx); + + // Parse certificate + std::string cert_str(cert); + int ret = mbedtls_x509_crt_parse(&mctx->own_cert, reinterpret_cast(cert_str.c_str()), cert_str.size() + 1); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } + + // Parse private key + std::string key_str(key); + const unsigned char *pwd = password ? reinterpret_cast(password) : nullptr; + size_t pwd_len = password ? strlen(password) : 0; + +#if defined(CPPHTTPLIB_MBEDTLS_V3) && !defined(CPPHTTPLIB_MBEDTLS_V4) + ret = mbedtls_pk_parse_key(&mctx->own_key, + reinterpret_cast(key_str.c_str()), + key_str.size() + 1, + pwd, + pwd_len, + mbedtls_ctr_drbg_random, + &mctx->ctr_drbg); +#else + ret = mbedtls_pk_parse_key(&mctx->own_key, reinterpret_cast(key_str.c_str()), key_str.size() + 1, pwd, pwd_len); +#endif + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } + + // Verify that the certificate and private key match. + // Mbed TLS 4.x: mbedtls_pk_check_pair() reports a spurious mismatch for + // PSA-backed keys, so skip it and let the handshake surface a real mismatch. +#ifndef CPPHTTPLIB_MBEDTLS_V4 +#ifdef CPPHTTPLIB_MBEDTLS_V3 + ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key, mbedtls_ctr_drbg_random, &mctx->ctr_drbg); +#else + ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key); +#endif + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } +#endif + + ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } + + return true; +} + +inline bool set_client_cert_file(ctx_t ctx, const char *cert_path, const char *key_path, const char *password) +{ + if (!ctx || !cert_path || !key_path) { + return false; + } + auto mctx = static_cast(ctx); + + // Parse certificate file + int ret = mbedtls_x509_crt_parse_file(&mctx->own_cert, cert_path); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } + + // Parse private key file +#if defined(CPPHTTPLIB_MBEDTLS_V3) && !defined(CPPHTTPLIB_MBEDTLS_V4) + ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password, mbedtls_ctr_drbg_random, &mctx->ctr_drbg); +#else + ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password); +#endif + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } + + // Verify that the certificate and private key match. + // Mbed TLS 4.x: see set_client_cert() — skip the spurious check_pair. +#ifndef CPPHTTPLIB_MBEDTLS_V4 +#ifdef CPPHTTPLIB_MBEDTLS_V3 + ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key, mbedtls_ctr_drbg_random, &mctx->ctr_drbg); +#else + ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key); +#endif + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } +#endif + + ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } + + return true; +} + +inline void set_verify_client(ctx_t ctx, bool require) +{ + if (!ctx) { + return; + } + auto mctx = static_cast(ctx); + mctx->verify_client = require; + if (require) { + mbedtls_ssl_conf_authmode(&mctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED); + } + else { + // If a verify callback is set, use OPTIONAL mode to ensure the callback + // is called (matching OpenSSL behavior). Otherwise use NONE. + mbedtls_ssl_conf_authmode(&mctx->conf, mctx->has_verify_callback ? MBEDTLS_SSL_VERIFY_OPTIONAL : MBEDTLS_SSL_VERIFY_NONE); + } +} + +inline session_t create_session(ctx_t ctx, socket_t sock) +{ + if (!ctx || sock == INVALID_SOCKET) { + return nullptr; + } + auto mctx = static_cast(ctx); + + auto session = new (std::nothrow) impl::MbedTlsSession(); + if (!session) { + return nullptr; + } + + session->sock = sock; + + int ret = mbedtls_ssl_setup(&session->ssl, &mctx->conf); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + delete session; + return nullptr; + } + + // Explicitly opt out of in-handshake hostname verification by default; + // since Mbed TLS 3.6.4 a client handshake with certificate verification + // fails outright when no hostname was set. set_sni() installs the real + // hostname for DNS hosts; for IP hosts (where SNI must not be set) the + // caller verifies the certificate identity post-handshake via + // verify_hostname(). + mbedtls_ssl_set_hostname(&session->ssl, nullptr); + + // Set BIO callbacks + mbedtls_ssl_set_bio(&session->ssl, &session->sock, impl::mbedtls_net_send_cb, impl::mbedtls_net_recv_cb, nullptr); + + // Set per-session verify callback with session pointer if callback is + // registered + if (mctx->has_verify_callback) { + mbedtls_ssl_set_verify(&session->ssl, impl::mbedtls_verify_callback, session); + } + + return static_cast(session); +} + +inline void free_session(session_t session) +{ + if (session) { + delete static_cast(session); + } +} + +inline bool set_sni(session_t session, const char *hostname) +{ + if (!session || !hostname) { + return false; + } + auto msession = static_cast(session); + + int ret = mbedtls_ssl_set_hostname(&msession->ssl, hostname); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } + + msession->hostname = hostname; + return true; +} + +inline bool set_hostname(session_t session, const char *hostname) +{ + // In Mbed TLS, set_hostname also sets up hostname verification + return set_sni(session, hostname); +} + +inline TlsError connect(session_t session) +{ + TlsError err; + if (!session) { + err.code = ErrorCode::Fatal; + return err; + } + + auto msession = static_cast(session); + int ret; + do { + ret = mbedtls_ssl_handshake(&msession->ssl); + } while (impl::mbedtls_is_session_ticket(ret)); + + if (ret == 0) { + err.code = ErrorCode::Success; + } + else { + err.code = impl::map_mbedtls_error(ret, err.sys_errno); + err.backend_code = static_cast(-ret); + impl::mbedtls_last_error() = ret; + } + + return err; +} + +inline TlsError accept(session_t session) +{ + // Same as connect for Mbed TLS - handshake works for both client and server + auto result = connect(session); + + // After successful handshake, capture SNI from thread-local storage + if (result.code == ErrorCode::Success && session) { + auto msession = static_cast(session); + msession->sni_hostname = std::move(impl::mbedpending_sni()); + impl::mbedpending_sni().clear(); + } + + return result; +} + +inline bool connect_nonblocking(session_t session, socket_t sock, time_t timeout_sec, time_t timeout_usec, TlsError *err) +{ + if (!session) { + if (err) { + err->code = ErrorCode::Fatal; + } + return false; + } + + auto msession = static_cast(session); + + // Set socket to non-blocking mode + detail::set_nonblocking(sock, true); + auto cleanup = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); }); + + int ret; + while ((ret = mbedtls_ssl_handshake(&msession->ssl)) != 0) { + // Non-fatal TLS 1.3 ticket; retry immediately. + if (impl::mbedtls_is_session_ticket(ret)) { + continue; + } + if (ret == MBEDTLS_ERR_SSL_WANT_READ) { + if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) { + continue; + } + } + else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) { + continue; + } + } + + // TlsError or timeout + if (err) { + err->code = impl::map_mbedtls_error(ret, err->sys_errno); + err->backend_code = static_cast(-ret); + } + impl::mbedtls_last_error() = ret; + return false; + } + + if (err) { + err->code = ErrorCode::Success; + } + return true; +} + +inline bool accept_nonblocking(session_t session, socket_t sock, time_t timeout_sec, time_t timeout_usec, TlsError *err) +{ + // Same implementation as connect for Mbed TLS + bool result = connect_nonblocking(session, sock, timeout_sec, timeout_usec, err); + + // After successful handshake, capture SNI from thread-local storage + if (result && session) { + auto msession = static_cast(session); + msession->sni_hostname = std::move(impl::mbedpending_sni()); + impl::mbedpending_sni().clear(); + } + + return result; +} + +inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) +{ + if (!session || !buf) { + err.code = ErrorCode::Fatal; + return -1; + } + + auto msession = static_cast(session); + + // Serve a byte consumed by the is_peer_closed() probe before reading more. + if (msession->has_peeked_byte) { + if (len == 0) { + return 0; + } + auto p = static_cast(buf); + p[0] = msession->peeked_byte; + msession->has_peeked_byte = false; + size_t n = 1; + // Top up with any already-decrypted bytes without risking a block. + if (len > 1 && mbedtls_ssl_get_bytes_avail(&msession->ssl) > 0) { + int extra = mbedtls_ssl_read(&msession->ssl, p + 1, len - 1); + if (extra > 0) { + n += static_cast(extra); + } + } + err.code = ErrorCode::Success; + return static_cast(n); + } + + int ret; + do { + ret = mbedtls_ssl_read(&msession->ssl, static_cast(buf), len); + } while (impl::mbedtls_is_session_ticket(ret)); + + if (ret > 0) { + err.code = ErrorCode::Success; + return static_cast(ret); + } + + if (ret == 0) { + err.code = ErrorCode::PeerClosed; + return 0; + } + + err.code = impl::map_mbedtls_error(ret, err.sys_errno); + err.backend_code = static_cast(-ret); + impl::mbedtls_last_error() = ret; + // mbedTLS signals a clean close_notify via a negative error code rather + // than 0; surface it as a clean EOF the way OpenSSL/wolfSSL do. + if (err.code == ErrorCode::PeerClosed) { + return 0; + } + return -1; +} + +inline ssize_t write(session_t session, const void *buf, size_t len, TlsError &err) +{ + if (!session || !buf) { + err.code = ErrorCode::Fatal; + return -1; + } + + auto msession = static_cast(session); + int ret; + do { + ret = mbedtls_ssl_write(&msession->ssl, static_cast(buf), len); + } while (impl::mbedtls_is_session_ticket(ret)); + + if (ret > 0) { + err.code = ErrorCode::Success; + return static_cast(ret); + } + + if (ret == 0) { + err.code = ErrorCode::PeerClosed; + return 0; + } + + err.code = impl::map_mbedtls_error(ret, err.sys_errno); + err.backend_code = static_cast(-ret); + impl::mbedtls_last_error() = ret; + return -1; +} + +inline int pending(const_session_t session) +{ + if (!session) { + return 0; + } + auto msession = static_cast(const_cast(session)); + return static_cast(mbedtls_ssl_get_bytes_avail(&msession->ssl)) + (msession->has_peeked_byte ? 1 : 0); +} + +inline void shutdown(session_t session, bool graceful) +{ + if (!session) { + return; + } + auto msession = static_cast(session); + + if (graceful) { + // Try to send close_notify, but don't block forever + int ret; + int attempts = 0; + while ((ret = mbedtls_ssl_close_notify(&msession->ssl)) != 0 && attempts < 3) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + break; + } + attempts++; + } + } +} + +inline bool is_peer_closed(session_t session, socket_t sock) +{ + if (!session || sock == INVALID_SOCKET) { + return true; + } + auto msession = static_cast(session); + + // Check if there's already decrypted or pushed-back data available. + // If so, the connection is definitely alive. + if (msession->has_peeked_byte || mbedtls_ssl_get_bytes_avail(&msession->ssl) > 0) { + return false; + } + + // Set socket to non-blocking to avoid blocking on read + detail::set_nonblocking(sock, true); + auto cleanup = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); }); + + // Probe with a 1-byte read (Mbed TLS has no peek API). If the probe lands + // on application data — e.g. a response that already arrived — push the + // byte back so the next read() delivers it instead of losing it. + unsigned char buf; + int ret; + do { + ret = mbedtls_ssl_read(&msession->ssl, &buf, 1); + } while (impl::mbedtls_is_session_ticket(ret)); + + // If we got data or WANT_READ (would block), connection is alive + if (ret > 0) { + msession->peeked_byte = buf; + msession->has_peeked_byte = true; + return false; + } + if (ret == MBEDTLS_ERR_SSL_WANT_READ) { + return false; + } + + // If we get a peer close notify or a connection reset, the peer is closed + return ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY || ret == MBEDTLS_ERR_NET_CONN_RESET || ret == 0; +} + +inline cert_t get_peer_cert(const_session_t session) +{ + if (!session) { + return nullptr; + } + auto msession = static_cast(const_cast(session)); + + // Mbed TLS returns a pointer to the internal peer cert chain. + // WARNING: This pointer is only valid while the session is active. + // Do not use the certificate after calling free_session(). + const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(&msession->ssl); + return const_cast(cert); +} + +inline void free_cert(cert_t cert) +{ + // Mbed TLS: peer certificate is owned by the SSL context. + // No-op here, but callers should still call this for cross-backend + // portability. + (void)cert; +} + +inline bool verify_hostname(cert_t cert, const char *hostname) +{ + if (!cert || !hostname) { + return false; + } + auto mcert = static_cast(cert); + std::string host_str(hostname); + + // Check if hostname is an IP address (IPv4 or IPv6) + unsigned char ip_bytes[16]; + auto ip_len = impl::parse_ip_address(host_str, ip_bytes); + auto is_ip = ip_len > 0; + + // Check Subject Alternative Names (SAN) + // In Mbed TLS 3.x, subject_alt_names contains raw values without ASN.1 tags + // - DNS names: raw string bytes + // - IP addresses: raw IP bytes (4 for IPv4, 16 for IPv6) + const mbedtls_x509_sequence *san = &mcert->subject_alt_names; + while (san != nullptr && san->buf.p != nullptr && san->buf.len > 0) { + const unsigned char *p = san->buf.p; + size_t len = san->buf.len; + + if (is_ip) { + // For an IP host, only a matching iPAddress SAN of the same family + // (4 bytes for IPv4, 16 bytes for IPv6) may authenticate it. + if (len == ip_len && memcmp(p, ip_bytes, ip_len) == 0) { + return true; + } + } + else { + // Check if this SAN is a DNS name (printable ASCII string) + bool is_dns = len > 0; + for (size_t i = 0; i < len && is_dns; i++) { + if (p[i] < 32 || p[i] > 126) { + is_dns = false; + } + } + if (is_dns) { + std::string san_name(reinterpret_cast(p), len); + if (detail::match_hostname(san_name, host_str)) { + return true; + } + } + } + san = san->next; + } + + // Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts: + // an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110; + // the OpenSSL backend's X509_check_ip behaves the same way). + if (!is_ip) { + char cn[256]; + int ret = mbedtls_x509_dn_gets(cn, sizeof(cn), &mcert->subject); + if (ret > 0) { + std::string cn_str(cn); + + // Look for "CN=" in the DN string + size_t cn_pos = cn_str.find("CN="); + if (cn_pos != std::string::npos) { + size_t start = cn_pos + 3; + size_t end = cn_str.find(',', start); + std::string cn_value = cn_str.substr(start, end == std::string::npos ? end : end - start); + + if (detail::match_hostname(cn_value, host_str)) { + return true; + } + } + } + } + + return false; +} + +inline uint64_t hostname_mismatch_code() +{ + return static_cast(MBEDTLS_X509_BADCERT_CN_MISMATCH); +} + +inline long get_verify_result(const_session_t session) +{ + if (!session) { + return -1; + } + auto msession = static_cast(const_cast(session)); + uint32_t flags = mbedtls_ssl_get_verify_result(&msession->ssl); + // Return 0 (X509_V_OK equivalent) if verification passed + return flags == 0 ? 0 : static_cast(flags); +} + +inline std::string get_cert_subject_cn(cert_t cert) +{ + if (!cert) + return ""; + auto x509 = static_cast(cert); + + // Find the CN in the subject + const mbedtls_x509_name *name = &x509->subject; + while (name != nullptr) { + if (MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid) == 0) { + return std::string(reinterpret_cast(name->val.p), name->val.len); + } + name = name->next; + } + return ""; +} + +inline std::string get_cert_issuer_name(cert_t cert) +{ + if (!cert) + return ""; + auto x509 = static_cast(cert); + + // Build a human-readable issuer name string + char buf[512]; + int ret = mbedtls_x509_dn_gets(buf, sizeof(buf), &x509->issuer); + if (ret < 0) + return ""; + return std::string(buf); +} + +inline bool get_cert_sans(cert_t cert, std::vector &sans) +{ + sans.clear(); + if (!cert) + return false; + auto x509 = static_cast(cert); + + // Parse the Subject Alternative Name extension + const mbedtls_x509_sequence *cur = &x509->subject_alt_names; + while (cur != nullptr) { + if (cur->buf.len > 0) { + // Mbed TLS stores SAN as ASN.1 sequences + // The tag byte indicates the type + const unsigned char *p = cur->buf.p; + size_t len = cur->buf.len; + + // First byte is the tag + unsigned char tag = *p; + p++; + len--; + + // Parse length (simple single-byte length assumed) + if (len > 0 && *p < 0x80) { + size_t value_len = *p; + p++; + len--; + + if (value_len <= len) { + SanEntry entry; + // ASN.1 context tags for GeneralName + switch (tag & 0x1F) { + case 2: // dNSName + entry.type = SanType::DNS; + entry.value = std::string(reinterpret_cast(p), value_len); + break; + case 7: // iPAddress + entry.type = SanType::IP; + if (value_len == 4) { + // IPv4 + char buf[16]; + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + entry.value = buf; + } + else if (value_len == 16) { + // IPv6 + char buf[64]; + snprintf(buf, + sizeof(buf), + "%02x%02x:%02x%02x:%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:%02x%02x:%02x%02x", + p[0], + p[1], + p[2], + p[3], + p[4], + p[5], + p[6], + p[7], + p[8], + p[9], + p[10], + p[11], + p[12], + p[13], + p[14], + p[15]); + entry.value = buf; + } + break; + case 1: // rfc822Name (email) + entry.type = SanType::EMAIL; + entry.value = std::string(reinterpret_cast(p), value_len); + break; + case 6: // uniformResourceIdentifier + entry.type = SanType::URI; + entry.value = std::string(reinterpret_cast(p), value_len); + break; + default: + entry.type = SanType::OTHER; + break; + } + + if (!entry.value.empty()) { + sans.push_back(std::move(entry)); + } + } + } + } + cur = cur->next; + } + return true; +} + +inline bool get_cert_validity(cert_t cert, time_t ¬_before, time_t ¬_after) +{ + if (!cert) + return false; + auto x509 = static_cast(cert); + + // Convert mbedtls_x509_time to time_t + auto to_time_t = [](const mbedtls_x509_time &t) -> time_t { + struct tm tm_time = {}; + tm_time.tm_year = t.year - 1900; + tm_time.tm_mon = t.mon - 1; + tm_time.tm_mday = t.day; + tm_time.tm_hour = t.hour; + tm_time.tm_min = t.min; + tm_time.tm_sec = t.sec; +#ifdef _WIN32 + return _mkgmtime(&tm_time); +#else + return timegm(&tm_time); +#endif + }; + + not_before = to_time_t(x509->valid_from); + not_after = to_time_t(x509->valid_to); + return true; +} + +inline std::string get_cert_serial(cert_t cert) +{ + if (!cert) + return ""; + auto x509 = static_cast(cert); + + // Convert serial number to hex string + std::string result; + result.reserve(x509->serial.len * 2); + for (size_t i = 0; i < x509->serial.len; i++) { + char hex[3]; + snprintf(hex, sizeof(hex), "%02X", x509->serial.p[i]); + result += hex; + } + return result; +} + +inline bool get_cert_der(cert_t cert, std::vector &der) +{ + if (!cert) + return false; + auto crt = static_cast(cert); + if (!crt->raw.p || crt->raw.len == 0) + return false; + der.assign(crt->raw.p, crt->raw.p + crt->raw.len); + return true; +} + +inline const char *get_sni(const_session_t session) +{ + if (!session) + return nullptr; + auto msession = static_cast(session); + + // For server: return SNI received from client during handshake + if (!msession->sni_hostname.empty()) { + return msession->sni_hostname.c_str(); + } + + // For client: return the hostname set via set_sni + if (!msession->hostname.empty()) { + return msession->hostname.c_str(); + } + + return nullptr; +} + +inline uint64_t peek_error() +{ + // Mbed TLS doesn't have an error queue, return the last error + return static_cast(-impl::mbedtls_last_error()); +} + +inline uint64_t get_error() +{ + // Mbed TLS doesn't have an error queue, return and clear the last error + uint64_t err = static_cast(-impl::mbedtls_last_error()); + impl::mbedtls_last_error() = 0; + return err; +} + +inline std::string error_string(uint64_t code) +{ + char buf[256]; + mbedtls_strerror(-static_cast(code), buf, sizeof(buf)); + return std::string(buf); +} + +inline ca_store_t create_ca_store(const char *pem, size_t len) +{ + auto *ca_chain = new (std::nothrow) mbedtls_x509_crt; + if (!ca_chain) { + return nullptr; + } + + mbedtls_x509_crt_init(ca_chain); + + // mbedtls_x509_crt_parse expects null-terminated PEM + int ret = mbedtls_x509_crt_parse(ca_chain, reinterpret_cast(pem), + len + 1); // +1 for null terminator + if (ret != 0) { + // Try without +1 in case PEM is already null-terminated + ret = mbedtls_x509_crt_parse(ca_chain, reinterpret_cast(pem), len); + if (ret != 0) { + mbedtls_x509_crt_free(ca_chain); + delete ca_chain; + return nullptr; + } + } + + return static_cast(ca_chain); +} + +inline void free_ca_store(ca_store_t store) +{ + if (store) { + auto *ca_chain = static_cast(store); + mbedtls_x509_crt_free(ca_chain); + delete ca_chain; + } +} + +inline bool set_ca_store(ctx_t ctx, ca_store_t store) +{ + if (!ctx || !store) { + return false; + } + auto *mbed_ctx = static_cast(ctx); + auto *ca_chain = static_cast(store); + + // Free existing CA chain + mbedtls_x509_crt_free(&mbed_ctx->ca_chain); + mbedtls_x509_crt_init(&mbed_ctx->ca_chain); + + // Copy the CA chain (deep copy) + // Parse from the raw data of the source cert + mbedtls_x509_crt *src = ca_chain; + while (src != nullptr) { + int ret = mbedtls_x509_crt_parse_der(&mbed_ctx->ca_chain, src->raw.p, src->raw.len); + if (ret != 0) { + free_ca_store(store); + return false; + } + src = src->next; + } + + // This function takes ownership of the store; the chain was deep-copied + // above, so release the source + free_ca_store(store); + + // Update the SSL config to use the new CA chain + mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr); + return true; +} + +inline size_t get_ca_certs(ctx_t ctx, std::vector &certs) +{ + certs.clear(); + if (!ctx) { + return 0; + } + auto *mbed_ctx = static_cast(ctx); + + // Iterate through the CA chain + mbedtls_x509_crt *cert = &mbed_ctx->ca_chain; + while (cert != nullptr && cert->raw.len > 0) { + // Create a copy of the certificate for the caller + auto *copy = new mbedtls_x509_crt; + mbedtls_x509_crt_init(copy); + int ret = mbedtls_x509_crt_parse_der(copy, cert->raw.p, cert->raw.len); + if (ret == 0) { + certs.push_back(static_cast(copy)); + } + else { + mbedtls_x509_crt_free(copy); + delete copy; + } + cert = cert->next; + } + return certs.size(); +} + +inline std::vector get_ca_names(ctx_t ctx) +{ + std::vector names; + if (!ctx) { + return names; + } + auto *mbed_ctx = static_cast(ctx); + + // Iterate through the CA chain + mbedtls_x509_crt *cert = &mbed_ctx->ca_chain; + while (cert != nullptr && cert->raw.len > 0) { + char buf[512]; + int ret = mbedtls_x509_dn_gets(buf, sizeof(buf), &cert->subject); + if (ret > 0) { + names.push_back(buf); + } + cert = cert->next; + } + return names; +} + +inline bool update_server_cert(ctx_t ctx, const char *cert_pem, const char *key_pem, const char *password) +{ + if (!ctx || !cert_pem || !key_pem) { + return false; + } + auto *mbed_ctx = static_cast(ctx); + + // Free existing certificate and key + mbedtls_x509_crt_free(&mbed_ctx->own_cert); + mbedtls_pk_free(&mbed_ctx->own_key); + mbedtls_x509_crt_init(&mbed_ctx->own_cert); + mbedtls_pk_init(&mbed_ctx->own_key); + + // Parse certificate PEM + int ret = mbedtls_x509_crt_parse(&mbed_ctx->own_cert, reinterpret_cast(cert_pem), strlen(cert_pem) + 1); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } + + // Parse private key PEM +#if defined(CPPHTTPLIB_MBEDTLS_V3) && !defined(CPPHTTPLIB_MBEDTLS_V4) + ret = mbedtls_pk_parse_key(&mbed_ctx->own_key, + reinterpret_cast(key_pem), + strlen(key_pem) + 1, + password ? reinterpret_cast(password) : nullptr, + password ? strlen(password) : 0, + mbedtls_ctr_drbg_random, + &mbed_ctx->ctr_drbg); +#else + ret = mbedtls_pk_parse_key(&mbed_ctx->own_key, + reinterpret_cast(key_pem), + strlen(key_pem) + 1, + password ? reinterpret_cast(password) : nullptr, + password ? strlen(password) : 0); +#endif + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } + + // Configure SSL to use the new certificate and key + ret = mbedtls_ssl_conf_own_cert(&mbed_ctx->conf, &mbed_ctx->own_cert, &mbed_ctx->own_key); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } + + return true; +} + +inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) +{ + if (!ctx || !ca_pem) { + return false; + } + auto *mbed_ctx = static_cast(ctx); + + // Free existing CA chain + mbedtls_x509_crt_free(&mbed_ctx->ca_chain); + mbedtls_x509_crt_init(&mbed_ctx->ca_chain); + + // Parse CA PEM + int ret = mbedtls_x509_crt_parse(&mbed_ctx->ca_chain, reinterpret_cast(ca_pem), strlen(ca_pem) + 1); + if (ret != 0) { + impl::mbedtls_last_error() = ret; + return false; + } + + // Update SSL config to use new CA chain + mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr); + return true; +} + +inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) +{ + if (!ctx) { + return false; + } + auto *mbed_ctx = static_cast(ctx); + + impl::get_verify_callback() = std::move(callback); + mbed_ctx->has_verify_callback = static_cast(impl::get_verify_callback()); + + if (mbed_ctx->has_verify_callback) { + // Set OPTIONAL mode to ensure callback is called even when verification + // is disabled (matching OpenSSL behavior where SSL_VERIFY_PEER is set) + mbedtls_ssl_conf_authmode(&mbed_ctx->conf, MBEDTLS_SSL_VERIFY_OPTIONAL); + mbedtls_ssl_conf_verify(&mbed_ctx->conf, impl::mbedtls_verify_callback, nullptr); + } + else { + mbedtls_ssl_conf_verify(&mbed_ctx->conf, nullptr, nullptr); + } + return true; +} + +inline long get_verify_error(const_session_t session) +{ + if (!session) { + return -1; + } + auto *msession = static_cast(const_cast(session)); + return static_cast(mbedtls_ssl_get_verify_result(&msession->ssl)); +} + +inline std::string verify_error_string(long error_code) +{ + if (error_code == 0) { + return ""; + } + char buf[256]; + mbedtls_x509_crt_verify_info(buf, sizeof(buf), "", static_cast(error_code)); + // Remove trailing newline if present + std::string result(buf); + while (!result.empty() && (result.back() == '\n' || result.back() == ' ')) { + result.pop_back(); + } + return result; +} + +} // namespace tls + +#endif // CPPHTTPLIB_MBEDTLS_SUPPORT + +/* + * Group 10: TLS abstraction layer - wolfSSL backend + */ + +/* + * wolfSSL Backend Implementation + */ + +#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT +namespace tls { + +namespace impl { + +// wolfSSL session wrapper +struct WolfSSLSession { + WOLFSSL *ssl = nullptr; + socket_t sock = INVALID_SOCKET; + std::string hostname; // For client: set via set_sni + std::string sni_hostname; // For server: received from client via SNI callback + + WolfSSLSession() = default; + + ~WolfSSLSession() + { + if (ssl) { + wolfSSL_free(ssl); + } + } + + WolfSSLSession(const WolfSSLSession &) = delete; + WolfSSLSession &operator=(const WolfSSLSession &) = delete; +}; + +// Thread-local error code accessor for wolfSSL +inline uint64_t &wolfssl_last_error() +{ + static thread_local uint64_t err = 0; + return err; +} + +// Helper to map wolfSSL error to ErrorCode. +// ssl_error is the value from wolfSSL_get_error(). +// raw_ret is the raw return value from the wolfSSL call (for low-level error). +inline ErrorCode map_wolfssl_error(WOLFSSL *ssl, int ssl_error, int &out_errno) +{ + switch (ssl_error) { + case SSL_ERROR_NONE: + return ErrorCode::Success; + case SSL_ERROR_WANT_READ: + return ErrorCode::WantRead; + case SSL_ERROR_WANT_WRITE: + return ErrorCode::WantWrite; + case SSL_ERROR_ZERO_RETURN: + return ErrorCode::PeerClosed; + case SSL_ERROR_SYSCALL: + out_errno = errno; + return ErrorCode::SyscallError; + default: + if (ssl) { + // wolfSSL stores the low-level error code as a negative value. + // DOMAIN_NAME_MISMATCH (-322) indicates hostname verification failure. + int low_err = ssl_error; // wolfSSL_get_error returns the low-level code + if (low_err == DOMAIN_NAME_MISMATCH) { + return ErrorCode::HostnameMismatch; + } + // Check verify result to distinguish cert verification from generic SSL + // errors. + long vr = wolfSSL_get_verify_result(ssl); + if (vr != 0) { + return ErrorCode::CertVerifyFailed; + } + } + return ErrorCode::Fatal; + } +} + +// WolfSSLContext constructor/destructor implementations +inline WolfSSLContext::WolfSSLContext() +{ + wolfSSL_Init(); +} + +inline WolfSSLContext::~WolfSSLContext() +{ + if (ctx) { + wolfSSL_CTX_free(ctx); + } +} + +// Thread-local storage for SNI captured during handshake +inline std::string &wolfssl_pending_sni() +{ + static thread_local std::string sni; + return sni; +} + +// SNI callback for wolfSSL server to capture client's SNI hostname +inline int wolfssl_sni_callback(WOLFSSL *ssl, int *ret, void *exArg) +{ + (void)ret; + (void)exArg; + + void *name_data = nullptr; + unsigned short name_len = wolfSSL_SNI_GetRequest(ssl, WOLFSSL_SNI_HOST_NAME, &name_data); + + if (name_data && name_len > 0) { + wolfssl_pending_sni().assign(static_cast(name_data), name_len); + } + else { + wolfssl_pending_sni().clear(); + } + return 0; // Continue regardless +} + +// wolfSSL verify callback wrapper +inline int wolfssl_verify_callback(int preverify_ok, WOLFSSL_X509_STORE_CTX *x509_ctx) +{ + auto &callback = get_verify_callback(); + if (!callback) { + return preverify_ok; + } + + WOLFSSL_X509 *cert = wolfSSL_X509_STORE_CTX_get_current_cert(x509_ctx); + int depth = wolfSSL_X509_STORE_CTX_get_error_depth(x509_ctx); + int err = wolfSSL_X509_STORE_CTX_get_error(x509_ctx); + + // Get the WOLFSSL object from the X509_STORE_CTX + WOLFSSL *ssl = static_cast(wolfSSL_X509_STORE_CTX_get_ex_data(x509_ctx, wolfSSL_get_ex_data_X509_STORE_CTX_idx())); + + VerifyContext verify_ctx; + verify_ctx.session = static_cast(ssl); + verify_ctx.cert = static_cast(cert); + verify_ctx.depth = depth; + verify_ctx.preverify_ok = (preverify_ok != 0); + verify_ctx.error_code = static_cast(err); + + if (err != 0) { + verify_ctx.error_string = wolfSSL_X509_verify_cert_error_string(err); + } + else { + verify_ctx.error_string = nullptr; + } + + bool accepted = callback(verify_ctx); + return accepted ? 1 : 0; +} + +inline void set_wolfssl_password_cb(WOLFSSL_CTX *ctx, const char *password) +{ + wolfSSL_CTX_set_default_passwd_cb_userdata(ctx, const_cast(password)); + wolfSSL_CTX_set_default_passwd_cb(ctx, [](char *buf, int size, int /*rwflag*/, void *userdata) -> int { + auto *pwd = static_cast(userdata); + if (!pwd) + return 0; + auto len = static_cast(strlen(pwd)); + if (len > size) + len = size; + memcpy(buf, pwd, static_cast(len)); + return len; + }); +} + +} // namespace impl + +inline ctx_t create_client_context() +{ + auto ctx = new (std::nothrow) impl::WolfSSLContext(); + if (!ctx) { + return nullptr; + } + + ctx->is_server = false; + + WOLFSSL_METHOD *method = wolfTLSv1_2_client_method(); + if (!method) { + delete ctx; + return nullptr; + } + + ctx->ctx = wolfSSL_CTX_new(method); + if (!ctx->ctx) { + delete ctx; + return nullptr; + } + + // Default: verify peer certificate + wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_PEER, nullptr); + + return static_cast(ctx); +} + +inline ctx_t create_server_context() +{ + auto ctx = new (std::nothrow) impl::WolfSSLContext(); + if (!ctx) { + return nullptr; + } + + ctx->is_server = true; + + WOLFSSL_METHOD *method = wolfTLSv1_2_server_method(); + if (!method) { + delete ctx; + return nullptr; + } + + ctx->ctx = wolfSSL_CTX_new(method); + if (!ctx->ctx) { + delete ctx; + return nullptr; + } + + // Default: don't verify client + wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_NONE, nullptr); + + // Enable SNI on server + wolfSSL_CTX_SNI_SetOptions(ctx->ctx, WOLFSSL_SNI_HOST_NAME, WOLFSSL_SNI_CONTINUE_ON_MISMATCH); + wolfSSL_CTX_set_servername_callback(ctx->ctx, impl::wolfssl_sni_callback); + + return static_cast(ctx); +} + +inline void free_context(ctx_t ctx) +{ + if (ctx) { + delete static_cast(ctx); + } +} + +inline bool set_min_version(ctx_t ctx, Version version) +{ + if (!ctx) { + return false; + } + auto wctx = static_cast(ctx); + + int min_ver = WOLFSSL_TLSV1_2; + if (version >= Version::TLS1_3) { + min_ver = WOLFSSL_TLSV1_3; + } + + return wolfSSL_CTX_SetMinVersion(wctx->ctx, min_ver) == WOLFSSL_SUCCESS; +} + +inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) +{ + if (!ctx || !pem) { + return false; + } + auto wctx = static_cast(ctx); + + int ret = + wolfSSL_CTX_load_verify_buffer(wctx->ctx, reinterpret_cast(pem), static_cast(len), SSL_FILETYPE_PEM); + if (ret != SSL_SUCCESS) { + impl::wolfssl_last_error() = static_cast(wolfSSL_ERR_peek_last_error()); + return false; + } + wctx->ca_pem_data_.append(pem, len); + return true; +} + +inline bool load_ca_file(ctx_t ctx, const char *file_path) +{ + if (!ctx || !file_path) { + return false; + } + auto wctx = static_cast(ctx); + + int ret = wolfSSL_CTX_load_verify_locations(wctx->ctx, file_path, nullptr); + if (ret != SSL_SUCCESS) { + impl::wolfssl_last_error() = static_cast(wolfSSL_ERR_peek_last_error()); + return false; + } + return true; +} + +inline bool load_ca_dir(ctx_t ctx, const char *dir_path) +{ + if (!ctx || !dir_path) { + return false; + } + auto wctx = static_cast(ctx); + + int ret = wolfSSL_CTX_load_verify_locations(wctx->ctx, nullptr, dir_path); + // wolfSSL may fail if the directory doesn't contain properly hashed certs. + // Unlike OpenSSL which lazily loads certs from directories, wolfSSL scans + // immediately. Return true even on failure since the CA file may have + // already been loaded, matching OpenSSL's lenient behavior. + (void)ret; + return true; +} + +inline bool load_system_certs(ctx_t ctx) +{ + if (!ctx) { + return false; + } + auto wctx = static_cast(ctx); + bool loaded = false; + +#ifdef _WIN32 + loaded = impl::enumerate_windows_system_certs([&](const unsigned char *data, size_t len) { + return wolfSSL_CTX_load_verify_buffer(wctx->ctx, data, static_cast(len), SSL_FILETYPE_ASN1) == SSL_SUCCESS; + }); +#elif defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) + loaded = impl::enumerate_macos_keychain_certs([&](const unsigned char *data, size_t len) { + return wolfSSL_CTX_load_verify_buffer(wctx->ctx, data, static_cast(len), SSL_FILETYPE_ASN1) == SSL_SUCCESS; + }); +#else + for (auto path = impl::system_ca_paths(); *path; ++path) { + if (wolfSSL_CTX_load_verify_locations(wctx->ctx, *path, nullptr) == SSL_SUCCESS) { + loaded = true; + break; + } + } + + if (!loaded) { + for (auto dir = impl::system_ca_dirs(); *dir; ++dir) { + if (wolfSSL_CTX_load_verify_locations(wctx->ctx, nullptr, *dir) == SSL_SUCCESS) { + loaded = true; + break; + } + } + } +#endif + + return loaded; +} + +inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key, const char *password) +{ + if (!ctx || !cert || !key) { + return false; + } + auto wctx = static_cast(ctx); + + // Load certificate + int ret = wolfSSL_CTX_use_certificate_buffer( + wctx->ctx, reinterpret_cast(cert), static_cast(strlen(cert)), SSL_FILETYPE_PEM); + if (ret != SSL_SUCCESS) { + impl::wolfssl_last_error() = static_cast(wolfSSL_ERR_peek_last_error()); + return false; + } + + // Set password callback if password is provided + if (password) { + impl::set_wolfssl_password_cb(wctx->ctx, password); + } + + // Load private key + ret = wolfSSL_CTX_use_PrivateKey_buffer( + wctx->ctx, reinterpret_cast(key), static_cast(strlen(key)), SSL_FILETYPE_PEM); + if (ret != SSL_SUCCESS) { + impl::wolfssl_last_error() = static_cast(wolfSSL_ERR_peek_last_error()); + return false; + } + + // Verify that the certificate and private key match + return wolfSSL_CTX_check_private_key(wctx->ctx) == SSL_SUCCESS; +} + +inline bool set_client_cert_file(ctx_t ctx, const char *cert_path, const char *key_path, const char *password) +{ + if (!ctx || !cert_path || !key_path) { + return false; + } + auto wctx = static_cast(ctx); + + // Load certificate file + int ret = wolfSSL_CTX_use_certificate_file(wctx->ctx, cert_path, SSL_FILETYPE_PEM); + if (ret != SSL_SUCCESS) { + impl::wolfssl_last_error() = static_cast(wolfSSL_ERR_peek_last_error()); + return false; + } + + // Set password callback if password is provided + if (password) { + impl::set_wolfssl_password_cb(wctx->ctx, password); + } + + // Load private key file + ret = wolfSSL_CTX_use_PrivateKey_file(wctx->ctx, key_path, SSL_FILETYPE_PEM); + if (ret != SSL_SUCCESS) { + impl::wolfssl_last_error() = static_cast(wolfSSL_ERR_peek_last_error()); + return false; + } + + // Verify that the certificate and private key match + return wolfSSL_CTX_check_private_key(wctx->ctx) == SSL_SUCCESS; +} + +inline void set_verify_client(ctx_t ctx, bool require) +{ + if (!ctx) { + return; + } + auto wctx = static_cast(ctx); + wctx->verify_client = require; + if (require) { + wolfSSL_CTX_set_verify( + wctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, wctx->has_verify_callback ? impl::wolfssl_verify_callback : nullptr); + } + else { + if (wctx->has_verify_callback) { + wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER, impl::wolfssl_verify_callback); + } + else { + wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_NONE, nullptr); + } + } +} + +inline session_t create_session(ctx_t ctx, socket_t sock) +{ + if (!ctx || sock == INVALID_SOCKET) { + return nullptr; + } + auto wctx = static_cast(ctx); + + auto session = new (std::nothrow) impl::WolfSSLSession(); + if (!session) { + return nullptr; + } + + session->sock = sock; + session->ssl = wolfSSL_new(wctx->ctx); + if (!session->ssl) { + impl::wolfssl_last_error() = static_cast(wolfSSL_ERR_peek_last_error()); + delete session; + return nullptr; + } + + wolfSSL_set_fd(session->ssl, static_cast(sock)); + + return static_cast(session); +} + +inline void free_session(session_t session) +{ + if (session) { + delete static_cast(session); + } +} + +inline bool set_sni(session_t session, const char *hostname) +{ + if (!session || !hostname) { + return false; + } + auto wsession = static_cast(session); + + int ret = wolfSSL_UseSNI(wsession->ssl, WOLFSSL_SNI_HOST_NAME, hostname, static_cast(strlen(hostname))); + if (ret != WOLFSSL_SUCCESS) { + impl::wolfssl_last_error() = static_cast(wolfSSL_ERR_peek_last_error()); + return false; + } + + // Also set hostname for verification + wolfSSL_check_domain_name(wsession->ssl, hostname); + + wsession->hostname = hostname; + return true; +} + +inline bool set_hostname(session_t session, const char *hostname) +{ + // In wolfSSL, set_hostname also sets up hostname verification + return set_sni(session, hostname); +} + +inline TlsError connect(session_t session) +{ + TlsError err; + if (!session) { + err.code = ErrorCode::Fatal; + return err; + } + + auto wsession = static_cast(session); + int ret = wolfSSL_connect(wsession->ssl); + + if (ret == SSL_SUCCESS) { + err.code = ErrorCode::Success; + } + else { + int ssl_error = wolfSSL_get_error(wsession->ssl, ret); + err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno); + err.backend_code = static_cast(ssl_error); + impl::wolfssl_last_error() = err.backend_code; + } + + return err; +} + +inline TlsError accept(session_t session) +{ + TlsError err; + if (!session) { + err.code = ErrorCode::Fatal; + return err; + } + + auto wsession = static_cast(session); + int ret = wolfSSL_accept(wsession->ssl); + + if (ret == SSL_SUCCESS) { + err.code = ErrorCode::Success; + // Capture SNI from thread-local storage after successful handshake + wsession->sni_hostname = std::move(impl::wolfssl_pending_sni()); + impl::wolfssl_pending_sni().clear(); + } + else { + int ssl_error = wolfSSL_get_error(wsession->ssl, ret); + err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno); + err.backend_code = static_cast(ssl_error); + impl::wolfssl_last_error() = err.backend_code; + } + + return err; +} + +inline bool connect_nonblocking(session_t session, socket_t sock, time_t timeout_sec, time_t timeout_usec, TlsError *err) +{ + if (!session) { + if (err) { + err->code = ErrorCode::Fatal; + } + return false; + } + + auto wsession = static_cast(session); + + // Set socket to non-blocking mode + detail::set_nonblocking(sock, true); + auto cleanup = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); }); + + int ret; + while ((ret = wolfSSL_connect(wsession->ssl)) != SSL_SUCCESS) { + int ssl_error = wolfSSL_get_error(wsession->ssl, ret); + if (ssl_error == SSL_ERROR_WANT_READ) { + if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) { + continue; + } + } + else if (ssl_error == SSL_ERROR_WANT_WRITE) { + if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) { + continue; + } + } + + // Error or timeout + if (err) { + err->code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err->sys_errno); + err->backend_code = static_cast(ssl_error); + } + impl::wolfssl_last_error() = static_cast(ssl_error); + return false; + } + + if (err) { + err->code = ErrorCode::Success; + } + return true; +} + +inline bool accept_nonblocking(session_t session, socket_t sock, time_t timeout_sec, time_t timeout_usec, TlsError *err) +{ + if (!session) { + if (err) { + err->code = ErrorCode::Fatal; + } + return false; + } + + auto wsession = static_cast(session); + + // Set socket to non-blocking mode + detail::set_nonblocking(sock, true); + auto cleanup = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); }); + + int ret; + while ((ret = wolfSSL_accept(wsession->ssl)) != SSL_SUCCESS) { + int ssl_error = wolfSSL_get_error(wsession->ssl, ret); + if (ssl_error == SSL_ERROR_WANT_READ) { + if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) { + continue; + } + } + else if (ssl_error == SSL_ERROR_WANT_WRITE) { + if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) { + continue; + } + } + + // Error or timeout + if (err) { + err->code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err->sys_errno); + err->backend_code = static_cast(ssl_error); + } + impl::wolfssl_last_error() = static_cast(ssl_error); + return false; + } + + if (err) { + err->code = ErrorCode::Success; + } + + // Capture SNI from thread-local storage after successful handshake + wsession->sni_hostname = std::move(impl::wolfssl_pending_sni()); + impl::wolfssl_pending_sni().clear(); + + return true; +} + +inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) +{ + if (!session || !buf) { + err.code = ErrorCode::Fatal; + return -1; + } + + auto wsession = static_cast(session); + int ret = wolfSSL_read(wsession->ssl, buf, static_cast(len)); + + if (ret > 0) { + err.code = ErrorCode::Success; + return static_cast(ret); + } + + if (ret == 0) { + err.code = ErrorCode::PeerClosed; + return 0; + } + + int ssl_error = wolfSSL_get_error(wsession->ssl, ret); + err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno); + err.backend_code = static_cast(ssl_error); + impl::wolfssl_last_error() = err.backend_code; + return -1; +} + +inline ssize_t write(session_t session, const void *buf, size_t len, TlsError &err) +{ + if (!session || !buf) { + err.code = ErrorCode::Fatal; + return -1; + } + + auto wsession = static_cast(session); + int ret = wolfSSL_write(wsession->ssl, buf, static_cast(len)); + + if (ret > 0) { + err.code = ErrorCode::Success; + return static_cast(ret); + } + + // wolfSSL_write returns 0 when the peer has sent a close_notify. + // Treat this as an error (return -1) so callers don't spin in a + // write loop adding zero to the offset. + if (ret == 0) { + err.code = ErrorCode::PeerClosed; + return -1; + } + + int ssl_error = wolfSSL_get_error(wsession->ssl, ret); + err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno); + err.backend_code = static_cast(ssl_error); + impl::wolfssl_last_error() = err.backend_code; + return -1; +} + +inline int pending(const_session_t session) +{ + if (!session) { + return 0; + } + auto wsession = static_cast(const_cast(session)); + return wolfSSL_pending(wsession->ssl); +} + +inline void shutdown(session_t session, bool graceful) +{ + if (!session) { + return; + } + auto wsession = static_cast(session); + + if (graceful) { + int ret; + int attempts = 0; + while ((ret = wolfSSL_shutdown(wsession->ssl)) != SSL_SUCCESS && attempts < 3) { + int ssl_error = wolfSSL_get_error(wsession->ssl, ret); + if (ssl_error != SSL_ERROR_WANT_READ && ssl_error != SSL_ERROR_WANT_WRITE) { + break; + } + attempts++; + } + } + else { + wolfSSL_shutdown(wsession->ssl); + } +} + +inline bool is_peer_closed(session_t session, socket_t sock) +{ + if (!session || sock == INVALID_SOCKET) { + return true; + } + auto wsession = static_cast(session); + + // Check if there's already decrypted data available + if (wolfSSL_pending(wsession->ssl) > 0) { + return false; + } + + // Set socket to non-blocking to avoid blocking on read + detail::set_nonblocking(sock, true); + auto cleanup = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); }); + + // Peek 1 byte to check connection status without consuming data + unsigned char buf; + int ret = wolfSSL_peek(wsession->ssl, &buf, 1); + + // If we got data or WANT_READ (would block), connection is alive + if (ret > 0) { + return false; + } + + int ssl_error = wolfSSL_get_error(wsession->ssl, ret); + if (ssl_error == SSL_ERROR_WANT_READ) { + return false; + } + + return ssl_error == SSL_ERROR_ZERO_RETURN || ssl_error == SSL_ERROR_SYSCALL || ret == 0; +} + +inline cert_t get_peer_cert(const_session_t session) +{ + if (!session) { + return nullptr; + } + auto wsession = static_cast(const_cast(session)); + + WOLFSSL_X509 *cert = wolfSSL_get_peer_certificate(wsession->ssl); + return static_cast(cert); +} + +inline void free_cert(cert_t cert) +{ + if (cert) { + wolfSSL_X509_free(static_cast(cert)); + } +} + +inline bool verify_hostname(cert_t cert, const char *hostname) +{ + if (!cert || !hostname) { + return false; + } + auto x509 = static_cast(cert); + std::string host_str(hostname); + + // Check if hostname is an IP address (IPv4 or IPv6) + unsigned char ip_bytes[16]; + auto ip_len = impl::parse_ip_address(host_str, ip_bytes); + auto is_ip = ip_len > 0; + + // Check Subject Alternative Names + auto *san_names = + static_cast(wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr)); + + if (san_names) { + int san_count = wolfSSL_sk_num(san_names); + for (int i = 0; i < san_count; i++) { + auto *names = static_cast(wolfSSL_sk_value(san_names, i)); + if (!names) + continue; + + if (!is_ip && names->type == WOLFSSL_GEN_DNS) { + // DNS name + unsigned char *dns_name = nullptr; + int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, names->d.dNSName); + if (dns_name && dns_len > 0) { + std::string san_name(reinterpret_cast(dns_name), static_cast(dns_len)); + XFREE(dns_name, nullptr, DYNAMIC_TYPE_OPENSSL); + if (detail::match_hostname(san_name, host_str)) { + wolfSSL_sk_free(san_names); + return true; + } + } + } + else if (is_ip && names->type == WOLFSSL_GEN_IPADD) { + // IP address: only an iPAddress SAN of the same family (4 bytes for + // IPv4, 16 bytes for IPv6) may authenticate the host. + unsigned char *ip_data = wolfSSL_ASN1_STRING_data(names->d.iPAddress); + auto san_ip_len = wolfSSL_ASN1_STRING_length(names->d.iPAddress); + if (ip_data && san_ip_len == static_cast(ip_len) && memcmp(ip_data, ip_bytes, ip_len) == 0) { + wolfSSL_sk_free(san_names); + return true; + } + } + } + wolfSSL_sk_free(san_names); + } + + // Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts: + // an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110; + // the OpenSSL backend's X509_check_ip behaves the same way). + auto subject = is_ip ? nullptr : wolfSSL_X509_get_subject_name(x509); + if (subject) { + char cn[256] = {}; + int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn, sizeof(cn)); + if (cn_len > 0) { + std::string cn_str(cn, static_cast(cn_len)); + if (detail::match_hostname(cn_str, host_str)) { + return true; + } + } + } + + return false; +} + +inline uint64_t hostname_mismatch_code() +{ + return static_cast(DOMAIN_NAME_MISMATCH); +} + +inline long get_verify_result(const_session_t session) +{ + if (!session) { + return -1; + } + auto wsession = static_cast(const_cast(session)); + long result = wolfSSL_get_verify_result(wsession->ssl); + return result; +} + +inline std::string get_cert_subject_cn(cert_t cert) +{ + if (!cert) + return ""; + auto x509 = static_cast(cert); + + WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509); + if (!subject) + return ""; + + char cn[256] = {}; + int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn, sizeof(cn)); + if (cn_len <= 0) + return ""; + return std::string(cn, static_cast(cn_len)); +} + +inline std::string get_cert_issuer_name(cert_t cert) +{ + if (!cert) + return ""; + auto x509 = static_cast(cert); + + WOLFSSL_X509_NAME *issuer = wolfSSL_X509_get_issuer_name(x509); + if (!issuer) + return ""; + + char *name_str = wolfSSL_X509_NAME_oneline(issuer, nullptr, 0); + if (!name_str) + return ""; + + std::string result(name_str); + XFREE(name_str, nullptr, DYNAMIC_TYPE_OPENSSL); + return result; +} + +inline bool get_cert_sans(cert_t cert, std::vector &sans) +{ + sans.clear(); + if (!cert) + return false; + auto x509 = static_cast(cert); + + auto *san_names = + static_cast(wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr)); + if (!san_names) + return true; // No SANs is not an error + + int count = wolfSSL_sk_num(san_names); + for (int i = 0; i < count; i++) { + auto *name = static_cast(wolfSSL_sk_value(san_names, i)); + if (!name) + continue; + + SanEntry entry; + switch (name->type) { + case WOLFSSL_GEN_DNS: { + entry.type = SanType::DNS; + unsigned char *dns_name = nullptr; + int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, name->d.dNSName); + if (dns_name && dns_len > 0) { + entry.value = std::string(reinterpret_cast(dns_name), static_cast(dns_len)); + XFREE(dns_name, nullptr, DYNAMIC_TYPE_OPENSSL); + } + break; + } + case WOLFSSL_GEN_IPADD: { + entry.type = SanType::IP; + unsigned char *ip_data = wolfSSL_ASN1_STRING_data(name->d.iPAddress); + int ip_len = wolfSSL_ASN1_STRING_length(name->d.iPAddress); + if (ip_data && ip_len == 4) { + char buf[16]; + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", ip_data[0], ip_data[1], ip_data[2], ip_data[3]); + entry.value = buf; + } + else if (ip_data && ip_len == 16) { + char buf[64]; + snprintf(buf, + sizeof(buf), + "%02x%02x:%02x%02x:%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:%02x%02x:%02x%02x", + ip_data[0], + ip_data[1], + ip_data[2], + ip_data[3], + ip_data[4], + ip_data[5], + ip_data[6], + ip_data[7], + ip_data[8], + ip_data[9], + ip_data[10], + ip_data[11], + ip_data[12], + ip_data[13], + ip_data[14], + ip_data[15]); + entry.value = buf; + } + break; + } + case WOLFSSL_GEN_EMAIL: + entry.type = SanType::EMAIL; + { + unsigned char *email = nullptr; + int email_len = wolfSSL_ASN1_STRING_to_UTF8(&email, name->d.rfc822Name); + if (email && email_len > 0) { + entry.value = std::string(reinterpret_cast(email), static_cast(email_len)); + XFREE(email, nullptr, DYNAMIC_TYPE_OPENSSL); + } + } + break; + case WOLFSSL_GEN_URI: + entry.type = SanType::URI; + { + unsigned char *uri = nullptr; + int uri_len = wolfSSL_ASN1_STRING_to_UTF8(&uri, name->d.uniformResourceIdentifier); + if (uri && uri_len > 0) { + entry.value = std::string(reinterpret_cast(uri), static_cast(uri_len)); + XFREE(uri, nullptr, DYNAMIC_TYPE_OPENSSL); + } + } + break; + default: + entry.type = SanType::OTHER; + break; + } + + if (!entry.value.empty()) { + sans.push_back(std::move(entry)); + } + } + wolfSSL_sk_free(san_names); + return true; +} + +inline bool get_cert_validity(cert_t cert, time_t ¬_before, time_t ¬_after) +{ + if (!cert) + return false; + auto x509 = static_cast(cert); + + const WOLFSSL_ASN1_TIME *nb = wolfSSL_X509_get_notBefore(x509); + const WOLFSSL_ASN1_TIME *na = wolfSSL_X509_get_notAfter(x509); + + if (!nb || !na) + return false; + + // wolfSSL_ASN1_TIME_to_tm is available + struct tm tm_nb = {}, tm_na = {}; + if (wolfSSL_ASN1_TIME_to_tm(nb, &tm_nb) != WOLFSSL_SUCCESS) + return false; + if (wolfSSL_ASN1_TIME_to_tm(na, &tm_na) != WOLFSSL_SUCCESS) + return false; + +#ifdef _WIN32 + not_before = _mkgmtime(&tm_nb); + not_after = _mkgmtime(&tm_na); +#else + not_before = timegm(&tm_nb); + not_after = timegm(&tm_na); +#endif + return true; +} + +inline std::string get_cert_serial(cert_t cert) +{ + if (!cert) + return ""; + auto x509 = static_cast(cert); + + WOLFSSL_ASN1_INTEGER *serial_asn1 = wolfSSL_X509_get_serialNumber(x509); + if (!serial_asn1) + return ""; + + // Get the serial number data + int len = serial_asn1->length; + unsigned char *data = serial_asn1->data; + if (!data || len <= 0) + return ""; + + std::string result; + result.reserve(static_cast(len) * 2); + for (int i = 0; i < len; i++) { + char hex[3]; + snprintf(hex, sizeof(hex), "%02X", data[i]); + result += hex; + } + return result; +} + +inline bool get_cert_der(cert_t cert, std::vector &der) +{ + if (!cert) + return false; + auto x509 = static_cast(cert); + + int der_len = 0; + const unsigned char *der_data = wolfSSL_X509_get_der(x509, &der_len); + if (!der_data || der_len <= 0) + return false; + + der.assign(der_data, der_data + der_len); + return true; +} + +inline const char *get_sni(const_session_t session) +{ + if (!session) + return nullptr; + auto wsession = static_cast(session); + + // For server: return SNI received from client during handshake + if (!wsession->sni_hostname.empty()) { + return wsession->sni_hostname.c_str(); + } + + // For client: return the hostname set via set_sni + if (!wsession->hostname.empty()) { + return wsession->hostname.c_str(); + } + + return nullptr; +} + +inline uint64_t peek_error() +{ + return static_cast(wolfSSL_ERR_peek_last_error()); +} + +inline uint64_t get_error() +{ + uint64_t err = impl::wolfssl_last_error(); + impl::wolfssl_last_error() = 0; + return err; +} + +inline std::string error_string(uint64_t code) +{ + char buf[256]; + wolfSSL_ERR_error_string(static_cast(code), buf); + return std::string(buf); +} + +inline ca_store_t create_ca_store(const char *pem, size_t len) +{ + if (!pem || len == 0) { + return nullptr; + } + // Validate by attempting to load into a temporary ctx + WOLFSSL_CTX *tmp_ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method()); + if (!tmp_ctx) { + return nullptr; + } + int ret = wolfSSL_CTX_load_verify_buffer(tmp_ctx, reinterpret_cast(pem), static_cast(len), SSL_FILETYPE_PEM); + wolfSSL_CTX_free(tmp_ctx); + if (ret != SSL_SUCCESS) { + return nullptr; + } + return static_cast(new impl::WolfSSLCAStore{std::string(pem, len)}); +} + +inline void free_ca_store(ca_store_t store) +{ + delete static_cast(store); +} + +inline bool set_ca_store(ctx_t ctx, ca_store_t store) +{ + if (!ctx || !store) { + return false; + } + auto *wctx = static_cast(ctx); + auto *ca = static_cast(store); + int ret = wolfSSL_CTX_load_verify_buffer( + wctx->ctx, reinterpret_cast(ca->pem_data.data()), static_cast(ca->pem_data.size()), SSL_FILETYPE_PEM); + if (ret == SSL_SUCCESS) { + wctx->ca_pem_data_ += ca->pem_data; + } + // This function takes ownership of the store; the PEM data was copied into + // the context, so release the source + free_ca_store(store); + return ret == SSL_SUCCESS; +} + +inline size_t get_ca_certs(ctx_t ctx, std::vector &certs) +{ + certs.clear(); + if (!ctx) { + return 0; + } + auto *wctx = static_cast(ctx); + if (wctx->ca_pem_data_.empty()) { + return 0; + } + + const std::string &pem = wctx->ca_pem_data_; + const std::string begin_marker = "-----BEGIN CERTIFICATE-----"; + const std::string end_marker = "-----END CERTIFICATE-----"; + size_t pos = 0; + while ((pos = pem.find(begin_marker, pos)) != std::string::npos) { + size_t end_pos = pem.find(end_marker, pos); + if (end_pos == std::string::npos) { + break; + } + end_pos += end_marker.size(); + std::string cert_pem = pem.substr(pos, end_pos - pos); + WOLFSSL_X509 *x509 = wolfSSL_X509_load_certificate_buffer( + reinterpret_cast(cert_pem.data()), static_cast(cert_pem.size()), WOLFSSL_FILETYPE_PEM); + if (x509) { + certs.push_back(static_cast(x509)); + } + pos = end_pos; + } + return certs.size(); +} + +inline std::vector get_ca_names(ctx_t ctx) +{ + std::vector names; + if (!ctx) { + return names; + } + auto *wctx = static_cast(ctx); + if (wctx->ca_pem_data_.empty()) { + return names; + } + + const std::string &pem = wctx->ca_pem_data_; + const std::string begin_marker = "-----BEGIN CERTIFICATE-----"; + const std::string end_marker = "-----END CERTIFICATE-----"; + size_t pos = 0; + while ((pos = pem.find(begin_marker, pos)) != std::string::npos) { + size_t end_pos = pem.find(end_marker, pos); + if (end_pos == std::string::npos) { + break; + } + end_pos += end_marker.size(); + std::string cert_pem = pem.substr(pos, end_pos - pos); + WOLFSSL_X509 *x509 = wolfSSL_X509_load_certificate_buffer( + reinterpret_cast(cert_pem.data()), static_cast(cert_pem.size()), WOLFSSL_FILETYPE_PEM); + if (x509) { + WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509); + if (subject) { + char *name_str = wolfSSL_X509_NAME_oneline(subject, nullptr, 0); + if (name_str) { + names.push_back(name_str); + XFREE(name_str, nullptr, DYNAMIC_TYPE_OPENSSL); + } + } + wolfSSL_X509_free(x509); + } + pos = end_pos; + } + return names; +} + +inline bool update_server_cert(ctx_t ctx, const char *cert_pem, const char *key_pem, const char *password) +{ + if (!ctx || !cert_pem || !key_pem) { + return false; + } + auto *wctx = static_cast(ctx); + + // Load new certificate + int ret = wolfSSL_CTX_use_certificate_buffer( + wctx->ctx, reinterpret_cast(cert_pem), static_cast(strlen(cert_pem)), SSL_FILETYPE_PEM); + if (ret != SSL_SUCCESS) { + impl::wolfssl_last_error() = static_cast(wolfSSL_ERR_peek_last_error()); + return false; + } + + // Set password if provided + if (password) { + impl::set_wolfssl_password_cb(wctx->ctx, password); + } + + // Load new private key + ret = wolfSSL_CTX_use_PrivateKey_buffer( + wctx->ctx, reinterpret_cast(key_pem), static_cast(strlen(key_pem)), SSL_FILETYPE_PEM); + if (ret != SSL_SUCCESS) { + impl::wolfssl_last_error() = static_cast(wolfSSL_ERR_peek_last_error()); + return false; + } + + return true; +} + +inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) +{ + if (!ctx || !ca_pem) { + return false; + } + auto *wctx = static_cast(ctx); + + int ret = wolfSSL_CTX_load_verify_buffer( + wctx->ctx, reinterpret_cast(ca_pem), static_cast(strlen(ca_pem)), SSL_FILETYPE_PEM); + if (ret != SSL_SUCCESS) { + impl::wolfssl_last_error() = static_cast(wolfSSL_ERR_peek_last_error()); + return false; + } + return true; +} + +inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) +{ + if (!ctx) { + return false; + } + auto *wctx = static_cast(ctx); + + impl::get_verify_callback() = std::move(callback); + wctx->has_verify_callback = static_cast(impl::get_verify_callback()); + + if (wctx->has_verify_callback) { + wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER, impl::wolfssl_verify_callback); + } + else { + wolfSSL_CTX_set_verify(wctx->ctx, wctx->verify_client ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT) : SSL_VERIFY_NONE, nullptr); + } + return true; +} + +inline long get_verify_error(const_session_t session) +{ + if (!session) { + return -1; + } + auto *wsession = static_cast(const_cast(session)); + return wolfSSL_get_verify_result(wsession->ssl); +} + +inline std::string verify_error_string(long error_code) +{ + if (error_code == 0) { + return ""; + } + const char *str = wolfSSL_X509_verify_cert_error_string(static_cast(error_code)); + return str ? std::string(str) : std::string(); +} + +} // namespace tls + +#endif // CPPHTTPLIB_WOLFSSL_SUPPORT + +// WebSocket implementation +namespace ws { + +inline bool WebSocket::send_frame(Opcode op, const char *data, size_t len, bool fin) +{ + std::lock_guard lock(write_mutex_); + if (closed_) { + return false; + } + return detail::write_websocket_frame(strm_, op, data, len, fin, !is_server_); +} + +inline ReadResult WebSocket::read(std::string &msg) +{ + while (!closed_) { + Opcode opcode; + std::string payload; + bool fin; + + if (!impl::read_websocket_frame(strm_, opcode, payload, fin, is_server_, CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH)) { + closed_ = true; + return Fail; + } + + switch (opcode) { + case Opcode::Ping: { + std::lock_guard lock(write_mutex_); + detail::write_websocket_frame(strm_, Opcode::Pong, payload.data(), payload.size(), true, !is_server_); + continue; + } + case Opcode::Pong: { + std::lock_guard lock(ping_mutex_); + unacked_pings_ = 0; + continue; + } + case Opcode::Close: { + if (!closed_.exchange(true)) { + // Echo close frame back + std::lock_guard lock(write_mutex_); + detail::write_websocket_frame(strm_, Opcode::Close, payload.data(), payload.size(), true, !is_server_); + } + return Fail; + } + case Opcode::Text: + case Opcode::Binary: { + auto result = opcode == Opcode::Text ? Text : Binary; + msg = std::move(payload); + + // Handle fragmentation + if (!fin) { + while (true) { + Opcode cont_opcode; + std::string cont_payload; + bool cont_fin; + if (!impl::read_websocket_frame( + strm_, cont_opcode, cont_payload, cont_fin, is_server_, CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH)) { + closed_ = true; + return Fail; + } + if (cont_opcode == Opcode::Ping) { + std::lock_guard lock(write_mutex_); + detail::write_websocket_frame(strm_, Opcode::Pong, cont_payload.data(), cont_payload.size(), true, !is_server_); + continue; + } + if (cont_opcode == Opcode::Pong) { + std::lock_guard lock(ping_mutex_); + unacked_pings_ = 0; + continue; + } + if (cont_opcode == Opcode::Close) { + if (!closed_.exchange(true)) { + std::lock_guard lock(write_mutex_); + detail::write_websocket_frame(strm_, Opcode::Close, cont_payload.data(), cont_payload.size(), true, !is_server_); + } + return Fail; + } + // RFC 6455: continuation frames must use opcode 0x0 + if (cont_opcode != Opcode::Continuation) { + closed_ = true; + return Fail; + } + msg += cont_payload; + if (msg.size() > CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH) { + closed_ = true; + return Fail; + } + if (cont_fin) { + break; + } + } + } + // RFC 6455 Section 5.6: text frames must contain valid UTF-8 + if (result == Text && !impl::is_valid_utf8(msg)) { + close(CloseStatus::InvalidPayload, "invalid UTF-8"); + return Fail; + } + return result; + } + default: + closed_ = true; + return Fail; + } + } + return Fail; +} + +inline bool WebSocket::send(const std::string &data) +{ + return send_frame(Opcode::Text, data.data(), data.size()); +} + +inline bool WebSocket::send(const char *data, size_t len) +{ + return send_frame(Opcode::Binary, data, len); +} + +inline void WebSocket::close(CloseStatus status, const std::string &reason) +{ + if (closed_.exchange(true)) { + return; + } + ping_cv_.notify_all(); + std::string payload; + auto code = static_cast(status); + payload.push_back(static_cast((code >> 8) & 0xFF)); + payload.push_back(static_cast(code & 0xFF)); + // RFC 6455 Section 5.5: control frame payload must not exceed 125 bytes + // Close frame has 2-byte status code, so reason is limited to 123 bytes + payload += reason.substr(0, 123); + { + std::lock_guard lock(write_mutex_); + detail::write_websocket_frame(strm_, Opcode::Close, payload.data(), payload.size(), true, !is_server_); + } + + // RFC 6455 Section 7.1.1: after sending a Close frame, wait for the peer's + // Close response before closing the TCP connection. Use a short timeout to + // avoid hanging if the peer doesn't respond. + strm_.set_read_timeout(CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND, 0); + Opcode op; + std::string resp; + bool fin; + while (impl::read_websocket_frame(strm_, op, resp, fin, is_server_, 125)) { + if (op == Opcode::Close) { + break; + } + } +} + +inline WebSocket::~WebSocket() +{ + { + std::lock_guard lock(ping_mutex_); + closed_ = true; + } + ping_cv_.notify_all(); + if (ping_thread_.joinable()) { + ping_thread_.join(); + } +} + +inline void WebSocket::start_heartbeat() +{ + if (ping_interval_sec_ == 0) { + return; + } + ping_thread_ = std::thread([this]() { + std::unique_lock lock(ping_mutex_); + while (!closed_) { + ping_cv_.wait_for(lock, std::chrono::seconds(ping_interval_sec_)); + if (closed_) { + break; + } + // If the peer has failed to respond to the previous pings, give up. + // RFC 6455 does not define a pong-timeout mechanism; this is an + // opt-in liveness check controlled by max_missed_pongs_. + if (max_missed_pongs_ > 0 && unacked_pings_ >= max_missed_pongs_) { + lock.unlock(); + close(CloseStatus::GoingAway, "pong timeout"); + return; + } + lock.unlock(); + if (!send_frame(Opcode::Ping, nullptr, 0)) { + lock.lock(); + closed_ = true; + break; + } + lock.lock(); + unacked_pings_++; + } + }); +} + +inline const Request &WebSocket::request() const +{ + return req_; +} + +inline bool WebSocket::is_open() const +{ + return !closed_; +} + +// WebSocketClient implementation +inline WebSocketClient::WebSocketClient(const std::string &scheme_host_port_path, const Headers &headers) + : headers_(headers) +{ + detail::UrlComponents uc; + if (detail::parse_url(scheme_host_port_path, uc) && !uc.scheme.empty() && !uc.host.empty() && !uc.path.empty()) { + auto &scheme = uc.scheme; + +#ifdef CPPHTTPLIB_SSL_ENABLED + if (scheme != "ws" && scheme != "wss") { +#else + if (scheme != "ws") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); +#endif + return; + } + + auto is_ssl = scheme == "wss"; + + host_ = std::move(uc.host); + + port_ = is_ssl ? 443 : 80; + if (!uc.port.empty() && !detail::parse_port(uc.port, port_)) { + return; + } + + path_ = std::move(uc.path); + if (!uc.query.empty()) { + path_ += uc.query; + } + +#ifdef CPPHTTPLIB_SSL_ENABLED + is_ssl_ = is_ssl; + if (is_ssl_) { + // The context lives as long as the client so that CA configuration + // survives reconnects; sessions are created per connection. + tls_ctx_ = tls::create_client_context(); + if (!tls_ctx_) { + return; + } + } +#else + if (is_ssl) { + return; + } +#endif + + is_valid_ = true; + } +} + +inline WebSocketClient::~WebSocketClient() +{ + shutdown_and_close(); +#ifdef CPPHTTPLIB_SSL_ENABLED + if (tls_ctx_) { + tls::free_context(tls_ctx_); + tls_ctx_ = nullptr; + } +#endif +} + +inline bool WebSocketClient::is_valid() const +{ + return is_valid_; +} + +inline void WebSocketClient::shutdown_and_close() +{ + // Send the close frame while the TLS session is still alive: ws_ holds an + // SSLSocketStream that keeps a raw pointer to tls_session_, so the session + // must outlive ws_->close() and ws_.reset() to avoid a use-after-free. + if (ws_ && ws_->is_open()) { + ws_->close(); + } + ws_.reset(); +#ifdef CPPHTTPLIB_SSL_ENABLED + if (is_ssl_) { + if (tls_session_) { + tls::shutdown(tls_session_, true); + tls::free_session(tls_session_); + tls_session_ = nullptr; + } + } +#endif + if (sock_ != INVALID_SOCKET) { + detail::shutdown_socket(sock_); + detail::close_socket(sock_); + sock_ = INVALID_SOCKET; + } +} + +inline bool WebSocketClient::create_stream(std::unique_ptr &strm) +{ +#ifdef CPPHTTPLIB_SSL_ENABLED + if (is_ssl_) { + if (server_certificate_verification_ && !certs_loaded_) { + uint64_t backend_error = 0; + detail::load_client_ca_config(tls_ctx_, ca_cert_file_path_, std::string(), custom_ca_loaded_, system_ca_mode_, backend_error); + certs_loaded_ = true; + } + + if (!detail::setup_client_tls_session( + host_, tls_ctx_, tls_session_, sock_, server_certificate_verification_, read_timeout_sec_, read_timeout_usec_)) { + return false; + } + + strm = std::unique_ptr( + new detail::SSLSocketStream(sock_, tls_session_, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_)); + return true; + } +#endif + strm = std::unique_ptr( + new detail::SocketStream(sock_, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_)); + return true; +} + +inline void WebSocketClient::prepare_default_headers(Request &req) +{ +#ifdef CPPHTTPLIB_SSL_ENABLED + auto is_ssl = is_ssl_; +#else + auto is_ssl = false; +#endif + + if (!req.has_header("Host")) { + if (address_family_ == AF_UNIX) { + req.headers.emplace("Host", "localhost"); + } + else { + req.headers.emplace("Host", detail::make_host_and_port_string(host_, port_, is_ssl)); + } + } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.set_header("User-Agent", agent); + } +#endif +} + +inline bool WebSocketClient::connect() +{ + if (!is_valid_) { + return false; + } + shutdown_and_close(); + + // Check is custom IP or hostname specified for host_ + std::string connect_host; + std::string ip; + detail::apply_addr_map(addr_map_, host_, connect_host, ip); + + Error error; + sock_ = detail::create_client_socket(connect_host, + ip, + port_, + address_family_, + tcp_nodelay_, + ipv6_v6only_, + socket_options_, + connection_timeout_sec_, + connection_timeout_usec_, + read_timeout_sec_, + read_timeout_usec_, + write_timeout_sec_, + write_timeout_usec_, + interface_, + error); + + if (sock_ == INVALID_SOCKET) { + return false; + } + + std::unique_ptr strm; + if (!create_stream(strm)) { + shutdown_and_close(); + return false; + } + + Request req; + req.method = "GET"; + req.path = path_; + req.headers = headers_; + prepare_default_headers(req); + + std::string selected_subprotocol; + if (!detail::perform_websocket_handshake(*strm, req, selected_subprotocol)) { + shutdown_and_close(); + return false; + } + subprotocol_ = std::move(selected_subprotocol); + + ws_ = std::unique_ptr(new WebSocket(std::move(strm), req, false, websocket_ping_interval_sec_, websocket_max_missed_pongs_)); + return true; +} + +inline ReadResult WebSocketClient::read(std::string &msg) +{ + if (!ws_) { + return Fail; + } + return ws_->read(msg); +} + +inline bool WebSocketClient::send(const std::string &data) +{ + if (!ws_) { + return false; + } + return ws_->send(data); +} + +inline bool WebSocketClient::send(const char *data, size_t len) +{ + if (!ws_) { + return false; + } + return ws_->send(data, len); +} + +inline void WebSocketClient::close(CloseStatus status, const std::string &reason) +{ + if (ws_) { + ws_->close(status, reason); + } +} + +inline bool WebSocketClient::is_open() const +{ + return ws_ && ws_->is_open(); +} + +inline const std::string &WebSocketClient::subprotocol() const +{ + return subprotocol_; +} + +inline void WebSocketClient::set_read_timeout(time_t sec, time_t usec) +{ + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +inline void WebSocketClient::set_write_timeout(time_t sec, time_t usec) +{ + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; +} + +inline void WebSocketClient::set_websocket_ping_interval(time_t sec) +{ + websocket_ping_interval_sec_ = sec; +} + +inline void WebSocketClient::set_websocket_max_missed_pongs(int count) +{ + websocket_max_missed_pongs_ = count; +} + +inline void WebSocketClient::set_tcp_nodelay(bool on) +{ + tcp_nodelay_ = on; +} + +inline void WebSocketClient::set_address_family(int family) +{ + address_family_ = family; +} + +inline void WebSocketClient::set_ipv6_v6only(bool on) +{ + ipv6_v6only_ = on; +} + +inline void WebSocketClient::set_socket_options(SocketOptions socket_options) +{ + socket_options_ = std::move(socket_options); +} + +inline void WebSocketClient::set_connection_timeout(time_t sec, time_t usec) +{ + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +inline void WebSocketClient::set_interface(const std::string &intf) +{ + interface_ = intf; +} + +inline void WebSocketClient::set_hostname_addr_map(std::map addr_map) +{ + addr_map_ = std::move(addr_map); +} + +#ifdef CPPHTTPLIB_SSL_ENABLED + +inline void WebSocketClient::set_ca_cert_path(const std::string &path) +{ + ca_cert_file_path_ = path; +} + +inline void WebSocketClient::set_ca_cert_store(tls::ca_store_t store) +{ + if (store && tls_ctx_) { + // set_ca_store takes ownership of store + tls::set_ca_store(tls_ctx_, store); + custom_ca_loaded_ = true; + } + else if (store) { + tls::free_ca_store(store); + } +} + +inline void WebSocketClient::load_ca_cert_store(const char *ca_cert, std::size_t size) +{ + if (tls_ctx_ && ca_cert && size > 0) { + tls::load_ca_pem(tls_ctx_, ca_cert, size); + custom_ca_loaded_ = true; + } +} + +inline void WebSocketClient::enable_server_certificate_verification(bool enabled) +{ + server_certificate_verification_ = enabled; +} + +inline void WebSocketClient::enable_system_ca(bool enabled) +{ + system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled; +} + +#endif // CPPHTTPLIB_SSL_ENABLED + +} // namespace ws + +// ---------------------------------------------------------------------------- + +} // namespace httplib + +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/drone_simulation_api_call/include/interfaces/ballistic_solver.hpp b/drone_simulation_api_call/include/interfaces/ballistic_solver.hpp new file mode 100644 index 0000000..9d00630 --- /dev/null +++ b/drone_simulation_api_call/include/interfaces/ballistic_solver.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "simulation.hpp" + +struct BallisticResult { + float flightTime; + float hDist; +}; + +class IBallisticSolver { +public: + virtual BallisticResult solve(const AmmoParams& ammo) = 0; + virtual ~IBallisticSolver() {} +}; \ No newline at end of file diff --git a/drone_simulation_api_call/include/interfaces/config_loader.hpp b/drone_simulation_api_call/include/interfaces/config_loader.hpp new file mode 100644 index 0000000..385223f --- /dev/null +++ b/drone_simulation_api_call/include/interfaces/config_loader.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "simulation.hpp" + +class IConfigLoader { +public: + virtual void load() = 0; + virtual DroneConfig getConfig() = 0; + virtual AmmoParams getAmmoParams() = 0; + virtual ~IConfigLoader() {} +}; \ No newline at end of file diff --git a/drone_simulation_api_call/include/interfaces/drone_state.hpp b/drone_simulation_api_call/include/interfaces/drone_state.hpp new file mode 100644 index 0000000..b67a562 --- /dev/null +++ b/drone_simulation_api_call/include/interfaces/drone_state.hpp @@ -0,0 +1,16 @@ +#pragma once +#include +#include +#include "simulation.hpp" + +class IDroneState { +public: + virtual ~IDroneState() = default; + + // Виконати логіку стану, повернути наступний стан. + // Якщо стан не змінився — повернути nullptr + // (головний цикл залишить поточний). + virtual std::unique_ptr execute(const DroneStateInput& input) = 0; + + virtual DroneStates name() const = 0; +}; diff --git a/drone_simulation_api_call/include/interfaces/target_provider.hpp b/drone_simulation_api_call/include/interfaces/target_provider.hpp new file mode 100644 index 0000000..055970c --- /dev/null +++ b/drone_simulation_api_call/include/interfaces/target_provider.hpp @@ -0,0 +1,9 @@ +#pragma once +#include "simulation.hpp" + +class ITargetProvider { +public: + virtual int getTargetCount() = 0; + virtual Target getTarget(int index) = 0; + virtual ~ITargetProvider() {} +}; \ No newline at end of file diff --git a/drone_simulation_api_call/include/json.hpp b/drone_simulation_api_call/include/json.hpp new file mode 100644 index 0000000..89554bc --- /dev/null +++ b/drone_simulation_api_call/include/json.hpp @@ -0,0 +1,25830 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +/****************************************************************************\ + * Note on documentation: The source files contain links to the online * + * documentation of the public API at https://json.nlohmann.me. This URL * + * contains the most recent documentation and should also be applicable to * + * previous versions; documentation for deprecated functions is not * + * removed, but marked deprecated. See "Generate documentation" section in * + * file docs/README.md. * +\****************************************************************************/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#include // all_of, find, for_each +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#ifndef JSON_NO_IO + #include // istream, ostream +#endif // JSON_NO_IO +#include // random_access_iterator_tag +#include // unique_ptr +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// This file contains all macro definitions affecting or depending on the ABI + +#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK + #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0 + #warning "Already included a different version of the library!" + #endif + #endif +#endif + +#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum) + +#ifndef JSON_DIAGNOSTICS + #define JSON_DIAGNOSTICS 0 +#endif + +#ifndef JSON_DIAGNOSTIC_POSITIONS + #define JSON_DIAGNOSTIC_POSITIONS 0 +#endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_DIAGNOSTICS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS +#endif + +#if JSON_DIAGNOSTIC_POSITIONS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp +#else + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION + #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 +#endif + +// Construct the namespace ABI tags component +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) + +#define NLOHMANN_JSON_ABI_TAGS \ + NLOHMANN_JSON_ABI_TAGS_CONCAT( \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS) + +// Construct the namespace version component +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ + _v ## major ## _ ## minor ## _ ## patch +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) + +#if NLOHMANN_JSON_NAMESPACE_NO_VERSION +#define NLOHMANN_JSON_NAMESPACE_VERSION +#else +#define NLOHMANN_JSON_NAMESPACE_VERSION \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ + NLOHMANN_JSON_VERSION_MINOR, \ + NLOHMANN_JSON_VERSION_PATCH) +#endif + +// Combine namespace components +#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b +#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ + NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) + +#ifndef NLOHMANN_JSON_NAMESPACE +#define NLOHMANN_JSON_NAMESPACE \ + nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN +#define NLOHMANN_JSON_NAMESPACE_BEGIN \ + namespace nlohmann \ + { \ + inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) \ + { +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_END +#define NLOHMANN_JSON_NAMESPACE_END \ + } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ + } // namespace nlohmann +#endif + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // transform +#include // array +#include // forward_list +#include // inserter, front_inserter, end +#include // map +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // unordered_map +#include // pair, declval +#include // valarray + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // nullptr_t +#include // exception +#if JSON_DIAGNOSTICS + #include // accumulate +#endif +#include // runtime_error +#include // to_string +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // uint8_t +#include // string + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // declval, pair +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// https://en.cppreference.com/w/cpp/experimental/is_detected +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template class Op, class... Args> +using is_detected = typename detector::value_t; + +template class Op, class... Args> +struct is_detected_lazy : is_detected { }; + +template class Op, class... Args> +using detected_t = typename detector::type; + +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; + +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + + +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-License-Identifier: MIT + +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + * SPDX-License-Identifier: CC0-1.0 + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 15 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_CONCAT3_EX) + #undef JSON_HEDLEY_CONCAT3_EX +#endif +#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c + +#if defined(JSON_HEDLEY_CONCAT3) + #undef JSON_HEDLEY_CONCAT3 +#endif +#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(JSON_HEDLEY_MSVC_VERSION) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #undef JSON_HEDLEY_INTEL_CL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) + #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) +#if (__TI_COMPILER_VERSION__ >= 16000000) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #undef JSON_HEDLEY_TI_CL2000_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #undef JSON_HEDLEY_TI_CL430_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #undef JSON_HEDLEY_TI_ARMCL_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) + #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #undef JSON_HEDLEY_TI_CL6X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #undef JSON_HEDLEY_TI_CL7X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #undef JSON_HEDLEY_TI_CLPRU_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #undef JSON_HEDLEY_MCST_LCC_VERSION +#endif +#if defined(__LCC__) && defined(__LCC_MINOR__) + #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) + #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_CRAY_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) && \ + !defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if \ + defined(__has_attribute) && \ + ( \ + (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ + ) +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#endif +#if !defined(__cplusplus) || !defined(__has_cpp_attribute) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#elif \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") +# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") +# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# endif +#endif +#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) +# endif +#else +# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif \ + (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_NO_ESCAPE) + #undef JSON_HEDLEY_NO_ESCAPE +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) + #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) +#else + #define JSON_HEDLEY_NO_ESCAPE +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif defined(JSON_HEDLEY_ASSUME) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif +#if !defined(JSON_HEDLEY_ASSUME) + #if defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) + #else + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) + #endif +#endif +#if defined(JSON_HEDLEY_UNREACHABLE) + #if \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) + #else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() + #endif +#else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") + #pragma clang diagnostic ignored "-Wpedantic" +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) +#elif \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) +# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) +# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else +# define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#elif \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_CONST _Pragma("no_side_effect") +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) +# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else +# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC __declspec(dllexport) +# define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else +# if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) +# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) +# else +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC +# endif +# define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) +#elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough +#else + #define JSON_HEDLEY_FALL_THROUGH +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* JSON_HEDLEY_IS_CONSTEXPR_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #undef JSON_HEDLEY_IS_CONSTEXPR_ +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION)) || \ + (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ + defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ + defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ + defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_NULL) + #undef JSON_HEDLEY_NULL +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + #elif defined(NULL) + #define JSON_HEDLEY_NULL NULL + #else + #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) + #endif +#elif defined(NULL) + #define JSON_HEDLEY_NULL NULL +#else + #define JSON_HEDLEY_NULL ((void*) 0) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE(expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE(expr) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) +#endif + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#else + #define JSON_HEDLEY_FLAGS +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +#if defined(JSON_HEDLEY_EMPTY_BASES) + #undef JSON_HEDLEY_EMPTY_BASES +#endif +#if \ + (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) +#else + #define JSON_HEDLEY_EMPTY_BASES +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ + + +// This file contains all internal macro definitions (except those affecting ABI) +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// #include + + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +// if the user manually specified the used C++ version, this is skipped +#if !defined(JSON_HAS_CPP_26) && !defined(JSON_HAS_CPP_23) && !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus > 202302L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202302L) + #define JSON_HAS_CPP_26 + #define JSON_HAS_CPP_23 + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202002L) + #define JSON_HAS_CPP_23 + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG > 201703L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 201402L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 201103L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 + #endif + // the cpp 11 flag is always specified because it is the minimal required version + #define JSON_HAS_CPP_11 +#endif + +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + +#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) + #ifdef JSON_HAS_CPP_17 + #if defined(__cpp_lib_filesystem) + #define JSON_HAS_FILESYSTEM 1 + #elif defined(__cpp_lib_experimental_filesystem) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif !defined(__has_include) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #endif + + // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ + #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__clang_major__) && __clang_major__ < 7 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support + #if defined(_MSC_VER) && _MSC_VER < 1914 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before iOS 13 + #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before macOS Catalina + #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + #endif +#endif + +#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_FILESYSTEM + #define JSON_HAS_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_THREE_WAY_COMPARISON + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L + #define JSON_HAS_THREE_WAY_COMPARISON 1 + #else + #define JSON_HAS_THREE_WAY_COMPARISON 0 + #endif +#endif + +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has a syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifndef JSON_HAS_STATIC_RTTI + #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0 + #define JSON_HAS_STATIC_RTTI 1 + #else + #define JSON_HAS_STATIC_RTTI 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + +#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) + #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else + #define JSON_NO_UNIQUE_ADDRESS +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdocumentation" + #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#endif + +// allow disabling exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// allow overriding assert +#if !defined(JSON_ASSERT) + #include // assert + #define JSON_ASSERT(x) assert(x) +#endif + +// allow accessing some private functions (needed by the test suite) +#if defined(JSON_TESTS_PRIVATE) + #define JSON_PRIVATE_UNLESS_TESTED public +#else + #define JSON_PRIVATE_UNLESS_TESTED private +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer, \ + class BinaryType, \ + class CustomBaseClass> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1) : nlohmann_json_default_obj.v1; + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +// inspired from https://stackoverflow.com/a/26745591 +// allows calling any std function as if (e.g., with begin): +// using std::begin; begin(x); +// +// it allows using the detected idiom to retrieve the return type +// of such an expression +#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ + namespace detail { \ + using std::std_name; \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + } \ + \ + namespace detail2 { \ + struct std_name##_tag \ + { \ + }; \ + \ + template \ + std_name##_tag std_name(T&&...); \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + \ + template \ + struct would_call_std_##std_name \ + { \ + static constexpr auto const value = ::nlohmann::detail:: \ + is_detected_exact::value; \ + }; \ + } /* namespace detail2 */ \ + \ + template \ + struct would_call_std_##std_name : detail2::would_call_std_##std_name \ + { \ + } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif + +#ifndef JSON_DISABLE_ENUM_SERIALIZATION + #define JSON_DISABLE_ENUM_SERIALIZATION 0 +#endif + +#ifndef JSON_USE_GLOBAL_UDLS + #define JSON_USE_GLOBAL_UDLS 1 +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief replace all occurrences of a substring by another string + +@param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t +@param[in] f the substring to replace with @a t +@param[in] t the string to replace @a f + +@pre The search string @a f must not be empty. **This precondition is +enforced with an assertion.** + +@since version 2.0.0 +*/ +template +inline void replace_substring(StringType& s, const StringType& f, + const StringType& t) +{ + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find the first occurrence of f + pos != StringType::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find the next occurrence of f + {} +} + +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + */ +template +inline StringType escape(StringType s) +{ + replace_substring(s, StringType{"~"}, StringType{"~0"}); + replace_substring(s, StringType{"/"}, StringType{"~1"}); + return s; +} + +/*! + * @brief string unescaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to unescape + * @return unescaped string + * + * Note the order of escaping "~1" to "/" and "~0" to "~" is important. + */ +template +inline void unescape(StringType& s) +{ + replace_substring(s, StringType{"~1"}, StringType{"/"}); + replace_substring(s, StringType{"~0"}, StringType{"~"}); +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-FileCopyrightText: 2018 The Abseil Authors +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // index_sequence, make_index_sequence, index_sequence_for + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +using uncvref_t = typename std::remove_cv::type>::type; + +#ifdef JSON_HAS_CPP_14 + +// the following utilities are natively available in C++14 +using std::enable_if_t; +using std::index_sequence; +using std::make_index_sequence; +using std::index_sequence_for; + +#else + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h +// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. + +//// START OF CODE FROM GOOGLE ABSEIL + +// integer_sequence +// +// Class template representing a compile-time integer sequence. An instantiation +// of `integer_sequence` has a sequence of integers encoded in its +// type through its template arguments (which is a common need when +// working with C++11 variadic templates). `absl::integer_sequence` is designed +// to be a drop-in replacement for C++14's `std::integer_sequence`. +// +// Example: +// +// template< class T, T... Ints > +// void user_function(integer_sequence); +// +// int main() +// { +// // user_function's `T` will be deduced to `int` and `Ints...` +// // will be deduced to `0, 1, 2, 3, 4`. +// user_function(make_integer_sequence()); +// } +template +struct integer_sequence +{ + using value_type = T; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +// index_sequence +// +// A helper template for an `integer_sequence` of `size_t`, +// `absl::index_sequence` is designed to be a drop-in replacement for C++14's +// `std::index_sequence`. +template +using index_sequence = integer_sequence; + +namespace utility_internal +{ + +template +struct Extend; + +// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. +template +struct Extend, SeqSize, 0> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; +}; + +template +struct Extend, SeqSize, 1> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; +}; + +// Recursion helper for 'make_integer_sequence'. +// 'Gen::type' is an alias for 'integer_sequence'. +template +struct Gen +{ + using type = + typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; +}; + +template +struct Gen +{ + using type = integer_sequence; +}; + +} // namespace utility_internal + +// Compile-time sequences of integers + +// make_integer_sequence +// +// This template alias is equivalent to +// `integer_sequence`, and is designed to be a drop-in +// replacement for C++14's `std::make_integer_sequence`. +template +using make_integer_sequence = typename utility_internal::Gen::type; + +// make_index_sequence +// +// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, +// and is designed to be a drop-in replacement for C++14's +// `std::make_index_sequence`. +template +using make_index_sequence = make_integer_sequence; + +// index_sequence_for +// +// Converts a typename pack into an index sequence of the same length, and +// is designed to be a drop-in replacement for C++14's +// `std::index_sequence_for()` +template +using index_sequence_for = make_index_sequence; + +//// END OF CODE FROM GOOGLE ABSEIL + +#endif + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static JSON_INLINE_VARIABLE constexpr T value{}; +}; + +#ifndef JSON_HAS_CPP_17 + template + constexpr T static_const::value; +#endif + +template +constexpr std::array make_array(Args&& ... args) +{ + return std::array {{static_cast(std::forward(args))...}}; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // numeric_limits +#include // char_traits +#include // tuple +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval +#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L + #include // byte +#endif +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // random_access_iterator_tag + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); + +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); + +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ + #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + #include // int64_t, uint64_t + #include // map + #include // allocator + #include // string + #include // vector + + // #include + + + /*! + @brief namespace for Niels Lohmann + @see https://github.com/nlohmann + @since version 1.0.0 + */ + NLOHMANN_JSON_NAMESPACE_BEGIN + + /*! + @brief default JSONSerializer template argument + + This serializer ignores the template arguments and uses ADL + ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) + for serialization. + */ + template + struct adl_serializer; + + /// a class to store JSON values + /// @sa https://json.nlohmann.me/api/basic_json/ + template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector, // cppcheck-suppress syntaxError + class CustomBaseClass = void> + class basic_json; + + /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document + /// @sa https://json.nlohmann.me/api/json_pointer/ + template + class json_pointer; + + /*! + @brief default specialization + @sa https://json.nlohmann.me/api/json/ + */ + using json = basic_json<>; + + /// @brief a minimal map-like container that preserves insertion order + /// @sa https://json.nlohmann.me/api/ordered_map/ + template + struct ordered_map; + + /// @brief specialization that maintains the insertion order of object keys + /// @sa https://json.nlohmann.me/api/ordered_json/ + using ordered_json = basic_json; + + NLOHMANN_JSON_NAMESPACE_END + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + +NLOHMANN_JSON_NAMESPACE_BEGIN +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ + +///////////// +// helpers // +///////////// + +// Note to maintainers: +// +// Every trait in this file expects a non-CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e., those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g., to_json(BasicJsonType&, const T&)) + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// used by exceptions create() member functions +// true_type for the pointer to possibly cv-qualified basic_json or std::nullptr_t +// false_type otherwise +template +struct is_basic_json_context : + std::integral_constant < bool, + is_basic_json::type>::type>::value + || std::is_same::value > +{}; + +////////////////////// +// json_ref helpers // +////////////////////// + +template +class json_ref; + +template +struct is_json_ref : std::false_type {}; + +template +struct is_json_ref> : std::true_type {}; + +////////////////////////// +// aliases for detected // +////////////////////////// + +template +using mapped_type_t = typename T::mapped_type; + +template +using key_type_t = typename T::key_type; + +template +using value_type_t = typename T::value_type; + +template +using difference_type_t = typename T::difference_type; + +template +using pointer_t = typename T::pointer; + +template +using reference_t = typename T::reference; + +template +using iterator_category_t = typename T::iterator_category; + +template +using to_json_function = decltype(T::to_json(std::declval()...)); + +template +using from_json_function = decltype(T::from_json(std::declval()...)); + +template +using get_template_function = decltype(std::declval().template get()); + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +// trait checking if j.get is valid +// use this trait instead of std::is_constructible or std::is_convertible, +// both rely on, or make use of implicit conversions, and thus fail when T +// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) +template +struct is_getable +{ + static constexpr bool value = is_detected::value; +}; + +template +struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +template +using detect_key_compare = typename T::key_compare; + +template +struct has_key_compare : std::integral_constant::value> {}; + +// obtains the actual object key comparator +template +struct actual_object_comparator +{ + using object_t = typename BasicJsonType::object_t; + using object_comparator_t = typename BasicJsonType::default_object_comparator_t; + using type = typename std::conditional < has_key_compare::value, + typename object_t::key_compare, object_comparator_t>::type; +}; + +template +using actual_object_comparator_t = typename actual_object_comparator::type; + +///////////////// +// char_traits // +///////////////// + +// Primary template of char_traits calls std char_traits +template +struct char_traits : std::char_traits +{}; + +// Explicitly define char traits for unsigned char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = unsigned char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(std::char_traits::eof()); + } +}; + +// Explicitly define char traits for signed char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = signed char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(std::char_traits::eof()); + } +}; + +#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L +template<> +struct char_traits : std::char_traits +{ + using char_type = std::byte; + using int_type = uint64_t; + + static int_type to_int_type(char_type c) noexcept + { + return static_cast(std::to_integer(c)); + } + + static char_type to_char_type(int_type i) noexcept + { + return std::byte(static_cast(i)); + } + + static constexpr int_type eof() noexcept + { + return static_cast(std::char_traits::eof()); + } +}; +#endif + +/////////////////// +// is_ functions // +/////////////////// + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type { }; +template struct conjunction : B { }; +template +struct conjunction +: std::conditional(B::value), conjunction, B>::type {}; + +// https://en.cppreference.com/w/cpp/types/negation +template struct negation : std::integral_constant < bool, !B::value > { }; + +// Reimplementation of is_constructible and is_default_constructible, due to them being broken for +// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). +// This causes compile errors in e.g., Clang 3.5 or GCC 4.9. +template +struct is_default_constructible : std::is_default_constructible {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_constructible : std::is_constructible {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_iterator_traits : std::false_type {}; + +template +struct is_iterator_traits> +{ + private: + using traits = iterator_traits; + + public: + static constexpr auto value = + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value; +}; + +template +struct is_range +{ + private: + using t_ref = typename std::add_lvalue_reference::type; + + using iterator = detected_t; + using sentinel = detected_t; + + // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator + // and https://en.cppreference.com/w/cpp/iterator/sentinel_for + // but reimplementing these would be too much work, as a lot of other concepts are used underneath + static constexpr auto is_iterator_begin = + is_iterator_traits>::value; + + public: + static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; +}; + +template +using iterator_t = enable_if_t::value, result_of_begin())>>; + +template +using range_value_t = value_type_t>>; + +// The following implementation of is_complete_type is taken from +// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ +// and is written by Xiang Fan who agreed to use it in this library. + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl < + BasicJsonType, CompatibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + // macOS's is_constructible does not play well with nonesuch... + static constexpr bool value = + is_constructible::value && + is_constructible::value; +}; + +template +struct is_compatible_object_type + : is_compatible_object_type_impl {}; + +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + (is_default_constructible::value && + (std::is_move_assignable::value || + std::is_copy_assignable::value) && + (is_constructible::value && + std::is_same < + typename object_t::mapped_type, + typename ConstructibleObjectType::mapped_type >::value)) || + (has_from_json::value || + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + +template +struct is_compatible_string_type +{ + static constexpr auto value = + is_constructible::value; +}; + +template +struct is_constructible_string_type +{ + // launder type through decltype() to fix compilation failure on ICPC +#ifdef __INTEL_COMPILER + using laundered_type = decltype(std::declval()); +#else + using laundered_type = ConstructibleStringType; +#endif + + static constexpr auto value = + conjunction < + is_constructible, + is_detected_exact>::value; +}; + +template +struct is_compatible_array_type_impl : std::false_type {}; + +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < + is_detected::value&& + is_iterator_traits>>::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 + !std::is_same>::value >> +{ + static constexpr bool value = + is_constructible>::value; +}; + +template +struct is_compatible_array_type + : is_compatible_array_type_impl {}; + +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t < !std::is_same::value&& + !is_compatible_string_type::value&& + is_default_constructible::value&& +(std::is_move_assignable::value || + std::is_copy_assignable::value)&& +is_detected::value&& +is_iterator_traits>>::value&& +is_detected::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 +!std::is_same>::value&& +is_complete_type < +detected_t>::value >> +{ + using value_type = range_value_t; + + static constexpr bool value = + std::is_same::value || + has_from_json::value || + has_non_default_from_json < + BasicJsonType, + value_type >::value; +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral::value&& + std::is_integral::value&& + !std::is_same::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + is_constructible::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type + : is_compatible_integer_type_impl {}; + +template +struct is_compatible_type_impl: std::false_type {}; + +template +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t::value >> +{ + static constexpr bool value = + has_to_json::value; +}; + +template +struct is_compatible_type + : is_compatible_type_impl {}; + +template +struct is_compatible_reference_type_impl +{ + using JsonType = uncvref_t; + using CVType = typename std::remove_reference::type; + using Type = typename std::remove_cv::type; + constexpr static bool value = std::is_reference::value && + (!std::is_const::type>::value || std::is_const::value) && + (std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value); +}; + +template +struct is_compatible_reference_type + : is_compatible_reference_type_impl {}; + +template +struct is_constructible_tuple : std::false_type {}; + +template +struct is_constructible_tuple> : conjunction...> {}; + +template +struct is_json_iterator_of : std::false_type {}; + +template +struct is_json_iterator_of : std::true_type {}; + +template +struct is_json_iterator_of : std::true_type +{}; + +// checks if a given type T is a template specialization of Primary +template