summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.ci/scripts/windows/docker.sh11
-rw-r--r--.gitmodules5
-rw-r--r--externals/CMakeLists.txt6
m---------externals/cpp-httplib0
m---------externals/discord-rpc0
m---------externals/dynarmic0
-rw-r--r--externals/httplib/README.md15
-rw-r--r--externals/httplib/httplib.h6714
-rw-r--r--src/audio_core/CMakeLists.txt5
-rw-r--r--src/audio_core/sdl2_sink.cpp163
-rw-r--r--src/audio_core/sdl2_sink.h29
-rw-r--r--src/audio_core/sink_details.cpp10
-rw-r--r--src/common/detached_tasks.cpp2
-rw-r--r--src/common/fs/file.cpp4
-rw-r--r--src/common/fs/file.h12
-rw-r--r--src/common/fs/fs.cpp5
-rw-r--r--src/common/fs/fs.h30
-rw-r--r--src/common/host_memory.cpp2
-rw-r--r--src/common/logging/backend.cpp4
-rw-r--r--src/common/settings.cpp1
-rw-r--r--src/common/settings.h2
-rw-r--r--src/core/CMakeLists.txt3
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp3
-rw-r--r--src/core/core.cpp5
-rw-r--r--src/core/file_sys/system_archive/system_version.cpp48
-rw-r--r--src/core/file_sys/vfs_real.cpp7
-rw-r--r--src/core/hle/api_version.h38
-rw-r--r--src/core/hle/kernel/k_resource_limit.cpp1
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp4
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp4
-rw-r--r--src/core/hle/service/hid/controllers/npad.h4
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp3
-rw-r--r--src/core/hle/service/spl/csrng.cpp2
-rw-r--r--src/core/hle/service/spl/module.cpp124
-rw-r--r--src/core/hle/service/spl/module.h13
-rw-r--r--src/core/hle/service/spl/spl.cpp84
-rw-r--r--src/core/hle/service/spl/spl_results.h29
-rw-r--r--src/core/hle/service/spl/spl_types.h230
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.cpp2
-rw-r--r--src/input_common/mouse/mouse_input.cpp16
-rw-r--r--src/input_common/mouse/mouse_input.h6
-rw-r--r--src/video_core/rasterizer_interface.h4
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h2
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp6
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.cpp18
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.h9
-rw-r--r--src/video_core/texture_cache/util.cpp10
-rw-r--r--src/video_core/vulkan_common/nsight_aftermath_tracker.cpp2
-rw-r--r--src/video_core/vulkan_common/vulkan_debug_callback.cpp8
-rw-r--r--src/web_service/web_backend.cpp3
-rw-r--r--src/yuzu/bootmanager.cpp12
-rw-r--r--src/yuzu/bootmanager.h6
-rw-r--r--src/yuzu/configuration/config.cpp11
-rw-r--r--src/yuzu/configuration/config.h2
-rw-r--r--src/yuzu/configuration/configure_cpu.cpp9
-rw-r--r--src/yuzu/configuration/configure_cpu.h1
-rw-r--r--src/yuzu/configuration/configure_cpu.ui12
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp6
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.h1
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.ui18
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp2
-rw-r--r--src/yuzu/configuration/configure_per_game.ui7
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp4
-rw-r--r--src/yuzu/main.cpp30
-rw-r--r--src/yuzu_cmd/config.cpp2
-rw-r--r--src/yuzu_cmd/default_ini.h8
-rw-r--r--src/yuzu_cmd/yuzu.cpp2
69 files changed, 921 insertions, 6924 deletions
diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh
index feba3fd6e..155d8a5c8 100755
--- a/.ci/scripts/windows/docker.sh
+++ b/.ci/scripts/windows/docker.sh
@@ -18,19 +18,20 @@ cd ..
18mkdir package 18mkdir package
19 19
20if [ -d "/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/" ]; then 20if [ -d "/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/" ]; then
21 QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/' 21 QT_PLUGINS_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins'
22else 22else
23 #fallback to qt 23 #fallback to qt
24 QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt/plugins/platforms/' 24 QT_PLUGINS_PATH='/usr/x86_64-w64-mingw32/lib/qt/plugins'
25fi 25fi
26 26
27find build/ -name "yuzu*.exe" -exec cp {} 'package' \; 27find build/ -name "yuzu*.exe" -exec cp {} 'package' \;
28 28
29# copy Qt plugins 29# copy Qt plugins
30mkdir package/platforms 30mkdir package/platforms
31cp "${QT_PLATFORM_DLL_PATH}/qwindows.dll" package/platforms/ 31cp -v "${QT_PLUGINS_PATH}/platforms/qwindows.dll" package/platforms/
32cp -rv "${QT_PLATFORM_DLL_PATH}/../mediaservice/" package/ 32cp -rv "${QT_PLUGINS_PATH}/mediaservice/" package/
33cp -rv "${QT_PLATFORM_DLL_PATH}/../imageformats/" package/ 33cp -rv "${QT_PLUGINS_PATH}/imageformats/" package/
34cp -rv "${QT_PLUGINS_PATH}/styles/" package/
34rm -f package/mediaservice/*d.dll 35rm -f package/mediaservice/*d.dll
35 36
36for i in package/*.exe; do 37for i in package/*.exe; do
diff --git a/.gitmodules b/.gitmodules
index 8e5bc4581..749cd0408 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -18,7 +18,7 @@
18 url = https://github.com/libusb/libusb.git 18 url = https://github.com/libusb/libusb.git
19[submodule "discord-rpc"] 19[submodule "discord-rpc"]
20 path = externals/discord-rpc 20 path = externals/discord-rpc
21 url = https://github.com/discordapp/discord-rpc.git 21 url = https://github.com/discord/discord-rpc.git
22[submodule "Vulkan-Headers"] 22[submodule "Vulkan-Headers"]
23 path = externals/Vulkan-Headers 23 path = externals/Vulkan-Headers
24 url = https://github.com/KhronosGroup/Vulkan-Headers.git 24 url = https://github.com/KhronosGroup/Vulkan-Headers.git
@@ -43,3 +43,6 @@
43[submodule "SDL"] 43[submodule "SDL"]
44 path = externals/SDL 44 path = externals/SDL
45 url = https://github.com/libsdl-org/SDL.git 45 url = https://github.com/libsdl-org/SDL.git
46[submodule "externals/cpp-httplib"]
47 path = externals/cpp-httplib
48 url = https://github.com/yhirose/cpp-httplib.git
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 5402a532f..fd427a912 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -53,10 +53,10 @@ endif()
53# SDL2 53# SDL2
54if (NOT SDL2_FOUND AND ENABLE_SDL2) 54if (NOT SDL2_FOUND AND ENABLE_SDL2)
55 if (NOT WIN32) 55 if (NOT WIN32)
56 # Yuzu itself needs: Events Joystick Haptic Sensor Timers 56 # Yuzu itself needs: Events Joystick Haptic Sensor Timers Audio
57 # Yuzu-cmd also needs: Video (depends on Loadso/Dlopen) 57 # Yuzu-cmd also needs: Video (depends on Loadso/Dlopen)
58 set(SDL_UNUSED_SUBSYSTEMS 58 set(SDL_UNUSED_SUBSYSTEMS
59 Atomic Audio Render Power Threads 59 Atomic Render Power Threads
60 File CPUinfo Filesystem Locale) 60 File CPUinfo Filesystem Locale)
61 foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS}) 61 foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS})
62 string(TOUPPER ${_SUB} _OPT) 62 string(TOUPPER ${_SUB} _OPT)
@@ -115,7 +115,7 @@ if (ENABLE_WEB_SERVICE)
115 115
116 # httplib 116 # httplib
117 add_library(httplib INTERFACE) 117 add_library(httplib INTERFACE)
118 target_include_directories(httplib INTERFACE ./httplib) 118 target_include_directories(httplib INTERFACE ./cpp-httplib)
119 target_compile_definitions(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT) 119 target_compile_definitions(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
120 target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES}) 120 target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
121 if (WIN32) 121 if (WIN32)
diff --git a/externals/cpp-httplib b/externals/cpp-httplib
new file mode 160000
Subproject 9648f950f5a8a41d18833cf4a85f5821b1bcac5
diff --git a/externals/discord-rpc b/externals/discord-rpc
Subproject e32d001809c4aad56cef2a5321b54442d830174 Subproject 963aa9f3e5ce81a4682c6ca3d136cddda614db3
diff --git a/externals/dynarmic b/externals/dynarmic
Subproject 0c12614d1a7a72d778609920dde96a4c63074ec Subproject c6125082ea992c245edab26b6f86e7b904b86ee
diff --git a/externals/httplib/README.md b/externals/httplib/README.md
deleted file mode 100644
index 1940e446c..000000000
--- a/externals/httplib/README.md
+++ /dev/null
@@ -1,15 +0,0 @@
1From https://github.com/yhirose/cpp-httplib/tree/ff5677ad197947177c158fe857caff4f0e242045 with https://github.com/yhirose/cpp-httplib/pull/701
2
3MIT License
4
5===
6
7cpp-httplib
8
9A C++11 header-only HTTP library.
10
11It's extremely easy to setup. Just include httplib.h file in your code!
12
13Inspired by Sinatra and express.
14
15© 2017 Yuji Hirose
diff --git a/externals/httplib/httplib.h b/externals/httplib/httplib.h
deleted file mode 100644
index 8982054e2..000000000
--- a/externals/httplib/httplib.h
+++ /dev/null
@@ -1,6714 +0,0 @@
1//
2// httplib.h
3//
4// Copyright (c) 2020 Yuji Hirose. All rights reserved.
5// MIT License
6//
7
8#ifndef CPPHTTPLIB_HTTPLIB_H
9#define CPPHTTPLIB_HTTPLIB_H
10
11/*
12 * Configuration
13 */
14
15#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND
16#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
17#endif
18
19#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
20#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5
21#endif
22
23#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
24#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300
25#endif
26
27#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND
28#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
29#endif
30
31#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND
32#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5
33#endif
34
35#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND
36#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0
37#endif
38
39#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND
40#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5
41#endif
42
43#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND
44#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0
45#endif
46
47#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
48#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0
49#endif
50
51#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND
52#ifdef _WIN32
53#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000
54#else
55#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0
56#endif
57#endif
58
59#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH
60#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
61#endif
62
63#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT
64#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
65#endif
66
67#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
68#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits<size_t>::max)())
69#endif
70
71#ifndef CPPHTTPLIB_TCP_NODELAY
72#define CPPHTTPLIB_TCP_NODELAY false
73#endif
74
75#ifndef CPPHTTPLIB_RECV_BUFSIZ
76#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u)
77#endif
78
79#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
80#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
81#endif
82
83#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
84#define CPPHTTPLIB_THREAD_POOL_COUNT \
85 ((std::max)(8u, std::thread::hardware_concurrency() > 0 \
86 ? std::thread::hardware_concurrency() - 1 \
87 : 0))
88#endif
89
90/*
91 * Headers
92 */
93
94#ifdef _WIN32
95#ifndef _CRT_SECURE_NO_WARNINGS
96#define _CRT_SECURE_NO_WARNINGS
97#endif //_CRT_SECURE_NO_WARNINGS
98
99#ifndef _CRT_NONSTDC_NO_DEPRECATE
100#define _CRT_NONSTDC_NO_DEPRECATE
101#endif //_CRT_NONSTDC_NO_DEPRECATE
102
103#if defined(_MSC_VER)
104#ifdef _WIN64
105using ssize_t = __int64;
106#else
107using ssize_t = int;
108#endif
109
110#if _MSC_VER < 1900
111#define snprintf _snprintf_s
112#endif
113#endif // _MSC_VER
114
115#ifndef S_ISREG
116#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG)
117#endif // S_ISREG
118
119#ifndef S_ISDIR
120#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR)
121#endif // S_ISDIR
122
123#ifndef NOMINMAX
124#define NOMINMAX
125#endif // NOMINMAX
126
127#include <io.h>
128#include <winsock2.h>
129
130#include <wincrypt.h>
131#include <ws2tcpip.h>
132
133#ifndef WSA_FLAG_NO_HANDLE_INHERIT
134#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
135#endif
136
137#ifdef _MSC_VER
138#pragma comment(lib, "ws2_32.lib")
139#pragma comment(lib, "crypt32.lib")
140#pragma comment(lib, "cryptui.lib")
141#endif
142
143#ifndef strcasecmp
144#define strcasecmp _stricmp
145#endif // strcasecmp
146
147using socket_t = SOCKET;
148#ifdef CPPHTTPLIB_USE_POLL
149#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
150#endif
151
152#else // not _WIN32
153
154#include <arpa/inet.h>
155#include <cstring>
156#include <ifaddrs.h>
157#include <netdb.h>
158#include <netinet/in.h>
159#ifdef __linux__
160#include <resolv.h>
161#endif
162#include <netinet/tcp.h>
163#ifdef CPPHTTPLIB_USE_POLL
164#include <poll.h>
165#endif
166#include <csignal>
167#include <pthread.h>
168#include <sys/select.h>
169#include <sys/socket.h>
170#include <unistd.h>
171
172using socket_t = int;
173#define INVALID_SOCKET (-1)
174#endif //_WIN32
175
176#include <algorithm>
177#include <array>
178#include <atomic>
179#include <cassert>
180#include <cctype>
181#include <climits>
182#include <condition_variable>
183#include <errno.h>
184#include <fcntl.h>
185#include <fstream>
186#include <functional>
187#include <iostream>
188#include <list>
189#include <map>
190#include <memory>
191#include <mutex>
192#include <random>
193#include <regex>
194#include <sstream>
195#include <string>
196#include <sys/stat.h>
197#include <thread>
198
199#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
200#include <openssl/err.h>
201#include <openssl/md5.h>
202#include <openssl/ssl.h>
203#include <openssl/x509v3.h>
204
205#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK)
206#include <openssl/applink.c>
207#endif
208
209#include <iomanip>
210#include <iostream>
211#include <sstream>
212
213#if OPENSSL_VERSION_NUMBER < 0x1010100fL
214#error Sorry, OpenSSL versions prior to 1.1.1 are not supported
215#endif
216
217#if OPENSSL_VERSION_NUMBER < 0x10100000L
218#include <openssl/crypto.h>
219inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
220 return M_ASN1_STRING_data(asn1);
221}
222#endif
223#endif
224
225#ifdef CPPHTTPLIB_ZLIB_SUPPORT
226#include <zlib.h>
227#endif
228
229#ifdef CPPHTTPLIB_BROTLI_SUPPORT
230#include <brotli/decode.h>
231#include <brotli/encode.h>
232#endif
233
234/*
235 * Declaration
236 */
237namespace httplib {
238
239namespace detail {
240
241/*
242 * Backport std::make_unique from C++14.
243 *
244 * NOTE: This code came up with the following stackoverflow post:
245 * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique
246 *
247 */
248
249template <class T, class... Args>
250typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
251make_unique(Args &&... args) {
252 return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
253}
254
255template <class T>
256typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
257make_unique(std::size_t n) {
258 typedef typename std::remove_extent<T>::type RT;
259 return std::unique_ptr<T>(new RT[n]);
260}
261
262struct ci {
263 bool operator()(const std::string &s1, const std::string &s2) const {
264 return std::lexicographical_compare(
265 s1.begin(), s1.end(), s2.begin(), s2.end(),
266 [](char c1, char c2) { return ::tolower(c1) < ::tolower(c2); });
267 }
268};
269
270} // namespace detail
271
272using Headers = std::multimap<std::string, std::string, detail::ci>;
273
274using Params = std::multimap<std::string, std::string>;
275using Match = std::smatch;
276
277using Progress = std::function<bool(uint64_t current, uint64_t total)>;
278
279struct Response;
280using ResponseHandler = std::function<bool(const Response &response)>;
281
282struct MultipartFormData {
283 std::string name;
284 std::string content;
285 std::string filename;
286 std::string content_type;
287};
288using MultipartFormDataItems = std::vector<MultipartFormData>;
289using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>;
290
291class DataSink {
292public:
293 DataSink() : os(&sb_), sb_(*this) {}
294
295 DataSink(const DataSink &) = delete;
296 DataSink &operator=(const DataSink &) = delete;
297 DataSink(DataSink &&) = delete;
298 DataSink &operator=(DataSink &&) = delete;
299
300 std::function<void(const char *data, size_t data_len)> write;
301 std::function<void()> done;
302 std::function<bool()> is_writable;
303 std::ostream os;
304
305private:
306 class data_sink_streambuf : public std::streambuf {
307 public:
308 explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
309
310 protected:
311 std::streamsize xsputn(const char *s, std::streamsize n) {
312 sink_.write(s, static_cast<size_t>(n));
313 return n;
314 }
315
316 private:
317 DataSink &sink_;
318 };
319
320 data_sink_streambuf sb_;
321};
322
323using ContentProvider =
324 std::function<bool(size_t offset, size_t length, DataSink &sink)>;
325
326using ContentProviderWithoutLength =
327 std::function<bool(size_t offset, DataSink &sink)>;
328
329using ContentReceiverWithProgress =
330 std::function<bool(const char *data, size_t data_length, uint64_t offset,
331 uint64_t total_length)>;
332
333using ContentReceiver =
334 std::function<bool(const char *data, size_t data_length)>;
335
336using MultipartContentHeader =
337 std::function<bool(const MultipartFormData &file)>;
338
339class ContentReader {
340public:
341 using Reader = std::function<bool(ContentReceiver receiver)>;
342 using MultipartReader = std::function<bool(MultipartContentHeader header,
343 ContentReceiver receiver)>;
344
345 ContentReader(Reader reader, MultipartReader multipart_reader)
346 : reader_(std::move(reader)),
347 multipart_reader_(std::move(multipart_reader)) {}
348
349 bool operator()(MultipartContentHeader header,
350 ContentReceiver receiver) const {
351 return multipart_reader_(std::move(header), std::move(receiver));
352 }
353
354 bool operator()(ContentReceiver receiver) const {
355 return reader_(std::move(receiver));
356 }
357
358 Reader reader_;
359 MultipartReader multipart_reader_;
360};
361
362using Range = std::pair<ssize_t, ssize_t>;
363using Ranges = std::vector<Range>;
364
365struct Request {
366 std::string method;
367 std::string path;
368 Headers headers;
369 std::string body;
370
371 std::string remote_addr;
372 int remote_port = -1;
373
374 // for server
375 std::string version;
376 std::string target;
377 Params params;
378 MultipartFormDataMap files;
379 Ranges ranges;
380 Match matches;
381
382 // for client
383 size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT;
384 ResponseHandler response_handler;
385 ContentReceiverWithProgress content_receiver;
386 size_t content_length = 0;
387 ContentProvider content_provider;
388 Progress progress;
389
390#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
391 const SSL *ssl;
392#endif
393
394 bool has_header(const char *key) const;
395 std::string get_header_value(const char *key, size_t id = 0) const;
396 template <typename T>
397 T get_header_value(const char *key, size_t id = 0) const;
398 size_t get_header_value_count(const char *key) const;
399 void set_header(const char *key, const char *val);
400 void set_header(const char *key, const std::string &val);
401
402 bool has_param(const char *key) const;
403 std::string get_param_value(const char *key, size_t id = 0) const;
404 size_t get_param_value_count(const char *key) const;
405
406 bool is_multipart_form_data() const;
407
408 bool has_file(const char *key) const;
409 MultipartFormData get_file_value(const char *key) const;
410
411 // private members...
412 size_t authorization_count_ = 0;
413};
414
415struct Response {
416 std::string version;
417 int status = -1;
418 std::string reason;
419 Headers headers;
420 std::string body;
421
422 bool has_header(const char *key) const;
423 std::string get_header_value(const char *key, size_t id = 0) const;
424 template <typename T>
425 T get_header_value(const char *key, size_t id = 0) const;
426 size_t get_header_value_count(const char *key) const;
427 void set_header(const char *key, const char *val);
428 void set_header(const char *key, const std::string &val);
429
430 void set_redirect(const char *url, int status = 302);
431 void set_redirect(const std::string &url, int status = 302);
432 void set_content(const char *s, size_t n, const char *content_type);
433 void set_content(std::string s, const char *content_type);
434
435 void set_content_provider(
436 size_t length, const char *content_type, ContentProvider provider,
437 const std::function<void()> &resource_releaser = nullptr);
438
439 void set_content_provider(
440 const char *content_type, ContentProviderWithoutLength provider,
441 const std::function<void()> &resource_releaser = nullptr);
442
443 void set_chunked_content_provider(
444 const char *content_type, ContentProviderWithoutLength provider,
445 const std::function<void()> &resource_releaser = nullptr);
446
447 Response() = default;
448 Response(const Response &) = default;
449 Response &operator=(const Response &) = default;
450 Response(Response &&) = default;
451 Response &operator=(Response &&) = default;
452 ~Response() {
453 if (content_provider_resource_releaser_) {
454 content_provider_resource_releaser_();
455 }
456 }
457
458 // private members...
459 size_t content_length_ = 0;
460 ContentProvider content_provider_;
461 std::function<void()> content_provider_resource_releaser_;
462 bool is_chunked_content_provider = false;
463};
464
465class Stream {
466public:
467 virtual ~Stream() = default;
468
469 virtual bool is_readable() const = 0;
470 virtual bool is_writable() const = 0;
471
472 virtual ssize_t read(char *ptr, size_t size) = 0;
473 virtual ssize_t write(const char *ptr, size_t size) = 0;
474 virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
475
476 template <typename... Args>
477 ssize_t write_format(const char *fmt, const Args &... args);
478 ssize_t write(const char *ptr);
479 ssize_t write(const std::string &s);
480};
481
482class TaskQueue {
483public:
484 TaskQueue() = default;
485 virtual ~TaskQueue() = default;
486
487 virtual void enqueue(std::function<void()> fn) = 0;
488 virtual void shutdown() = 0;
489
490 virtual void on_idle(){};
491};
492
493class ThreadPool : public TaskQueue {
494public:
495 explicit ThreadPool(size_t n) : shutdown_(false) {
496 while (n) {
497 threads_.emplace_back(worker(*this));
498 n--;
499 }
500 }
501
502 ThreadPool(const ThreadPool &) = delete;
503 ~ThreadPool() override = default;
504
505 void enqueue(std::function<void()> fn) override {
506 std::unique_lock<std::mutex> lock(mutex_);
507 jobs_.push_back(std::move(fn));
508 cond_.notify_one();
509 }
510
511 void shutdown() override {
512 // Stop all worker threads...
513 {
514 std::unique_lock<std::mutex> lock(mutex_);
515 shutdown_ = true;
516 }
517
518 cond_.notify_all();
519
520 // Join...
521 for (auto &t : threads_) {
522 t.join();
523 }
524 }
525
526private:
527 struct worker {
528 explicit worker(ThreadPool &pool) : pool_(pool) {}
529
530 void operator()() {
531 for (;;) {
532 std::function<void()> fn;
533 {
534 std::unique_lock<std::mutex> lock(pool_.mutex_);
535
536 pool_.cond_.wait(
537 lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; });
538
539 if (pool_.shutdown_ && pool_.jobs_.empty()) { break; }
540
541 fn = pool_.jobs_.front();
542 pool_.jobs_.pop_front();
543 }
544
545 assert(true == static_cast<bool>(fn));
546 fn();
547 }
548 }
549
550 ThreadPool &pool_;
551 };
552 friend struct worker;
553
554 std::vector<std::thread> threads_;
555 std::list<std::function<void()>> jobs_;
556
557 bool shutdown_;
558
559 std::condition_variable cond_;
560 std::mutex mutex_;
561};
562
563using Logger = std::function<void(const Request &, const Response &)>;
564
565using SocketOptions = std::function<void(socket_t sock)>;
566
567inline void default_socket_options(socket_t sock) {
568 int yes = 1;
569#ifdef _WIN32
570 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes),
571 sizeof(yes));
572 setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
573 reinterpret_cast<char *>(&yes), sizeof(yes));
574#else
575#ifdef SO_REUSEPORT
576 setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<void *>(&yes),
577 sizeof(yes));
578#else
579 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<void *>(&yes),
580 sizeof(yes));
581#endif
582#endif
583}
584
585class Server {
586public:
587 using Handler = std::function<void(const Request &, Response &)>;
588 using HandlerWithContentReader = std::function<void(
589 const Request &, Response &, const ContentReader &content_reader)>;
590 using Expect100ContinueHandler =
591 std::function<int(const Request &, Response &)>;
592
593 Server();
594
595 virtual ~Server();
596
597 virtual bool is_valid() const;
598
599 Server &Get(const char *pattern, Handler handler);
600 Server &Post(const char *pattern, Handler handler);
601 Server &Post(const char *pattern, HandlerWithContentReader handler);
602 Server &Put(const char *pattern, Handler handler);
603 Server &Put(const char *pattern, HandlerWithContentReader handler);
604 Server &Patch(const char *pattern, Handler handler);
605 Server &Patch(const char *pattern, HandlerWithContentReader handler);
606 Server &Delete(const char *pattern, Handler handler);
607 Server &Delete(const char *pattern, HandlerWithContentReader handler);
608 Server &Options(const char *pattern, Handler handler);
609
610 bool set_base_dir(const char *dir, const char *mount_point = nullptr);
611 bool set_mount_point(const char *mount_point, const char *dir,
612 Headers headers = Headers());
613 bool remove_mount_point(const char *mount_point);
614 void set_file_extension_and_mimetype_mapping(const char *ext,
615 const char *mime);
616 void set_file_request_handler(Handler handler);
617
618 void set_error_handler(Handler handler);
619 void set_expect_100_continue_handler(Expect100ContinueHandler handler);
620 void set_logger(Logger logger);
621
622 void set_tcp_nodelay(bool on);
623 void set_socket_options(SocketOptions socket_options);
624
625 void set_keep_alive_max_count(size_t count);
626 void set_keep_alive_timeout(time_t sec);
627 void set_read_timeout(time_t sec, time_t usec = 0);
628 void set_write_timeout(time_t sec, time_t usec = 0);
629 void set_idle_interval(time_t sec, time_t usec = 0);
630
631 void set_payload_max_length(size_t length);
632
633 bool bind_to_port(const char *host, int port, int socket_flags = 0);
634 int bind_to_any_port(const char *host, int socket_flags = 0);
635 bool listen_after_bind();
636
637 bool listen(const char *host, int port, int socket_flags = 0);
638
639 bool is_running() const;
640 void stop();
641
642 std::function<TaskQueue *(void)> new_task_queue;
643
644protected:
645 bool process_request(Stream &strm, bool close_connection,
646 bool &connection_closed,
647 const std::function<void(Request &)> &setup_request);
648
649 std::atomic<socket_t> svr_sock_;
650 size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
651 time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
652 time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
653 time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
654 time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
655 time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
656 time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
657 time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
658 size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
659
660private:
661 using Handlers = std::vector<std::pair<std::regex, Handler>>;
662 using HandlersForContentReader =
663 std::vector<std::pair<std::regex, HandlerWithContentReader>>;
664
665 socket_t create_server_socket(const char *host, int port, int socket_flags,
666 SocketOptions socket_options) const;
667 int bind_internal(const char *host, int port, int socket_flags);
668 bool listen_internal();
669
670 bool routing(Request &req, Response &res, Stream &strm);
671 bool handle_file_request(Request &req, Response &res, bool head = false);
672 bool dispatch_request(Request &req, Response &res, const Handlers &handlers);
673 bool
674 dispatch_request_for_content_reader(Request &req, Response &res,
675 ContentReader content_reader,
676 const HandlersForContentReader &handlers);
677
678 bool parse_request_line(const char *s, Request &req);
679 bool write_response(Stream &strm, bool close_connection, const Request &req,
680 Response &res);
681 bool write_content_with_provider(Stream &strm, const Request &req,
682 Response &res, const std::string &boundary,
683 const std::string &content_type);
684 bool read_content(Stream &strm, Request &req, Response &res);
685 bool
686 read_content_with_content_receiver(Stream &strm, Request &req, Response &res,
687 ContentReceiver receiver,
688 MultipartContentHeader multipart_header,
689 ContentReceiver multipart_receiver);
690 bool read_content_core(Stream &strm, Request &req, Response &res,
691 ContentReceiver receiver,
692 MultipartContentHeader mulitpart_header,
693 ContentReceiver multipart_receiver);
694
695 virtual bool process_and_close_socket(socket_t sock);
696
697 struct MountPointEntry {
698 std::string mount_point;
699 std::string base_dir;
700 Headers headers;
701 };
702 std::vector<MountPointEntry> base_dirs_;
703
704 std::atomic<bool> is_running_;
705 std::map<std::string, std::string> file_extension_and_mimetype_map_;
706 Handler file_request_handler_;
707 Handlers get_handlers_;
708 Handlers post_handlers_;
709 HandlersForContentReader post_handlers_for_content_reader_;
710 Handlers put_handlers_;
711 HandlersForContentReader put_handlers_for_content_reader_;
712 Handlers patch_handlers_;
713 HandlersForContentReader patch_handlers_for_content_reader_;
714 Handlers delete_handlers_;
715 HandlersForContentReader delete_handlers_for_content_reader_;
716 Handlers options_handlers_;
717 Handler error_handler_;
718 Logger logger_;
719 Expect100ContinueHandler expect_100_continue_handler_;
720
721 bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
722 SocketOptions socket_options_ = default_socket_options;
723};
724
725enum Error {
726 Success = 0,
727 Unknown,
728 Connection,
729 BindIPAddress,
730 Read,
731 Write,
732 ExceedRedirectCount,
733 Canceled,
734 SSLConnection,
735 SSLLoadingCerts,
736 SSLServerVerification,
737 UnsupportedMultipartBoundaryChars
738};
739
740class Result {
741public:
742 Result(std::unique_ptr<Response> res, Error err)
743 : res_(std::move(res)), err_(err) {}
744 operator bool() const { return res_ != nullptr; }
745 bool operator==(std::nullptr_t) const { return res_ == nullptr; }
746 bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
747 const Response &value() const { return *res_; }
748 Response &value() { return *res_; }
749 const Response &operator*() const { return *res_; }
750 Response &operator*() { return *res_; }
751 const Response *operator->() const { return res_.get(); }
752 Response *operator->() { return res_.get(); }
753 Error error() const { return err_; }
754
755private:
756 std::unique_ptr<Response> res_;
757 Error err_;
758};
759
760class ClientImpl {
761public:
762 explicit ClientImpl(const std::string &host);
763
764 explicit ClientImpl(const std::string &host, int port);
765
766 explicit ClientImpl(const std::string &host, int port,
767 const std::string &client_cert_path,
768 const std::string &client_key_path);
769
770 virtual ~ClientImpl();
771
772 virtual bool is_valid() const;
773
774 Result Get(const char *path);
775 Result Get(const char *path, const Headers &headers);
776 Result Get(const char *path, Progress progress);
777 Result Get(const char *path, const Headers &headers, Progress progress);
778 Result Get(const char *path, ContentReceiver content_receiver);
779 Result Get(const char *path, const Headers &headers,
780 ContentReceiver content_receiver);
781 Result Get(const char *path, ContentReceiver content_receiver,
782 Progress progress);
783 Result Get(const char *path, const Headers &headers,
784 ContentReceiver content_receiver, Progress progress);
785 Result Get(const char *path, ResponseHandler response_handler,
786 ContentReceiver content_receiver);
787 Result Get(const char *path, const Headers &headers,
788 ResponseHandler response_handler,
789 ContentReceiver content_receiver);
790 Result Get(const char *path, ResponseHandler response_handler,
791 ContentReceiver content_receiver, Progress progress);
792 Result Get(const char *path, const Headers &headers,
793 ResponseHandler response_handler, ContentReceiver content_receiver,
794 Progress progress);
795
796 Result Head(const char *path);
797 Result Head(const char *path, const Headers &headers);
798
799 Result Post(const char *path);
800 Result Post(const char *path, const std::string &body,
801 const char *content_type);
802 Result Post(const char *path, const Headers &headers, const std::string &body,
803 const char *content_type);
804 Result Post(const char *path, size_t content_length,
805 ContentProvider content_provider, const char *content_type);
806 Result Post(const char *path, const Headers &headers, size_t content_length,
807 ContentProvider content_provider, const char *content_type);
808 Result Post(const char *path, const Params &params);
809 Result Post(const char *path, const Headers &headers, const Params &params);
810 Result Post(const char *path, const MultipartFormDataItems &items);
811 Result Post(const char *path, const Headers &headers,
812 const MultipartFormDataItems &items);
813 Result Post(const char *path, const Headers &headers,
814 const MultipartFormDataItems &items, const std::string &boundary);
815
816 Result Put(const char *path);
817 Result Put(const char *path, const std::string &body,
818 const char *content_type);
819 Result Put(const char *path, const Headers &headers, const std::string &body,
820 const char *content_type);
821 Result Put(const char *path, size_t content_length,
822 ContentProvider content_provider, const char *content_type);
823 Result Put(const char *path, const Headers &headers, size_t content_length,
824 ContentProvider content_provider, const char *content_type);
825 Result Put(const char *path, const Params &params);
826 Result Put(const char *path, const Headers &headers, const Params &params);
827
828 Result Patch(const char *path, const std::string &body,
829 const char *content_type);
830 Result Patch(const char *path, const Headers &headers,
831 const std::string &body, const char *content_type);
832 Result Patch(const char *path, size_t content_length,
833 ContentProvider content_provider, const char *content_type);
834 Result Patch(const char *path, const Headers &headers, size_t content_length,
835 ContentProvider content_provider, const char *content_type);
836
837 Result Delete(const char *path);
838 Result Delete(const char *path, const std::string &body,
839 const char *content_type);
840 Result Delete(const char *path, const Headers &headers);
841 Result Delete(const char *path, const Headers &headers,
842 const std::string &body, const char *content_type);
843
844 Result Options(const char *path);
845 Result Options(const char *path, const Headers &headers);
846
847 bool send(const Request &req, Response &res);
848
849 size_t is_socket_open() const;
850
851 void stop();
852
853 void set_default_headers(Headers headers);
854
855 void set_tcp_nodelay(bool on);
856 void set_socket_options(SocketOptions socket_options);
857
858 void set_connection_timeout(time_t sec, time_t usec = 0);
859 void set_read_timeout(time_t sec, time_t usec = 0);
860 void set_write_timeout(time_t sec, time_t usec = 0);
861
862 void set_basic_auth(const char *username, const char *password);
863 void set_bearer_token_auth(const char *token);
864#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
865 void set_digest_auth(const char *username, const char *password);
866#endif
867
868 void set_keep_alive(bool on);
869 void set_follow_location(bool on);
870
871 void set_compress(bool on);
872
873 void set_decompress(bool on);
874
875 void set_interface(const char *intf);
876
877 void set_proxy(const char *host, int port);
878 void set_proxy_basic_auth(const char *username, const char *password);
879 void set_proxy_bearer_token_auth(const char *token);
880#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
881 void set_proxy_digest_auth(const char *username, const char *password);
882#endif
883
884#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
885 void enable_server_certificate_verification(bool enabled);
886#endif
887
888 void set_logger(Logger logger);
889
890protected:
891 struct Socket {
892 socket_t sock = INVALID_SOCKET;
893#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
894 SSL *ssl = nullptr;
895#endif
896
897 bool is_open() const { return sock != INVALID_SOCKET; }
898 };
899
900 virtual bool create_and_connect_socket(Socket &socket);
901
902 // All of:
903 // shutdown_ssl
904 // shutdown_socket
905 // close_socket
906 // should ONLY be called when socket_mutex_ is locked.
907 // Also, shutdown_ssl and close_socket should also NOT be called concurrently
908 // with a DIFFERENT thread sending requests using that socket.
909 virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
910 void shutdown_socket(Socket &socket);
911 void close_socket(Socket &socket);
912
913 // Similar to shutdown_ssl and close_socket, this should NOT be called
914 // concurrently with a DIFFERENT thread sending requests from the socket
915 void lock_socket_and_shutdown_and_close();
916
917 bool process_request(Stream &strm, const Request &req, Response &res,
918 bool close_connection);
919
920 Error get_last_error() const;
921
922 void copy_settings(const ClientImpl &rhs);
923
924 // Error state
925 mutable std::atomic<Error> error_;
926
927 // Socket endoint information
928 const std::string host_;
929 const int port_;
930 const std::string host_and_port_;
931
932 // Current open socket
933 Socket socket_;
934 mutable std::mutex socket_mutex_;
935 std::recursive_mutex request_mutex_;
936
937 // These are all protected under socket_mutex
938 int socket_requests_in_flight_ = 0;
939 std::thread::id socket_requests_are_from_thread_ = std::thread::id();
940 bool socket_should_be_closed_when_request_is_done_ = false;
941
942 // Default headers
943 Headers default_headers_;
944
945 // Settings
946 std::string client_cert_path_;
947 std::string client_key_path_;
948
949 time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
950 time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
951 time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
952 time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
953 time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
954 time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
955
956 std::string basic_auth_username_;
957 std::string basic_auth_password_;
958 std::string bearer_token_auth_token_;
959#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
960 std::string digest_auth_username_;
961 std::string digest_auth_password_;
962#endif
963
964 bool keep_alive_ = false;
965 bool follow_location_ = false;
966
967 bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
968 SocketOptions socket_options_ = nullptr;
969
970 bool compress_ = false;
971 bool decompress_ = true;
972
973 std::string interface_;
974
975 std::string proxy_host_;
976 int proxy_port_ = -1;
977
978 std::string proxy_basic_auth_username_;
979 std::string proxy_basic_auth_password_;
980 std::string proxy_bearer_token_auth_token_;
981#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
982 std::string proxy_digest_auth_username_;
983 std::string proxy_digest_auth_password_;
984#endif
985
986#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
987 bool server_certificate_verification_ = true;
988#endif
989
990 Logger logger_;
991
992private:
993 socket_t create_client_socket() const;
994 bool read_response_line(Stream &strm, Response &res);
995 bool write_request(Stream &strm, const Request &req, bool close_connection);
996 bool redirect(const Request &req, Response &res);
997 bool handle_request(Stream &strm, const Request &req, Response &res,
998 bool close_connection);
999 std::unique_ptr<Response> send_with_content_provider(
1000 const char *method, const char *path, const Headers &headers,
1001 const std::string &body, size_t content_length,
1002 ContentProvider content_provider, const char *content_type);
1003
1004 // socket is const because this function is called when socket_mutex_ is not locked
1005 virtual bool process_socket(const Socket &socket,
1006 std::function<bool(Stream &strm)> callback);
1007 virtual bool is_ssl() const;
1008};
1009
1010class Client {
1011public:
1012 // Universal interface
1013 explicit Client(const char *scheme_host_port);
1014
1015 explicit Client(const char *scheme_host_port,
1016 const std::string &client_cert_path,
1017 const std::string &client_key_path);
1018
1019 // HTTP only interface
1020 explicit Client(const std::string &host, int port);
1021
1022 explicit Client(const std::string &host, int port,
1023 const std::string &client_cert_path,
1024 const std::string &client_key_path);
1025
1026 ~Client();
1027
1028 bool is_valid() const;
1029
1030 Result Get(const char *path);
1031 Result Get(const char *path, const Headers &headers);
1032 Result Get(const char *path, Progress progress);
1033 Result Get(const char *path, const Headers &headers, Progress progress);
1034 Result Get(const char *path, ContentReceiver content_receiver);
1035 Result Get(const char *path, const Headers &headers,
1036 ContentReceiver content_receiver);
1037 Result Get(const char *path, ContentReceiver content_receiver,
1038 Progress progress);
1039 Result Get(const char *path, const Headers &headers,
1040 ContentReceiver content_receiver, Progress progress);
1041 Result Get(const char *path, ResponseHandler response_handler,
1042 ContentReceiver content_receiver);
1043 Result Get(const char *path, const Headers &headers,
1044 ResponseHandler response_handler,
1045 ContentReceiver content_receiver);
1046 Result Get(const char *path, const Headers &headers,
1047 ResponseHandler response_handler, ContentReceiver content_receiver,
1048 Progress progress);
1049 Result Get(const char *path, ResponseHandler response_handler,
1050 ContentReceiver content_receiver, Progress progress);
1051
1052 Result Head(const char *path);
1053 Result Head(const char *path, const Headers &headers);
1054
1055 Result Post(const char *path);
1056 Result Post(const char *path, const std::string &body,
1057 const char *content_type);
1058 Result Post(const char *path, const Headers &headers, const std::string &body,
1059 const char *content_type);
1060 Result Post(const char *path, size_t content_length,
1061 ContentProvider content_provider, const char *content_type);
1062 Result Post(const char *path, const Headers &headers, size_t content_length,
1063 ContentProvider content_provider, const char *content_type);
1064 Result Post(const char *path, const Params &params);
1065 Result Post(const char *path, const Headers &headers, const Params &params);
1066 Result Post(const char *path, const MultipartFormDataItems &items);
1067 Result Post(const char *path, const Headers &headers,
1068 const MultipartFormDataItems &items);
1069 Result Post(const char *path, const Headers &headers,
1070 const MultipartFormDataItems &items, const std::string &boundary);
1071 Result Put(const char *path);
1072 Result Put(const char *path, const std::string &body,
1073 const char *content_type);
1074 Result Put(const char *path, const Headers &headers, const std::string &body,
1075 const char *content_type);
1076 Result Put(const char *path, size_t content_length,
1077 ContentProvider content_provider, const char *content_type);
1078 Result Put(const char *path, const Headers &headers, size_t content_length,
1079 ContentProvider content_provider, const char *content_type);
1080 Result Put(const char *path, const Params &params);
1081 Result Put(const char *path, const Headers &headers, const Params &params);
1082 Result Patch(const char *path, const std::string &body,
1083 const char *content_type);
1084 Result Patch(const char *path, const Headers &headers,
1085 const std::string &body, const char *content_type);
1086 Result Patch(const char *path, size_t content_length,
1087 ContentProvider content_provider, const char *content_type);
1088 Result Patch(const char *path, const Headers &headers, size_t content_length,
1089 ContentProvider content_provider, const char *content_type);
1090
1091 Result Delete(const char *path);
1092 Result Delete(const char *path, const std::string &body,
1093 const char *content_type);
1094 Result Delete(const char *path, const Headers &headers);
1095 Result Delete(const char *path, const Headers &headers,
1096 const std::string &body, const char *content_type);
1097
1098 Result Options(const char *path);
1099 Result Options(const char *path, const Headers &headers);
1100
1101 bool send(const Request &req, Response &res);
1102
1103 size_t is_socket_open() const;
1104
1105 void stop();
1106
1107 void set_default_headers(Headers headers);
1108
1109 void set_tcp_nodelay(bool on);
1110 void set_socket_options(SocketOptions socket_options);
1111
1112 void set_connection_timeout(time_t sec, time_t usec = 0);
1113 void set_read_timeout(time_t sec, time_t usec = 0);
1114 void set_write_timeout(time_t sec, time_t usec = 0);
1115
1116 void set_basic_auth(const char *username, const char *password);
1117 void set_bearer_token_auth(const char *token);
1118#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1119 void set_digest_auth(const char *username, const char *password);
1120#endif
1121
1122 void set_keep_alive(bool on);
1123 void set_follow_location(bool on);
1124
1125 void set_compress(bool on);
1126
1127 void set_decompress(bool on);
1128
1129 void set_interface(const char *intf);
1130
1131 void set_proxy(const char *host, int port);
1132 void set_proxy_basic_auth(const char *username, const char *password);
1133 void set_proxy_bearer_token_auth(const char *token);
1134#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1135 void set_proxy_digest_auth(const char *username, const char *password);
1136#endif
1137
1138#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1139 void enable_server_certificate_verification(bool enabled);
1140#endif
1141
1142 void set_logger(Logger logger);
1143
1144 // SSL
1145#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1146 void set_ca_cert_path(const char *ca_cert_file_path,
1147 const char *ca_cert_dir_path = nullptr);
1148
1149 void set_ca_cert_store(X509_STORE *ca_cert_store);
1150
1151 long get_openssl_verify_result() const;
1152
1153 SSL_CTX *ssl_context() const;
1154#endif
1155
1156private:
1157 std::unique_ptr<ClientImpl> cli_;
1158
1159#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1160 bool is_ssl_ = false;
1161#endif
1162}; // namespace httplib
1163
1164#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1165class SSLServer : public Server {
1166public:
1167 SSLServer(const char *cert_path, const char *private_key_path,
1168 const char *client_ca_cert_file_path = nullptr,
1169 const char *client_ca_cert_dir_path = nullptr);
1170
1171 SSLServer(X509 *cert, EVP_PKEY *private_key,
1172 X509_STORE *client_ca_cert_store = nullptr);
1173
1174 ~SSLServer() override;
1175
1176 bool is_valid() const override;
1177
1178private:
1179 bool process_and_close_socket(socket_t sock) override;
1180
1181 SSL_CTX *ctx_;
1182 std::mutex ctx_mutex_;
1183};
1184
1185class SSLClient : public ClientImpl {
1186public:
1187 explicit SSLClient(const std::string &host);
1188
1189 explicit SSLClient(const std::string &host, int port);
1190
1191 explicit SSLClient(const std::string &host, int port,
1192 const std::string &client_cert_path,
1193 const std::string &client_key_path);
1194
1195 explicit SSLClient(const std::string &host, int port, X509 *client_cert,
1196 EVP_PKEY *client_key);
1197
1198 ~SSLClient() override;
1199
1200 bool is_valid() const override;
1201
1202 void set_ca_cert_path(const char *ca_cert_file_path,
1203 const char *ca_cert_dir_path = nullptr);
1204
1205 void set_ca_cert_store(X509_STORE *ca_cert_store);
1206
1207 long get_openssl_verify_result() const;
1208
1209 SSL_CTX *ssl_context() const;
1210
1211private:
1212 bool create_and_connect_socket(Socket &socket) override;
1213 void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
1214
1215 bool process_socket(const Socket &socket,
1216 std::function<bool(Stream &strm)> callback) override;
1217 bool is_ssl() const override;
1218
1219 bool connect_with_proxy(Socket &sock, Response &res, bool &success);
1220 bool initialize_ssl(Socket &socket);
1221
1222 bool load_certs();
1223
1224 bool verify_host(X509 *server_cert) const;
1225 bool verify_host_with_subject_alt_name(X509 *server_cert) const;
1226 bool verify_host_with_common_name(X509 *server_cert) const;
1227 bool check_host_name(const char *pattern, size_t pattern_len) const;
1228
1229 SSL_CTX *ctx_;
1230 std::mutex ctx_mutex_;
1231 std::once_flag initialize_cert_;
1232
1233 std::vector<std::string> host_components_;
1234
1235 std::string ca_cert_file_path_;
1236 std::string ca_cert_dir_path_;
1237 long verify_result_ = 0;
1238
1239 friend class ClientImpl;
1240};
1241#endif
1242
1243// ----------------------------------------------------------------------------
1244
1245/*
1246 * Implementation
1247 */
1248
1249namespace detail {
1250
1251inline bool is_hex(char c, int &v) {
1252 if (0x20 <= c && isdigit(c)) {
1253 v = c - '0';
1254 return true;
1255 } else if ('A' <= c && c <= 'F') {
1256 v = c - 'A' + 10;
1257 return true;
1258 } else if ('a' <= c && c <= 'f') {
1259 v = c - 'a' + 10;
1260 return true;
1261 }
1262 return false;
1263}
1264
1265inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
1266 int &val) {
1267 if (i >= s.size()) { return false; }
1268
1269 val = 0;
1270 for (; cnt; i++, cnt--) {
1271 if (!s[i]) { return false; }
1272 int v = 0;
1273 if (is_hex(s[i], v)) {
1274 val = val * 16 + v;
1275 } else {
1276 return false;
1277 }
1278 }
1279 return true;
1280}
1281
1282inline std::string from_i_to_hex(size_t n) {
1283 const char *charset = "0123456789abcdef";
1284 std::string ret;
1285 do {
1286 ret = charset[n & 15] + ret;
1287 n >>= 4;
1288 } while (n > 0);
1289 return ret;
1290}
1291
1292inline bool start_with(const std::string &a, const std::string &b) {
1293 if (a.size() < b.size()) { return false; }
1294 for (size_t i = 0; i < b.size(); i++) {
1295 if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
1296 }
1297 return true;
1298}
1299
1300inline size_t to_utf8(int code, char *buff) {
1301 if (code < 0x0080) {
1302 buff[0] = (code & 0x7F);
1303 return 1;
1304 } else if (code < 0x0800) {
1305 buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
1306 buff[1] = static_cast<char>(0x80 | (code & 0x3F));
1307 return 2;
1308 } else if (code < 0xD800) {
1309 buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
1310 buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
1311 buff[2] = static_cast<char>(0x80 | (code & 0x3F));
1312 return 3;
1313 } else if (code < 0xE000) { // D800 - DFFF is invalid...
1314 return 0;
1315 } else if (code < 0x10000) {
1316 buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
1317 buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
1318 buff[2] = static_cast<char>(0x80 | (code & 0x3F));
1319 return 3;
1320 } else if (code < 0x110000) {
1321 buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7));
1322 buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F));
1323 buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
1324 buff[3] = static_cast<char>(0x80 | (code & 0x3F));
1325 return 4;
1326 }
1327
1328 // NOTREACHED
1329 return 0;
1330}
1331
1332// NOTE: This code came up with the following stackoverflow post:
1333// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c
1334inline std::string base64_encode(const std::string &in) {
1335 static const auto lookup =
1336 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1337
1338 std::string out;
1339 out.reserve(in.size());
1340
1341 int val = 0;
1342 int valb = -6;
1343
1344 for (auto c : in) {
1345 val = (val << 8) + static_cast<uint8_t>(c);
1346 valb += 8;
1347 while (valb >= 0) {
1348 out.push_back(lookup[(val >> valb) & 0x3F]);
1349 valb -= 6;
1350 }
1351 }
1352
1353 if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); }
1354
1355 while (out.size() % 4) {
1356 out.push_back('=');
1357 }
1358
1359 return out;
1360}
1361
1362inline bool is_file(const std::string &path) {
1363 struct stat st;
1364 return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
1365}
1366
1367inline bool is_dir(const std::string &path) {
1368 struct stat st;
1369 return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
1370}
1371
1372inline bool is_valid_path(const std::string &path) {
1373 size_t level = 0;
1374 size_t i = 0;
1375
1376 // Skip slash
1377 while (i < path.size() && path[i] == '/') {
1378 i++;
1379 }
1380
1381 while (i < path.size()) {
1382 // Read component
1383 auto beg = i;
1384 while (i < path.size() && path[i] != '/') {
1385 i++;
1386 }
1387
1388 auto len = i - beg;
1389 assert(len > 0);
1390
1391 if (!path.compare(beg, len, ".")) {
1392 ;
1393 } else if (!path.compare(beg, len, "..")) {
1394 if (level == 0) { return false; }
1395 level--;
1396 } else {
1397 level++;
1398 }
1399
1400 // Skip slash
1401 while (i < path.size() && path[i] == '/') {
1402 i++;
1403 }
1404 }
1405
1406 return true;
1407}
1408
1409inline std::string encode_url(const std::string &s) {
1410 std::string result;
1411
1412 for (size_t i = 0; s[i]; i++) {
1413 switch (s[i]) {
1414 case ' ': result += "%20"; break;
1415 case '+': result += "%2B"; break;
1416 case '\r': result += "%0D"; break;
1417 case '\n': result += "%0A"; break;
1418 case '\'': result += "%27"; break;
1419 case ',': result += "%2C"; break;
1420 // case ':': result += "%3A"; break; // ok? probably...
1421 case ';': result += "%3B"; break;
1422 default:
1423 auto c = static_cast<uint8_t>(s[i]);
1424 if (c >= 0x80) {
1425 result += '%';
1426 char hex[4];
1427 auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
1428 assert(len == 2);
1429 result.append(hex, static_cast<size_t>(len));
1430 } else {
1431 result += s[i];
1432 }
1433 break;
1434 }
1435 }
1436
1437 return result;
1438}
1439
1440inline std::string decode_url(const std::string &s,
1441 bool convert_plus_to_space) {
1442 std::string result;
1443
1444 for (size_t i = 0; i < s.size(); i++) {
1445 if (s[i] == '%' && i + 1 < s.size()) {
1446 if (s[i + 1] == 'u') {
1447 int val = 0;
1448 if (from_hex_to_i(s, i + 2, 4, val)) {
1449 // 4 digits Unicode codes
1450 char buff[4];
1451 size_t len = to_utf8(val, buff);
1452 if (len > 0) { result.append(buff, len); }
1453 i += 5; // 'u0000'
1454 } else {
1455 result += s[i];
1456 }
1457 } else {
1458 int val = 0;
1459 if (from_hex_to_i(s, i + 1, 2, val)) {
1460 // 2 digits hex codes
1461 result += static_cast<char>(val);
1462 i += 2; // '00'
1463 } else {
1464 result += s[i];
1465 }
1466 }
1467 } else if (convert_plus_to_space && s[i] == '+') {
1468 result += ' ';
1469 } else {
1470 result += s[i];
1471 }
1472 }
1473
1474 return result;
1475}
1476
1477inline void read_file(const std::string &path, std::string &out) {
1478 std::ifstream fs(path, std::ios_base::binary);
1479 fs.seekg(0, std::ios_base::end);
1480 auto size = fs.tellg();
1481 fs.seekg(0);
1482 out.resize(static_cast<size_t>(size));
1483 fs.read(&out[0], static_cast<std::streamsize>(size));
1484}
1485
1486inline std::string file_extension(const std::string &path) {
1487 std::smatch m;
1488 static auto re = std::regex("\\.([a-zA-Z0-9]+)$");
1489 if (std::regex_search(path, m, re)) { return m[1].str(); }
1490 return std::string();
1491}
1492
1493inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }
1494
1495inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
1496 size_t right) {
1497 while (b + left < e && is_space_or_tab(b[left])) {
1498 left++;
1499 }
1500 while (right > 0 && is_space_or_tab(b[right - 1])) {
1501 right--;
1502 }
1503 return std::make_pair(left, right);
1504}
1505
1506inline std::string trim_copy(const std::string &s) {
1507 auto r = trim(s.data(), s.data() + s.size(), 0, s.size());
1508 return s.substr(r.first, r.second - r.first);
1509}
1510
1511template <class Fn> void split(const char *b, const char *e, char d, Fn fn) {
1512 size_t i = 0;
1513 size_t beg = 0;
1514
1515 while (e ? (b + i < e) : (b[i] != '\0')) {
1516 if (b[i] == d) {
1517 auto r = trim(b, e, beg, i);
1518 if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
1519 beg = i + 1;
1520 }
1521 i++;
1522 }
1523
1524 if (i) {
1525 auto r = trim(b, e, beg, i);
1526 if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
1527 }
1528}
1529
1530// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
1531// to store data. The call can set memory on stack for performance.
1532class stream_line_reader {
1533public:
1534 stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size)
1535 : strm_(strm), fixed_buffer_(fixed_buffer),
1536 fixed_buffer_size_(fixed_buffer_size) {}
1537
1538 const char *ptr() const {
1539 if (glowable_buffer_.empty()) {
1540 return fixed_buffer_;
1541 } else {
1542 return glowable_buffer_.data();
1543 }
1544 }
1545
1546 size_t size() const {
1547 if (glowable_buffer_.empty()) {
1548 return fixed_buffer_used_size_;
1549 } else {
1550 return glowable_buffer_.size();
1551 }
1552 }
1553
1554 bool end_with_crlf() const {
1555 auto end = ptr() + size();
1556 return size() >= 2 && end[-2] == '\r' && end[-1] == '\n';
1557 }
1558
1559 bool getline() {
1560 fixed_buffer_used_size_ = 0;
1561 glowable_buffer_.clear();
1562
1563 for (size_t i = 0;; i++) {
1564 char byte;
1565 auto n = strm_.read(&byte, 1);
1566
1567 if (n < 0) {
1568 return false;
1569 } else if (n == 0) {
1570 if (i == 0) {
1571 return false;
1572 } else {
1573 break;
1574 }
1575 }
1576
1577 append(byte);
1578
1579 if (byte == '\n') { break; }
1580 }
1581
1582 return true;
1583 }
1584
1585private:
1586 void append(char c) {
1587 if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) {
1588 fixed_buffer_[fixed_buffer_used_size_++] = c;
1589 fixed_buffer_[fixed_buffer_used_size_] = '\0';
1590 } else {
1591 if (glowable_buffer_.empty()) {
1592 assert(fixed_buffer_[fixed_buffer_used_size_] == '\0');
1593 glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
1594 }
1595 glowable_buffer_ += c;
1596 }
1597 }
1598
1599 Stream &strm_;
1600 char *fixed_buffer_;
1601 const size_t fixed_buffer_size_;
1602 size_t fixed_buffer_used_size_ = 0;
1603 std::string glowable_buffer_;
1604};
1605
1606inline int close_socket(socket_t sock) {
1607#ifdef _WIN32
1608 return closesocket(sock);
1609#else
1610 return close(sock);
1611#endif
1612}
1613
1614template <typename T> inline ssize_t handle_EINTR(T fn) {
1615 ssize_t res = false;
1616 while (true) {
1617 res = fn();
1618 if (res < 0 && errno == EINTR) { continue; }
1619 break;
1620 }
1621 return res;
1622}
1623
1624inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
1625#ifdef CPPHTTPLIB_USE_POLL
1626 struct pollfd pfd_read;
1627 pfd_read.fd = sock;
1628 pfd_read.events = POLLIN;
1629
1630 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
1631
1632 return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
1633#else
1634 fd_set fds;
1635 FD_ZERO(&fds);
1636 FD_SET(sock, &fds);
1637
1638 timeval tv;
1639 tv.tv_sec = static_cast<long>(sec);
1640 tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
1641
1642 return handle_EINTR([&]() {
1643 return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
1644 });
1645#endif
1646}
1647
1648inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
1649#ifdef CPPHTTPLIB_USE_POLL
1650 struct pollfd pfd_read;
1651 pfd_read.fd = sock;
1652 pfd_read.events = POLLOUT;
1653
1654 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
1655
1656 return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
1657#else
1658 fd_set fds;
1659 FD_ZERO(&fds);
1660 FD_SET(sock, &fds);
1661
1662 timeval tv;
1663 tv.tv_sec = static_cast<long>(sec);
1664 tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
1665
1666 return handle_EINTR([&]() {
1667 return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
1668 });
1669#endif
1670}
1671
1672inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
1673#ifdef CPPHTTPLIB_USE_POLL
1674 struct pollfd pfd_read;
1675 pfd_read.fd = sock;
1676 pfd_read.events = POLLIN | POLLOUT;
1677
1678 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
1679
1680 auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
1681
1682 if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
1683 int error = 0;
1684 socklen_t len = sizeof(error);
1685 auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
1686 reinterpret_cast<char *>(&error), &len);
1687 return res >= 0 && !error;
1688 }
1689 return false;
1690#else
1691 fd_set fdsr;
1692 FD_ZERO(&fdsr);
1693 FD_SET(sock, &fdsr);
1694
1695 auto fdsw = fdsr;
1696 auto fdse = fdsr;
1697
1698 timeval tv;
1699 tv.tv_sec = static_cast<long>(sec);
1700 tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
1701
1702 auto ret = handle_EINTR([&]() {
1703 return select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv);
1704 });
1705
1706 if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
1707 int error = 0;
1708 socklen_t len = sizeof(error);
1709 return getsockopt(sock, SOL_SOCKET, SO_ERROR,
1710 reinterpret_cast<char *>(&error), &len) >= 0 &&
1711 !error;
1712 }
1713 return false;
1714#endif
1715}
1716
1717class SocketStream : public Stream {
1718public:
1719 SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
1720 time_t write_timeout_sec, time_t write_timeout_usec);
1721 ~SocketStream() override;
1722
1723 bool is_readable() const override;
1724 bool is_writable() const override;
1725 ssize_t read(char *ptr, size_t size) override;
1726 ssize_t write(const char *ptr, size_t size) override;
1727 void get_remote_ip_and_port(std::string &ip, int &port) const override;
1728
1729private:
1730 socket_t sock_;
1731 time_t read_timeout_sec_;
1732 time_t read_timeout_usec_;
1733 time_t write_timeout_sec_;
1734 time_t write_timeout_usec_;
1735};
1736
1737#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1738class SSLSocketStream : public Stream {
1739public:
1740 SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec,
1741 time_t read_timeout_usec, time_t write_timeout_sec,
1742 time_t write_timeout_usec);
1743 ~SSLSocketStream() override;
1744
1745 bool is_readable() const override;
1746 bool is_writable() const override;
1747 ssize_t read(char *ptr, size_t size) override;
1748 ssize_t write(const char *ptr, size_t size) override;
1749 void get_remote_ip_and_port(std::string &ip, int &port) const override;
1750
1751private:
1752 socket_t sock_;
1753 SSL *ssl_;
1754 time_t read_timeout_sec_;
1755 time_t read_timeout_usec_;
1756 time_t write_timeout_sec_;
1757 time_t write_timeout_usec_;
1758};
1759#endif
1760
1761class BufferStream : public Stream {
1762public:
1763 BufferStream() = default;
1764 ~BufferStream() override = default;
1765
1766 bool is_readable() const override;
1767 bool is_writable() const override;
1768 ssize_t read(char *ptr, size_t size) override;
1769 ssize_t write(const char *ptr, size_t size) override;
1770 void get_remote_ip_and_port(std::string &ip, int &port) const override;
1771
1772 const std::string &get_buffer() const;
1773
1774private:
1775 std::string buffer;
1776 size_t position = 0;
1777};
1778
1779inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) {
1780 using namespace std::chrono;
1781 auto start = steady_clock::now();
1782 while (true) {
1783 auto val = select_read(sock, 0, 10000);
1784 if (val < 0) {
1785 return false;
1786 } else if (val == 0) {
1787 auto current = steady_clock::now();
1788 auto duration = duration_cast<milliseconds>(current - start);
1789 auto timeout = keep_alive_timeout_sec * 1000;
1790 if (duration.count() > timeout) { return false; }
1791 std::this_thread::sleep_for(std::chrono::milliseconds(1));
1792 } else {
1793 return true;
1794 }
1795 }
1796}
1797
1798template <typename T>
1799inline bool
1800process_server_socket_core(socket_t sock, size_t keep_alive_max_count,
1801 time_t keep_alive_timeout_sec, T callback) {
1802 assert(keep_alive_max_count > 0);
1803 auto ret = false;
1804 auto count = keep_alive_max_count;
1805 while (count > 0 && keep_alive(sock, keep_alive_timeout_sec)) {
1806 auto close_connection = count == 1;
1807 auto connection_closed = false;
1808 ret = callback(close_connection, connection_closed);
1809 if (!ret || connection_closed) { break; }
1810 count--;
1811 }
1812 return ret;
1813}
1814
1815template <typename T>
1816inline bool
1817process_server_socket(socket_t sock, size_t keep_alive_max_count,
1818 time_t keep_alive_timeout_sec, time_t read_timeout_sec,
1819 time_t read_timeout_usec, time_t write_timeout_sec,
1820 time_t write_timeout_usec, T callback) {
1821 return process_server_socket_core(
1822 sock, keep_alive_max_count, keep_alive_timeout_sec,
1823 [&](bool close_connection, bool &connection_closed) {
1824 SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
1825 write_timeout_sec, write_timeout_usec);
1826 return callback(strm, close_connection, connection_closed);
1827 });
1828}
1829
1830template <typename T>
1831inline bool process_client_socket(socket_t sock, time_t read_timeout_sec,
1832 time_t read_timeout_usec,
1833 time_t write_timeout_sec,
1834 time_t write_timeout_usec, T callback) {
1835 SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
1836 write_timeout_sec, write_timeout_usec);
1837 return callback(strm);
1838}
1839
1840inline int shutdown_socket(socket_t sock) {
1841#ifdef _WIN32
1842 return shutdown(sock, SD_BOTH);
1843#else
1844 return shutdown(sock, SHUT_RDWR);
1845#endif
1846}
1847
1848template <typename BindOrConnect>
1849socket_t create_socket(const char *host, int port, int socket_flags,
1850 bool tcp_nodelay, SocketOptions socket_options,
1851 BindOrConnect bind_or_connect) {
1852 // Get address info
1853 struct addrinfo hints;
1854 struct addrinfo *result;
1855
1856 memset(&hints, 0, sizeof(struct addrinfo));
1857 hints.ai_family = AF_UNSPEC;
1858 hints.ai_socktype = SOCK_STREAM;
1859 hints.ai_flags = socket_flags;
1860 hints.ai_protocol = 0;
1861
1862 auto service = std::to_string(port);
1863
1864 if (getaddrinfo(host, service.c_str(), &hints, &result)) {
1865#ifdef __linux__
1866 res_init();
1867#endif
1868 return INVALID_SOCKET;
1869 }
1870
1871 for (auto rp = result; rp; rp = rp->ai_next) {
1872 // Create a socket
1873#ifdef _WIN32
1874 auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol,
1875 nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT);
1876 /**
1877 * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1
1878 * and above the socket creation fails on older Windows Systems.
1879 *
1880 * Let's try to create a socket the old way in this case.
1881 *
1882 * Reference:
1883 * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
1884 *
1885 * WSA_FLAG_NO_HANDLE_INHERIT:
1886 * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with
1887 * SP1, and later
1888 *
1889 */
1890 if (sock == INVALID_SOCKET) {
1891 sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
1892 }
1893#else
1894 auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
1895#endif
1896 if (sock == INVALID_SOCKET) { continue; }
1897
1898#ifndef _WIN32
1899 if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; }
1900#endif
1901
1902 if (tcp_nodelay) {
1903 int yes = 1;
1904 setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&yes),
1905 sizeof(yes));
1906 }
1907
1908 if (socket_options) { socket_options(sock); }
1909
1910 if (rp->ai_family == AF_INET6) {
1911 int no = 0;
1912 setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&no),
1913 sizeof(no));
1914 }
1915
1916 // bind or connect
1917 if (bind_or_connect(sock, *rp)) {
1918 freeaddrinfo(result);
1919 return sock;
1920 }
1921
1922 close_socket(sock);
1923 }
1924
1925 freeaddrinfo(result);
1926 return INVALID_SOCKET;
1927}
1928
1929inline void set_nonblocking(socket_t sock, bool nonblocking) {
1930#ifdef _WIN32
1931 auto flags = nonblocking ? 1UL : 0UL;
1932 ioctlsocket(sock, FIONBIO, &flags);
1933#else
1934 auto flags = fcntl(sock, F_GETFL, 0);
1935 fcntl(sock, F_SETFL,
1936 nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK)));
1937#endif
1938}
1939
1940inline bool is_connection_error() {
1941#ifdef _WIN32
1942 return WSAGetLastError() != WSAEWOULDBLOCK;
1943#else
1944 return errno != EINPROGRESS;
1945#endif
1946}
1947
1948inline bool bind_ip_address(socket_t sock, const char *host) {
1949 struct addrinfo hints;
1950 struct addrinfo *result;
1951
1952 memset(&hints, 0, sizeof(struct addrinfo));
1953 hints.ai_family = AF_UNSPEC;
1954 hints.ai_socktype = SOCK_STREAM;
1955 hints.ai_protocol = 0;
1956
1957 if (getaddrinfo(host, "0", &hints, &result)) { return false; }
1958
1959 auto ret = false;
1960 for (auto rp = result; rp; rp = rp->ai_next) {
1961 const auto &ai = *rp;
1962 if (!::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
1963 ret = true;
1964 break;
1965 }
1966 }
1967
1968 freeaddrinfo(result);
1969 return ret;
1970}
1971
1972#if !defined _WIN32 && !defined ANDROID
1973#define USE_IF2IP
1974#endif
1975
1976#ifdef USE_IF2IP
1977inline std::string if2ip(const std::string &ifn) {
1978 struct ifaddrs *ifap;
1979 getifaddrs(&ifap);
1980 for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
1981 if (ifa->ifa_addr && ifn == ifa->ifa_name) {
1982 if (ifa->ifa_addr->sa_family == AF_INET) {
1983 auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
1984 char buf[INET_ADDRSTRLEN];
1985 if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
1986 freeifaddrs(ifap);
1987 return std::string(buf, INET_ADDRSTRLEN);
1988 }
1989 }
1990 }
1991 }
1992 freeifaddrs(ifap);
1993 return std::string();
1994}
1995#endif
1996
1997inline socket_t create_client_socket(const char *host, int port,
1998 bool tcp_nodelay,
1999 SocketOptions socket_options,
2000 time_t timeout_sec, time_t timeout_usec,
2001 const std::string &intf, std::atomic<Error> &error) {
2002 auto sock = create_socket(
2003 host, port, 0, tcp_nodelay, std::move(socket_options),
2004 [&](socket_t sock, struct addrinfo &ai) -> bool {
2005 if (!intf.empty()) {
2006#ifdef USE_IF2IP
2007 auto ip = if2ip(intf);
2008 if (ip.empty()) { ip = intf; }
2009 if (!bind_ip_address(sock, ip.c_str())) {
2010 error = Error::BindIPAddress;
2011 return false;
2012 }
2013#endif
2014 }
2015
2016 set_nonblocking(sock, true);
2017
2018 auto ret =
2019 ::connect(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
2020
2021 if (ret < 0) {
2022 if (is_connection_error() ||
2023 !wait_until_socket_is_ready(sock, timeout_sec, timeout_usec)) {
2024 close_socket(sock);
2025 error = Error::Connection;
2026 return false;
2027 }
2028 }
2029
2030 set_nonblocking(sock, false);
2031 error = Error::Success;
2032 return true;
2033 });
2034
2035 if (sock != INVALID_SOCKET) {
2036 error = Error::Success;
2037 } else {
2038 if (error == Error::Success) { error = Error::Connection; }
2039 }
2040
2041 return sock;
2042}
2043
2044inline void get_remote_ip_and_port(const struct sockaddr_storage &addr,
2045 socklen_t addr_len, std::string &ip,
2046 int &port) {
2047 if (addr.ss_family == AF_INET) {
2048 port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
2049 } else if (addr.ss_family == AF_INET6) {
2050 port =
2051 ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
2052 }
2053
2054 std::array<char, NI_MAXHOST> ipstr{};
2055 if (!getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
2056 ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
2057 0, NI_NUMERICHOST)) {
2058 ip = ipstr.data();
2059 }
2060}
2061
2062inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
2063 struct sockaddr_storage addr;
2064 socklen_t addr_len = sizeof(addr);
2065
2066 if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
2067 &addr_len)) {
2068 get_remote_ip_and_port(addr, addr_len, ip, port);
2069 }
2070}
2071
2072inline const char *
2073find_content_type(const std::string &path,
2074 const std::map<std::string, std::string> &user_data) {
2075 auto ext = file_extension(path);
2076
2077 auto it = user_data.find(ext);
2078 if (it != user_data.end()) { return it->second.c_str(); }
2079
2080 if (ext == "txt") {
2081 return "text/plain";
2082 } else if (ext == "html" || ext == "htm") {
2083 return "text/html";
2084 } else if (ext == "css") {
2085 return "text/css";
2086 } else if (ext == "jpeg" || ext == "jpg") {
2087 return "image/jpg";
2088 } else if (ext == "png") {
2089 return "image/png";
2090 } else if (ext == "gif") {
2091 return "image/gif";
2092 } else if (ext == "svg") {
2093 return "image/svg+xml";
2094 } else if (ext == "ico") {
2095 return "image/x-icon";
2096 } else if (ext == "json") {
2097 return "application/json";
2098 } else if (ext == "pdf") {
2099 return "application/pdf";
2100 } else if (ext == "js") {
2101 return "application/javascript";
2102 } else if (ext == "wasm") {
2103 return "application/wasm";
2104 } else if (ext == "xml") {
2105 return "application/xml";
2106 } else if (ext == "xhtml") {
2107 return "application/xhtml+xml";
2108 }
2109 return nullptr;
2110}
2111
2112inline const char *status_message(int status) {
2113 switch (status) {
2114 case 100: return "Continue";
2115 case 101: return "Switching Protocol";
2116 case 102: return "Processing";
2117 case 103: return "Early Hints";
2118 case 200: return "OK";
2119 case 201: return "Created";
2120 case 202: return "Accepted";
2121 case 203: return "Non-Authoritative Information";
2122 case 204: return "No Content";
2123 case 205: return "Reset Content";
2124 case 206: return "Partial Content";
2125 case 207: return "Multi-Status";
2126 case 208: return "Already Reported";
2127 case 226: return "IM Used";
2128 case 300: return "Multiple Choice";
2129 case 301: return "Moved Permanently";
2130 case 302: return "Found";
2131 case 303: return "See Other";
2132 case 304: return "Not Modified";
2133 case 305: return "Use Proxy";
2134 case 306: return "unused";
2135 case 307: return "Temporary Redirect";
2136 case 308: return "Permanent Redirect";
2137 case 400: return "Bad Request";
2138 case 401: return "Unauthorized";
2139 case 402: return "Payment Required";
2140 case 403: return "Forbidden";
2141 case 404: return "Not Found";
2142 case 405: return "Method Not Allowed";
2143 case 406: return "Not Acceptable";
2144 case 407: return "Proxy Authentication Required";
2145 case 408: return "Request Timeout";
2146 case 409: return "Conflict";
2147 case 410: return "Gone";
2148 case 411: return "Length Required";
2149 case 412: return "Precondition Failed";
2150 case 413: return "Payload Too Large";
2151 case 414: return "URI Too Long";
2152 case 415: return "Unsupported Media Type";
2153 case 416: return "Range Not Satisfiable";
2154 case 417: return "Expectation Failed";
2155 case 418: return "I'm a teapot";
2156 case 421: return "Misdirected Request";
2157 case 422: return "Unprocessable Entity";
2158 case 423: return "Locked";
2159 case 424: return "Failed Dependency";
2160 case 425: return "Too Early";
2161 case 426: return "Upgrade Required";
2162 case 428: return "Precondition Required";
2163 case 429: return "Too Many Requests";
2164 case 431: return "Request Header Fields Too Large";
2165 case 451: return "Unavailable For Legal Reasons";
2166 case 501: return "Not Implemented";
2167 case 502: return "Bad Gateway";
2168 case 503: return "Service Unavailable";
2169 case 504: return "Gateway Timeout";
2170 case 505: return "HTTP Version Not Supported";
2171 case 506: return "Variant Also Negotiates";
2172 case 507: return "Insufficient Storage";
2173 case 508: return "Loop Detected";
2174 case 510: return "Not Extended";
2175 case 511: return "Network Authentication Required";
2176
2177 default:
2178 case 500: return "Internal Server Error";
2179 }
2180}
2181
2182inline bool can_compress_content_type(const std::string &content_type) {
2183 return (!content_type.find("text/") && content_type != "text/event-stream") ||
2184 content_type == "image/svg+xml" ||
2185 content_type == "application/javascript" ||
2186 content_type == "application/json" ||
2187 content_type == "application/xml" ||
2188 content_type == "application/xhtml+xml";
2189}
2190
2191enum class EncodingType { None = 0, Gzip, Brotli };
2192
2193inline EncodingType encoding_type(const Request &req, const Response &res) {
2194 auto ret =
2195 detail::can_compress_content_type(res.get_header_value("Content-Type"));
2196 if (!ret) { return EncodingType::None; }
2197
2198 const auto &s = req.get_header_value("Accept-Encoding");
2199 (void)(s);
2200
2201#ifdef CPPHTTPLIB_BROTLI_SUPPORT
2202 // TODO: 'Accept-Encoding' has br, not br;q=0
2203 ret = s.find("br") != std::string::npos;
2204 if (ret) { return EncodingType::Brotli; }
2205#endif
2206
2207#ifdef CPPHTTPLIB_ZLIB_SUPPORT
2208 // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
2209 ret = s.find("gzip") != std::string::npos;
2210 if (ret) { return EncodingType::Gzip; }
2211#endif
2212
2213 return EncodingType::None;
2214}
2215
2216class compressor {
2217public:
2218 virtual ~compressor(){};
2219
2220 typedef std::function<bool(const char *data, size_t data_len)> Callback;
2221 virtual bool compress(const char *data, size_t data_length, bool last,
2222 Callback callback) = 0;
2223};
2224
2225class decompressor {
2226public:
2227 virtual ~decompressor() {}
2228
2229 virtual bool is_valid() const = 0;
2230
2231 typedef std::function<bool(const char *data, size_t data_len)> Callback;
2232 virtual bool decompress(const char *data, size_t data_length,
2233 Callback callback) = 0;
2234};
2235
2236class nocompressor : public compressor {
2237public:
2238 ~nocompressor(){};
2239
2240 bool compress(const char *data, size_t data_length, bool /*last*/,
2241 Callback callback) override {
2242 if (!data_length) { return true; }
2243 return callback(data, data_length);
2244 }
2245};
2246
2247#ifdef CPPHTTPLIB_ZLIB_SUPPORT
2248class gzip_compressor : public compressor {
2249public:
2250 gzip_compressor() {
2251 std::memset(&strm_, 0, sizeof(strm_));
2252 strm_.zalloc = Z_NULL;
2253 strm_.zfree = Z_NULL;
2254 strm_.opaque = Z_NULL;
2255
2256 is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
2257 Z_DEFAULT_STRATEGY) == Z_OK;
2258 }
2259
2260 ~gzip_compressor() { deflateEnd(&strm_); }
2261
2262 bool compress(const char *data, size_t data_length, bool last,
2263 Callback callback) override {
2264 assert(is_valid_);
2265
2266 auto flush = last ? Z_FINISH : Z_NO_FLUSH;
2267
2268 strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length);
2269 strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
2270
2271 int ret = Z_OK;
2272
2273 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
2274 do {
2275 strm_.avail_out = buff.size();
2276 strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
2277
2278 ret = deflate(&strm_, flush);
2279 assert(ret != Z_STREAM_ERROR);
2280
2281 if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
2282 return false;
2283 }
2284 } while (strm_.avail_out == 0);
2285
2286 assert((last && ret == Z_STREAM_END) || (!last && ret == Z_OK));
2287 assert(strm_.avail_in == 0);
2288 return true;
2289 }
2290
2291private:
2292 bool is_valid_ = false;
2293 z_stream strm_;
2294};
2295
2296class gzip_decompressor : public decompressor {
2297public:
2298 gzip_decompressor() {
2299 std::memset(&strm_, 0, sizeof(strm_));
2300 strm_.zalloc = Z_NULL;
2301 strm_.zfree = Z_NULL;
2302 strm_.opaque = Z_NULL;
2303
2304 // 15 is the value of wbits, which should be at the maximum possible value
2305 // to ensure that any gzip stream can be decoded. The offset of 32 specifies
2306 // that the stream type should be automatically detected either gzip or
2307 // deflate.
2308 is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
2309 }
2310
2311 ~gzip_decompressor() { inflateEnd(&strm_); }
2312
2313 bool is_valid() const override { return is_valid_; }
2314
2315 bool decompress(const char *data, size_t data_length,
2316 Callback callback) override {
2317 assert(is_valid_);
2318
2319 int ret = Z_OK;
2320
2321 strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length);
2322 strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
2323
2324 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
2325 while (strm_.avail_in > 0) {
2326 strm_.avail_out = buff.size();
2327 strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
2328
2329 ret = inflate(&strm_, Z_NO_FLUSH);
2330 assert(ret != Z_STREAM_ERROR);
2331 switch (ret) {
2332 case Z_NEED_DICT:
2333 case Z_DATA_ERROR:
2334 case Z_MEM_ERROR: inflateEnd(&strm_); return false;
2335 }
2336
2337 if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
2338 return false;
2339 }
2340 }
2341
2342 return ret == Z_OK || ret == Z_STREAM_END;
2343 }
2344
2345private:
2346 bool is_valid_ = false;
2347 z_stream strm_;
2348};
2349#endif
2350
2351#ifdef CPPHTTPLIB_BROTLI_SUPPORT
2352class brotli_compressor : public compressor {
2353public:
2354 brotli_compressor() {
2355 state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
2356 }
2357
2358 ~brotli_compressor() { BrotliEncoderDestroyInstance(state_); }
2359
2360 bool compress(const char *data, size_t data_length, bool last,
2361 Callback callback) override {
2362 std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
2363
2364 auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
2365 auto available_in = data_length;
2366 auto next_in = reinterpret_cast<const uint8_t *>(data);
2367
2368 for (;;) {
2369 if (last) {
2370 if (BrotliEncoderIsFinished(state_)) { break; }
2371 } else {
2372 if (!available_in) { break; }
2373 }
2374
2375 auto available_out = buff.size();
2376 auto next_out = buff.data();
2377
2378 if (!BrotliEncoderCompressStream(state_, operation, &available_in,
2379 &next_in, &available_out, &next_out,
2380 nullptr)) {
2381 return false;
2382 }
2383
2384 auto output_bytes = buff.size() - available_out;
2385 if (output_bytes) {
2386 callback(reinterpret_cast<const char *>(buff.data()), output_bytes);
2387 }
2388 }
2389
2390 return true;
2391 }
2392
2393private:
2394 BrotliEncoderState *state_ = nullptr;
2395};
2396
2397class brotli_decompressor : public decompressor {
2398public:
2399 brotli_decompressor() {
2400 decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
2401 decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
2402 : BROTLI_DECODER_RESULT_ERROR;
2403 }
2404
2405 ~brotli_decompressor() {
2406 if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
2407 }
2408
2409 bool is_valid() const override { return decoder_s; }
2410
2411 bool decompress(const char *data, size_t data_length,
2412 Callback callback) override {
2413 if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
2414 decoder_r == BROTLI_DECODER_RESULT_ERROR) {
2415 return 0;
2416 }
2417
2418 const uint8_t *next_in = (const uint8_t *)data;
2419 size_t avail_in = data_length;
2420 size_t total_out;
2421
2422 decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
2423
2424 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
2425 while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
2426 char *next_out = buff.data();
2427 size_t avail_out = buff.size();
2428
2429 decoder_r = BrotliDecoderDecompressStream(
2430 decoder_s, &avail_in, &next_in, &avail_out,
2431 reinterpret_cast<uint8_t **>(&next_out), &total_out);
2432
2433 if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
2434
2435 if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
2436 }
2437
2438 return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
2439 decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
2440 }
2441
2442private:
2443 BrotliDecoderResult decoder_r;
2444 BrotliDecoderState *decoder_s = nullptr;
2445};
2446#endif
2447
2448inline bool has_header(const Headers &headers, const char *key) {
2449 return headers.find(key) != headers.end();
2450}
2451
2452inline const char *get_header_value(const Headers &headers, const char *key,
2453 size_t id = 0, const char *def = nullptr) {
2454 auto rng = headers.equal_range(key);
2455 auto it = rng.first;
2456 std::advance(it, static_cast<ssize_t>(id));
2457 if (it != rng.second) { return it->second.c_str(); }
2458 return def;
2459}
2460
2461template <typename T>
2462inline T get_header_value(const Headers & /*headers*/, const char * /*key*/,
2463 size_t /*id*/ = 0, uint64_t /*def*/ = 0) {}
2464
2465template <>
2466inline uint64_t get_header_value<uint64_t>(const Headers &headers,
2467 const char *key, size_t id,
2468 uint64_t def) {
2469 auto rng = headers.equal_range(key);
2470 auto it = rng.first;
2471 std::advance(it, static_cast<ssize_t>(id));
2472 if (it != rng.second) {
2473 return std::strtoull(it->second.data(), nullptr, 10);
2474 }
2475 return def;
2476}
2477
2478template <typename T>
2479inline bool parse_header(const char *beg, const char *end, T fn) {
2480 // Skip trailing spaces and tabs.
2481 while (beg < end && is_space_or_tab(end[-1])) {
2482 end--;
2483 }
2484
2485 auto p = beg;
2486 while (p < end && *p != ':') {
2487 p++;
2488 }
2489
2490 if (p == end) { return false; }
2491
2492 auto key_end = p;
2493
2494 if (*p++ != ':') { return false; }
2495
2496 while (p < end && is_space_or_tab(*p)) {
2497 p++;
2498 }
2499
2500 if (p < end) {
2501 fn(std::string(beg, key_end), decode_url(std::string(p, end), false));
2502 return true;
2503 }
2504
2505 return false;
2506}
2507
2508inline bool read_headers(Stream &strm, Headers &headers) {
2509 const auto bufsiz = 2048;
2510 char buf[bufsiz];
2511 stream_line_reader line_reader(strm, buf, bufsiz);
2512
2513 for (;;) {
2514 if (!line_reader.getline()) { return false; }
2515
2516 // Check if the line ends with CRLF.
2517 if (line_reader.end_with_crlf()) {
2518 // Blank line indicates end of headers.
2519 if (line_reader.size() == 2) { break; }
2520 } else {
2521 continue; // Skip invalid line.
2522 }
2523
2524 // Exclude CRLF
2525 auto end = line_reader.ptr() + line_reader.size() - 2;
2526
2527 parse_header(line_reader.ptr(), end,
2528 [&](std::string &&key, std::string &&val) {
2529 headers.emplace(std::move(key), std::move(val));
2530 });
2531 }
2532
2533 return true;
2534}
2535
2536inline bool read_content_with_length(Stream &strm, uint64_t len,
2537 Progress progress,
2538 ContentReceiverWithProgress out) {
2539 char buf[CPPHTTPLIB_RECV_BUFSIZ];
2540
2541 uint64_t r = 0;
2542 while (r < len) {
2543 auto read_len = static_cast<size_t>(len - r);
2544 auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
2545 if (n <= 0) { return false; }
2546
2547 if (!out(buf, static_cast<size_t>(n), r, len)) { return false; }
2548 r += static_cast<uint64_t>(n);
2549
2550 if (progress) {
2551 if (!progress(r, len)) { return false; }
2552 }
2553 }
2554
2555 return true;
2556}
2557
2558inline void skip_content_with_length(Stream &strm, uint64_t len) {
2559 char buf[CPPHTTPLIB_RECV_BUFSIZ];
2560 uint64_t r = 0;
2561 while (r < len) {
2562 auto read_len = static_cast<size_t>(len - r);
2563 auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
2564 if (n <= 0) { return; }
2565 r += static_cast<uint64_t>(n);
2566 }
2567}
2568
2569inline bool read_content_without_length(Stream &strm,
2570 ContentReceiverWithProgress out) {
2571 char buf[CPPHTTPLIB_RECV_BUFSIZ];
2572 uint64_t r = 0;
2573 for (;;) {
2574 auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
2575 if (n < 0) {
2576 return false;
2577 } else if (n == 0) {
2578 return true;
2579 }
2580
2581 if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; }
2582 r += static_cast<uint64_t>(n);
2583 }
2584
2585 return true;
2586}
2587
2588inline bool read_content_chunked(Stream &strm,
2589 ContentReceiverWithProgress out) {
2590 const auto bufsiz = 16;
2591 char buf[bufsiz];
2592
2593 stream_line_reader line_reader(strm, buf, bufsiz);
2594
2595 if (!line_reader.getline()) { return false; }
2596
2597 unsigned long chunk_len;
2598 while (true) {
2599 char *end_ptr;
2600
2601 chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16);
2602
2603 if (end_ptr == line_reader.ptr()) { return false; }
2604 if (chunk_len == ULONG_MAX) { return false; }
2605
2606 if (chunk_len == 0) { break; }
2607
2608 if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
2609 return false;
2610 }
2611
2612 if (!line_reader.getline()) { return false; }
2613
2614 if (strcmp(line_reader.ptr(), "\r\n")) { break; }
2615
2616 if (!line_reader.getline()) { return false; }
2617 }
2618
2619 if (chunk_len == 0) {
2620 // Reader terminator after chunks
2621 if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n"))
2622 return false;
2623 }
2624
2625 return true;
2626}
2627
2628inline bool is_chunked_transfer_encoding(const Headers &headers) {
2629 return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""),
2630 "chunked");
2631}
2632
2633template <typename T, typename U>
2634bool prepare_content_receiver(T &x, int &status,
2635 ContentReceiverWithProgress receiver,
2636 bool decompress, U callback) {
2637 if (decompress) {
2638 std::string encoding = x.get_header_value("Content-Encoding");
2639 std::unique_ptr<decompressor> decompressor;
2640
2641 if (encoding.find("gzip") != std::string::npos ||
2642 encoding.find("deflate") != std::string::npos) {
2643#ifdef CPPHTTPLIB_ZLIB_SUPPORT
2644 decompressor = detail::make_unique<gzip_decompressor>();
2645#else
2646 status = 415;
2647 return false;
2648#endif
2649 } else if (encoding.find("br") != std::string::npos) {
2650#ifdef CPPHTTPLIB_BROTLI_SUPPORT
2651 decompressor = detail::make_unique<brotli_decompressor>();
2652#else
2653 status = 415;
2654 return false;
2655#endif
2656 }
2657
2658 if (decompressor) {
2659 if (decompressor->is_valid()) {
2660 ContentReceiverWithProgress out = [&](const char *buf, size_t n,
2661 uint64_t off, uint64_t len) {
2662 return decompressor->decompress(buf, n,
2663 [&](const char *buf, size_t n) {
2664 return receiver(buf, n, off, len);
2665 });
2666 };
2667 return callback(std::move(out));
2668 } else {
2669 status = 500;
2670 return false;
2671 }
2672 }
2673 }
2674
2675 ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off,
2676 uint64_t len) {
2677 return receiver(buf, n, off, len);
2678 };
2679 return callback(std::move(out));
2680}
2681
2682template <typename T>
2683bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
2684 Progress progress, ContentReceiverWithProgress receiver,
2685 bool decompress) {
2686 return prepare_content_receiver(
2687 x, status, std::move(receiver), decompress,
2688 [&](const ContentReceiverWithProgress &out) {
2689 auto ret = true;
2690 auto exceed_payload_max_length = false;
2691
2692 if (is_chunked_transfer_encoding(x.headers)) {
2693 ret = read_content_chunked(strm, out);
2694 } else if (!has_header(x.headers, "Content-Length")) {
2695 ret = read_content_without_length(strm, out);
2696 } else {
2697 auto len = get_header_value<uint64_t>(x.headers, "Content-Length");
2698 if (len > payload_max_length) {
2699 exceed_payload_max_length = true;
2700 skip_content_with_length(strm, len);
2701 ret = false;
2702 } else if (len > 0) {
2703 ret = read_content_with_length(strm, len, std::move(progress), out);
2704 }
2705 }
2706
2707 if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
2708 return ret;
2709 });
2710}
2711
2712template <typename T>
2713inline ssize_t write_headers(Stream &strm, const T &info,
2714 const Headers &headers) {
2715 ssize_t write_len = 0;
2716 for (const auto &x : info.headers) {
2717 if (x.first == "EXCEPTION_WHAT") { continue; }
2718 auto len =
2719 strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str());
2720 if (len < 0) { return len; }
2721 write_len += len;
2722 }
2723 for (const auto &x : headers) {
2724 auto len =
2725 strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str());
2726 if (len < 0) { return len; }
2727 write_len += len;
2728 }
2729 auto len = strm.write("\r\n");
2730 if (len < 0) { return len; }
2731 write_len += len;
2732 return write_len;
2733}
2734
2735inline bool write_data(Stream &strm, const char *d, size_t l) {
2736 size_t offset = 0;
2737 while (offset < l) {
2738 auto length = strm.write(d + offset, l - offset);
2739 if (length < 0) { return false; }
2740 offset += static_cast<size_t>(length);
2741 }
2742 return true;
2743}
2744
2745template <typename T>
2746inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
2747 size_t offset, size_t length, T is_shutting_down) {
2748 size_t begin_offset = offset;
2749 size_t end_offset = offset + length;
2750 auto ok = true;
2751 DataSink data_sink;
2752
2753 data_sink.write = [&](const char *d, size_t l) {
2754 if (ok) {
2755 offset += l;
2756 if (!write_data(strm, d, l)) { ok = false; }
2757 }
2758 };
2759
2760 data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
2761
2762 while (offset < end_offset && !is_shutting_down()) {
2763 if (!content_provider(offset, end_offset - offset, data_sink)) {
2764 return -1;
2765 }
2766 if (!ok) { return -1; }
2767 }
2768
2769 return static_cast<ssize_t>(offset - begin_offset);
2770}
2771
2772template <typename T>
2773inline ssize_t write_content_without_length(Stream &strm,
2774 ContentProvider content_provider,
2775 T is_shutting_down) {
2776 size_t offset = 0;
2777 auto data_available = true;
2778 auto ok = true;
2779 DataSink data_sink;
2780
2781 data_sink.write = [&](const char *d, size_t l) {
2782 if (ok) {
2783 offset += l;
2784 if (!write_data(strm, d, l)) { ok = false; }
2785 }
2786 };
2787
2788 data_sink.done = [&](void) { data_available = false; };
2789
2790 data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
2791
2792 while (data_available && !is_shutting_down()) {
2793 if (!content_provider(offset, 0, data_sink)) { return -1; }
2794 if (!ok) { return -1; }
2795 }
2796
2797 return static_cast<ssize_t>(offset);
2798}
2799
2800template <typename T, typename U>
2801inline ssize_t write_content_chunked(Stream &strm,
2802 ContentProvider content_provider,
2803 T is_shutting_down, U &compressor) {
2804 size_t offset = 0;
2805 auto data_available = true;
2806 ssize_t total_written_length = 0;
2807 auto ok = true;
2808 DataSink data_sink;
2809
2810 data_sink.write = [&](const char *d, size_t l) {
2811 if (!ok) { return; }
2812
2813 data_available = l > 0;
2814 offset += l;
2815
2816 std::string payload;
2817 if (!compressor.compress(d, l, false,
2818 [&](const char *data, size_t data_len) {
2819 payload.append(data, data_len);
2820 return true;
2821 })) {
2822 ok = false;
2823 return;
2824 }
2825
2826 if (!payload.empty()) {
2827 // Emit chunked response header and footer for each chunk
2828 auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
2829 if (write_data(strm, chunk.data(), chunk.size())) {
2830 total_written_length += chunk.size();
2831 } else {
2832 ok = false;
2833 return;
2834 }
2835 }
2836 };
2837
2838 data_sink.done = [&](void) {
2839 if (!ok) { return; }
2840
2841 data_available = false;
2842
2843 std::string payload;
2844 if (!compressor.compress(nullptr, 0, true,
2845 [&](const char *data, size_t data_len) {
2846 payload.append(data, data_len);
2847 return true;
2848 })) {
2849 ok = false;
2850 return;
2851 }
2852
2853 if (!payload.empty()) {
2854 // Emit chunked response header and footer for each chunk
2855 auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
2856 if (write_data(strm, chunk.data(), chunk.size())) {
2857 total_written_length += chunk.size();
2858 } else {
2859 ok = false;
2860 return;
2861 }
2862 }
2863
2864 static const std::string done_marker("0\r\n\r\n");
2865 if (write_data(strm, done_marker.data(), done_marker.size())) {
2866 total_written_length += done_marker.size();
2867 } else {
2868 ok = false;
2869 }
2870 };
2871
2872 data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
2873
2874 while (data_available && !is_shutting_down()) {
2875 if (!content_provider(offset, 0, data_sink)) { return -1; }
2876 if (!ok) { return -1; }
2877 }
2878
2879 return total_written_length;
2880}
2881
2882template <typename T>
2883inline bool redirect(T &cli, const Request &req, Response &res,
2884 const std::string &path) {
2885 Request new_req = req;
2886 new_req.path = path;
2887 new_req.redirect_count -= 1;
2888
2889 if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) {
2890 new_req.method = "GET";
2891 new_req.body.clear();
2892 new_req.headers.clear();
2893 }
2894
2895 Response new_res;
2896
2897 auto ret = cli.send(new_req, new_res);
2898 if (ret) { res = new_res; }
2899 return ret;
2900}
2901
2902inline std::string params_to_query_str(const Params &params) {
2903 std::string query;
2904
2905 for (auto it = params.begin(); it != params.end(); ++it) {
2906 if (it != params.begin()) { query += "&"; }
2907 query += it->first;
2908 query += "=";
2909 query += encode_url(it->second);
2910 }
2911 return query;
2912}
2913
2914inline void parse_query_text(const std::string &s, Params &params) {
2915 split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) {
2916 std::string key;
2917 std::string val;
2918 split(b, e, '=', [&](const char *b2, const char *e2) {
2919 if (key.empty()) {
2920 key.assign(b2, e2);
2921 } else {
2922 val.assign(b2, e2);
2923 }
2924 });
2925
2926 if (!key.empty()) {
2927 params.emplace(decode_url(key, true), decode_url(val, true));
2928 }
2929 });
2930}
2931
2932inline bool parse_multipart_boundary(const std::string &content_type,
2933 std::string &boundary) {
2934 auto pos = content_type.find("boundary=");
2935 if (pos == std::string::npos) { return false; }
2936 boundary = content_type.substr(pos + 9);
2937 if (boundary.length() >= 2 && boundary.front() == '"' &&
2938 boundary.back() == '"') {
2939 boundary = boundary.substr(1, boundary.size() - 2);
2940 }
2941 return !boundary.empty();
2942}
2943
2944inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
2945 static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
2946 std::smatch m;
2947 if (std::regex_match(s, m, re_first_range)) {
2948 auto pos = static_cast<size_t>(m.position(1));
2949 auto len = static_cast<size_t>(m.length(1));
2950 bool all_valid_ranges = true;
2951 split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
2952 if (!all_valid_ranges) return;
2953 static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
2954 std::cmatch cm;
2955 if (std::regex_match(b, e, cm, re_another_range)) {
2956 ssize_t first = -1;
2957 if (!cm.str(1).empty()) {
2958 first = static_cast<ssize_t>(std::stoll(cm.str(1)));
2959 }
2960
2961 ssize_t last = -1;
2962 if (!cm.str(2).empty()) {
2963 last = static_cast<ssize_t>(std::stoll(cm.str(2)));
2964 }
2965
2966 if (first != -1 && last != -1 && first > last) {
2967 all_valid_ranges = false;
2968 return;
2969 }
2970 ranges.emplace_back(std::make_pair(first, last));
2971 }
2972 });
2973 return all_valid_ranges;
2974 }
2975 return false;
2976} catch (...) { return false; }
2977
2978class MultipartFormDataParser {
2979public:
2980 MultipartFormDataParser() = default;
2981
2982 void set_boundary(std::string &&boundary) { boundary_ = boundary; }
2983
2984 bool is_valid() const { return is_valid_; }
2985
2986 template <typename T, typename U>
2987 bool parse(const char *buf, size_t n, const T &content_callback,
2988 const U &header_callback) {
2989
2990 static const std::regex re_content_disposition(
2991 "^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename="
2992 "\"(.*?)\")?\\s*$",
2993 std::regex_constants::icase);
2994 static const std::string dash_ = "--";
2995 static const std::string crlf_ = "\r\n";
2996
2997 buf_.append(buf, n); // TODO: performance improvement
2998
2999 while (!buf_.empty()) {
3000 switch (state_) {
3001 case 0: { // Initial boundary
3002 auto pattern = dash_ + boundary_ + crlf_;
3003 if (pattern.size() > buf_.size()) { return true; }
3004 auto pos = buf_.find(pattern);
3005 if (pos != 0) { return false; }
3006 buf_.erase(0, pattern.size());
3007 off_ += pattern.size();
3008 state_ = 1;
3009 break;
3010 }
3011 case 1: { // New entry
3012 clear_file_info();
3013 state_ = 2;
3014 break;
3015 }
3016 case 2: { // Headers
3017 auto pos = buf_.find(crlf_);
3018 while (pos != std::string::npos) {
3019 // Empty line
3020 if (pos == 0) {
3021 if (!header_callback(file_)) {
3022 is_valid_ = false;
3023 return false;
3024 }
3025 buf_.erase(0, crlf_.size());
3026 off_ += crlf_.size();
3027 state_ = 3;
3028 break;
3029 }
3030
3031 static const std::string header_name = "content-type:";
3032 const auto header = buf_.substr(0, pos);
3033 if (start_with(header, header_name)) {
3034 file_.content_type = trim_copy(header.substr(header_name.size()));
3035 } else {
3036 std::smatch m;
3037 if (std::regex_match(header, m, re_content_disposition)) {
3038 file_.name = m[1];
3039 file_.filename = m[2];
3040 }
3041 }
3042
3043 buf_.erase(0, pos + crlf_.size());
3044 off_ += pos + crlf_.size();
3045 pos = buf_.find(crlf_);
3046 }
3047 if (state_ != 3) { return true; }
3048 break;
3049 }
3050 case 3: { // Body
3051 {
3052 auto pattern = crlf_ + dash_;
3053 if (pattern.size() > buf_.size()) { return true; }
3054
3055 auto pos = buf_.find(pattern);
3056 if (pos == std::string::npos) {
3057 pos = buf_.size();
3058 while (pos > 0) {
3059 auto c = buf_[pos - 1];
3060 if (c != '\r' && c != '\n' && c != '-') { break; }
3061 pos--;
3062 }
3063 }
3064
3065 if (!content_callback(buf_.data(), pos)) {
3066 is_valid_ = false;
3067 return false;
3068 }
3069
3070 off_ += pos;
3071 buf_.erase(0, pos);
3072 }
3073
3074 {
3075 auto pattern = crlf_ + dash_ + boundary_;
3076 if (pattern.size() > buf_.size()) { return true; }
3077
3078 auto pos = buf_.find(pattern);
3079 if (pos != std::string::npos) {
3080 if (!content_callback(buf_.data(), pos)) {
3081 is_valid_ = false;
3082 return false;
3083 }
3084
3085 off_ += pos + pattern.size();
3086 buf_.erase(0, pos + pattern.size());
3087 state_ = 4;
3088 } else {
3089 if (!content_callback(buf_.data(), pattern.size())) {
3090 is_valid_ = false;
3091 return false;
3092 }
3093
3094 off_ += pattern.size();
3095 buf_.erase(0, pattern.size());
3096 }
3097 }
3098 break;
3099 }
3100 case 4: { // Boundary
3101 if (crlf_.size() > buf_.size()) { return true; }
3102 if (buf_.compare(0, crlf_.size(), crlf_) == 0) {
3103 buf_.erase(0, crlf_.size());
3104 off_ += crlf_.size();
3105 state_ = 1;
3106 } else {
3107 auto pattern = dash_ + crlf_;
3108 if (pattern.size() > buf_.size()) { return true; }
3109 if (buf_.compare(0, pattern.size(), pattern) == 0) {
3110 buf_.erase(0, pattern.size());
3111 off_ += pattern.size();
3112 is_valid_ = true;
3113 state_ = 5;
3114 } else {
3115 return true;
3116 }
3117 }
3118 break;
3119 }
3120 case 5: { // Done
3121 is_valid_ = false;
3122 return false;
3123 }
3124 }
3125 }
3126
3127 return true;
3128 }
3129
3130private:
3131 void clear_file_info() {
3132 file_.name.clear();
3133 file_.filename.clear();
3134 file_.content_type.clear();
3135 }
3136
3137 std::string boundary_;
3138
3139 std::string buf_;
3140 size_t state_ = 0;
3141 bool is_valid_ = false;
3142 size_t off_ = 0;
3143 MultipartFormData file_;
3144};
3145
3146inline std::string to_lower(const char *beg, const char *end) {
3147 std::string out;
3148 auto it = beg;
3149 while (it != end) {
3150 out += static_cast<char>(::tolower(*it));
3151 it++;
3152 }
3153 return out;
3154}
3155
3156inline std::string make_multipart_data_boundary() {
3157 static const char data[] =
3158 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
3159
3160 // std::random_device might actually be deterministic on some
3161 // platforms, but due to lack of support in the c++ standard library,
3162 // doing better requires either some ugly hacks or breaking portability.
3163 std::random_device seed_gen;
3164 // Request 128 bits of entropy for initialization
3165 std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
3166 std::mt19937 engine(seed_sequence);
3167
3168 std::string result = "--cpp-httplib-multipart-data-";
3169
3170 for (auto i = 0; i < 16; i++) {
3171 result += data[engine() % (sizeof(data) - 1)];
3172 }
3173
3174 return result;
3175}
3176
3177inline std::pair<size_t, size_t>
3178get_range_offset_and_length(const Request &req, size_t content_length,
3179 size_t index) {
3180 auto r = req.ranges[index];
3181
3182 if (r.first == -1 && r.second == -1) {
3183 return std::make_pair(0, content_length);
3184 }
3185
3186 auto slen = static_cast<ssize_t>(content_length);
3187
3188 if (r.first == -1) {
3189 r.first = (std::max)(static_cast<ssize_t>(0), slen - r.second);
3190 r.second = slen - 1;
3191 }
3192
3193 if (r.second == -1) { r.second = slen - 1; }
3194
3195 return std::make_pair(r.first, r.second - r.first + 1);
3196}
3197
3198inline std::string make_content_range_header_field(size_t offset, size_t length,
3199 size_t content_length) {
3200 std::string field = "bytes ";
3201 field += std::to_string(offset);
3202 field += "-";
3203 field += std::to_string(offset + length - 1);
3204 field += "/";
3205 field += std::to_string(content_length);
3206 return field;
3207}
3208
3209template <typename SToken, typename CToken, typename Content>
3210bool process_multipart_ranges_data(const Request &req, Response &res,
3211 const std::string &boundary,
3212 const std::string &content_type,
3213 SToken stoken, CToken ctoken,
3214 Content content) {
3215 for (size_t i = 0; i < req.ranges.size(); i++) {
3216 ctoken("--");
3217 stoken(boundary);
3218 ctoken("\r\n");
3219 if (!content_type.empty()) {
3220 ctoken("Content-Type: ");
3221 stoken(content_type);
3222 ctoken("\r\n");
3223 }
3224
3225 auto offsets = get_range_offset_and_length(req, res.body.size(), i);
3226 auto offset = offsets.first;
3227 auto length = offsets.second;
3228
3229 ctoken("Content-Range: ");
3230 stoken(make_content_range_header_field(offset, length, res.body.size()));
3231 ctoken("\r\n");
3232 ctoken("\r\n");
3233 if (!content(offset, length)) { return false; }
3234 ctoken("\r\n");
3235 }
3236
3237 ctoken("--");
3238 stoken(boundary);
3239 ctoken("--\r\n");
3240
3241 return true;
3242}
3243
3244inline std::string make_multipart_ranges_data(const Request &req, Response &res,
3245 const std::string &boundary,
3246 const std::string &content_type) {
3247 std::string data;
3248
3249 process_multipart_ranges_data(
3250 req, res, boundary, content_type,
3251 [&](const std::string &token) { data += token; },
3252 [&](const char *token) { data += token; },
3253 [&](size_t offset, size_t length) {
3254 data += res.body.substr(offset, length);
3255 return true;
3256 });
3257
3258 return data;
3259}
3260
3261inline size_t
3262get_multipart_ranges_data_length(const Request &req, Response &res,
3263 const std::string &boundary,
3264 const std::string &content_type) {
3265 size_t data_length = 0;
3266
3267 process_multipart_ranges_data(
3268 req, res, boundary, content_type,
3269 [&](const std::string &token) { data_length += token.size(); },
3270 [&](const char *token) { data_length += strlen(token); },
3271 [&](size_t /*offset*/, size_t length) {
3272 data_length += length;
3273 return true;
3274 });
3275
3276 return data_length;
3277}
3278
3279template <typename T>
3280inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
3281 Response &res,
3282 const std::string &boundary,
3283 const std::string &content_type,
3284 T is_shutting_down) {
3285 return process_multipart_ranges_data(
3286 req, res, boundary, content_type,
3287 [&](const std::string &token) { strm.write(token); },
3288 [&](const char *token) { strm.write(token); },
3289 [&](size_t offset, size_t length) {
3290 return write_content(strm, res.content_provider_, offset, length,
3291 is_shutting_down) >= 0;
3292 });
3293}
3294
3295inline std::pair<size_t, size_t>
3296get_range_offset_and_length(const Request &req, const Response &res,
3297 size_t index) {
3298 auto r = req.ranges[index];
3299
3300 if (r.second == -1) {
3301 r.second = static_cast<ssize_t>(res.content_length_) - 1;
3302 }
3303
3304 return std::make_pair(r.first, r.second - r.first + 1);
3305}
3306
3307inline bool expect_content(const Request &req) {
3308 if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
3309 req.method == "PRI" || req.method == "DELETE") {
3310 return true;
3311 }
3312 // TODO: check if Content-Length is set
3313 return false;
3314}
3315
3316inline bool has_crlf(const char *s) {
3317 auto p = s;
3318 while (*p) {
3319 if (*p == '\r' || *p == '\n') { return true; }
3320 p++;
3321 }
3322 return false;
3323}
3324
3325#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
3326template <typename CTX, typename Init, typename Update, typename Final>
3327inline std::string message_digest(const std::string &s, Init init,
3328 Update update, Final final,
3329 size_t digest_length) {
3330 using namespace std;
3331
3332 std::vector<unsigned char> md(digest_length, 0);
3333 CTX ctx;
3334 init(&ctx);
3335 update(&ctx, s.data(), s.size());
3336 final(md.data(), &ctx);
3337
3338 stringstream ss;
3339 for (auto c : md) {
3340 ss << setfill('0') << setw(2) << hex << (unsigned int)c;
3341 }
3342 return ss.str();
3343}
3344
3345inline std::string MD5(const std::string &s) {
3346 return message_digest<MD5_CTX>(s, MD5_Init, MD5_Update, MD5_Final,
3347 MD5_DIGEST_LENGTH);
3348}
3349
3350inline std::string SHA_256(const std::string &s) {
3351 return message_digest<SHA256_CTX>(s, SHA256_Init, SHA256_Update, SHA256_Final,
3352 SHA256_DIGEST_LENGTH);
3353}
3354
3355inline std::string SHA_512(const std::string &s) {
3356 return message_digest<SHA512_CTX>(s, SHA512_Init, SHA512_Update, SHA512_Final,
3357 SHA512_DIGEST_LENGTH);
3358}
3359#endif
3360
3361#ifdef _WIN32
3362#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
3363// NOTE: This code came up with the following stackoverflow post:
3364// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
3365inline bool load_system_certs_on_windows(X509_STORE *store) {
3366 auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT");
3367
3368 if (!hStore) { return false; }
3369
3370 PCCERT_CONTEXT pContext = NULL;
3371 while (pContext = CertEnumCertificatesInStore(hStore, pContext)) {
3372 auto encoded_cert =
3373 static_cast<const unsigned char *>(pContext->pbCertEncoded);
3374
3375 auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
3376 if (x509) {
3377 X509_STORE_add_cert(store, x509);
3378 X509_free(x509);
3379 }
3380 }
3381
3382 CertFreeCertificateContext(pContext);
3383 CertCloseStore(hStore, 0);
3384
3385 return true;
3386}
3387#endif
3388
3389class WSInit {
3390public:
3391 WSInit() {
3392 WSADATA wsaData;
3393 WSAStartup(0x0002, &wsaData);
3394 }
3395
3396 ~WSInit() { WSACleanup(); }
3397};
3398
3399static WSInit wsinit_;
3400#endif
3401
3402#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
3403inline std::pair<std::string, std::string> make_digest_authentication_header(
3404 const Request &req, const std::map<std::string, std::string> &auth,
3405 size_t cnonce_count, const std::string &cnonce, const std::string &username,
3406 const std::string &password, bool is_proxy = false) {
3407 using namespace std;
3408
3409 string nc;
3410 {
3411 stringstream ss;
3412 ss << setfill('0') << setw(8) << hex << cnonce_count;
3413 nc = ss.str();
3414 }
3415
3416 auto qop = auth.at("qop");
3417 if (qop.find("auth-int") != std::string::npos) {
3418 qop = "auth-int";
3419 } else {
3420 qop = "auth";
3421 }
3422
3423 std::string algo = "MD5";
3424 if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
3425
3426 string response;
3427 {
3428 auto H = algo == "SHA-256"
3429 ? detail::SHA_256
3430 : algo == "SHA-512" ? detail::SHA_512 : detail::MD5;
3431
3432 auto A1 = username + ":" + auth.at("realm") + ":" + password;
3433
3434 auto A2 = req.method + ":" + req.path;
3435 if (qop == "auth-int") { A2 += ":" + H(req.body); }
3436
3437 response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
3438 ":" + qop + ":" + H(A2));
3439 }
3440
3441 auto field = "Digest username=\"" + username + "\", realm=\"" +
3442 auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
3443 "\", uri=\"" + req.path + "\", algorithm=" + algo +
3444 ", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce +
3445 "\", response=\"" + response + "\"";
3446
3447 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
3448 return std::make_pair(key, field);
3449}
3450#endif
3451
3452inline bool parse_www_authenticate(const Response &res,
3453 std::map<std::string, std::string> &auth,
3454 bool is_proxy) {
3455 auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
3456 if (res.has_header(auth_key)) {
3457 static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
3458 auto s = res.get_header_value(auth_key);
3459 auto pos = s.find(' ');
3460 if (pos != std::string::npos) {
3461 auto type = s.substr(0, pos);
3462 if (type == "Basic") {
3463 return false;
3464 } else if (type == "Digest") {
3465 s = s.substr(pos + 1);
3466 auto beg = std::sregex_iterator(s.begin(), s.end(), re);
3467 for (auto i = beg; i != std::sregex_iterator(); ++i) {
3468 auto m = *i;
3469 auto key = s.substr(static_cast<size_t>(m.position(1)),
3470 static_cast<size_t>(m.length(1)));
3471 auto val = m.length(2) > 0
3472 ? s.substr(static_cast<size_t>(m.position(2)),
3473 static_cast<size_t>(m.length(2)))
3474 : s.substr(static_cast<size_t>(m.position(3)),
3475 static_cast<size_t>(m.length(3)));
3476 auth[key] = val;
3477 }
3478 return true;
3479 }
3480 }
3481 }
3482 return false;
3483}
3484
3485// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240
3486inline std::string random_string(size_t length) {
3487 auto randchar = []() -> char {
3488 const char charset[] = "0123456789"
3489 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3490 "abcdefghijklmnopqrstuvwxyz";
3491 const size_t max_index = (sizeof(charset) - 1);
3492 return charset[static_cast<size_t>(rand()) % max_index];
3493 };
3494 std::string str(length, 0);
3495 std::generate_n(str.begin(), length, randchar);
3496 return str;
3497}
3498
3499class ContentProviderAdapter {
3500public:
3501 explicit ContentProviderAdapter(
3502 ContentProviderWithoutLength &&content_provider)
3503 : content_provider_(content_provider) {}
3504
3505 bool operator()(size_t offset, size_t, DataSink &sink) {
3506 return content_provider_(offset, sink);
3507 }
3508
3509private:
3510 ContentProviderWithoutLength content_provider_;
3511};
3512
3513} // namespace detail
3514
3515// Header utilities
3516inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
3517 std::string field = "bytes=";
3518 auto i = 0;
3519 for (auto r : ranges) {
3520 if (i != 0) { field += ", "; }
3521 if (r.first != -1) { field += std::to_string(r.first); }
3522 field += '-';
3523 if (r.second != -1) { field += std::to_string(r.second); }
3524 i++;
3525 }
3526 return std::make_pair("Range", std::move(field));
3527}
3528
3529inline std::pair<std::string, std::string>
3530make_basic_authentication_header(const std::string &username,
3531 const std::string &password,
3532 bool is_proxy = false) {
3533 auto field = "Basic " + detail::base64_encode(username + ":" + password);
3534 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
3535 return std::make_pair(key, std::move(field));
3536}
3537
3538inline std::pair<std::string, std::string>
3539make_bearer_token_authentication_header(const std::string &token,
3540 bool is_proxy = false) {
3541 auto field = "Bearer " + token;
3542 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
3543 return std::make_pair(key, std::move(field));
3544}
3545
3546// Request implementation
3547inline bool Request::has_header(const char *key) const {
3548 return detail::has_header(headers, key);
3549}
3550
3551inline std::string Request::get_header_value(const char *key, size_t id) const {
3552 return detail::get_header_value(headers, key, id, "");
3553}
3554
3555template <typename T>
3556inline T Request::get_header_value(const char *key, size_t id) const {
3557 return detail::get_header_value<T>(headers, key, id, 0);
3558}
3559
3560inline size_t Request::get_header_value_count(const char *key) const {
3561 auto r = headers.equal_range(key);
3562 return static_cast<size_t>(std::distance(r.first, r.second));
3563}
3564
3565inline void Request::set_header(const char *key, const char *val) {
3566 if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
3567 headers.emplace(key, val);
3568 }
3569}
3570
3571inline void Request::set_header(const char *key, const std::string &val) {
3572 if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) {
3573 headers.emplace(key, val);
3574 }
3575}
3576
3577inline bool Request::has_param(const char *key) const {
3578 return params.find(key) != params.end();
3579}
3580
3581inline std::string Request::get_param_value(const char *key, size_t id) const {
3582 auto rng = params.equal_range(key);
3583 auto it = rng.first;
3584 std::advance(it, static_cast<ssize_t>(id));
3585 if (it != rng.second) { return it->second; }
3586 return std::string();
3587}
3588
3589inline size_t Request::get_param_value_count(const char *key) const {
3590 auto r = params.equal_range(key);
3591 return static_cast<size_t>(std::distance(r.first, r.second));
3592}
3593
3594inline bool Request::is_multipart_form_data() const {
3595 const auto &content_type = get_header_value("Content-Type");
3596 return !content_type.find("multipart/form-data");
3597}
3598
3599inline bool Request::has_file(const char *key) const {
3600 return files.find(key) != files.end();
3601}
3602
3603inline MultipartFormData Request::get_file_value(const char *key) const {
3604 auto it = files.find(key);
3605 if (it != files.end()) { return it->second; }
3606 return MultipartFormData();
3607}
3608
3609// Response implementation
3610inline bool Response::has_header(const char *key) const {
3611 return headers.find(key) != headers.end();
3612}
3613
3614inline std::string Response::get_header_value(const char *key,
3615 size_t id) const {
3616 return detail::get_header_value(headers, key, id, "");
3617}
3618
3619template <typename T>
3620inline T Response::get_header_value(const char *key, size_t id) const {
3621 return detail::get_header_value<T>(headers, key, id, 0);
3622}
3623
3624inline size_t Response::get_header_value_count(const char *key) const {
3625 auto r = headers.equal_range(key);
3626 return static_cast<size_t>(std::distance(r.first, r.second));
3627}
3628
3629inline void Response::set_header(const char *key, const char *val) {
3630 if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
3631 headers.emplace(key, val);
3632 }
3633}
3634
3635inline void Response::set_header(const char *key, const std::string &val) {
3636 if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) {
3637 headers.emplace(key, val);
3638 }
3639}
3640
3641inline void Response::set_redirect(const char *url, int stat) {
3642 if (!detail::has_crlf(url)) {
3643 set_header("Location", url);
3644 if (300 <= stat && stat < 400) {
3645 this->status = stat;
3646 } else {
3647 this->status = 302;
3648 }
3649 }
3650}
3651
3652inline void Response::set_redirect(const std::string &url, int stat) {
3653 set_redirect(url.c_str(), stat);
3654}
3655
3656inline void Response::set_content(const char *s, size_t n,
3657 const char *content_type) {
3658 body.assign(s, n);
3659 set_header("Content-Type", content_type);
3660}
3661
3662inline void Response::set_content(std::string s, const char *content_type) {
3663 body = std::move(s);
3664 set_header("Content-Type", content_type);
3665}
3666
3667inline void
3668Response::set_content_provider(size_t in_length, const char *content_type,
3669 ContentProvider provider,
3670 const std::function<void()> &resource_releaser) {
3671 assert(in_length > 0);
3672 set_header("Content-Type", content_type);
3673 content_length_ = in_length;
3674 content_provider_ = std::move(provider);
3675 content_provider_resource_releaser_ = resource_releaser;
3676 is_chunked_content_provider = false;
3677}
3678
3679inline void
3680Response::set_content_provider(const char *content_type,
3681 ContentProviderWithoutLength provider,
3682 const std::function<void()> &resource_releaser) {
3683 set_header("Content-Type", content_type);
3684 content_length_ = 0;
3685 content_provider_ = detail::ContentProviderAdapter(std::move(provider));
3686 content_provider_resource_releaser_ = resource_releaser;
3687 is_chunked_content_provider = false;
3688}
3689
3690inline void Response::set_chunked_content_provider(
3691 const char *content_type, ContentProviderWithoutLength provider,
3692 const std::function<void()> &resource_releaser) {
3693 set_header("Content-Type", content_type);
3694 content_length_ = 0;
3695 content_provider_ = detail::ContentProviderAdapter(std::move(provider));
3696 content_provider_resource_releaser_ = resource_releaser;
3697 is_chunked_content_provider = true;
3698}
3699
3700// Rstream implementation
3701inline ssize_t Stream::write(const char *ptr) {
3702 return write(ptr, strlen(ptr));
3703}
3704
3705inline ssize_t Stream::write(const std::string &s) {
3706 return write(s.data(), s.size());
3707}
3708
3709template <typename... Args>
3710inline ssize_t Stream::write_format(const char *fmt, const Args &... args) {
3711 const auto bufsiz = 2048;
3712 std::array<char, bufsiz> buf;
3713
3714#if defined(_MSC_VER) && _MSC_VER < 1900
3715 auto sn = _snprintf_s(buf.data(), bufsiz - 1, buf.size() - 1, fmt, args...);
3716#else
3717 auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...);
3718#endif
3719 if (sn <= 0) { return sn; }
3720
3721 auto n = static_cast<size_t>(sn);
3722
3723 if (n >= buf.size() - 1) {
3724 std::vector<char> glowable_buf(buf.size());
3725
3726 while (n >= glowable_buf.size() - 1) {
3727 glowable_buf.resize(glowable_buf.size() * 2);
3728#if defined(_MSC_VER) && _MSC_VER < 1900
3729 n = static_cast<size_t>(_snprintf_s(&glowable_buf[0], glowable_buf.size(),
3730 glowable_buf.size() - 1, fmt,
3731 args...));
3732#else
3733 n = static_cast<size_t>(
3734 snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...));
3735#endif
3736 }
3737 return write(&glowable_buf[0], n);
3738 } else {
3739 return write(buf.data(), n);
3740 }
3741}
3742
3743namespace detail {
3744
3745// Socket stream implementation
3746inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
3747 time_t read_timeout_usec,
3748 time_t write_timeout_sec,
3749 time_t write_timeout_usec)
3750 : sock_(sock), read_timeout_sec_(read_timeout_sec),
3751 read_timeout_usec_(read_timeout_usec),
3752 write_timeout_sec_(write_timeout_sec),
3753 write_timeout_usec_(write_timeout_usec) {}
3754
3755inline SocketStream::~SocketStream() {}
3756
3757inline bool SocketStream::is_readable() const {
3758 return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
3759}
3760
3761inline bool SocketStream::is_writable() const {
3762 return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
3763}
3764
3765inline ssize_t SocketStream::read(char *ptr, size_t size) {
3766 if (!is_readable()) { return -1; }
3767
3768#ifdef _WIN32
3769 if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
3770 return -1;
3771 }
3772 return recv(sock_, ptr, static_cast<int>(size), 0);
3773#else
3774 return handle_EINTR([&]() { return recv(sock_, ptr, size, 0); });
3775#endif
3776}
3777
3778inline ssize_t SocketStream::write(const char *ptr, size_t size) {
3779 if (!is_writable()) { return -1; }
3780
3781#ifdef _WIN32
3782 if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
3783 return -1;
3784 }
3785 return send(sock_, ptr, static_cast<int>(size), 0);
3786#else
3787 return handle_EINTR([&]() { return send(sock_, ptr, size, 0); });
3788#endif
3789}
3790
3791inline void SocketStream::get_remote_ip_and_port(std::string &ip,
3792 int &port) const {
3793 return detail::get_remote_ip_and_port(sock_, ip, port);
3794}
3795
3796// Buffer stream implementation
3797inline bool BufferStream::is_readable() const { return true; }
3798
3799inline bool BufferStream::is_writable() const { return true; }
3800
3801inline ssize_t BufferStream::read(char *ptr, size_t size) {
3802#if defined(_MSC_VER) && _MSC_VER <= 1900
3803 auto len_read = buffer._Copy_s(ptr, size, size, position);
3804#else
3805 auto len_read = buffer.copy(ptr, size, position);
3806#endif
3807 position += static_cast<size_t>(len_read);
3808 return static_cast<ssize_t>(len_read);
3809}
3810
3811inline ssize_t BufferStream::write(const char *ptr, size_t size) {
3812 buffer.append(ptr, size);
3813 return static_cast<ssize_t>(size);
3814}
3815
3816inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
3817 int & /*port*/) const {}
3818
3819inline const std::string &BufferStream::get_buffer() const { return buffer; }
3820
3821} // namespace detail
3822
3823// HTTP server implementation
3824inline Server::Server()
3825 : new_task_queue(
3826 [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }),
3827 svr_sock_(INVALID_SOCKET), is_running_(false) {
3828#ifndef _WIN32
3829 signal(SIGPIPE, SIG_IGN);
3830#endif
3831}
3832
3833inline Server::~Server() {}
3834
3835inline Server &Server::Get(const char *pattern, Handler handler) {
3836 get_handlers_.push_back(
3837 std::make_pair(std::regex(pattern), std::move(handler)));
3838 return *this;
3839}
3840
3841inline Server &Server::Post(const char *pattern, Handler handler) {
3842 post_handlers_.push_back(
3843 std::make_pair(std::regex(pattern), std::move(handler)));
3844 return *this;
3845}
3846
3847inline Server &Server::Post(const char *pattern,
3848 HandlerWithContentReader handler) {
3849 post_handlers_for_content_reader_.push_back(
3850 std::make_pair(std::regex(pattern), std::move(handler)));
3851 return *this;
3852}
3853
3854inline Server &Server::Put(const char *pattern, Handler handler) {
3855 put_handlers_.push_back(
3856 std::make_pair(std::regex(pattern), std::move(handler)));
3857 return *this;
3858}
3859
3860inline Server &Server::Put(const char *pattern,
3861 HandlerWithContentReader handler) {
3862 put_handlers_for_content_reader_.push_back(
3863 std::make_pair(std::regex(pattern), std::move(handler)));
3864 return *this;
3865}
3866
3867inline Server &Server::Patch(const char *pattern, Handler handler) {
3868 patch_handlers_.push_back(
3869 std::make_pair(std::regex(pattern), std::move(handler)));
3870 return *this;
3871}
3872
3873inline Server &Server::Patch(const char *pattern,
3874 HandlerWithContentReader handler) {
3875 patch_handlers_for_content_reader_.push_back(
3876 std::make_pair(std::regex(pattern), std::move(handler)));
3877 return *this;
3878}
3879
3880inline Server &Server::Delete(const char *pattern, Handler handler) {
3881 delete_handlers_.push_back(
3882 std::make_pair(std::regex(pattern), std::move(handler)));
3883 return *this;
3884}
3885
3886inline Server &Server::Delete(const char *pattern,
3887 HandlerWithContentReader handler) {
3888 delete_handlers_for_content_reader_.push_back(
3889 std::make_pair(std::regex(pattern), std::move(handler)));
3890 return *this;
3891}
3892
3893inline Server &Server::Options(const char *pattern, Handler handler) {
3894 options_handlers_.push_back(
3895 std::make_pair(std::regex(pattern), std::move(handler)));
3896 return *this;
3897}
3898
3899inline bool Server::set_base_dir(const char *dir, const char *mount_point) {
3900 return set_mount_point(mount_point, dir);
3901}
3902
3903inline bool Server::set_mount_point(const char *mount_point, const char *dir,
3904 Headers headers) {
3905 if (detail::is_dir(dir)) {
3906 std::string mnt = mount_point ? mount_point : "/";
3907 if (!mnt.empty() && mnt[0] == '/') {
3908 base_dirs_.push_back({mnt, dir, std::move(headers)});
3909 return true;
3910 }
3911 }
3912 return false;
3913}
3914
3915inline bool Server::remove_mount_point(const char *mount_point) {
3916 for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
3917 if (it->mount_point == mount_point) {
3918 base_dirs_.erase(it);
3919 return true;
3920 }
3921 }
3922 return false;
3923}
3924
3925inline void Server::set_file_extension_and_mimetype_mapping(const char *ext,
3926 const char *mime) {
3927 file_extension_and_mimetype_map_[ext] = mime;
3928}
3929
3930inline void Server::set_file_request_handler(Handler handler) {
3931 file_request_handler_ = std::move(handler);
3932}
3933
3934inline void Server::set_error_handler(Handler handler) {
3935 error_handler_ = std::move(handler);
3936}
3937
3938inline void Server::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
3939
3940inline void Server::set_socket_options(SocketOptions socket_options) {
3941 socket_options_ = std::move(socket_options);
3942}
3943
3944inline void Server::set_logger(Logger logger) { logger_ = std::move(logger); }
3945
3946inline void
3947Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
3948 expect_100_continue_handler_ = std::move(handler);
3949}
3950
3951inline void Server::set_keep_alive_max_count(size_t count) {
3952 keep_alive_max_count_ = count;
3953}
3954
3955inline void Server::set_keep_alive_timeout(time_t sec) {
3956 keep_alive_timeout_sec_ = sec;
3957}
3958
3959inline void Server::set_read_timeout(time_t sec, time_t usec) {
3960 read_timeout_sec_ = sec;
3961 read_timeout_usec_ = usec;
3962}
3963
3964inline void Server::set_write_timeout(time_t sec, time_t usec) {
3965 write_timeout_sec_ = sec;
3966 write_timeout_usec_ = usec;
3967}
3968
3969inline void Server::set_idle_interval(time_t sec, time_t usec) {
3970 idle_interval_sec_ = sec;
3971 idle_interval_usec_ = usec;
3972}
3973
3974inline void Server::set_payload_max_length(size_t length) {
3975 payload_max_length_ = length;
3976}
3977
3978inline bool Server::bind_to_port(const char *host, int port, int socket_flags) {
3979 if (bind_internal(host, port, socket_flags) < 0) return false;
3980 return true;
3981}
3982inline int Server::bind_to_any_port(const char *host, int socket_flags) {
3983 return bind_internal(host, 0, socket_flags);
3984}
3985
3986inline bool Server::listen_after_bind() { return listen_internal(); }
3987
3988inline bool Server::listen(const char *host, int port, int socket_flags) {
3989 return bind_to_port(host, port, socket_flags) && listen_internal();
3990}
3991
3992inline bool Server::is_running() const { return is_running_; }
3993
3994inline void Server::stop() {
3995 if (is_running_) {
3996 assert(svr_sock_ != INVALID_SOCKET);
3997 std::atomic<socket_t> sock(svr_sock_.exchange(INVALID_SOCKET));
3998 detail::shutdown_socket(sock);
3999 detail::close_socket(sock);
4000 }
4001}
4002
4003inline bool Server::parse_request_line(const char *s, Request &req) {
4004 const static std::regex re(
4005 "(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) "
4006 "(([^?]+)(?:\\?(.*?))?) (HTTP/1\\.[01])\r\n");
4007
4008 std::cmatch m;
4009 if (std::regex_match(s, m, re)) {
4010 req.version = std::string(m[5]);
4011 req.method = std::string(m[1]);
4012 req.target = std::string(m[2]);
4013 req.path = detail::decode_url(m[3], false);
4014
4015 // Parse query text
4016 auto len = std::distance(m[4].first, m[4].second);
4017 if (len > 0) { detail::parse_query_text(m[4], req.params); }
4018
4019 return true;
4020 }
4021
4022 return false;
4023}
4024
4025inline bool Server::write_response(Stream &strm, bool close_connection,
4026 const Request &req, Response &res) {
4027 assert(res.status != -1);
4028
4029 if (400 <= res.status && error_handler_) { error_handler_(req, res); }
4030
4031 detail::BufferStream bstrm;
4032
4033 // Response line
4034 if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
4035 detail::status_message(res.status))) {
4036 return false;
4037 }
4038
4039 // Headers
4040 if (close_connection || req.get_header_value("Connection") == "close") {
4041 res.set_header("Connection", "close");
4042 } else {
4043 std::stringstream ss;
4044 ss << "timeout=" << keep_alive_timeout_sec_
4045 << ", max=" << keep_alive_max_count_;
4046 res.set_header("Keep-Alive", ss.str());
4047 }
4048
4049 if (!res.has_header("Content-Type") &&
4050 (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) {
4051 res.set_header("Content-Type", "text/plain");
4052 }
4053
4054 if (!res.has_header("Accept-Ranges") && req.method == "HEAD") {
4055 res.set_header("Accept-Ranges", "bytes");
4056 }
4057
4058 std::string content_type;
4059 std::string boundary;
4060
4061 if (req.ranges.size() > 1) {
4062 boundary = detail::make_multipart_data_boundary();
4063
4064 auto it = res.headers.find("Content-Type");
4065 if (it != res.headers.end()) {
4066 content_type = it->second;
4067 res.headers.erase(it);
4068 }
4069
4070 res.headers.emplace("Content-Type",
4071 "multipart/byteranges; boundary=" + boundary);
4072 }
4073
4074 auto type = detail::encoding_type(req, res);
4075
4076 if (res.body.empty()) {
4077 if (res.content_length_ > 0) {
4078 size_t length = 0;
4079 if (req.ranges.empty()) {
4080 length = res.content_length_;
4081 } else if (req.ranges.size() == 1) {
4082 auto offsets =
4083 detail::get_range_offset_and_length(req, res.content_length_, 0);
4084 auto offset = offsets.first;
4085 length = offsets.second;
4086 auto content_range = detail::make_content_range_header_field(
4087 offset, length, res.content_length_);
4088 res.set_header("Content-Range", content_range);
4089 } else {
4090 length = detail::get_multipart_ranges_data_length(req, res, boundary,
4091 content_type);
4092 }
4093 res.set_header("Content-Length", std::to_string(length));
4094 } else {
4095 if (res.content_provider_) {
4096 if (res.is_chunked_content_provider) {
4097 res.set_header("Transfer-Encoding", "chunked");
4098 if (type == detail::EncodingType::Gzip) {
4099 res.set_header("Content-Encoding", "gzip");
4100 } else if (type == detail::EncodingType::Brotli) {
4101 res.set_header("Content-Encoding", "br");
4102 }
4103 }
4104 } else {
4105 res.set_header("Content-Length", "0");
4106 }
4107 }
4108 } else {
4109 if (req.ranges.empty()) {
4110 ;
4111 } else if (req.ranges.size() == 1) {
4112 auto offsets =
4113 detail::get_range_offset_and_length(req, res.body.size(), 0);
4114 auto offset = offsets.first;
4115 auto length = offsets.second;
4116 auto content_range = detail::make_content_range_header_field(
4117 offset, length, res.body.size());
4118 res.set_header("Content-Range", content_range);
4119 res.body = res.body.substr(offset, length);
4120 } else {
4121 res.body =
4122 detail::make_multipart_ranges_data(req, res, boundary, content_type);
4123 }
4124
4125 if (type != detail::EncodingType::None) {
4126 std::unique_ptr<detail::compressor> compressor;
4127
4128 if (type == detail::EncodingType::Gzip) {
4129#ifdef CPPHTTPLIB_ZLIB_SUPPORT
4130 compressor = detail::make_unique<detail::gzip_compressor>();
4131 res.set_header("Content-Encoding", "gzip");
4132#endif
4133 } else if (type == detail::EncodingType::Brotli) {
4134#ifdef CPPHTTPLIB_BROTLI_SUPPORT
4135 compressor = detail::make_unique<detail::brotli_compressor>();
4136 res.set_header("Content-Encoding", "brotli");
4137#endif
4138 }
4139
4140 if (compressor) {
4141 std::string compressed;
4142
4143 if (!compressor->compress(res.body.data(), res.body.size(), true,
4144 [&](const char *data, size_t data_len) {
4145 compressed.append(data, data_len);
4146 return true;
4147 })) {
4148 return false;
4149 }
4150
4151 res.body.swap(compressed);
4152 }
4153 }
4154
4155 auto length = std::to_string(res.body.size());
4156 res.set_header("Content-Length", length);
4157 }
4158
4159 if (!detail::write_headers(bstrm, res, Headers())) { return false; }
4160
4161 // Flush buffer
4162 auto &data = bstrm.get_buffer();
4163 strm.write(data.data(), data.size());
4164
4165 // Body
4166 auto ret = true;
4167 if (req.method != "HEAD") {
4168 if (!res.body.empty()) {
4169 if (!strm.write(res.body)) { ret = false; }
4170 } else if (res.content_provider_) {
4171 if (!write_content_with_provider(strm, req, res, boundary,
4172 content_type)) {
4173 ret = false;
4174 }
4175 }
4176 }
4177
4178 // Log
4179 if (logger_) { logger_(req, res); }
4180
4181 return ret;
4182}
4183
4184inline bool
4185Server::write_content_with_provider(Stream &strm, const Request &req,
4186 Response &res, const std::string &boundary,
4187 const std::string &content_type) {
4188 auto is_shutting_down = [this]() {
4189 return this->svr_sock_ == INVALID_SOCKET;
4190 };
4191
4192 if (res.content_length_ > 0) {
4193 if (req.ranges.empty()) {
4194 if (detail::write_content(strm, res.content_provider_, 0,
4195 res.content_length_, is_shutting_down) < 0) {
4196 return false;
4197 }
4198 } else if (req.ranges.size() == 1) {
4199 auto offsets =
4200 detail::get_range_offset_and_length(req, res.content_length_, 0);
4201 auto offset = offsets.first;
4202 auto length = offsets.second;
4203 if (detail::write_content(strm, res.content_provider_, offset, length,
4204 is_shutting_down) < 0) {
4205 return false;
4206 }
4207 } else {
4208 if (!detail::write_multipart_ranges_data(
4209 strm, req, res, boundary, content_type, is_shutting_down)) {
4210 return false;
4211 }
4212 }
4213 } else {
4214 if (res.is_chunked_content_provider) {
4215 auto type = detail::encoding_type(req, res);
4216
4217 std::unique_ptr<detail::compressor> compressor;
4218 if (type == detail::EncodingType::Gzip) {
4219#ifdef CPPHTTPLIB_ZLIB_SUPPORT
4220 compressor = detail::make_unique<detail::gzip_compressor>();
4221#endif
4222 } else if (type == detail::EncodingType::Brotli) {
4223#ifdef CPPHTTPLIB_BROTLI_SUPPORT
4224 compressor = detail::make_unique<detail::brotli_compressor>();
4225#endif
4226 } else {
4227 compressor = detail::make_unique<detail::nocompressor>();
4228 }
4229 assert(compressor != nullptr);
4230
4231 if (detail::write_content_chunked(strm, res.content_provider_,
4232 is_shutting_down, *compressor) < 0) {
4233 return false;
4234 }
4235 } else {
4236 if (detail::write_content_without_length(strm, res.content_provider_,
4237 is_shutting_down) < 0) {
4238 return false;
4239 }
4240 }
4241 }
4242 return true;
4243}
4244
4245inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
4246 MultipartFormDataMap::iterator cur;
4247 if (read_content_core(
4248 strm, req, res,
4249 // Regular
4250 [&](const char *buf, size_t n) {
4251 if (req.body.size() + n > req.body.max_size()) { return false; }
4252 req.body.append(buf, n);
4253 return true;
4254 },
4255 // Multipart
4256 [&](const MultipartFormData &file) {
4257 cur = req.files.emplace(file.name, file);
4258 return true;
4259 },
4260 [&](const char *buf, size_t n) {
4261 auto &content = cur->second.content;
4262 if (content.size() + n > content.max_size()) { return false; }
4263 content.append(buf, n);
4264 return true;
4265 })) {
4266 const auto &content_type = req.get_header_value("Content-Type");
4267 if (!content_type.find("application/x-www-form-urlencoded")) {
4268 detail::parse_query_text(req.body, req.params);
4269 }
4270 return true;
4271 }
4272 return false;
4273}
4274
4275inline bool Server::read_content_with_content_receiver(
4276 Stream &strm, Request &req, Response &res, ContentReceiver receiver,
4277 MultipartContentHeader multipart_header,
4278 ContentReceiver multipart_receiver) {
4279 return read_content_core(strm, req, res, std::move(receiver),
4280 std::move(multipart_header),
4281 std::move(multipart_receiver));
4282}
4283
4284inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
4285 ContentReceiver receiver,
4286 MultipartContentHeader mulitpart_header,
4287 ContentReceiver multipart_receiver) {
4288 detail::MultipartFormDataParser multipart_form_data_parser;
4289 ContentReceiverWithProgress out;
4290
4291 if (req.is_multipart_form_data()) {
4292 const auto &content_type = req.get_header_value("Content-Type");
4293 std::string boundary;
4294 if (!detail::parse_multipart_boundary(content_type, boundary)) {
4295 res.status = 400;
4296 return false;
4297 }
4298
4299 multipart_form_data_parser.set_boundary(std::move(boundary));
4300 out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) {
4301 /* For debug
4302 size_t pos = 0;
4303 while (pos < n) {
4304 auto read_size = std::min<size_t>(1, n - pos);
4305 auto ret = multipart_form_data_parser.parse(
4306 buf + pos, read_size, multipart_receiver, mulitpart_header);
4307 if (!ret) { return false; }
4308 pos += read_size;
4309 }
4310 return true;
4311 */
4312 return multipart_form_data_parser.parse(buf, n, multipart_receiver,
4313 mulitpart_header);
4314 };
4315 } else {
4316 out = [receiver](const char *buf, size_t n, uint64_t /*off*/,
4317 uint64_t /*len*/) { return receiver(buf, n); };
4318 }
4319
4320 if (req.method == "DELETE" && !req.has_header("Content-Length")) {
4321 return true;
4322 }
4323
4324 if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr,
4325 out, true)) {
4326 return false;
4327 }
4328
4329 if (req.is_multipart_form_data()) {
4330 if (!multipart_form_data_parser.is_valid()) {
4331 res.status = 400;
4332 return false;
4333 }
4334 }
4335
4336 return true;
4337}
4338
4339inline bool Server::handle_file_request(Request &req, Response &res,
4340 bool head) {
4341 for (const auto &entry : base_dirs_) {
4342 // Prefix match
4343 if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
4344 std::string sub_path = "/" + req.path.substr(entry.mount_point.size());
4345 if (detail::is_valid_path(sub_path)) {
4346 auto path = entry.base_dir + sub_path;
4347 if (path.back() == '/') { path += "index.html"; }
4348
4349 if (detail::is_file(path)) {
4350 detail::read_file(path, res.body);
4351 auto type =
4352 detail::find_content_type(path, file_extension_and_mimetype_map_);
4353 if (type) { res.set_header("Content-Type", type); }
4354 for (const auto &kv : entry.headers) {
4355 res.set_header(kv.first.c_str(), kv.second);
4356 }
4357 res.status = 200;
4358 if (!head && file_request_handler_) {
4359 file_request_handler_(req, res);
4360 }
4361 return true;
4362 }
4363 }
4364 }
4365 }
4366 return false;
4367}
4368
4369inline socket_t
4370Server::create_server_socket(const char *host, int port, int socket_flags,
4371 SocketOptions socket_options) const {
4372 return detail::create_socket(
4373 host, port, socket_flags, tcp_nodelay_, std::move(socket_options),
4374 [](socket_t sock, struct addrinfo &ai) -> bool {
4375 if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
4376 return false;
4377 }
4378 if (::listen(sock, 5)) { // Listen through 5 channels
4379 return false;
4380 }
4381 return true;
4382 });
4383}
4384
4385inline int Server::bind_internal(const char *host, int port, int socket_flags) {
4386 if (!is_valid()) { return -1; }
4387
4388 svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
4389 if (svr_sock_ == INVALID_SOCKET) { return -1; }
4390
4391 if (port == 0) {
4392 struct sockaddr_storage addr;
4393 socklen_t addr_len = sizeof(addr);
4394 if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
4395 &addr_len) == -1) {
4396 return -1;
4397 }
4398 if (addr.ss_family == AF_INET) {
4399 return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
4400 } else if (addr.ss_family == AF_INET6) {
4401 return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
4402 } else {
4403 return -1;
4404 }
4405 } else {
4406 return port;
4407 }
4408}
4409
4410inline bool Server::listen_internal() {
4411 auto ret = true;
4412 is_running_ = true;
4413
4414 {
4415 std::unique_ptr<TaskQueue> task_queue(new_task_queue());
4416
4417 while (svr_sock_ != INVALID_SOCKET) {
4418#ifndef _WIN32
4419 if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
4420#endif
4421 auto val = detail::select_read(svr_sock_, idle_interval_sec_,
4422 idle_interval_usec_);
4423 if (val == 0) { // Timeout
4424 task_queue->on_idle();
4425 continue;
4426 }
4427#ifndef _WIN32
4428 }
4429#endif
4430 socket_t sock = accept(svr_sock_, nullptr, nullptr);
4431
4432 if (sock == INVALID_SOCKET) {
4433 if (errno == EMFILE) {
4434 // The per-process limit of open file descriptors has been reached.
4435 // Try to accept new connections after a short sleep.
4436 std::this_thread::sleep_for(std::chrono::milliseconds(1));
4437 continue;
4438 }
4439 if (svr_sock_ != INVALID_SOCKET) {
4440 detail::close_socket(svr_sock_);
4441 ret = false;
4442 } else {
4443 ; // The server socket was closed by user.
4444 }
4445 break;
4446 }
4447
4448#if __cplusplus > 201703L
4449 task_queue->enqueue([=, this]() { process_and_close_socket(sock); });
4450#else
4451 task_queue->enqueue([=]() { process_and_close_socket(sock); });
4452#endif
4453 }
4454
4455 task_queue->shutdown();
4456 }
4457
4458 is_running_ = false;
4459 return ret;
4460}
4461
4462inline bool Server::routing(Request &req, Response &res, Stream &strm) {
4463 // File handler
4464 bool is_head_request = req.method == "HEAD";
4465 if ((req.method == "GET" || is_head_request) &&
4466 handle_file_request(req, res, is_head_request)) {
4467 return true;
4468 }
4469
4470 if (detail::expect_content(req)) {
4471 // Content reader handler
4472 {
4473 ContentReader reader(
4474 [&](ContentReceiver receiver) {
4475 return read_content_with_content_receiver(
4476 strm, req, res, std::move(receiver), nullptr, nullptr);
4477 },
4478 [&](MultipartContentHeader header, ContentReceiver receiver) {
4479 return read_content_with_content_receiver(strm, req, res, nullptr,
4480 std::move(header),
4481 std::move(receiver));
4482 });
4483
4484 if (req.method == "POST") {
4485 if (dispatch_request_for_content_reader(
4486 req, res, std::move(reader),
4487 post_handlers_for_content_reader_)) {
4488 return true;
4489 }
4490 } else if (req.method == "PUT") {
4491 if (dispatch_request_for_content_reader(
4492 req, res, std::move(reader),
4493 put_handlers_for_content_reader_)) {
4494 return true;
4495 }
4496 } else if (req.method == "PATCH") {
4497 if (dispatch_request_for_content_reader(
4498 req, res, std::move(reader),
4499 patch_handlers_for_content_reader_)) {
4500 return true;
4501 }
4502 } else if (req.method == "DELETE") {
4503 if (dispatch_request_for_content_reader(
4504 req, res, std::move(reader),
4505 delete_handlers_for_content_reader_)) {
4506 return true;
4507 }
4508 }
4509 }
4510
4511 // Read content into `req.body`
4512 if (!read_content(strm, req, res)) { return false; }
4513 }
4514
4515 // Regular handler
4516 if (req.method == "GET" || req.method == "HEAD") {
4517 return dispatch_request(req, res, get_handlers_);
4518 } else if (req.method == "POST") {
4519 return dispatch_request(req, res, post_handlers_);
4520 } else if (req.method == "PUT") {
4521 return dispatch_request(req, res, put_handlers_);
4522 } else if (req.method == "DELETE") {
4523 return dispatch_request(req, res, delete_handlers_);
4524 } else if (req.method == "OPTIONS") {
4525 return dispatch_request(req, res, options_handlers_);
4526 } else if (req.method == "PATCH") {
4527 return dispatch_request(req, res, patch_handlers_);
4528 }
4529
4530 res.status = 400;
4531 return false;
4532}
4533
4534inline bool Server::dispatch_request(Request &req, Response &res,
4535 const Handlers &handlers) {
4536 try {
4537 for (const auto &x : handlers) {
4538 const auto &pattern = x.first;
4539 const auto &handler = x.second;
4540
4541 if (std::regex_match(req.path, req.matches, pattern)) {
4542 handler(req, res);
4543 return true;
4544 }
4545 }
4546 } catch (const std::exception &ex) {
4547 res.status = 500;
4548 res.set_header("EXCEPTION_WHAT", ex.what());
4549 } catch (...) {
4550 res.status = 500;
4551 res.set_header("EXCEPTION_WHAT", "UNKNOWN");
4552 }
4553 return false;
4554}
4555
4556inline bool Server::dispatch_request_for_content_reader(
4557 Request &req, Response &res, ContentReader content_reader,
4558 const HandlersForContentReader &handlers) {
4559 for (const auto &x : handlers) {
4560 const auto &pattern = x.first;
4561 const auto &handler = x.second;
4562
4563 if (std::regex_match(req.path, req.matches, pattern)) {
4564 handler(req, res, content_reader);
4565 return true;
4566 }
4567 }
4568 return false;
4569}
4570
4571inline bool
4572Server::process_request(Stream &strm, bool close_connection,
4573 bool &connection_closed,
4574 const std::function<void(Request &)> &setup_request) {
4575 std::array<char, 2048> buf{};
4576
4577 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
4578
4579 // Connection has been closed on client
4580 if (!line_reader.getline()) { return false; }
4581
4582 Request req;
4583 Response res;
4584
4585 res.version = "HTTP/1.1";
4586
4587 // Check if the request URI doesn't exceed the limit
4588 if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
4589 Headers dummy;
4590 detail::read_headers(strm, dummy);
4591 res.status = 414;
4592 return write_response(strm, close_connection, req, res);
4593 }
4594
4595 // Request line and headers
4596 if (!parse_request_line(line_reader.ptr(), req) ||
4597 !detail::read_headers(strm, req.headers)) {
4598 res.status = 400;
4599 return write_response(strm, close_connection, req, res);
4600 }
4601
4602 if (req.get_header_value("Connection") == "close") {
4603 connection_closed = true;
4604 }
4605
4606 if (req.version == "HTTP/1.0" &&
4607 req.get_header_value("Connection") != "Keep-Alive") {
4608 connection_closed = true;
4609 }
4610
4611 strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
4612 req.set_header("REMOTE_ADDR", req.remote_addr);
4613 req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
4614
4615 if (req.has_header("Range")) {
4616 const auto &range_header_value = req.get_header_value("Range");
4617 if (!detail::parse_range_header(range_header_value, req.ranges)) {
4618 res.status = 416;
4619 return write_response(strm, close_connection, req, res);
4620 }
4621 }
4622
4623 if (setup_request) { setup_request(req); }
4624
4625 if (req.get_header_value("Expect") == "100-continue") {
4626 auto status = 100;
4627 if (expect_100_continue_handler_) {
4628 status = expect_100_continue_handler_(req, res);
4629 }
4630 switch (status) {
4631 case 100:
4632 case 417:
4633 strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
4634 detail::status_message(status));
4635 break;
4636 default: return write_response(strm, close_connection, req, res);
4637 }
4638 }
4639
4640 // Rounting
4641 if (routing(req, res, strm)) {
4642 if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
4643 } else {
4644 if (res.status == -1) { res.status = 404; }
4645 }
4646
4647 return write_response(strm, close_connection, req, res);
4648}
4649
4650inline bool Server::is_valid() const { return true; }
4651
4652inline bool Server::process_and_close_socket(socket_t sock) {
4653 auto ret = detail::process_server_socket(
4654 sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_,
4655 read_timeout_usec_, write_timeout_sec_, write_timeout_usec_,
4656 [this](Stream &strm, bool close_connection, bool &connection_closed) {
4657 return process_request(strm, close_connection, connection_closed,
4658 nullptr);
4659 });
4660
4661 detail::shutdown_socket(sock);
4662 detail::close_socket(sock);
4663 return ret;
4664}
4665
4666// HTTP client implementation
4667inline ClientImpl::ClientImpl(const std::string &host)
4668 : ClientImpl(host, 80, std::string(), std::string()) {}
4669
4670inline ClientImpl::ClientImpl(const std::string &host, int port)
4671 : ClientImpl(host, port, std::string(), std::string()) {}
4672
4673inline ClientImpl::ClientImpl(const std::string &host, int port,
4674 const std::string &client_cert_path,
4675 const std::string &client_key_path)
4676 : error_(Error::Success), host_(host), port_(port),
4677 host_and_port_(host_ + ":" + std::to_string(port_)),
4678 client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
4679
4680inline ClientImpl::~ClientImpl() { lock_socket_and_shutdown_and_close(); }
4681
4682inline bool ClientImpl::is_valid() const { return true; }
4683
4684inline Error ClientImpl::get_last_error() const { return error_; }
4685
4686inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
4687 client_cert_path_ = rhs.client_cert_path_;
4688 client_key_path_ = rhs.client_key_path_;
4689 connection_timeout_sec_ = rhs.connection_timeout_sec_;
4690 read_timeout_sec_ = rhs.read_timeout_sec_;
4691 read_timeout_usec_ = rhs.read_timeout_usec_;
4692 write_timeout_sec_ = rhs.write_timeout_sec_;
4693 write_timeout_usec_ = rhs.write_timeout_usec_;
4694 basic_auth_username_ = rhs.basic_auth_username_;
4695 basic_auth_password_ = rhs.basic_auth_password_;
4696 bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
4697#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
4698 digest_auth_username_ = rhs.digest_auth_username_;
4699 digest_auth_password_ = rhs.digest_auth_password_;
4700#endif
4701 keep_alive_ = rhs.keep_alive_;
4702 follow_location_ = rhs.follow_location_;
4703 tcp_nodelay_ = rhs.tcp_nodelay_;
4704 socket_options_ = rhs.socket_options_;
4705 compress_ = rhs.compress_;
4706 decompress_ = rhs.decompress_;
4707 interface_ = rhs.interface_;
4708 proxy_host_ = rhs.proxy_host_;
4709 proxy_port_ = rhs.proxy_port_;
4710 proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
4711 proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
4712 proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
4713#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
4714 proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
4715 proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
4716#endif
4717#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
4718 server_certificate_verification_ = rhs.server_certificate_verification_;
4719#endif
4720 logger_ = rhs.logger_;
4721}
4722
4723inline socket_t ClientImpl::create_client_socket() const {
4724 if (!proxy_host_.empty() && proxy_port_ != -1) {
4725 return detail::create_client_socket(
4726 proxy_host_.c_str(), proxy_port_, tcp_nodelay_, socket_options_,
4727 connection_timeout_sec_, connection_timeout_usec_, interface_, error_);
4728 }
4729 return detail::create_client_socket(
4730 host_.c_str(), port_, tcp_nodelay_, socket_options_,
4731 connection_timeout_sec_, connection_timeout_usec_, interface_, error_);
4732}
4733
4734inline bool ClientImpl::create_and_connect_socket(Socket &socket) {
4735 auto sock = create_client_socket();
4736 if (sock == INVALID_SOCKET) { return false; }
4737 socket.sock = sock;
4738 return true;
4739}
4740
4741inline void ClientImpl::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
4742 (void)socket;
4743 (void)shutdown_gracefully;
4744 //If there are any requests in flight from threads other than us, then it's
4745 //a thread-unsafe race because individual ssl* objects are not thread-safe.
4746 assert(socket_requests_in_flight_ == 0 ||
4747 socket_requests_are_from_thread_ == std::this_thread::get_id());
4748}
4749
4750inline void ClientImpl::shutdown_socket(Socket &socket) {
4751 if (socket.sock == INVALID_SOCKET)
4752 return;
4753 detail::shutdown_socket(socket.sock);
4754}
4755
4756inline void ClientImpl::close_socket(Socket &socket) {
4757 // If there are requests in flight in another thread, usually closing
4758 // the socket will be fine and they will simply receive an error when
4759 // using the closed socket, but it is still a bug since rarely the OS
4760 // may reassign the socket id to be used for a new socket, and then
4761 // suddenly they will be operating on a live socket that is different
4762 // than the one they intended!
4763 assert(socket_requests_in_flight_ == 0 ||
4764 socket_requests_are_from_thread_ == std::this_thread::get_id());
4765 // It is also a bug if this happens while SSL is still active
4766#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
4767 assert(socket.ssl == nullptr);
4768#endif
4769 if (socket.sock == INVALID_SOCKET)
4770 return;
4771 detail::close_socket(socket.sock);
4772 socket.sock = INVALID_SOCKET;
4773}
4774
4775inline void ClientImpl::lock_socket_and_shutdown_and_close() {
4776 std::lock_guard<std::mutex> guard(socket_mutex_);
4777 shutdown_ssl(socket_, true);
4778 shutdown_socket(socket_);
4779 close_socket(socket_);
4780}
4781
4782inline bool ClientImpl::read_response_line(Stream &strm, Response &res) {
4783 std::array<char, 2048> buf;
4784
4785 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
4786
4787 if (!line_reader.getline()) { return false; }
4788
4789 const static std::regex re("(HTTP/1\\.[01]) (\\d+) (.*?)\r\n");
4790
4791 std::cmatch m;
4792 if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
4793 res.version = std::string(m[1]);
4794 res.status = std::stoi(std::string(m[2]));
4795 res.reason = std::string(m[3]);
4796
4797 // Ignore '100 Continue'
4798 while (res.status == 100) {
4799 if (!line_reader.getline()) { return false; } // CRLF
4800 if (!line_reader.getline()) { return false; } // next response line
4801
4802 if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
4803 res.version = std::string(m[1]);
4804 res.status = std::stoi(std::string(m[2]));
4805 res.reason = std::string(m[3]);
4806 }
4807
4808 return true;
4809}
4810
4811inline bool ClientImpl::send(const Request &req, Response &res) {
4812 std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
4813
4814 {
4815 std::lock_guard<std::mutex> guard(socket_mutex_);
4816 // Set this to false immediately - if it ever gets set to true by the end of the
4817 // request, we know another thread instructed us to close the socket.
4818 socket_should_be_closed_when_request_is_done_ = false;
4819
4820 auto is_alive = false;
4821 if (socket_.is_open()) {
4822 is_alive = detail::select_write(socket_.sock, 0, 0) > 0;
4823 if (!is_alive) {
4824 // Attempt to avoid sigpipe by shutting down nongracefully if it seems like
4825 // the other side has already closed the connection
4826 // Also, there cannot be any requests in flight from other threads since we locked
4827 // request_mutex_, so safe to close everything immediately
4828 const bool shutdown_gracefully = false;
4829 shutdown_ssl(socket_, shutdown_gracefully);
4830 shutdown_socket(socket_);
4831 close_socket(socket_);
4832 }
4833 }
4834
4835 if (!is_alive) {
4836 if (!create_and_connect_socket(socket_)) { return false; }
4837
4838#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
4839 // TODO: refactoring
4840 if (is_ssl()) {
4841 auto &scli = static_cast<SSLClient &>(*this);
4842 if (!proxy_host_.empty() && proxy_port_ != -1) {
4843 bool success = false;
4844 if (!scli.connect_with_proxy(socket_, res, success)) {
4845 return success;
4846 }
4847 }
4848
4849 if (!scli.initialize_ssl(socket_)) { return false; }
4850 }
4851#endif
4852 }
4853
4854 // Mark the current socket as being in use so that it cannot be closed by anyone
4855 // else while this request is ongoing, even though we will be releasing the mutex.
4856 if (socket_requests_in_flight_ > 1) {
4857 assert(socket_requests_are_from_thread_ == std::this_thread::get_id());
4858 }
4859 socket_requests_in_flight_ += 1;
4860 socket_requests_are_from_thread_ = std::this_thread::get_id();
4861 }
4862
4863 auto close_connection = !keep_alive_;
4864 auto ret = process_socket(socket_, [&](Stream &strm) {
4865 return handle_request(strm, req, res, close_connection);
4866 });
4867
4868 //Briefly lock mutex in order to mark that a request is no longer ongoing
4869 {
4870 std::lock_guard<std::mutex> guard(socket_mutex_);
4871 socket_requests_in_flight_ -= 1;
4872 if (socket_requests_in_flight_ <= 0) {
4873 assert(socket_requests_in_flight_ == 0);
4874 socket_requests_are_from_thread_ = std::thread::id();
4875 }
4876
4877 if (socket_should_be_closed_when_request_is_done_ ||
4878 close_connection ||
4879 !ret ) {
4880 shutdown_ssl(socket_, true);
4881 shutdown_socket(socket_);
4882 close_socket(socket_);
4883 }
4884 }
4885
4886 if (!ret) {
4887 if (error_ == Error::Success) { error_ = Error::Unknown; }
4888 }
4889
4890 return ret;
4891}
4892
4893inline bool ClientImpl::handle_request(Stream &strm, const Request &req,
4894 Response &res, bool close_connection) {
4895 if (req.path.empty()) {
4896 error_ = Error::Connection;
4897 return false;
4898 }
4899
4900 bool ret;
4901
4902 if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) {
4903 auto req2 = req;
4904 req2.path = "http://" + host_and_port_ + req.path;
4905 ret = process_request(strm, req2, res, close_connection);
4906 } else {
4907 ret = process_request(strm, req, res, close_connection);
4908 }
4909
4910 if (!ret) { return false; }
4911
4912 if (300 < res.status && res.status < 400 && follow_location_) {
4913 ret = redirect(req, res);
4914 }
4915
4916#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
4917 if ((res.status == 401 || res.status == 407) &&
4918 req.authorization_count_ < 5) {
4919 auto is_proxy = res.status == 407;
4920 const auto &username =
4921 is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
4922 const auto &password =
4923 is_proxy ? proxy_digest_auth_password_ : digest_auth_password_;
4924
4925 if (!username.empty() && !password.empty()) {
4926 std::map<std::string, std::string> auth;
4927 if (detail::parse_www_authenticate(res, auth, is_proxy)) {
4928 Request new_req = req;
4929 new_req.authorization_count_ += 1;
4930 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
4931 new_req.headers.erase(key);
4932 new_req.headers.insert(detail::make_digest_authentication_header(
4933 req, auth, new_req.authorization_count_, detail::random_string(10),
4934 username, password, is_proxy));
4935
4936 Response new_res;
4937
4938 ret = send(new_req, new_res);
4939 if (ret) { res = new_res; }
4940 }
4941 }
4942 }
4943#endif
4944
4945 return ret;
4946}
4947
4948inline bool ClientImpl::redirect(const Request &req, Response &res) {
4949 if (req.redirect_count == 0) {
4950 error_ = Error::ExceedRedirectCount;
4951 return false;
4952 }
4953
4954 auto location = detail::decode_url(res.get_header_value("location"), true);
4955 if (location.empty()) { return false; }
4956
4957 const static std::regex re(
4958 R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
4959
4960 std::smatch m;
4961 if (!std::regex_match(location, m, re)) { return false; }
4962
4963 auto scheme = is_ssl() ? "https" : "http";
4964
4965 auto next_scheme = m[1].str();
4966 auto next_host = m[2].str();
4967 auto port_str = m[3].str();
4968 auto next_path = m[4].str();
4969
4970 auto next_port = port_;
4971 if (!port_str.empty()) {
4972 next_port = std::stoi(port_str);
4973 } else if (!next_scheme.empty()) {
4974 next_port = next_scheme == "https" ? 443 : 80;
4975 }
4976
4977 if (next_scheme.empty()) { next_scheme = scheme; }
4978 if (next_host.empty()) { next_host = host_; }
4979 if (next_path.empty()) { next_path = "/"; }
4980
4981 if (next_scheme == scheme && next_host == host_ && next_port == port_) {
4982 return detail::redirect(*this, req, res, next_path);
4983 } else {
4984 if (next_scheme == "https") {
4985#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
4986 SSLClient cli(next_host.c_str(), next_port);
4987 cli.copy_settings(*this);
4988 auto ret = detail::redirect(cli, req, res, next_path);
4989 if (!ret) { error_ = cli.get_last_error(); }
4990 return ret;
4991#else
4992 return false;
4993#endif
4994 } else {
4995 ClientImpl cli(next_host.c_str(), next_port);
4996 cli.copy_settings(*this);
4997 auto ret = detail::redirect(cli, req, res, next_path);
4998 if (!ret) { error_ = cli.get_last_error(); }
4999 return ret;
5000 }
5001 }
5002}
5003
5004inline bool ClientImpl::write_request(Stream &strm, const Request &req,
5005 bool close_connection) {
5006 detail::BufferStream bstrm;
5007
5008 // Request line
5009 const auto &path = detail::encode_url(req.path);
5010
5011 bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
5012
5013 // Additonal headers
5014 Headers headers;
5015 if (close_connection) { headers.emplace("Connection", "close"); }
5016
5017 if (!req.has_header("Host")) {
5018 if (is_ssl()) {
5019 if (port_ == 443) {
5020 headers.emplace("Host", host_);
5021 } else {
5022 headers.emplace("Host", host_and_port_);
5023 }
5024 } else {
5025 if (port_ == 80) {
5026 headers.emplace("Host", host_);
5027 } else {
5028 headers.emplace("Host", host_and_port_);
5029 }
5030 }
5031 }
5032
5033 if (!req.has_header("Accept")) { headers.emplace("Accept", "*/*"); }
5034
5035 if (!req.has_header("User-Agent")) {
5036 headers.emplace("User-Agent", "cpp-httplib/0.7");
5037 }
5038
5039 if (req.body.empty()) {
5040 if (req.content_provider) {
5041 auto length = std::to_string(req.content_length);
5042 headers.emplace("Content-Length", length);
5043 } else {
5044 if (req.method == "POST" || req.method == "PUT" ||
5045 req.method == "PATCH") {
5046 headers.emplace("Content-Length", "0");
5047 }
5048 }
5049 } else {
5050 if (!req.has_header("Content-Type")) {
5051 headers.emplace("Content-Type", "text/plain");
5052 }
5053
5054 if (!req.has_header("Content-Length")) {
5055 auto length = std::to_string(req.body.size());
5056 headers.emplace("Content-Length", length);
5057 }
5058 }
5059
5060 if (!basic_auth_password_.empty()) {
5061 headers.insert(make_basic_authentication_header(
5062 basic_auth_username_, basic_auth_password_, false));
5063 }
5064
5065 if (!proxy_basic_auth_username_.empty() &&
5066 !proxy_basic_auth_password_.empty()) {
5067 headers.insert(make_basic_authentication_header(
5068 proxy_basic_auth_username_, proxy_basic_auth_password_, true));
5069 }
5070
5071 if (!bearer_token_auth_token_.empty()) {
5072 headers.insert(make_bearer_token_authentication_header(
5073 bearer_token_auth_token_, false));
5074 }
5075
5076 if (!proxy_bearer_token_auth_token_.empty()) {
5077 headers.insert(make_bearer_token_authentication_header(
5078 proxy_bearer_token_auth_token_, true));
5079 }
5080
5081 detail::write_headers(bstrm, req, headers);
5082
5083 // Flush buffer
5084 auto &data = bstrm.get_buffer();
5085 if (!detail::write_data(strm, data.data(), data.size())) {
5086 error_ = Error::Write;
5087 return false;
5088 }
5089
5090 // Body
5091 if (req.body.empty()) {
5092 if (req.content_provider) {
5093 size_t offset = 0;
5094 size_t end_offset = req.content_length;
5095
5096 bool ok = true;
5097
5098 DataSink data_sink;
5099 data_sink.write = [&](const char *d, size_t l) {
5100 if (ok) {
5101 if (detail::write_data(strm, d, l)) {
5102 offset += l;
5103 } else {
5104 ok = false;
5105 }
5106 }
5107 };
5108 data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
5109
5110 while (offset < end_offset) {
5111 if (!req.content_provider(offset, end_offset - offset, data_sink)) {
5112 error_ = Error::Canceled;
5113 return false;
5114 }
5115 if (!ok) {
5116 error_ = Error::Write;
5117 return false;
5118 }
5119 }
5120 }
5121 } else {
5122 return detail::write_data(strm, req.body.data(), req.body.size());
5123 }
5124
5125 return true;
5126}
5127
5128inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
5129 const char *method, const char *path, const Headers &headers,
5130 const std::string &body, size_t content_length,
5131 ContentProvider content_provider, const char *content_type) {
5132
5133 Request req;
5134 req.method = method;
5135 req.headers = default_headers_;
5136 req.headers.insert(headers.begin(), headers.end());
5137 req.path = path;
5138
5139 if (content_type) { req.headers.emplace("Content-Type", content_type); }
5140
5141#ifdef CPPHTTPLIB_ZLIB_SUPPORT
5142 if (compress_) {
5143 detail::gzip_compressor compressor;
5144
5145 if (content_provider) {
5146 auto ok = true;
5147 size_t offset = 0;
5148
5149 DataSink data_sink;
5150 data_sink.write = [&](const char *data, size_t data_len) {
5151 if (ok) {
5152 auto last = offset + data_len == content_length;
5153
5154 auto ret = compressor.compress(
5155 data, data_len, last, [&](const char *data, size_t data_len) {
5156 req.body.append(data, data_len);
5157 return true;
5158 });
5159
5160 if (ret) {
5161 offset += data_len;
5162 } else {
5163 ok = false;
5164 }
5165 }
5166 };
5167 data_sink.is_writable = [&](void) { return ok && true; };
5168
5169 while (ok && offset < content_length) {
5170 if (!content_provider(offset, content_length - offset, data_sink)) {
5171 error_ = Error::Canceled;
5172 return nullptr;
5173 }
5174 }
5175 } else {
5176 if (!compressor.compress(body.data(), body.size(), true,
5177 [&](const char *data, size_t data_len) {
5178 req.body.append(data, data_len);
5179 return true;
5180 })) {
5181 return nullptr;
5182 }
5183 }
5184
5185 req.headers.emplace("Content-Encoding", "gzip");
5186 } else
5187#endif
5188 {
5189 if (content_provider) {
5190 req.content_length = content_length;
5191 req.content_provider = std::move(content_provider);
5192 } else {
5193 req.body = body;
5194 }
5195 }
5196
5197 auto res = detail::make_unique<Response>();
5198
5199 return send(req, *res) ? std::move(res) : nullptr;
5200}
5201
5202inline bool ClientImpl::process_request(Stream &strm, const Request &req,
5203 Response &res, bool close_connection) {
5204 // Send request
5205 if (!write_request(strm, req, close_connection)) { return false; }
5206
5207 // Receive response and headers
5208 if (!read_response_line(strm, res) ||
5209 !detail::read_headers(strm, res.headers)) {
5210 error_ = Error::Read;
5211 return false;
5212 }
5213
5214 if (req.response_handler) {
5215 if (!req.response_handler(res)) {
5216 error_ = Error::Canceled;
5217 return false;
5218 }
5219 }
5220
5221 // Body
5222 if (req.method != "HEAD" && req.method != "CONNECT") {
5223 auto out =
5224 req.content_receiver
5225 ? static_cast<ContentReceiverWithProgress>(
5226 [&](const char *buf, size_t n, uint64_t off, uint64_t len) {
5227 auto ret = req.content_receiver(buf, n, off, len);
5228 if (!ret) { error_ = Error::Canceled; }
5229 return ret;
5230 })
5231 : static_cast<ContentReceiverWithProgress>(
5232 [&](const char *buf, size_t n, uint64_t /*off*/,
5233 uint64_t /*len*/) {
5234 if (res.body.size() + n > res.body.max_size()) {
5235 return false;
5236 }
5237 res.body.append(buf, n);
5238 return true;
5239 });
5240
5241 auto progress = [&](uint64_t current, uint64_t total) {
5242 if (!req.progress) { return true; }
5243 auto ret = req.progress(current, total);
5244 if (!ret) { error_ = Error::Canceled; }
5245 return ret;
5246 };
5247
5248 int dummy_status;
5249 if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
5250 dummy_status, std::move(progress), std::move(out),
5251 decompress_)) {
5252 if (error_ != Error::Canceled) { error_ = Error::Read; }
5253 return false;
5254 }
5255 }
5256
5257 if (res.get_header_value("Connection") == "close" ||
5258 (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
5259 // TODO this requires a not-entirely-obvious chain of calls to be correct
5260 // for this to be safe. Maybe a code refactor (such as moving this out to
5261 // the send function and getting rid of the recursiveness of the mutex)
5262 // could make this more obvious.
5263
5264 // This is safe to call because process_request is only called by handle_request
5265 // which is only called by send, which locks the request mutex during the process.
5266 // It would be a bug to call it from a different thread since it's a thread-safety
5267 // issue to do these things to the socket if another thread is using the socket.
5268 lock_socket_and_shutdown_and_close();
5269 }
5270
5271 // Log
5272 if (logger_) { logger_(req, res); }
5273
5274 return true;
5275}
5276
5277inline bool
5278ClientImpl::process_socket(const Socket &socket,
5279 std::function<bool(Stream &strm)> callback) {
5280 return detail::process_client_socket(
5281 socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
5282 write_timeout_usec_, std::move(callback));
5283}
5284
5285inline bool ClientImpl::is_ssl() const { return false; }
5286
5287inline Result ClientImpl::Get(const char *path) {
5288 return Get(path, Headers(), Progress());
5289}
5290
5291inline Result ClientImpl::Get(const char *path, Progress progress) {
5292 return Get(path, Headers(), std::move(progress));
5293}
5294
5295inline Result ClientImpl::Get(const char *path, const Headers &headers) {
5296 return Get(path, headers, Progress());
5297}
5298
5299inline Result ClientImpl::Get(const char *path, const Headers &headers,
5300 Progress progress) {
5301 Request req;
5302 req.method = "GET";
5303 req.path = path;
5304 req.headers = default_headers_;
5305 req.headers.insert(headers.begin(), headers.end());
5306 req.progress = std::move(progress);
5307
5308 auto res = detail::make_unique<Response>();
5309 auto ret = send(req, *res);
5310 return Result{ret ? std::move(res) : nullptr, get_last_error()};
5311}
5312
5313inline Result ClientImpl::Get(const char *path,
5314 ContentReceiver content_receiver) {
5315 return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr);
5316}
5317
5318inline Result ClientImpl::Get(const char *path,
5319 ContentReceiver content_receiver,
5320 Progress progress) {
5321 return Get(path, Headers(), nullptr, std::move(content_receiver),
5322 std::move(progress));
5323}
5324
5325inline Result ClientImpl::Get(const char *path, const Headers &headers,
5326 ContentReceiver content_receiver) {
5327 return Get(path, headers, nullptr, std::move(content_receiver), nullptr);
5328}
5329
5330inline Result ClientImpl::Get(const char *path, const Headers &headers,
5331 ContentReceiver content_receiver,
5332 Progress progress) {
5333 return Get(path, headers, nullptr, std::move(content_receiver),
5334 std::move(progress));
5335}
5336
5337inline Result ClientImpl::Get(const char *path,
5338 ResponseHandler response_handler,
5339 ContentReceiver content_receiver) {
5340 return Get(path, Headers(), std::move(response_handler),
5341 std::move(content_receiver), nullptr);
5342}
5343
5344inline Result ClientImpl::Get(const char *path, const Headers &headers,
5345 ResponseHandler response_handler,
5346 ContentReceiver content_receiver) {
5347 return Get(path, headers, std::move(response_handler),
5348 std::move(content_receiver), nullptr);
5349}
5350
5351inline Result ClientImpl::Get(const char *path,
5352 ResponseHandler response_handler,
5353 ContentReceiver content_receiver,
5354 Progress progress) {
5355 return Get(path, Headers(), std::move(response_handler),
5356 std::move(content_receiver), std::move(progress));
5357}
5358
5359inline Result ClientImpl::Get(const char *path, const Headers &headers,
5360 ResponseHandler response_handler,
5361 ContentReceiver content_receiver,
5362 Progress progress) {
5363 Request req;
5364 req.method = "GET";
5365 req.path = path;
5366 req.headers = default_headers_;
5367 req.headers.insert(headers.begin(), headers.end());
5368 req.response_handler = std::move(response_handler);
5369 req.content_receiver =
5370 [content_receiver](const char *data, size_t data_length,
5371 uint64_t /*offset*/, uint64_t /*total_length*/) {
5372 return content_receiver(data, data_length);
5373 };
5374 req.progress = std::move(progress);
5375
5376 auto res = detail::make_unique<Response>();
5377 auto ret = send(req, *res);
5378 return Result{ret ? std::move(res) : nullptr, get_last_error()};
5379}
5380
5381inline Result ClientImpl::Head(const char *path) {
5382 return Head(path, Headers());
5383}
5384
5385inline Result ClientImpl::Head(const char *path, const Headers &headers) {
5386 Request req;
5387 req.method = "HEAD";
5388 req.headers = default_headers_;
5389 req.headers.insert(headers.begin(), headers.end());
5390 req.path = path;
5391
5392 auto res = detail::make_unique<Response>();
5393 auto ret = send(req, *res);
5394 return Result{ret ? std::move(res) : nullptr, get_last_error()};
5395}
5396
5397inline Result ClientImpl::Post(const char *path) {
5398 return Post(path, std::string(), nullptr);
5399}
5400
5401inline Result ClientImpl::Post(const char *path, const std::string &body,
5402 const char *content_type) {
5403 return Post(path, Headers(), body, content_type);
5404}
5405
5406inline Result ClientImpl::Post(const char *path, const Headers &headers,
5407 const std::string &body,
5408 const char *content_type) {
5409 auto ret = send_with_content_provider("POST", path, headers, body, 0, nullptr,
5410 content_type);
5411 return Result{std::move(ret), get_last_error()};
5412}
5413
5414inline Result ClientImpl::Post(const char *path, const Params &params) {
5415 return Post(path, Headers(), params);
5416}
5417
5418inline Result ClientImpl::Post(const char *path, size_t content_length,
5419 ContentProvider content_provider,
5420 const char *content_type) {
5421 return Post(path, Headers(), content_length, std::move(content_provider),
5422 content_type);
5423}
5424
5425inline Result ClientImpl::Post(const char *path, const Headers &headers,
5426 size_t content_length,
5427 ContentProvider content_provider,
5428 const char *content_type) {
5429 auto ret = send_with_content_provider(
5430 "POST", path, headers, std::string(), content_length,
5431 std::move(content_provider), content_type);
5432 return Result{std::move(ret), get_last_error()};
5433}
5434
5435inline Result ClientImpl::Post(const char *path, const Headers &headers,
5436 const Params &params) {
5437 auto query = detail::params_to_query_str(params);
5438 return Post(path, headers, query, "application/x-www-form-urlencoded");
5439}
5440
5441inline Result ClientImpl::Post(const char *path,
5442 const MultipartFormDataItems &items) {
5443 return Post(path, Headers(), items);
5444}
5445
5446inline Result ClientImpl::Post(const char *path, const Headers &headers,
5447 const MultipartFormDataItems &items) {
5448 return Post(path, headers, items, detail::make_multipart_data_boundary());
5449}
5450inline Result ClientImpl::Post(const char *path, const Headers &headers,
5451 const MultipartFormDataItems &items,
5452 const std::string &boundary) {
5453 for (size_t i = 0; i < boundary.size(); i++) {
5454 char c = boundary[i];
5455 if (!std::isalnum(c) && c != '-' && c != '_') {
5456 error_ = Error::UnsupportedMultipartBoundaryChars;
5457 return Result{nullptr, error_};
5458 }
5459 }
5460
5461 std::string body;
5462
5463 for (const auto &item : items) {
5464 body += "--" + boundary + "\r\n";
5465 body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
5466 if (!item.filename.empty()) {
5467 body += "; filename=\"" + item.filename + "\"";
5468 }
5469 body += "\r\n";
5470 if (!item.content_type.empty()) {
5471 body += "Content-Type: " + item.content_type + "\r\n";
5472 }
5473 body += "\r\n";
5474 body += item.content + "\r\n";
5475 }
5476
5477 body += "--" + boundary + "--\r\n";
5478
5479 std::string content_type = "multipart/form-data; boundary=" + boundary;
5480 return Post(path, headers, body, content_type.c_str());
5481}
5482
5483inline Result ClientImpl::Put(const char *path) {
5484 return Put(path, std::string(), nullptr);
5485}
5486
5487inline Result ClientImpl::Put(const char *path, const std::string &body,
5488 const char *content_type) {
5489 return Put(path, Headers(), body, content_type);
5490}
5491
5492inline Result ClientImpl::Put(const char *path, const Headers &headers,
5493 const std::string &body,
5494 const char *content_type) {
5495 auto ret = send_with_content_provider("PUT", path, headers, body, 0, nullptr,
5496 content_type);
5497 return Result{std::move(ret), get_last_error()};
5498}
5499
5500inline Result ClientImpl::Put(const char *path, size_t content_length,
5501 ContentProvider content_provider,
5502 const char *content_type) {
5503 return Put(path, Headers(), content_length, std::move(content_provider),
5504 content_type);
5505}
5506
5507inline Result ClientImpl::Put(const char *path, const Headers &headers,
5508 size_t content_length,
5509 ContentProvider content_provider,
5510 const char *content_type) {
5511 auto ret = send_with_content_provider(
5512 "PUT", path, headers, std::string(), content_length,
5513 std::move(content_provider), content_type);
5514 return Result{std::move(ret), get_last_error()};
5515}
5516
5517inline Result ClientImpl::Put(const char *path, const Params &params) {
5518 return Put(path, Headers(), params);
5519}
5520
5521inline Result ClientImpl::Put(const char *path, const Headers &headers,
5522 const Params &params) {
5523 auto query = detail::params_to_query_str(params);
5524 return Put(path, headers, query, "application/x-www-form-urlencoded");
5525}
5526
5527inline Result ClientImpl::Patch(const char *path, const std::string &body,
5528 const char *content_type) {
5529 return Patch(path, Headers(), body, content_type);
5530}
5531
5532inline Result ClientImpl::Patch(const char *path, const Headers &headers,
5533 const std::string &body,
5534 const char *content_type) {
5535 auto ret = send_with_content_provider("PATCH", path, headers, body, 0,
5536 nullptr, content_type);
5537 return Result{std::move(ret), get_last_error()};
5538}
5539
5540inline Result ClientImpl::Patch(const char *path, size_t content_length,
5541 ContentProvider content_provider,
5542 const char *content_type) {
5543 return Patch(path, Headers(), content_length, std::move(content_provider),
5544 content_type);
5545}
5546
5547inline Result ClientImpl::Patch(const char *path, const Headers &headers,
5548 size_t content_length,
5549 ContentProvider content_provider,
5550 const char *content_type) {
5551 auto ret = send_with_content_provider(
5552 "PATCH", path, headers, std::string(), content_length,
5553 std::move(content_provider), content_type);
5554 return Result{std::move(ret), get_last_error()};
5555}
5556
5557inline Result ClientImpl::Delete(const char *path) {
5558 return Delete(path, Headers(), std::string(), nullptr);
5559}
5560
5561inline Result ClientImpl::Delete(const char *path, const std::string &body,
5562 const char *content_type) {
5563 return Delete(path, Headers(), body, content_type);
5564}
5565
5566inline Result ClientImpl::Delete(const char *path, const Headers &headers) {
5567 return Delete(path, headers, std::string(), nullptr);
5568}
5569
5570inline Result ClientImpl::Delete(const char *path, const Headers &headers,
5571 const std::string &body,
5572 const char *content_type) {
5573 Request req;
5574 req.method = "DELETE";
5575 req.headers = default_headers_;
5576 req.headers.insert(headers.begin(), headers.end());
5577 req.path = path;
5578
5579 if (content_type) { req.headers.emplace("Content-Type", content_type); }
5580 req.body = body;
5581
5582 auto res = detail::make_unique<Response>();
5583 auto ret = send(req, *res);
5584 return Result{ret ? std::move(res) : nullptr, get_last_error()};
5585}
5586
5587inline Result ClientImpl::Options(const char *path) {
5588 return Options(path, Headers());
5589}
5590
5591inline Result ClientImpl::Options(const char *path, const Headers &headers) {
5592 Request req;
5593 req.method = "OPTIONS";
5594 req.headers = default_headers_;
5595 req.headers.insert(headers.begin(), headers.end());
5596 req.path = path;
5597
5598 auto res = detail::make_unique<Response>();
5599 auto ret = send(req, *res);
5600 return Result{ret ? std::move(res) : nullptr, get_last_error()};
5601}
5602
5603inline size_t ClientImpl::is_socket_open() const {
5604 std::lock_guard<std::mutex> guard(socket_mutex_);
5605 return socket_.is_open();
5606}
5607
5608inline void ClientImpl::stop() {
5609 std::lock_guard<std::mutex> guard(socket_mutex_);
5610 // There is no guarantee that this doesn't get overwritten later, but set it so that
5611 // there is a good chance that any threads stopping as a result pick up this error.
5612 error_ = Error::Canceled;
5613
5614 // If there is anything ongoing right now, the ONLY thread-safe thing we can do
5615 // is to shutdown_socket, so that threads using this socket suddenly discover
5616 // they can't read/write any more and error out.
5617 // Everything else (closing the socket, shutting ssl down) is unsafe because these
5618 // actions are not thread-safe.
5619 if (socket_requests_in_flight_ > 0) {
5620 shutdown_socket(socket_);
5621 // Aside from that, we set a flag for the socket to be closed when we're done.
5622 socket_should_be_closed_when_request_is_done_ = true;
5623 return;
5624 }
5625
5626 //Otherwise, sitll holding the mutex, we can shut everything down ourselves
5627 shutdown_ssl(socket_, true);
5628 shutdown_socket(socket_);
5629 close_socket(socket_);
5630}
5631
5632inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
5633 connection_timeout_sec_ = sec;
5634 connection_timeout_usec_ = usec;
5635}
5636
5637inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) {
5638 read_timeout_sec_ = sec;
5639 read_timeout_usec_ = usec;
5640}
5641
5642inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
5643 write_timeout_sec_ = sec;
5644 write_timeout_usec_ = usec;
5645}
5646
5647inline void ClientImpl::set_basic_auth(const char *username,
5648 const char *password) {
5649 basic_auth_username_ = username;
5650 basic_auth_password_ = password;
5651}
5652
5653inline void ClientImpl::set_bearer_token_auth(const char *token) {
5654 bearer_token_auth_token_ = token;
5655}
5656
5657#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
5658inline void ClientImpl::set_digest_auth(const char *username,
5659 const char *password) {
5660 digest_auth_username_ = username;
5661 digest_auth_password_ = password;
5662}
5663#endif
5664
5665inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
5666
5667inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
5668
5669inline void ClientImpl::set_default_headers(Headers headers) {
5670 default_headers_ = std::move(headers);
5671}
5672
5673inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
5674
5675inline void ClientImpl::set_socket_options(SocketOptions socket_options) {
5676 socket_options_ = std::move(socket_options);
5677}
5678
5679inline void ClientImpl::set_compress(bool on) { compress_ = on; }
5680
5681inline void ClientImpl::set_decompress(bool on) { decompress_ = on; }
5682
5683inline void ClientImpl::set_interface(const char *intf) { interface_ = intf; }
5684
5685inline void ClientImpl::set_proxy(const char *host, int port) {
5686 proxy_host_ = host;
5687 proxy_port_ = port;
5688}
5689
5690inline void ClientImpl::set_proxy_basic_auth(const char *username,
5691 const char *password) {
5692 proxy_basic_auth_username_ = username;
5693 proxy_basic_auth_password_ = password;
5694}
5695
5696inline void ClientImpl::set_proxy_bearer_token_auth(const char *token) {
5697 proxy_bearer_token_auth_token_ = token;
5698}
5699
5700#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
5701inline void ClientImpl::set_proxy_digest_auth(const char *username,
5702 const char *password) {
5703 proxy_digest_auth_username_ = username;
5704 proxy_digest_auth_password_ = password;
5705}
5706#endif
5707
5708#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
5709inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
5710 server_certificate_verification_ = enabled;
5711}
5712#endif
5713
5714inline void ClientImpl::set_logger(Logger logger) {
5715 logger_ = std::move(logger);
5716}
5717
5718/*
5719 * SSL Implementation
5720 */
5721#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
5722namespace detail {
5723
5724template <typename U, typename V>
5725inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex,
5726 U SSL_connect_or_accept, V setup) {
5727 SSL *ssl = nullptr;
5728 {
5729 std::lock_guard<std::mutex> guard(ctx_mutex);
5730 ssl = SSL_new(ctx);
5731 }
5732
5733 if (ssl) {
5734 auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
5735 SSL_set_bio(ssl, bio, bio);
5736
5737 if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) {
5738 SSL_shutdown(ssl);
5739 {
5740 std::lock_guard<std::mutex> guard(ctx_mutex);
5741 SSL_free(ssl);
5742 }
5743 return nullptr;
5744 }
5745 }
5746
5747 return ssl;
5748}
5749
5750inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl,
5751 bool shutdown_gracefully) {
5752 // sometimes we may want to skip this to try to avoid SIGPIPE if we know
5753 // the remote has closed the network connection
5754 // Note that it is not always possible to avoid SIGPIPE, this is merely a best-efforts.
5755 if (shutdown_gracefully) {
5756 SSL_shutdown(ssl);
5757 }
5758
5759 std::lock_guard<std::mutex> guard(ctx_mutex);
5760 SSL_free(ssl);
5761}
5762
5763template <typename T>
5764inline bool
5765process_server_socket_ssl(SSL *ssl, socket_t sock, size_t keep_alive_max_count,
5766 time_t keep_alive_timeout_sec,
5767 time_t read_timeout_sec, time_t read_timeout_usec,
5768 time_t write_timeout_sec, time_t write_timeout_usec,
5769 T callback) {
5770 return process_server_socket_core(
5771 sock, keep_alive_max_count, keep_alive_timeout_sec,
5772 [&](bool close_connection, bool &connection_closed) {
5773 SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
5774 write_timeout_sec, write_timeout_usec);
5775 return callback(strm, close_connection, connection_closed);
5776 });
5777}
5778
5779template <typename T>
5780inline bool
5781process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec,
5782 time_t read_timeout_usec, time_t write_timeout_sec,
5783 time_t write_timeout_usec, T callback) {
5784 SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
5785 write_timeout_sec, write_timeout_usec);
5786 return callback(strm);
5787}
5788
5789#if OPENSSL_VERSION_NUMBER < 0x10100000L
5790static std::shared_ptr<std::vector<std::mutex>> openSSL_locks_;
5791
5792class SSLThreadLocks {
5793public:
5794 SSLThreadLocks() {
5795 openSSL_locks_ =
5796 std::make_shared<std::vector<std::mutex>>(CRYPTO_num_locks());
5797 CRYPTO_set_locking_callback(locking_callback);
5798 }
5799
5800 ~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); }
5801
5802private:
5803 static void locking_callback(int mode, int type, const char * /*file*/,
5804 int /*line*/) {
5805 auto &lk = (*openSSL_locks_)[static_cast<size_t>(type)];
5806 if (mode & CRYPTO_LOCK) {
5807 lk.lock();
5808 } else {
5809 lk.unlock();
5810 }
5811 }
5812};
5813
5814#endif
5815
5816class SSLInit {
5817public:
5818 SSLInit() {
5819#if OPENSSL_VERSION_NUMBER < 0x1010001fL
5820 SSL_load_error_strings();
5821 SSL_library_init();
5822#else
5823 OPENSSL_init_ssl(
5824 OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
5825#endif
5826 }
5827
5828 ~SSLInit() {
5829#if OPENSSL_VERSION_NUMBER < 0x1010001fL
5830 ERR_free_strings();
5831#endif
5832 }
5833
5834private:
5835#if OPENSSL_VERSION_NUMBER < 0x10100000L
5836 SSLThreadLocks thread_init_;
5837#endif
5838};
5839
5840// SSL socket stream implementation
5841inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl,
5842 time_t read_timeout_sec,
5843 time_t read_timeout_usec,
5844 time_t write_timeout_sec,
5845 time_t write_timeout_usec)
5846 : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec),
5847 read_timeout_usec_(read_timeout_usec),
5848 write_timeout_sec_(write_timeout_sec),
5849 write_timeout_usec_(write_timeout_usec) {
5850 SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
5851}
5852
5853inline SSLSocketStream::~SSLSocketStream() {}
5854
5855inline bool SSLSocketStream::is_readable() const {
5856 return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
5857}
5858
5859inline bool SSLSocketStream::is_writable() const {
5860 return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) >
5861 0;
5862}
5863
5864inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
5865 if (SSL_pending(ssl_) > 0) {
5866 return SSL_read(ssl_, ptr, static_cast<int>(size));
5867 } else if (is_readable()) {
5868 auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
5869 if (ret < 0) {
5870 auto err = SSL_get_error(ssl_, ret);
5871 while (err == SSL_ERROR_WANT_READ) {
5872 if (SSL_pending(ssl_) > 0) {
5873 return SSL_read(ssl_, ptr, static_cast<int>(size));
5874 } else if (is_readable()) {
5875 ret = SSL_read(ssl_, ptr, static_cast<int>(size));
5876 if (ret >= 0) {
5877 return ret;
5878 }
5879 err = SSL_get_error(ssl_, ret);
5880 } else {
5881 return -1;
5882 }
5883 }
5884 }
5885 return ret;
5886 }
5887 return -1;
5888}
5889
5890inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
5891 if (is_writable()) { return SSL_write(ssl_, ptr, static_cast<int>(size)); }
5892 return -1;
5893}
5894
5895inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
5896 int &port) const {
5897 detail::get_remote_ip_and_port(sock_, ip, port);
5898}
5899
5900static SSLInit sslinit_;
5901
5902} // namespace detail
5903
5904// SSL HTTP server implementation
5905inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
5906 const char *client_ca_cert_file_path,
5907 const char *client_ca_cert_dir_path) {
5908 ctx_ = SSL_CTX_new(SSLv23_server_method());
5909
5910 if (ctx_) {
5911 SSL_CTX_set_options(ctx_,
5912 SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
5913 SSL_OP_NO_COMPRESSION |
5914 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
5915
5916 // auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
5917 // SSL_CTX_set_tmp_ecdh(ctx_, ecdh);
5918 // EC_KEY_free(ecdh);
5919
5920 if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
5921 SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
5922 1) {
5923 SSL_CTX_free(ctx_);
5924 ctx_ = nullptr;
5925 } else if (client_ca_cert_file_path || client_ca_cert_dir_path) {
5926 // if (client_ca_cert_file_path) {
5927 // auto list = SSL_load_client_CA_file(client_ca_cert_file_path);
5928 // SSL_CTX_set_client_CA_list(ctx_, list);
5929 // }
5930
5931 SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path,
5932 client_ca_cert_dir_path);
5933
5934 SSL_CTX_set_verify(
5935 ctx_,
5936 SSL_VERIFY_PEER |
5937 SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE,
5938 nullptr);
5939 }
5940 }
5941}
5942
5943inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
5944 X509_STORE *client_ca_cert_store) {
5945 ctx_ = SSL_CTX_new(SSLv23_server_method());
5946
5947 if (ctx_) {
5948 SSL_CTX_set_options(ctx_,
5949 SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
5950 SSL_OP_NO_COMPRESSION |
5951 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
5952
5953 if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
5954 SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
5955 SSL_CTX_free(ctx_);
5956 ctx_ = nullptr;
5957 } else if (client_ca_cert_store) {
5958
5959 SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
5960
5961 SSL_CTX_set_verify(
5962 ctx_,
5963 SSL_VERIFY_PEER |
5964 SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE,
5965 nullptr);
5966 }
5967 }
5968}
5969
5970inline SSLServer::~SSLServer() {
5971 if (ctx_) { SSL_CTX_free(ctx_); }
5972}
5973
5974inline bool SSLServer::is_valid() const { return ctx_; }
5975
5976inline bool SSLServer::process_and_close_socket(socket_t sock) {
5977 auto ssl = detail::ssl_new(sock, ctx_, ctx_mutex_, SSL_accept,
5978 [](SSL * /*ssl*/) { return true; });
5979
5980 if (ssl) {
5981 auto ret = detail::process_server_socket_ssl(
5982 ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
5983 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
5984 write_timeout_usec_,
5985 [this, ssl](Stream &strm, bool close_connection,
5986 bool &connection_closed) {
5987 return process_request(strm, close_connection, connection_closed,
5988 [&](Request &req) { req.ssl = ssl; });
5989 });
5990
5991 detail::ssl_delete(ctx_mutex_, ssl, ret);
5992 detail::shutdown_socket(sock);
5993 detail::close_socket(sock);
5994 return ret;
5995 }
5996
5997 detail::shutdown_socket(sock);
5998 detail::close_socket(sock);
5999 return false;
6000}
6001
6002// SSL HTTP client implementation
6003inline SSLClient::SSLClient(const std::string &host)
6004 : SSLClient(host, 443, std::string(), std::string()) {}
6005
6006inline SSLClient::SSLClient(const std::string &host, int port)
6007 : SSLClient(host, port, std::string(), std::string()) {}
6008
6009inline SSLClient::SSLClient(const std::string &host, int port,
6010 const std::string &client_cert_path,
6011 const std::string &client_key_path)
6012 : ClientImpl(host, port, client_cert_path, client_key_path) {
6013 ctx_ = SSL_CTX_new(SSLv23_client_method());
6014
6015 detail::split(&host_[0], &host_[host_.size()], '.',
6016 [&](const char *b, const char *e) {
6017 host_components_.emplace_back(std::string(b, e));
6018 });
6019 if (!client_cert_path.empty() && !client_key_path.empty()) {
6020 if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(),
6021 SSL_FILETYPE_PEM) != 1 ||
6022 SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(),
6023 SSL_FILETYPE_PEM) != 1) {
6024 SSL_CTX_free(ctx_);
6025 ctx_ = nullptr;
6026 }
6027 }
6028}
6029
6030inline SSLClient::SSLClient(const std::string &host, int port,
6031 X509 *client_cert, EVP_PKEY *client_key)
6032 : ClientImpl(host, port) {
6033 ctx_ = SSL_CTX_new(SSLv23_client_method());
6034
6035 detail::split(&host_[0], &host_[host_.size()], '.',
6036 [&](const char *b, const char *e) {
6037 host_components_.emplace_back(std::string(b, e));
6038 });
6039 if (client_cert != nullptr && client_key != nullptr) {
6040 if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
6041 SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
6042 SSL_CTX_free(ctx_);
6043 ctx_ = nullptr;
6044 }
6045 }
6046}
6047
6048inline SSLClient::~SSLClient() {
6049 if (ctx_) { SSL_CTX_free(ctx_); }
6050 // Make sure to shut down SSL since shutdown_ssl will resolve to the
6051 // base function rather than the derived function once we get to the
6052 // base class destructor, and won't free the SSL (causing a leak).
6053 SSLClient::shutdown_ssl(socket_, true);
6054}
6055
6056inline bool SSLClient::is_valid() const { return ctx_; }
6057
6058inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path,
6059 const char *ca_cert_dir_path) {
6060 if (ca_cert_file_path) { ca_cert_file_path_ = ca_cert_file_path; }
6061 if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; }
6062}
6063
6064inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
6065 if (ca_cert_store) {
6066 if (ctx_) {
6067 if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) {
6068 // Free memory allocated for old cert and use new store `ca_cert_store`
6069 SSL_CTX_set_cert_store(ctx_, ca_cert_store);
6070 }
6071 } else {
6072 X509_STORE_free(ca_cert_store);
6073 }
6074 }
6075}
6076
6077inline long SSLClient::get_openssl_verify_result() const {
6078 return verify_result_;
6079}
6080
6081inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; }
6082
6083inline bool SSLClient::create_and_connect_socket(Socket &socket) {
6084 return is_valid() && ClientImpl::create_and_connect_socket(socket);
6085}
6086
6087// Assumes that socket_mutex_ is locked and that there are no requests in flight
6088inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
6089 bool &success) {
6090 success = true;
6091 Response res2;
6092 if (!detail::process_client_socket(
6093 socket.sock, read_timeout_sec_, read_timeout_usec_,
6094 write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
6095 Request req2;
6096 req2.method = "CONNECT";
6097 req2.path = host_and_port_;
6098 return process_request(strm, req2, res2, false);
6099 })) {
6100 // Thread-safe to close everything because we are assuming there are no requests in flight
6101 shutdown_ssl(socket, true);
6102 shutdown_socket(socket);
6103 close_socket(socket);
6104 success = false;
6105 return false;
6106 }
6107
6108 if (res2.status == 407) {
6109 if (!proxy_digest_auth_username_.empty() &&
6110 !proxy_digest_auth_password_.empty()) {
6111 std::map<std::string, std::string> auth;
6112 if (detail::parse_www_authenticate(res2, auth, true)) {
6113 Response res3;
6114 if (!detail::process_client_socket(
6115 socket.sock, read_timeout_sec_, read_timeout_usec_,
6116 write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
6117 Request req3;
6118 req3.method = "CONNECT";
6119 req3.path = host_and_port_;
6120 req3.headers.insert(detail::make_digest_authentication_header(
6121 req3, auth, 1, detail::random_string(10),
6122 proxy_digest_auth_username_, proxy_digest_auth_password_,
6123 true));
6124 return process_request(strm, req3, res3, false);
6125 })) {
6126 // Thread-safe to close everything because we are assuming there are no requests in flight
6127 shutdown_ssl(socket, true);
6128 shutdown_socket(socket);
6129 close_socket(socket);
6130 success = false;
6131 return false;
6132 }
6133 }
6134 } else {
6135 res = res2;
6136 return false;
6137 }
6138 }
6139
6140 return true;
6141}
6142
6143inline bool SSLClient::load_certs() {
6144 bool ret = true;
6145
6146 std::call_once(initialize_cert_, [&]() {
6147 std::lock_guard<std::mutex> guard(ctx_mutex_);
6148 if (!ca_cert_file_path_.empty()) {
6149 if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(),
6150 nullptr)) {
6151 ret = false;
6152 }
6153 } else if (!ca_cert_dir_path_.empty()) {
6154 if (!SSL_CTX_load_verify_locations(ctx_, nullptr,
6155 ca_cert_dir_path_.c_str())) {
6156 ret = false;
6157 }
6158 } else {
6159#ifdef _WIN32
6160 detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
6161#else
6162 SSL_CTX_set_default_verify_paths(ctx_);
6163#endif
6164 }
6165 });
6166
6167 return ret;
6168}
6169
6170inline bool SSLClient::initialize_ssl(Socket &socket) {
6171 auto ssl = detail::ssl_new(
6172 socket.sock, ctx_, ctx_mutex_,
6173 [&](SSL *ssl) {
6174 if (server_certificate_verification_) {
6175 if (!load_certs()) {
6176 error_ = Error::SSLLoadingCerts;
6177 return false;
6178 }
6179 SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr);
6180 }
6181
6182 if (SSL_connect(ssl) != 1) {
6183 error_ = Error::SSLConnection;
6184 return false;
6185 }
6186
6187 if (server_certificate_verification_) {
6188 verify_result_ = SSL_get_verify_result(ssl);
6189
6190 if (verify_result_ != X509_V_OK) {
6191 error_ = Error::SSLServerVerification;
6192 return false;
6193 }
6194
6195 auto server_cert = SSL_get_peer_certificate(ssl);
6196
6197 if (server_cert == nullptr) {
6198 error_ = Error::SSLServerVerification;
6199 return false;
6200 }
6201
6202 if (!verify_host(server_cert)) {
6203 X509_free(server_cert);
6204 error_ = Error::SSLServerVerification;
6205 return false;
6206 }
6207 X509_free(server_cert);
6208 }
6209
6210 return true;
6211 },
6212 [&](SSL *ssl) {
6213 SSL_set_tlsext_host_name(ssl, host_.c_str());
6214 return true;
6215 });
6216
6217 if (ssl) {
6218 socket.ssl = ssl;
6219 return true;
6220 }
6221
6222 shutdown_socket(socket);
6223 close_socket(socket);
6224 return false;
6225}
6226
6227inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
6228 if (socket.sock == INVALID_SOCKET) {
6229 assert(socket.ssl == nullptr);
6230 return;
6231 }
6232 if (socket.ssl) {
6233 detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully);
6234 socket.ssl = nullptr;
6235 }
6236 assert(socket.ssl == nullptr);
6237}
6238
6239inline bool
6240SSLClient::process_socket(const Socket &socket,
6241 std::function<bool(Stream &strm)> callback) {
6242 assert(socket.ssl);
6243 return detail::process_client_socket_ssl(
6244 socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
6245 write_timeout_sec_, write_timeout_usec_, std::move(callback));
6246}
6247
6248inline bool SSLClient::is_ssl() const { return true; }
6249
6250inline bool SSLClient::verify_host(X509 *server_cert) const {
6251 /* Quote from RFC2818 section 3.1 "Server Identity"
6252
6253 If a subjectAltName extension of type dNSName is present, that MUST
6254 be used as the identity. Otherwise, the (most specific) Common Name
6255 field in the Subject field of the certificate MUST be used. Although
6256 the use of the Common Name is existing practice, it is deprecated and
6257 Certification Authorities are encouraged to use the dNSName instead.
6258
6259 Matching is performed using the matching rules specified by
6260 [RFC2459]. If more than one identity of a given type is present in
6261 the certificate (e.g., more than one dNSName name, a match in any one
6262 of the set is considered acceptable.) Names may contain the wildcard
6263 character * which is considered to match any single domain name
6264 component or component fragment. E.g., *.a.com matches foo.a.com but
6265 not bar.foo.a.com. f*.com matches foo.com but not bar.com.
6266
6267 In some cases, the URI is specified as an IP address rather than a
6268 hostname. In this case, the iPAddress subjectAltName must be present
6269 in the certificate and must exactly match the IP in the URI.
6270
6271 */
6272 return verify_host_with_subject_alt_name(server_cert) ||
6273 verify_host_with_common_name(server_cert);
6274}
6275
6276inline bool
6277SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
6278 auto ret = false;
6279
6280 auto type = GEN_DNS;
6281
6282 struct in6_addr addr6;
6283 struct in_addr addr;
6284 size_t addr_len = 0;
6285
6286#ifndef __MINGW32__
6287 if (inet_pton(AF_INET6, host_.c_str(), &addr6)) {
6288 type = GEN_IPADD;
6289 addr_len = sizeof(struct in6_addr);
6290 } else if (inet_pton(AF_INET, host_.c_str(), &addr)) {
6291 type = GEN_IPADD;
6292 addr_len = sizeof(struct in_addr);
6293 }
6294#endif
6295
6296 auto alt_names = static_cast<const struct stack_st_GENERAL_NAME *>(
6297 X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
6298
6299 if (alt_names) {
6300 auto dsn_matched = false;
6301 auto ip_mached = false;
6302
6303 auto count = sk_GENERAL_NAME_num(alt_names);
6304
6305 for (decltype(count) i = 0; i < count && !dsn_matched; i++) {
6306 auto val = sk_GENERAL_NAME_value(alt_names, i);
6307 if (val->type == type) {
6308 auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5);
6309 auto name_len = (size_t)ASN1_STRING_length(val->d.ia5);
6310
6311 if (strlen(name) == name_len) {
6312 switch (type) {
6313 case GEN_DNS: dsn_matched = check_host_name(name, name_len); break;
6314
6315 case GEN_IPADD:
6316 if (!memcmp(&addr6, name, addr_len) ||
6317 !memcmp(&addr, name, addr_len)) {
6318 ip_mached = true;
6319 }
6320 break;
6321 }
6322 }
6323 }
6324 }
6325
6326 if (dsn_matched || ip_mached) { ret = true; }
6327 }
6328
6329 GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
6330 return ret;
6331}
6332
6333inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const {
6334 const auto subject_name = X509_get_subject_name(server_cert);
6335
6336 if (subject_name != nullptr) {
6337 char name[BUFSIZ];
6338 auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
6339 name, sizeof(name));
6340
6341 if (name_len != -1) {
6342 return check_host_name(name, static_cast<size_t>(name_len));
6343 }
6344 }
6345
6346 return false;
6347}
6348
6349inline bool SSLClient::check_host_name(const char *pattern,
6350 size_t pattern_len) const {
6351 if (host_.size() == pattern_len && host_ == pattern) { return true; }
6352
6353 // Wildcard match
6354 // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
6355 std::vector<std::string> pattern_components;
6356 detail::split(&pattern[0], &pattern[pattern_len], '.',
6357 [&](const char *b, const char *e) {
6358 pattern_components.emplace_back(std::string(b, e));
6359 });
6360
6361 if (host_components_.size() != pattern_components.size()) { return false; }
6362
6363 auto itr = pattern_components.begin();
6364 for (const auto &h : host_components_) {
6365 auto &p = *itr;
6366 if (p != h && p != "*") {
6367 auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' &&
6368 !p.compare(0, p.size() - 1, h));
6369 if (!partial_match) { return false; }
6370 }
6371 ++itr;
6372 }
6373
6374 return true;
6375}
6376#endif
6377
6378// Universal client implementation
6379inline Client::Client(const char *scheme_host_port)
6380 : Client(scheme_host_port, std::string(), std::string()) {}
6381
6382inline Client::Client(const char *scheme_host_port,
6383 const std::string &client_cert_path,
6384 const std::string &client_key_path) {
6385 const static std::regex re(R"(^(?:([a-z]+)://)?([^:/?#]+)(?::(\d+))?)");
6386
6387 std::cmatch m;
6388 if (std::regex_match(scheme_host_port, m, re)) {
6389 auto scheme = m[1].str();
6390
6391#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6392 if (!scheme.empty() && (scheme != "http" && scheme != "https")) {
6393#else
6394 if (!scheme.empty() && scheme != "http") {
6395#endif
6396 std::string msg = "'" + scheme + "' scheme is not supported.";
6397 throw std::invalid_argument(msg);
6398 return;
6399 }
6400
6401 auto is_ssl = scheme == "https";
6402
6403 auto host = m[2].str();
6404
6405 auto port_str = m[3].str();
6406 auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80);
6407
6408 if (is_ssl) {
6409#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6410 cli_ = detail::make_unique<SSLClient>(host.c_str(), port,
6411 client_cert_path, client_key_path);
6412 is_ssl_ = is_ssl;
6413#endif
6414 } else {
6415 cli_ = detail::make_unique<ClientImpl>(host.c_str(), port,
6416 client_cert_path, client_key_path);
6417 }
6418 } else {
6419 cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
6420 client_cert_path, client_key_path);
6421 }
6422}
6423
6424inline Client::Client(const std::string &host, int port)
6425 : cli_(detail::make_unique<ClientImpl>(host, port)) {}
6426
6427inline Client::Client(const std::string &host, int port,
6428 const std::string &client_cert_path,
6429 const std::string &client_key_path)
6430 : cli_(detail::make_unique<ClientImpl>(host, port, client_cert_path,
6431 client_key_path)) {}
6432
6433inline Client::~Client() {}
6434
6435inline bool Client::is_valid() const {
6436 return cli_ != nullptr && cli_->is_valid();
6437}
6438
6439inline Result Client::Get(const char *path) { return cli_->Get(path); }
6440inline Result Client::Get(const char *path, const Headers &headers) {
6441 return cli_->Get(path, headers);
6442}
6443inline Result Client::Get(const char *path, Progress progress) {
6444 return cli_->Get(path, std::move(progress));
6445}
6446inline Result Client::Get(const char *path, const Headers &headers,
6447 Progress progress) {
6448 return cli_->Get(path, headers, std::move(progress));
6449}
6450inline Result Client::Get(const char *path, ContentReceiver content_receiver) {
6451 return cli_->Get(path, std::move(content_receiver));
6452}
6453inline Result Client::Get(const char *path, const Headers &headers,
6454 ContentReceiver content_receiver) {
6455 return cli_->Get(path, headers, std::move(content_receiver));
6456}
6457inline Result Client::Get(const char *path, ContentReceiver content_receiver,
6458 Progress progress) {
6459 return cli_->Get(path, std::move(content_receiver), std::move(progress));
6460}
6461inline Result Client::Get(const char *path, const Headers &headers,
6462 ContentReceiver content_receiver, Progress progress) {
6463 return cli_->Get(path, headers, std::move(content_receiver),
6464 std::move(progress));
6465}
6466inline Result Client::Get(const char *path, ResponseHandler response_handler,
6467 ContentReceiver content_receiver) {
6468 return cli_->Get(path, std::move(response_handler),
6469 std::move(content_receiver));
6470}
6471inline Result Client::Get(const char *path, const Headers &headers,
6472 ResponseHandler response_handler,
6473 ContentReceiver content_receiver) {
6474 return cli_->Get(path, headers, std::move(response_handler),
6475 std::move(content_receiver));
6476}
6477inline Result Client::Get(const char *path, ResponseHandler response_handler,
6478 ContentReceiver content_receiver, Progress progress) {
6479 return cli_->Get(path, std::move(response_handler),
6480 std::move(content_receiver), std::move(progress));
6481}
6482inline Result Client::Get(const char *path, const Headers &headers,
6483 ResponseHandler response_handler,
6484 ContentReceiver content_receiver, Progress progress) {
6485 return cli_->Get(path, headers, std::move(response_handler),
6486 std::move(content_receiver), std::move(progress));
6487}
6488
6489inline Result Client::Head(const char *path) { return cli_->Head(path); }
6490inline Result Client::Head(const char *path, const Headers &headers) {
6491 return cli_->Head(path, headers);
6492}
6493
6494inline Result Client::Post(const char *path) { return cli_->Post(path); }
6495inline Result Client::Post(const char *path, const std::string &body,
6496 const char *content_type) {
6497 return cli_->Post(path, body, content_type);
6498}
6499inline Result Client::Post(const char *path, const Headers &headers,
6500 const std::string &body, const char *content_type) {
6501 return cli_->Post(path, headers, body, content_type);
6502}
6503inline Result Client::Post(const char *path, size_t content_length,
6504 ContentProvider content_provider,
6505 const char *content_type) {
6506 return cli_->Post(path, content_length, std::move(content_provider),
6507 content_type);
6508}
6509inline Result Client::Post(const char *path, const Headers &headers,
6510 size_t content_length,
6511 ContentProvider content_provider,
6512 const char *content_type) {
6513 return cli_->Post(path, headers, content_length, std::move(content_provider),
6514 content_type);
6515}
6516inline Result Client::Post(const char *path, const Params &params) {
6517 return cli_->Post(path, params);
6518}
6519inline Result Client::Post(const char *path, const Headers &headers,
6520 const Params &params) {
6521 return cli_->Post(path, headers, params);
6522}
6523inline Result Client::Post(const char *path,
6524 const MultipartFormDataItems &items) {
6525 return cli_->Post(path, items);
6526}
6527inline Result Client::Post(const char *path, const Headers &headers,
6528 const MultipartFormDataItems &items) {
6529 return cli_->Post(path, headers, items);
6530}
6531inline Result Client::Post(const char *path, const Headers &headers,
6532 const MultipartFormDataItems &items,
6533 const std::string &boundary) {
6534 return cli_->Post(path, headers, items, boundary);
6535}
6536inline Result Client::Put(const char *path) { return cli_->Put(path); }
6537inline Result Client::Put(const char *path, const std::string &body,
6538 const char *content_type) {
6539 return cli_->Put(path, body, content_type);
6540}
6541inline Result Client::Put(const char *path, const Headers &headers,
6542 const std::string &body, const char *content_type) {
6543 return cli_->Put(path, headers, body, content_type);
6544}
6545inline Result Client::Put(const char *path, size_t content_length,
6546 ContentProvider content_provider,
6547 const char *content_type) {
6548 return cli_->Put(path, content_length, std::move(content_provider),
6549 content_type);
6550}
6551inline Result Client::Put(const char *path, const Headers &headers,
6552 size_t content_length,
6553 ContentProvider content_provider,
6554 const char *content_type) {
6555 return cli_->Put(path, headers, content_length, std::move(content_provider),
6556 content_type);
6557}
6558inline Result Client::Put(const char *path, const Params &params) {
6559 return cli_->Put(path, params);
6560}
6561inline Result Client::Put(const char *path, const Headers &headers,
6562 const Params &params) {
6563 return cli_->Put(path, headers, params);
6564}
6565inline Result Client::Patch(const char *path, const std::string &body,
6566 const char *content_type) {
6567 return cli_->Patch(path, body, content_type);
6568}
6569inline Result Client::Patch(const char *path, const Headers &headers,
6570 const std::string &body, const char *content_type) {
6571 return cli_->Patch(path, headers, body, content_type);
6572}
6573inline Result Client::Patch(const char *path, size_t content_length,
6574 ContentProvider content_provider,
6575 const char *content_type) {
6576 return cli_->Patch(path, content_length, std::move(content_provider),
6577 content_type);
6578}
6579inline Result Client::Patch(const char *path, const Headers &headers,
6580 size_t content_length,
6581 ContentProvider content_provider,
6582 const char *content_type) {
6583 return cli_->Patch(path, headers, content_length, std::move(content_provider),
6584 content_type);
6585}
6586inline Result Client::Delete(const char *path) { return cli_->Delete(path); }
6587inline Result Client::Delete(const char *path, const std::string &body,
6588 const char *content_type) {
6589 return cli_->Delete(path, body, content_type);
6590}
6591inline Result Client::Delete(const char *path, const Headers &headers) {
6592 return cli_->Delete(path, headers);
6593}
6594inline Result Client::Delete(const char *path, const Headers &headers,
6595 const std::string &body,
6596 const char *content_type) {
6597 return cli_->Delete(path, headers, body, content_type);
6598}
6599inline Result Client::Options(const char *path) { return cli_->Options(path); }
6600inline Result Client::Options(const char *path, const Headers &headers) {
6601 return cli_->Options(path, headers);
6602}
6603
6604inline bool Client::send(const Request &req, Response &res) {
6605 return cli_->send(req, res);
6606}
6607
6608inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
6609
6610inline void Client::stop() { cli_->stop(); }
6611
6612inline void Client::set_default_headers(Headers headers) {
6613 cli_->set_default_headers(std::move(headers));
6614}
6615
6616inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); }
6617inline void Client::set_socket_options(SocketOptions socket_options) {
6618 cli_->set_socket_options(std::move(socket_options));
6619}
6620
6621inline void Client::set_connection_timeout(time_t sec, time_t usec) {
6622 cli_->set_connection_timeout(sec, usec);
6623}
6624inline void Client::set_read_timeout(time_t sec, time_t usec) {
6625 cli_->set_read_timeout(sec, usec);
6626}
6627inline void Client::set_write_timeout(time_t sec, time_t usec) {
6628 cli_->set_write_timeout(sec, usec);
6629}
6630
6631inline void Client::set_basic_auth(const char *username, const char *password) {
6632 cli_->set_basic_auth(username, password);
6633}
6634inline void Client::set_bearer_token_auth(const char *token) {
6635 cli_->set_bearer_token_auth(token);
6636}
6637#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6638inline void Client::set_digest_auth(const char *username,
6639 const char *password) {
6640 cli_->set_digest_auth(username, password);
6641}
6642#endif
6643
6644inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); }
6645inline void Client::set_follow_location(bool on) {
6646 cli_->set_follow_location(on);
6647}
6648
6649inline void Client::set_compress(bool on) { cli_->set_compress(on); }
6650
6651inline void Client::set_decompress(bool on) { cli_->set_decompress(on); }
6652
6653inline void Client::set_interface(const char *intf) {
6654 cli_->set_interface(intf);
6655}
6656
6657inline void Client::set_proxy(const char *host, int port) {
6658 cli_->set_proxy(host, port);
6659}
6660inline void Client::set_proxy_basic_auth(const char *username,
6661 const char *password) {
6662 cli_->set_proxy_basic_auth(username, password);
6663}
6664inline void Client::set_proxy_bearer_token_auth(const char *token) {
6665 cli_->set_proxy_bearer_token_auth(token);
6666}
6667#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6668inline void Client::set_proxy_digest_auth(const char *username,
6669 const char *password) {
6670 cli_->set_proxy_digest_auth(username, password);
6671}
6672#endif
6673
6674#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6675inline void Client::enable_server_certificate_verification(bool enabled) {
6676 cli_->enable_server_certificate_verification(enabled);
6677}
6678#endif
6679
6680inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); }
6681
6682#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6683inline void Client::set_ca_cert_path(const char *ca_cert_file_path,
6684 const char *ca_cert_dir_path) {
6685 if (is_ssl_) {
6686 static_cast<SSLClient &>(*cli_).set_ca_cert_path(ca_cert_file_path,
6687 ca_cert_dir_path);
6688 }
6689}
6690
6691inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
6692 if (is_ssl_) {
6693 static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
6694 }
6695}
6696
6697inline long Client::get_openssl_verify_result() const {
6698 if (is_ssl_) {
6699 return static_cast<SSLClient &>(*cli_).get_openssl_verify_result();
6700 }
6701 return -1; // NOTE: -1 doesn't match any of X509_V_ERR_???
6702}
6703
6704inline SSL_CTX *Client::ssl_context() const {
6705 if (is_ssl_) { return static_cast<SSLClient &>(*cli_).ssl_context(); }
6706 return nullptr;
6707}
6708#endif
6709
6710// ----------------------------------------------------------------------------
6711
6712} // namespace httplib
6713
6714#endif // CPPHTTPLIB_HTTPLIB_H
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index a0ae07752..d25a1a645 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -42,6 +42,7 @@ add_library(audio_core STATIC
42 voice_context.h 42 voice_context.h
43 43
44 $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h> 44 $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
45 $<$<BOOL:${ENABLE_SDL2}>:sdl2_sink.cpp sdl2_sink.h>
45) 46)
46 47
47create_target_directory_groups(audio_core) 48create_target_directory_groups(audio_core)
@@ -71,3 +72,7 @@ if(ENABLE_CUBEB)
71 target_link_libraries(audio_core PRIVATE cubeb) 72 target_link_libraries(audio_core PRIVATE cubeb)
72 target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1) 73 target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1)
73endif() 74endif()
75if(ENABLE_SDL2)
76 target_link_libraries(audio_core PRIVATE SDL2)
77 target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
78endif()
diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp
new file mode 100644
index 000000000..62d3716a6
--- /dev/null
+++ b/src/audio_core/sdl2_sink.cpp
@@ -0,0 +1,163 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <atomic>
7#include <cstring>
8#include "audio_core/sdl2_sink.h"
9#include "audio_core/stream.h"
10#include "audio_core/time_stretch.h"
11#include "common/assert.h"
12#include "common/logging/log.h"
13//#include "common/settings.h"
14
15// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
16#ifdef __clang__
17#pragma clang diagnostic push
18#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
19#endif
20#include <SDL.h>
21#ifdef __clang__
22#pragma clang diagnostic pop
23#endif
24
25namespace AudioCore {
26
27class SDLSinkStream final : public SinkStream {
28public:
29 SDLSinkStream(u32 sample_rate, u32 num_channels_, const std::string& output_device)
30 : num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate, num_channels} {
31
32 SDL_AudioSpec spec;
33 spec.freq = sample_rate;
34 spec.channels = static_cast<u8>(num_channels);
35 spec.format = AUDIO_S16SYS;
36 spec.samples = 4096;
37 spec.callback = nullptr;
38
39 SDL_AudioSpec obtained;
40 if (output_device.empty()) {
41 dev = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained, 0);
42 } else {
43 dev = SDL_OpenAudioDevice(output_device.c_str(), 0, &spec, &obtained, 0);
44 }
45
46 if (dev == 0) {
47 LOG_CRITICAL(Audio_Sink, "Error opening sdl audio device: {}", SDL_GetError());
48 return;
49 }
50
51 SDL_PauseAudioDevice(dev, 0);
52 }
53
54 ~SDLSinkStream() override {
55 if (dev == 0) {
56 return;
57 }
58
59 SDL_CloseAudioDevice(dev);
60 }
61
62 void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override {
63 if (source_num_channels > num_channels) {
64 // Downsample 6 channels to 2
65 ASSERT_MSG(source_num_channels == 6, "Channel count must be 6");
66
67 std::vector<s16> buf;
68 buf.reserve(samples.size() * num_channels / source_num_channels);
69 for (std::size_t i = 0; i < samples.size(); i += source_num_channels) {
70 // Downmixing implementation taken from the ATSC standard
71 const s16 left{samples[i + 0]};
72 const s16 right{samples[i + 1]};
73 const s16 center{samples[i + 2]};
74 const s16 surround_left{samples[i + 4]};
75 const s16 surround_right{samples[i + 5]};
76 // Not used in the ATSC reference implementation
77 [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]};
78
79 constexpr s32 clev{707}; // center mixing level coefficient
80 constexpr s32 slev{707}; // surround mixing level coefficient
81
82 buf.push_back(static_cast<s16>(left + (clev * center / 1000) +
83 (slev * surround_left / 1000)));
84 buf.push_back(static_cast<s16>(right + (clev * center / 1000) +
85 (slev * surround_right / 1000)));
86 }
87 int ret = SDL_QueueAudio(dev, static_cast<const void*>(buf.data()),
88 static_cast<u32>(buf.size() * sizeof(s16)));
89 if (ret < 0)
90 LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError());
91 return;
92 }
93
94 int ret = SDL_QueueAudio(dev, static_cast<const void*>(samples.data()),
95 static_cast<u32>(samples.size() * sizeof(s16)));
96 if (ret < 0)
97 LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError());
98 }
99
100 std::size_t SamplesInQueue(u32 channel_count) const override {
101 if (dev == 0)
102 return 0;
103
104 return SDL_GetQueuedAudioSize(dev) / (channel_count * sizeof(s16));
105 }
106
107 void Flush() override {
108 should_flush = true;
109 }
110
111 u32 GetNumChannels() const {
112 return num_channels;
113 }
114
115private:
116 SDL_AudioDeviceID dev = 0;
117 u32 num_channels{};
118 std::atomic<bool> should_flush{};
119 TimeStretcher time_stretch;
120};
121
122SDLSink::SDLSink(std::string_view target_device_name) {
123 if (!SDL_WasInit(SDL_INIT_AUDIO)) {
124 if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
125 LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
126 return;
127 }
128 }
129
130 if (target_device_name != auto_device_name && !target_device_name.empty()) {
131 output_device = target_device_name;
132 } else {
133 output_device.clear();
134 }
135}
136
137SDLSink::~SDLSink() = default;
138
139SinkStream& SDLSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, const std::string&) {
140 sink_streams.push_back(
141 std::make_unique<SDLSinkStream>(sample_rate, num_channels, output_device));
142 return *sink_streams.back();
143}
144
145std::vector<std::string> ListSDLSinkDevices() {
146 std::vector<std::string> device_list;
147
148 if (!SDL_WasInit(SDL_INIT_AUDIO)) {
149 if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
150 LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
151 return {};
152 }
153 }
154
155 const int device_count = SDL_GetNumAudioDevices(0);
156 for (int i = 0; i < device_count; ++i) {
157 device_list.emplace_back(SDL_GetAudioDeviceName(i, 0));
158 }
159
160 return device_list;
161}
162
163} // namespace AudioCore
diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h
new file mode 100644
index 000000000..8ec1526d8
--- /dev/null
+++ b/src/audio_core/sdl2_sink.h
@@ -0,0 +1,29 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <string>
8#include <vector>
9
10#include "audio_core/sink.h"
11
12namespace AudioCore {
13
14class SDLSink final : public Sink {
15public:
16 explicit SDLSink(std::string_view device_id);
17 ~SDLSink() override;
18
19 SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,
20 const std::string& name) override;
21
22private:
23 std::string output_device;
24 std::vector<SinkStreamPtr> sink_streams;
25};
26
27std::vector<std::string> ListSDLSinkDevices();
28
29} // namespace AudioCore
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp
index a848eb1c9..de10aecd2 100644
--- a/src/audio_core/sink_details.cpp
+++ b/src/audio_core/sink_details.cpp
@@ -11,6 +11,9 @@
11#ifdef HAVE_CUBEB 11#ifdef HAVE_CUBEB
12#include "audio_core/cubeb_sink.h" 12#include "audio_core/cubeb_sink.h"
13#endif 13#endif
14#ifdef HAVE_SDL2
15#include "audio_core/sdl2_sink.h"
16#endif
14#include "common/logging/log.h" 17#include "common/logging/log.h"
15 18
16namespace AudioCore { 19namespace AudioCore {
@@ -36,6 +39,13 @@ constexpr SinkDetails sink_details[] = {
36 }, 39 },
37 &ListCubebSinkDevices}, 40 &ListCubebSinkDevices},
38#endif 41#endif
42#ifdef HAVE_SDL2
43 SinkDetails{"sdl2",
44 [](std::string_view device_id) -> std::unique_ptr<Sink> {
45 return std::make_unique<SDLSink>(device_id);
46 },
47 &ListSDLSinkDevices},
48#endif
39 SinkDetails{"null", 49 SinkDetails{"null",
40 [](std::string_view device_id) -> std::unique_ptr<Sink> { 50 [](std::string_view device_id) -> std::unique_ptr<Sink> {
41 return std::make_unique<NullSink>(device_id); 51 return std::make_unique<NullSink>(device_id);
diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp
index f2b4939df..c1362631e 100644
--- a/src/common/detached_tasks.cpp
+++ b/src/common/detached_tasks.cpp
@@ -21,6 +21,8 @@ void DetachedTasks::WaitForAllTasks() {
21} 21}
22 22
23DetachedTasks::~DetachedTasks() { 23DetachedTasks::~DetachedTasks() {
24 WaitForAllTasks();
25
24 std::unique_lock lock{mutex}; 26 std::unique_lock lock{mutex};
25 ASSERT(count == 0); 27 ASSERT(count == 0);
26 instance = nullptr; 28 instance = nullptr;
diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp
index 710e88b39..077f34995 100644
--- a/src/common/fs/file.cpp
+++ b/src/common/fs/file.cpp
@@ -172,7 +172,7 @@ std::string ReadStringFromFile(const std::filesystem::path& path, FileType type)
172 172
173size_t WriteStringToFile(const std::filesystem::path& path, FileType type, 173size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
174 std::string_view string) { 174 std::string_view string) {
175 if (!IsFile(path)) { 175 if (Exists(path) && !IsFile(path)) {
176 return 0; 176 return 0;
177 } 177 }
178 178
@@ -183,7 +183,7 @@ size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
183 183
184size_t AppendStringToFile(const std::filesystem::path& path, FileType type, 184size_t AppendStringToFile(const std::filesystem::path& path, FileType type,
185 std::string_view string) { 185 std::string_view string) {
186 if (!IsFile(path)) { 186 if (Exists(path) && !IsFile(path)) {
187 return 0; 187 return 0;
188 } 188 }
189 189
diff --git a/src/common/fs/file.h b/src/common/fs/file.h
index 0f10b6003..588fe619d 100644
--- a/src/common/fs/file.h
+++ b/src/common/fs/file.h
@@ -49,7 +49,7 @@ void OpenFileStream(FileStream& file_stream, const Path& path, std::ios_base::op
49 49
50/** 50/**
51 * Reads an entire file at path and returns a string of the contents read from the file. 51 * Reads an entire file at path and returns a string of the contents read from the file.
52 * If the filesystem object at path is not a file, this function returns an empty string. 52 * If the filesystem object at path is not a regular file, this function returns an empty string.
53 * 53 *
54 * @param path Filesystem path 54 * @param path Filesystem path
55 * @param type File type 55 * @param type File type
@@ -72,7 +72,8 @@ template <typename Path>
72/** 72/**
73 * Writes a string to a file at path and returns the number of characters successfully written. 73 * Writes a string to a file at path and returns the number of characters successfully written.
74 * If a file already exists at path, its contents will be erased. 74 * If a file already exists at path, its contents will be erased.
75 * If the filesystem object at path is not a file, this function returns 0. 75 * If a file does not exist at path, it creates and opens a new empty file for writing.
76 * If the filesystem object at path exists and is not a regular file, this function returns 0.
76 * 77 *
77 * @param path Filesystem path 78 * @param path Filesystem path
78 * @param type File type 79 * @param type File type
@@ -95,7 +96,8 @@ template <typename Path>
95 96
96/** 97/**
97 * Appends a string to a file at path and returns the number of characters successfully written. 98 * Appends a string to a file at path and returns the number of characters successfully written.
98 * If the filesystem object at path is not a file, this function returns 0. 99 * If a file does not exist at path, it creates and opens a new empty file for appending.
100 * If the filesystem object at path exists and is not a regular file, this function returns 0.
99 * 101 *
100 * @param path Filesystem path 102 * @param path Filesystem path
101 * @param type File type 103 * @param type File type
@@ -394,11 +396,11 @@ public:
394 [[nodiscard]] size_t WriteString(std::span<const char> string) const; 396 [[nodiscard]] size_t WriteString(std::span<const char> string) const;
395 397
396 /** 398 /**
397 * Flushes any unwritten buffered data into the file. 399 * Attempts to flush any unwritten buffered data into the file and flush the file into the disk.
398 * 400 *
399 * @returns True if the flush was successful, false otherwise. 401 * @returns True if the flush was successful, false otherwise.
400 */ 402 */
401 [[nodiscard]] bool Flush() const; 403 bool Flush() const;
402 404
403 /** 405 /**
404 * Resizes the file to a given size. 406 * Resizes the file to a given size.
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp
index d3159e908..9089cad67 100644
--- a/src/common/fs/fs.cpp
+++ b/src/common/fs/fs.cpp
@@ -135,8 +135,9 @@ std::shared_ptr<IOFile> FileOpen(const fs::path& path, FileAccessMode mode, File
135 return nullptr; 135 return nullptr;
136 } 136 }
137 137
138 if (!IsFile(path)) { 138 if (Exists(path) && !IsFile(path)) {
139 LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file", 139 LOG_ERROR(Common_Filesystem,
140 "Filesystem object at path={} exists and is not a regular file",
140 PathToUTF8String(path)); 141 PathToUTF8String(path));
141 return nullptr; 142 return nullptr;
142 } 143 }
diff --git a/src/common/fs/fs.h b/src/common/fs/fs.h
index f6f256349..183126de3 100644
--- a/src/common/fs/fs.h
+++ b/src/common/fs/fs.h
@@ -48,18 +48,18 @@ template <typename Path>
48 * 48 *
49 * Failures occur when: 49 * Failures occur when:
50 * - Input path is not valid 50 * - Input path is not valid
51 * - Filesystem object at path is not a file 51 * - Filesystem object at path is not a regular file
52 * - Filesystem at path is read only 52 * - Filesystem at path is read only
53 * 53 *
54 * @param path Filesystem path 54 * @param path Filesystem path
55 * 55 *
56 * @returns True if file removal succeeds or file does not exist, false otherwise. 56 * @returns True if file removal succeeds or file does not exist, false otherwise.
57 */ 57 */
58[[nodiscard]] bool RemoveFile(const std::filesystem::path& path); 58bool RemoveFile(const std::filesystem::path& path);
59 59
60#ifdef _WIN32 60#ifdef _WIN32
61template <typename Path> 61template <typename Path>
62[[nodiscard]] bool RemoveFile(const Path& path) { 62bool RemoveFile(const Path& path) {
63 if constexpr (IsChar<typename Path::value_type>) { 63 if constexpr (IsChar<typename Path::value_type>) {
64 return RemoveFile(ToU8String(path)); 64 return RemoveFile(ToU8String(path));
65 } else { 65 } else {
@@ -74,7 +74,7 @@ template <typename Path>
74 * Failures occur when: 74 * Failures occur when:
75 * - One or both input path(s) is not valid 75 * - One or both input path(s) is not valid
76 * - Filesystem object at old_path does not exist 76 * - Filesystem object at old_path does not exist
77 * - Filesystem object at old_path is not a file 77 * - Filesystem object at old_path is not a regular file
78 * - Filesystem object at new_path exists 78 * - Filesystem object at new_path exists
79 * - Filesystem at either path is read only 79 * - Filesystem at either path is read only
80 * 80 *
@@ -110,8 +110,8 @@ template <typename Path1, typename Path2>
110 * 110 *
111 * Failures occur when: 111 * Failures occur when:
112 * - Input path is not valid 112 * - Input path is not valid
113 * - Filesystem object at path is not a file 113 * - Filesystem object at path exists and is not a regular file
114 * - The file is not opened 114 * - The file is not open
115 * 115 *
116 * @param path Filesystem path 116 * @param path Filesystem path
117 * @param mode File access mode 117 * @param mode File access mode
@@ -251,11 +251,11 @@ template <typename Path>
251 * 251 *
252 * @returns True if directory removal succeeds or directory does not exist, false otherwise. 252 * @returns True if directory removal succeeds or directory does not exist, false otherwise.
253 */ 253 */
254[[nodiscard]] bool RemoveDir(const std::filesystem::path& path); 254bool RemoveDir(const std::filesystem::path& path);
255 255
256#ifdef _WIN32 256#ifdef _WIN32
257template <typename Path> 257template <typename Path>
258[[nodiscard]] bool RemoveDir(const Path& path) { 258bool RemoveDir(const Path& path) {
259 if constexpr (IsChar<typename Path::value_type>) { 259 if constexpr (IsChar<typename Path::value_type>) {
260 return RemoveDir(ToU8String(path)); 260 return RemoveDir(ToU8String(path));
261 } else { 261 } else {
@@ -276,11 +276,11 @@ template <typename Path>
276 * 276 *
277 * @returns True if the directory and all of its contents are removed successfully, false otherwise. 277 * @returns True if the directory and all of its contents are removed successfully, false otherwise.
278 */ 278 */
279[[nodiscard]] bool RemoveDirRecursively(const std::filesystem::path& path); 279bool RemoveDirRecursively(const std::filesystem::path& path);
280 280
281#ifdef _WIN32 281#ifdef _WIN32
282template <typename Path> 282template <typename Path>
283[[nodiscard]] bool RemoveDirRecursively(const Path& path) { 283bool RemoveDirRecursively(const Path& path) {
284 if constexpr (IsChar<typename Path::value_type>) { 284 if constexpr (IsChar<typename Path::value_type>) {
285 return RemoveDirRecursively(ToU8String(path)); 285 return RemoveDirRecursively(ToU8String(path));
286 } else { 286 } else {
@@ -301,11 +301,11 @@ template <typename Path>
301 * 301 *
302 * @returns True if all of the directory's contents are removed successfully, false otherwise. 302 * @returns True if all of the directory's contents are removed successfully, false otherwise.
303 */ 303 */
304[[nodiscard]] bool RemoveDirContentsRecursively(const std::filesystem::path& path); 304bool RemoveDirContentsRecursively(const std::filesystem::path& path);
305 305
306#ifdef _WIN32 306#ifdef _WIN32
307template <typename Path> 307template <typename Path>
308[[nodiscard]] bool RemoveDirContentsRecursively(const Path& path) { 308bool RemoveDirContentsRecursively(const Path& path) {
309 if constexpr (IsChar<typename Path::value_type>) { 309 if constexpr (IsChar<typename Path::value_type>) {
310 return RemoveDirContentsRecursively(ToU8String(path)); 310 return RemoveDirContentsRecursively(ToU8String(path));
311 } else { 311 } else {
@@ -435,11 +435,13 @@ template <typename Path>
435#endif 435#endif
436 436
437/** 437/**
438 * Returns whether a filesystem object at path is a file. 438 * Returns whether a filesystem object at path is a regular file.
439 * A regular file is a file that stores text or binary data.
440 * It is not a directory, symlink, FIFO, socket, block device, or character device.
439 * 441 *
440 * @param path Filesystem path 442 * @param path Filesystem path
441 * 443 *
442 * @returns True if a filesystem object at path is a file, false otherwise. 444 * @returns True if a filesystem object at path is a regular file, false otherwise.
443 */ 445 */
444[[nodiscard]] bool IsFile(const std::filesystem::path& path); 446[[nodiscard]] bool IsFile(const std::filesystem::path& path);
445 447
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp
index 8bd70abc7..2a5a7596c 100644
--- a/src/common/host_memory.cpp
+++ b/src/common/host_memory.cpp
@@ -34,7 +34,7 @@ constexpr size_t HugePageSize = 0x200000;
34 34
35// Manually imported for MinGW compatibility 35// Manually imported for MinGW compatibility
36#ifndef MEM_RESERVE_PLACEHOLDER 36#ifndef MEM_RESERVE_PLACEHOLDER
37#define MEM_RESERVE_PLACEHOLDER 0x0004000 37#define MEM_RESERVE_PLACEHOLDER 0x00040000
38#endif 38#endif
39#ifndef MEM_REPLACE_PLACEHOLDER 39#ifndef MEM_REPLACE_PLACEHOLDER
40#define MEM_REPLACE_PLACEHOLDER 0x00004000 40#define MEM_REPLACE_PLACEHOLDER 0x00004000
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index d5cff400f..47ce06478 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -159,7 +159,7 @@ FileBackend::FileBackend(const std::filesystem::path& filename) {
159 159
160 // Existence checks are done within the functions themselves. 160 // Existence checks are done within the functions themselves.
161 // We don't particularly care if these succeed or not. 161 // We don't particularly care if these succeed or not.
162 void(FS::RemoveFile(old_filename)); 162 FS::RemoveFile(old_filename);
163 void(FS::RenameFile(filename, old_filename)); 163 void(FS::RenameFile(filename, old_filename));
164 164
165 file = 165 file =
@@ -186,7 +186,7 @@ void FileBackend::Write(const Entry& entry) {
186 186
187 bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n')); 187 bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
188 if (entry.log_level >= Level::Error) { 188 if (entry.log_level >= Level::Error) {
189 void(file->Flush()); 189 file->Flush();
190 } 190 }
191} 191}
192 192
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index ab5cbe67b..e1bb4b7ff 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -123,6 +123,7 @@ void RestoreGlobalState(bool is_powered_on) {
123 values.cpu_accuracy.SetGlobal(true); 123 values.cpu_accuracy.SetGlobal(true);
124 values.cpuopt_unsafe_unfuse_fma.SetGlobal(true); 124 values.cpuopt_unsafe_unfuse_fma.SetGlobal(true);
125 values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true); 125 values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true);
126 values.cpuopt_unsafe_ignore_standard_fpcr.SetGlobal(true);
126 values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true); 127 values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true);
127 values.cpuopt_unsafe_fastmem_check.SetGlobal(true); 128 values.cpuopt_unsafe_fastmem_check.SetGlobal(true);
128 129
diff --git a/src/common/settings.h b/src/common/settings.h
index a1c0bf3ad..82ec18e27 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -129,6 +129,7 @@ struct Values {
129 129
130 Setting<bool> cpuopt_unsafe_unfuse_fma; 130 Setting<bool> cpuopt_unsafe_unfuse_fma;
131 Setting<bool> cpuopt_unsafe_reduce_fp_error; 131 Setting<bool> cpuopt_unsafe_reduce_fp_error;
132 Setting<bool> cpuopt_unsafe_ignore_standard_fpcr;
132 Setting<bool> cpuopt_unsafe_inaccurate_nan; 133 Setting<bool> cpuopt_unsafe_inaccurate_nan;
133 Setting<bool> cpuopt_unsafe_fastmem_check; 134 Setting<bool> cpuopt_unsafe_fastmem_check;
134 135
@@ -149,6 +150,7 @@ struct Values {
149 Setting<bool> use_nvdec_emulation; 150 Setting<bool> use_nvdec_emulation;
150 Setting<bool> accelerate_astc; 151 Setting<bool> accelerate_astc;
151 Setting<bool> use_vsync; 152 Setting<bool> use_vsync;
153 Setting<bool> disable_fps_limit;
152 Setting<bool> use_assembly_shaders; 154 Setting<bool> use_assembly_shaders;
153 Setting<bool> use_asynchronous_shaders; 155 Setting<bool> use_asynchronous_shaders;
154 Setting<bool> use_fast_gpu_time; 156 Setting<bool> use_fast_gpu_time;
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index efb851f5a..83b5b7676 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -139,6 +139,7 @@ add_library(core STATIC
139 frontend/input.h 139 frontend/input.h
140 hardware_interrupt_manager.cpp 140 hardware_interrupt_manager.cpp
141 hardware_interrupt_manager.h 141 hardware_interrupt_manager.h
142 hle/api_version.h
142 hle/ipc.h 143 hle/ipc.h
143 hle/ipc_helpers.h 144 hle/ipc_helpers.h
144 hle/kernel/board/nintendo/nx/k_system_control.cpp 145 hle/kernel/board/nintendo/nx/k_system_control.cpp
@@ -550,6 +551,8 @@ add_library(core STATIC
550 hle/service/spl/module.h 551 hle/service/spl/module.h
551 hle/service/spl/spl.cpp 552 hle/service/spl/spl.cpp
552 hle/service/spl/spl.h 553 hle/service/spl/spl.h
554 hle/service/spl/spl_results.h
555 hle/service/spl/spl_types.h
553 hle/service/ssl/ssl.cpp 556 hle/service/ssl/ssl.cpp
554 hle/service/ssl/ssl.h 557 hle/service/ssl/ssl.h
555 hle/service/time/clock_types.h 558 hle/service/time/clock_types.h
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index c8f6dc765..f871f7bf4 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -186,6 +186,9 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
186 if (Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()) { 186 if (Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()) {
187 config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; 187 config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
188 } 188 }
189 if (Settings::values.cpuopt_unsafe_ignore_standard_fpcr.GetValue()) {
190 config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
191 }
189 if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) { 192 if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) {
190 config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; 193 config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
191 } 194 }
diff --git a/src/core/core.cpp b/src/core/core.cpp
index c5004b7b4..e6f1aa0e7 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -3,6 +3,7 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <array> 5#include <array>
6#include <atomic>
6#include <memory> 7#include <memory>
7#include <utility> 8#include <utility>
8 9
@@ -377,7 +378,7 @@ struct System::Impl {
377 std::unique_ptr<Core::DeviceMemory> device_memory; 378 std::unique_ptr<Core::DeviceMemory> device_memory;
378 Core::Memory::Memory memory; 379 Core::Memory::Memory memory;
379 CpuManager cpu_manager; 380 CpuManager cpu_manager;
380 bool is_powered_on = false; 381 std::atomic_bool is_powered_on{};
381 bool exit_lock = false; 382 bool exit_lock = false;
382 383
383 Reporter reporter; 384 Reporter reporter;
@@ -463,7 +464,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
463} 464}
464 465
465bool System::IsPoweredOn() const { 466bool System::IsPoweredOn() const {
466 return impl->is_powered_on; 467 return impl->is_powered_on.load(std::memory_order::relaxed);
467} 468}
468 469
469void System::PrepareReschedule() { 470void System::PrepareReschedule() {
diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp
index 54704105b..9b76d007e 100644
--- a/src/core/file_sys/system_archive/system_version.cpp
+++ b/src/core/file_sys/system_archive/system_version.cpp
@@ -4,47 +4,29 @@
4 4
5#include "core/file_sys/system_archive/system_version.h" 5#include "core/file_sys/system_archive/system_version.h"
6#include "core/file_sys/vfs_vector.h" 6#include "core/file_sys/vfs_vector.h"
7#include "core/hle/api_version.h"
7 8
8namespace FileSys::SystemArchive { 9namespace FileSys::SystemArchive {
9 10
10namespace SystemVersionData {
11
12// This section should reflect the best system version to describe yuzu's HLE api.
13// TODO(DarkLordZach): Update when HLE gets better.
14
15constexpr u8 VERSION_MAJOR = 11;
16constexpr u8 VERSION_MINOR = 0;
17constexpr u8 VERSION_MICRO = 1;
18
19constexpr u8 REVISION_MAJOR = 1;
20constexpr u8 REVISION_MINOR = 0;
21
22constexpr char PLATFORM_STRING[] = "NX";
23constexpr char VERSION_HASH[] = "69103fcb2004dace877094c2f8c29e6113be5dbf";
24constexpr char DISPLAY_VERSION[] = "11.0.1";
25constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 11.0.1-1.0";
26
27} // namespace SystemVersionData
28
29std::string GetLongDisplayVersion() { 11std::string GetLongDisplayVersion() {
30 return SystemVersionData::DISPLAY_TITLE; 12 return HLE::ApiVersion::DISPLAY_TITLE;
31} 13}
32 14
33VirtualDir SystemVersion() { 15VirtualDir SystemVersion() {
34 VirtualFile file = std::make_shared<VectorVfsFile>(std::vector<u8>(0x100), "file"); 16 VirtualFile file = std::make_shared<VectorVfsFile>(std::vector<u8>(0x100), "file");
35 file->WriteObject(SystemVersionData::VERSION_MAJOR, 0); 17 file->WriteObject(HLE::ApiVersion::HOS_VERSION_MAJOR, 0);
36 file->WriteObject(SystemVersionData::VERSION_MINOR, 1); 18 file->WriteObject(HLE::ApiVersion::HOS_VERSION_MINOR, 1);
37 file->WriteObject(SystemVersionData::VERSION_MICRO, 2); 19 file->WriteObject(HLE::ApiVersion::HOS_VERSION_MICRO, 2);
38 file->WriteObject(SystemVersionData::REVISION_MAJOR, 4); 20 file->WriteObject(HLE::ApiVersion::SDK_REVISION_MAJOR, 4);
39 file->WriteObject(SystemVersionData::REVISION_MINOR, 5); 21 file->WriteObject(HLE::ApiVersion::SDK_REVISION_MINOR, 5);
40 file->WriteArray(SystemVersionData::PLATFORM_STRING, 22 file->WriteArray(HLE::ApiVersion::PLATFORM_STRING,
41 std::min<u64>(sizeof(SystemVersionData::PLATFORM_STRING), 0x20ULL), 0x8); 23 std::min<u64>(sizeof(HLE::ApiVersion::PLATFORM_STRING), 0x20ULL), 0x8);
42 file->WriteArray(SystemVersionData::VERSION_HASH, 24 file->WriteArray(HLE::ApiVersion::VERSION_HASH,
43 std::min<u64>(sizeof(SystemVersionData::VERSION_HASH), 0x40ULL), 0x28); 25 std::min<u64>(sizeof(HLE::ApiVersion::VERSION_HASH), 0x40ULL), 0x28);
44 file->WriteArray(SystemVersionData::DISPLAY_VERSION, 26 file->WriteArray(HLE::ApiVersion::DISPLAY_VERSION,
45 std::min<u64>(sizeof(SystemVersionData::DISPLAY_VERSION), 0x18ULL), 0x68); 27 std::min<u64>(sizeof(HLE::ApiVersion::DISPLAY_VERSION), 0x18ULL), 0x68);
46 file->WriteArray(SystemVersionData::DISPLAY_TITLE, 28 file->WriteArray(HLE::ApiVersion::DISPLAY_TITLE,
47 std::min<u64>(sizeof(SystemVersionData::DISPLAY_TITLE), 0x80ULL), 0x80); 29 std::min<u64>(sizeof(HLE::ApiVersion::DISPLAY_TITLE), 0x80ULL), 0x80);
48 return std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{file}, 30 return std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{file},
49 std::vector<VirtualDir>{}, "data"); 31 std::vector<VirtualDir>{}, "data");
50} 32}
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index d0b8fd046..3dad54f49 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -24,17 +24,12 @@ constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) {
24 case Mode::Read: 24 case Mode::Read:
25 return FS::FileAccessMode::Read; 25 return FS::FileAccessMode::Read;
26 case Mode::Write: 26 case Mode::Write:
27 return FS::FileAccessMode::Write;
28 case Mode::ReadWrite: 27 case Mode::ReadWrite:
29 return FS::FileAccessMode::ReadWrite;
30 case Mode::Append: 28 case Mode::Append:
31 return FS::FileAccessMode::Append;
32 case Mode::ReadAppend: 29 case Mode::ReadAppend:
33 return FS::FileAccessMode::ReadAppend;
34 case Mode::WriteAppend: 30 case Mode::WriteAppend:
35 return FS::FileAccessMode::Append;
36 case Mode::All: 31 case Mode::All:
37 return FS::FileAccessMode::ReadAppend; 32 return FS::FileAccessMode::ReadWrite;
38 default: 33 default:
39 return {}; 34 return {};
40 } 35 }
diff --git a/src/core/hle/api_version.h b/src/core/hle/api_version.h
new file mode 100644
index 000000000..811732179
--- /dev/null
+++ b/src/core/hle/api_version.h
@@ -0,0 +1,38 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/common_types.h"
6
7// This file contains yuzu's HLE API version constants.
8
9namespace HLE::ApiVersion {
10
11// Horizon OS version constants.
12
13constexpr u8 HOS_VERSION_MAJOR = 11;
14constexpr u8 HOS_VERSION_MINOR = 0;
15constexpr u8 HOS_VERSION_MICRO = 1;
16
17// NintendoSDK version constants.
18
19constexpr u8 SDK_REVISION_MAJOR = 1;
20constexpr u8 SDK_REVISION_MINOR = 0;
21
22constexpr char PLATFORM_STRING[] = "NX";
23constexpr char VERSION_HASH[] = "69103fcb2004dace877094c2f8c29e6113be5dbf";
24constexpr char DISPLAY_VERSION[] = "11.0.1";
25constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 11.0.1-1.0";
26
27// Atmosphere version constants.
28
29constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 0;
30constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 19;
31constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 4;
32
33constexpr u32 GetTargetFirmware() {
34 return u32{HOS_VERSION_MAJOR} << 24 | u32{HOS_VERSION_MINOR} << 16 |
35 u32{HOS_VERSION_MICRO} << 8 | 0U;
36}
37
38} // namespace HLE::ApiVersion
diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp
index da88f35bc..0c4bba66b 100644
--- a/src/core/hle/kernel/k_resource_limit.cpp
+++ b/src/core/hle/kernel/k_resource_limit.cpp
@@ -79,6 +79,7 @@ ResultCode KResourceLimit::SetLimitValue(LimitableResource which, s64 value) {
79 R_UNLESS(current_values[index] <= value, ResultInvalidState); 79 R_UNLESS(current_values[index] <= value, ResultInvalidState);
80 80
81 limit_values[index] = value; 81 limit_values[index] = value;
82 peak_values[index] = current_values[index];
82 83
83 return ResultSuccess; 84 return ResultSuccess;
84} 85}
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index a2844ea8c..dc15cf58b 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -313,7 +313,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe
313 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); 313 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
314 314
315 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { 315 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
316 void(Common::FS::RemoveFile(zip_path)); 316 Common::FS::RemoveFile(zip_path);
317 } 317 }
318 318
319 HandleDownloadDisplayResult(applet_manager, res); 319 HandleDownloadDisplayResult(applet_manager, res);
@@ -445,7 +445,7 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)
445 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); 445 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
446 446
447 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { 447 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
448 void(Common::FS::RemoveFile(bin_file_path)); 448 Common::FS::RemoveFile(bin_file_path);
449 } 449 }
450 450
451 HandleDownloadDisplayResult(applet_manager, res); 451 HandleDownloadDisplayResult(applet_manager, res);
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 7acad3798..1eb02aee2 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -314,6 +314,8 @@ void Controller_NPad::OnInit() {
314 314
315void Controller_NPad::OnLoadInputDevices() { 315void Controller_NPad::OnLoadInputDevices() {
316 const auto& players = Settings::values.players.GetValue(); 316 const auto& players = Settings::values.players.GetValue();
317
318 std::lock_guard lock{mutex};
317 for (std::size_t i = 0; i < players.size(); ++i) { 319 for (std::size_t i = 0; i < players.size(); ++i) {
318 std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, 320 std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
319 players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END, 321 players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
@@ -348,6 +350,8 @@ void Controller_NPad::OnRelease() {
348} 350}
349 351
350void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { 352void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
353 std::lock_guard lock{mutex};
354
351 const auto controller_idx = NPadIdToIndex(npad_id); 355 const auto controller_idx = NPadIdToIndex(npad_id);
352 const auto controller_type = connected_controllers[controller_idx].type; 356 const auto controller_type = connected_controllers[controller_idx].type;
353 if (!connected_controllers[controller_idx].is_connected) { 357 if (!connected_controllers[controller_idx].is_connected) {
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index c050c9a44..1409d82a2 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -6,6 +6,8 @@
6 6
7#include <array> 7#include <array>
8#include <atomic> 8#include <atomic>
9#include <mutex>
10
9#include "common/bit_field.h" 11#include "common/bit_field.h"
10#include "common/common_types.h" 12#include "common/common_types.h"
11#include "common/quaternion.h" 13#include "common/quaternion.h"
@@ -563,6 +565,8 @@ private:
563 using MotionArray = std::array< 565 using MotionArray = std::array<
564 std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>, 566 std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>,
565 10>; 567 10>;
568
569 std::mutex mutex;
566 ButtonArray buttons; 570 ButtonArray buttons;
567 StickArray sticks; 571 StickArray sticks;
568 VibrationArray vibrations; 572 VibrationArray vibrations;
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index d1dbc659b..1d810562f 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -307,6 +307,9 @@ void NVFlinger::Compose() {
307} 307}
308 308
309s64 NVFlinger::GetNextTicks() const { 309s64 NVFlinger::GetNextTicks() const {
310 if (Settings::values.disable_fps_limit.GetValue()) {
311 return 0;
312 }
310 constexpr s64 max_hertz = 120LL; 313 constexpr s64 max_hertz = 120LL;
311 return (1000000000 * (1LL << swap_interval)) / max_hertz; 314 return (1000000000 * (1LL << swap_interval)) / max_hertz;
312} 315}
diff --git a/src/core/hle/service/spl/csrng.cpp b/src/core/hle/service/spl/csrng.cpp
index 1beca417c..9c7f89475 100644
--- a/src/core/hle/service/spl/csrng.cpp
+++ b/src/core/hle/service/spl/csrng.cpp
@@ -9,7 +9,7 @@ namespace Service::SPL {
9CSRNG::CSRNG(Core::System& system_, std::shared_ptr<Module> module_) 9CSRNG::CSRNG(Core::System& system_, std::shared_ptr<Module> module_)
10 : Interface(system_, std::move(module_), "csrng") { 10 : Interface(system_, std::move(module_), "csrng") {
11 static const FunctionInfo functions[] = { 11 static const FunctionInfo functions[] = {
12 {0, &CSRNG::GetRandomBytes, "GetRandomBytes"}, 12 {0, &CSRNG::GenerateRandomBytes, "GenerateRandomBytes"},
13 }; 13 };
14 RegisterHandlers(functions); 14 RegisterHandlers(functions);
15} 15}
diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp
index 0b5e2b7c3..ebb179aa8 100644
--- a/src/core/hle/service/spl/module.cpp
+++ b/src/core/hle/service/spl/module.cpp
@@ -10,6 +10,7 @@
10#include <vector> 10#include <vector>
11#include "common/logging/log.h" 11#include "common/logging/log.h"
12#include "common/settings.h" 12#include "common/settings.h"
13#include "core/hle/api_version.h"
13#include "core/hle/ipc_helpers.h" 14#include "core/hle/ipc_helpers.h"
14#include "core/hle/service/spl/csrng.h" 15#include "core/hle/service/spl/csrng.h"
15#include "core/hle/service/spl/module.h" 16#include "core/hle/service/spl/module.h"
@@ -24,7 +25,46 @@ Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> modu
24 25
25Module::Interface::~Interface() = default; 26Module::Interface::~Interface() = default;
26 27
27void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) { 28void Module::Interface::GetConfig(Kernel::HLERequestContext& ctx) {
29 IPC::RequestParser rp{ctx};
30 const auto config_item = rp.PopEnum<ConfigItem>();
31
32 // This should call svcCallSecureMonitor with the appropriate args.
33 // Since we do not have it implemented yet, we will use this for now.
34 const auto smc_result = GetConfigImpl(config_item);
35 const auto result_code = smc_result.Code();
36
37 if (smc_result.Failed()) {
38 LOG_ERROR(Service_SPL, "called, config_item={}, result_code={}", config_item,
39 result_code.raw);
40
41 IPC::ResponseBuilder rb{ctx, 2};
42 rb.Push(result_code);
43 }
44
45 LOG_DEBUG(Service_SPL, "called, config_item={}, result_code={}, smc_result={}", config_item,
46 result_code.raw, *smc_result);
47
48 IPC::ResponseBuilder rb{ctx, 4};
49 rb.Push(result_code);
50 rb.Push(*smc_result);
51}
52
53void Module::Interface::ModularExponentiate(Kernel::HLERequestContext& ctx) {
54 UNIMPLEMENTED_MSG("ModularExponentiate is not implemented!");
55
56 IPC::ResponseBuilder rb{ctx, 2};
57 rb.Push(ResultSecureMonitorNotImplemented);
58}
59
60void Module::Interface::SetConfig(Kernel::HLERequestContext& ctx) {
61 UNIMPLEMENTED_MSG("SetConfig is not implemented!");
62
63 IPC::ResponseBuilder rb{ctx, 2};
64 rb.Push(ResultSecureMonitorNotImplemented);
65}
66
67void Module::Interface::GenerateRandomBytes(Kernel::HLERequestContext& ctx) {
28 LOG_DEBUG(Service_SPL, "called"); 68 LOG_DEBUG(Service_SPL, "called");
29 69
30 const std::size_t size = ctx.GetWriteBufferSize(); 70 const std::size_t size = ctx.GetWriteBufferSize();
@@ -39,6 +79,88 @@ void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) {
39 rb.Push(ResultSuccess); 79 rb.Push(ResultSuccess);
40} 80}
41 81
82void Module::Interface::IsDevelopment(Kernel::HLERequestContext& ctx) {
83 UNIMPLEMENTED_MSG("IsDevelopment is not implemented!");
84
85 IPC::ResponseBuilder rb{ctx, 2};
86 rb.Push(ResultSecureMonitorNotImplemented);
87}
88
89void Module::Interface::SetBootReason(Kernel::HLERequestContext& ctx) {
90 UNIMPLEMENTED_MSG("SetBootReason is not implemented!");
91
92 IPC::ResponseBuilder rb{ctx, 2};
93 rb.Push(ResultSecureMonitorNotImplemented);
94}
95
96void Module::Interface::GetBootReason(Kernel::HLERequestContext& ctx) {
97 UNIMPLEMENTED_MSG("GetBootReason is not implemented!");
98
99 IPC::ResponseBuilder rb{ctx, 2};
100 rb.Push(ResultSecureMonitorNotImplemented);
101}
102
103ResultVal<u64> Module::Interface::GetConfigImpl(ConfigItem config_item) const {
104 switch (config_item) {
105 case ConfigItem::DisableProgramVerification:
106 case ConfigItem::DramId:
107 case ConfigItem::SecurityEngineInterruptNumber:
108 case ConfigItem::FuseVersion:
109 case ConfigItem::HardwareType:
110 case ConfigItem::HardwareState:
111 case ConfigItem::IsRecoveryBoot:
112 case ConfigItem::DeviceId:
113 case ConfigItem::BootReason:
114 case ConfigItem::MemoryMode:
115 case ConfigItem::IsDevelopmentFunctionEnabled:
116 case ConfigItem::KernelConfiguration:
117 case ConfigItem::IsChargerHiZModeEnabled:
118 case ConfigItem::QuestState:
119 case ConfigItem::RegulatorType:
120 case ConfigItem::DeviceUniqueKeyGeneration:
121 case ConfigItem::Package2Hash:
122 return ResultSecureMonitorNotImplemented;
123 case ConfigItem::ExosphereApiVersion:
124 // Get information about the current exosphere version.
125 return MakeResult((u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MAJOR} << 56) |
126 (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MINOR} << 48) |
127 (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MICRO} << 40) |
128 (static_cast<u64>(HLE::ApiVersion::GetTargetFirmware())));
129 case ConfigItem::ExosphereNeedsReboot:
130 // We are executing, so we aren't in the process of rebooting.
131 return MakeResult(u64{0});
132 case ConfigItem::ExosphereNeedsShutdown:
133 // We are executing, so we aren't in the process of shutting down.
134 return MakeResult(u64{0});
135 case ConfigItem::ExosphereGitCommitHash:
136 // Get information about the current exosphere git commit hash.
137 return MakeResult(u64{0});
138 case ConfigItem::ExosphereHasRcmBugPatch:
139 // Get information about whether this unit has the RCM bug patched.
140 return MakeResult(u64{0});
141 case ConfigItem::ExosphereBlankProdInfo:
142 // Get whether this unit should simulate a "blanked" PRODINFO.
143 return MakeResult(u64{0});
144 case ConfigItem::ExosphereAllowCalWrites:
145 // Get whether this unit should allow writing to the calibration partition.
146 return MakeResult(u64{0});
147 case ConfigItem::ExosphereEmummcType:
148 // Get what kind of emummc this unit has active.
149 return MakeResult(u64{0});
150 case ConfigItem::ExospherePayloadAddress:
151 // Gets the physical address of the reboot payload buffer, if one exists.
152 return ResultSecureMonitorNotInitialized;
153 case ConfigItem::ExosphereLogConfiguration:
154 // Get the log configuration.
155 return MakeResult(u64{0});
156 case ConfigItem::ExosphereForceEnableUsb30:
157 // Get whether usb 3.0 should be force-enabled.
158 return MakeResult(u64{0});
159 default:
160 return ResultSecureMonitorInvalidArgument;
161 }
162}
163
42void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { 164void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
43 auto module = std::make_shared<Module>(); 165 auto module = std::make_shared<Module>();
44 std::make_shared<CSRNG>(system, module)->InstallAsService(service_manager); 166 std::make_shared<CSRNG>(system, module)->InstallAsService(service_manager);
diff --git a/src/core/hle/service/spl/module.h b/src/core/hle/service/spl/module.h
index 71855c1bf..61630df80 100644
--- a/src/core/hle/service/spl/module.h
+++ b/src/core/hle/service/spl/module.h
@@ -6,6 +6,8 @@
6 6
7#include <random> 7#include <random>
8#include "core/hle/service/service.h" 8#include "core/hle/service/service.h"
9#include "core/hle/service/spl/spl_results.h"
10#include "core/hle/service/spl/spl_types.h"
9 11
10namespace Core { 12namespace Core {
11class System; 13class System;
@@ -21,12 +23,21 @@ public:
21 const char* name); 23 const char* name);
22 ~Interface() override; 24 ~Interface() override;
23 25
24 void GetRandomBytes(Kernel::HLERequestContext& ctx); 26 // General
27 void GetConfig(Kernel::HLERequestContext& ctx);
28 void ModularExponentiate(Kernel::HLERequestContext& ctx);
29 void SetConfig(Kernel::HLERequestContext& ctx);
30 void GenerateRandomBytes(Kernel::HLERequestContext& ctx);
31 void IsDevelopment(Kernel::HLERequestContext& ctx);
32 void SetBootReason(Kernel::HLERequestContext& ctx);
33 void GetBootReason(Kernel::HLERequestContext& ctx);
25 34
26 protected: 35 protected:
27 std::shared_ptr<Module> module; 36 std::shared_ptr<Module> module;
28 37
29 private: 38 private:
39 ResultVal<u64> GetConfigImpl(ConfigItem config_item) const;
40
30 std::mt19937 rng; 41 std::mt19937 rng;
31 }; 42 };
32}; 43};
diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp
index fff3f3c42..20384042f 100644
--- a/src/core/hle/service/spl/spl.cpp
+++ b/src/core/hle/service/spl/spl.cpp
@@ -10,13 +10,13 @@ SPL::SPL(Core::System& system_, std::shared_ptr<Module> module_)
10 : Interface(system_, std::move(module_), "spl:") { 10 : Interface(system_, std::move(module_), "spl:") {
11 // clang-format off 11 // clang-format off
12 static const FunctionInfo functions[] = { 12 static const FunctionInfo functions[] = {
13 {0, nullptr, "GetConfig"}, 13 {0, &SPL::GetConfig, "GetConfig"},
14 {1, nullptr, "ModularExponentiate"}, 14 {1, &SPL::ModularExponentiate, "ModularExponentiate"},
15 {5, nullptr, "SetConfig"}, 15 {5, &SPL::SetConfig, "SetConfig"},
16 {7, &SPL::GetRandomBytes, "GetRandomBytes"}, 16 {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"},
17 {11, nullptr, "IsDevelopment"}, 17 {11, &SPL::IsDevelopment, "IsDevelopment"},
18 {24, nullptr, "SetBootReason"}, 18 {24, &SPL::SetBootReason, "SetBootReason"},
19 {25, nullptr, "GetBootReason"}, 19 {25, &SPL::GetBootReason, "GetBootReason"},
20 }; 20 };
21 // clang-format on 21 // clang-format on
22 22
@@ -27,22 +27,22 @@ SPL_MIG::SPL_MIG(Core::System& system_, std::shared_ptr<Module> module_)
27 : Interface(system_, std::move(module_), "spl:mig") { 27 : Interface(system_, std::move(module_), "spl:mig") {
28 // clang-format off 28 // clang-format off
29 static const FunctionInfo functions[] = { 29 static const FunctionInfo functions[] = {
30 {0, nullptr, "GetConfig"}, 30 {0, &SPL::GetConfig, "GetConfig"},
31 {1, nullptr, "ModularExponentiate"}, 31 {1, &SPL::ModularExponentiate, "ModularExponentiate"},
32 {2, nullptr, "GenerateAesKek"}, 32 {2, nullptr, "GenerateAesKek"},
33 {3, nullptr, "LoadAesKey"}, 33 {3, nullptr, "LoadAesKey"},
34 {4, nullptr, "GenerateAesKey"}, 34 {4, nullptr, "GenerateAesKey"},
35 {5, nullptr, "SetConfig"}, 35 {5, &SPL::SetConfig, "SetConfig"},
36 {7, &SPL::GetRandomBytes, "GenerateRandomBytes"}, 36 {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"},
37 {11, nullptr, "IsDevelopment"}, 37 {11, &SPL::IsDevelopment, "IsDevelopment"},
38 {14, nullptr, "DecryptAesKey"}, 38 {14, nullptr, "DecryptAesKey"},
39 {15, nullptr, "CryptAesCtr"}, 39 {15, nullptr, "CryptAesCtr"},
40 {16, nullptr, "ComputeCmac"}, 40 {16, nullptr, "ComputeCmac"},
41 {21, nullptr, "AllocateAesKeyslot"}, 41 {21, nullptr, "AllocateAesKeyslot"},
42 {22, nullptr, "DeallocateAesKeySlot"}, 42 {22, nullptr, "DeallocateAesKeySlot"},
43 {23, nullptr, "GetAesKeyslotAvailableEvent"}, 43 {23, nullptr, "GetAesKeyslotAvailableEvent"},
44 {24, nullptr, "SetBootReason"}, 44 {24, &SPL::SetBootReason, "SetBootReason"},
45 {25, nullptr, "GetBootReason"}, 45 {25, &SPL::GetBootReason, "GetBootReason"},
46 }; 46 };
47 // clang-format on 47 // clang-format on
48 48
@@ -53,16 +53,16 @@ SPL_FS::SPL_FS(Core::System& system_, std::shared_ptr<Module> module_)
53 : Interface(system_, std::move(module_), "spl:fs") { 53 : Interface(system_, std::move(module_), "spl:fs") {
54 // clang-format off 54 // clang-format off
55 static const FunctionInfo functions[] = { 55 static const FunctionInfo functions[] = {
56 {0, nullptr, "GetConfig"}, 56 {0, &SPL::GetConfig, "GetConfig"},
57 {1, nullptr, "ModularExponentiate"}, 57 {1, &SPL::ModularExponentiate, "ModularExponentiate"},
58 {2, nullptr, "GenerateAesKek"}, 58 {2, nullptr, "GenerateAesKek"},
59 {3, nullptr, "LoadAesKey"}, 59 {3, nullptr, "LoadAesKey"},
60 {4, nullptr, "GenerateAesKey"}, 60 {4, nullptr, "GenerateAesKey"},
61 {5, nullptr, "SetConfig"}, 61 {5, &SPL::SetConfig, "SetConfig"},
62 {7, &SPL::GetRandomBytes, "GenerateRandomBytes"}, 62 {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"},
63 {9, nullptr, "ImportLotusKey"}, 63 {9, nullptr, "ImportLotusKey"},
64 {10, nullptr, "DecryptLotusMessage"}, 64 {10, nullptr, "DecryptLotusMessage"},
65 {11, nullptr, "IsDevelopment"}, 65 {11, &SPL::IsDevelopment, "IsDevelopment"},
66 {12, nullptr, "GenerateSpecificAesKey"}, 66 {12, nullptr, "GenerateSpecificAesKey"},
67 {14, nullptr, "DecryptAesKey"}, 67 {14, nullptr, "DecryptAesKey"},
68 {15, nullptr, "CryptAesCtr"}, 68 {15, nullptr, "CryptAesCtr"},
@@ -71,8 +71,8 @@ SPL_FS::SPL_FS(Core::System& system_, std::shared_ptr<Module> module_)
71 {21, nullptr, "AllocateAesKeyslot"}, 71 {21, nullptr, "AllocateAesKeyslot"},
72 {22, nullptr, "DeallocateAesKeySlot"}, 72 {22, nullptr, "DeallocateAesKeySlot"},
73 {23, nullptr, "GetAesKeyslotAvailableEvent"}, 73 {23, nullptr, "GetAesKeyslotAvailableEvent"},
74 {24, nullptr, "SetBootReason"}, 74 {24, &SPL::SetBootReason, "SetBootReason"},
75 {25, nullptr, "GetBootReason"}, 75 {25, &SPL::GetBootReason, "GetBootReason"},
76 {31, nullptr, "GetPackage2Hash"}, 76 {31, nullptr, "GetPackage2Hash"},
77 }; 77 };
78 // clang-format on 78 // clang-format on
@@ -84,14 +84,14 @@ SPL_SSL::SPL_SSL(Core::System& system_, std::shared_ptr<Module> module_)
84 : Interface(system_, std::move(module_), "spl:ssl") { 84 : Interface(system_, std::move(module_), "spl:ssl") {
85 // clang-format off 85 // clang-format off
86 static const FunctionInfo functions[] = { 86 static const FunctionInfo functions[] = {
87 {0, nullptr, "GetConfig"}, 87 {0, &SPL::GetConfig, "GetConfig"},
88 {1, nullptr, "ModularExponentiate"}, 88 {1, &SPL::ModularExponentiate, "ModularExponentiate"},
89 {2, nullptr, "GenerateAesKek"}, 89 {2, nullptr, "GenerateAesKek"},
90 {3, nullptr, "LoadAesKey"}, 90 {3, nullptr, "LoadAesKey"},
91 {4, nullptr, "GenerateAesKey"}, 91 {4, nullptr, "GenerateAesKey"},
92 {5, nullptr, "SetConfig"}, 92 {5, &SPL::SetConfig, "SetConfig"},
93 {7, &SPL::GetRandomBytes, "GetRandomBytes"}, 93 {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"},
94 {11, nullptr, "IsDevelopment"}, 94 {11, &SPL::IsDevelopment, "IsDevelopment"},
95 {13, nullptr, "DecryptDeviceUniqueData"}, 95 {13, nullptr, "DecryptDeviceUniqueData"},
96 {14, nullptr, "DecryptAesKey"}, 96 {14, nullptr, "DecryptAesKey"},
97 {15, nullptr, "CryptAesCtr"}, 97 {15, nullptr, "CryptAesCtr"},
@@ -99,8 +99,8 @@ SPL_SSL::SPL_SSL(Core::System& system_, std::shared_ptr<Module> module_)
99 {21, nullptr, "AllocateAesKeyslot"}, 99 {21, nullptr, "AllocateAesKeyslot"},
100 {22, nullptr, "DeallocateAesKeySlot"}, 100 {22, nullptr, "DeallocateAesKeySlot"},
101 {23, nullptr, "GetAesKeyslotAvailableEvent"}, 101 {23, nullptr, "GetAesKeyslotAvailableEvent"},
102 {24, nullptr, "SetBootReason"}, 102 {24, &SPL::SetBootReason, "SetBootReason"},
103 {25, nullptr, "GetBootReason"}, 103 {25, &SPL::GetBootReason, "GetBootReason"},
104 {26, nullptr, "DecryptAndStoreSslClientCertKey"}, 104 {26, nullptr, "DecryptAndStoreSslClientCertKey"},
105 {27, nullptr, "ModularExponentiateWithSslClientCertKey"}, 105 {27, nullptr, "ModularExponentiateWithSslClientCertKey"},
106 }; 106 };
@@ -113,14 +113,14 @@ SPL_ES::SPL_ES(Core::System& system_, std::shared_ptr<Module> module_)
113 : Interface(system_, std::move(module_), "spl:es") { 113 : Interface(system_, std::move(module_), "spl:es") {
114 // clang-format off 114 // clang-format off
115 static const FunctionInfo functions[] = { 115 static const FunctionInfo functions[] = {
116 {0, nullptr, "GetConfig"}, 116 {0, &SPL::GetConfig, "GetConfig"},
117 {1, nullptr, "ModularExponentiate"}, 117 {1, &SPL::ModularExponentiate, "ModularExponentiate"},
118 {2, nullptr, "GenerateAesKek"}, 118 {2, nullptr, "GenerateAesKek"},
119 {3, nullptr, "LoadAesKey"}, 119 {3, nullptr, "LoadAesKey"},
120 {4, nullptr, "GenerateAesKey"}, 120 {4, nullptr, "GenerateAesKey"},
121 {5, nullptr, "SetConfig"}, 121 {5, &SPL::SetConfig, "SetConfig"},
122 {7, &SPL::GetRandomBytes, "GenerateRandomBytes"}, 122 {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"},
123 {11, nullptr, "IsDevelopment"}, 123 {11, &SPL::IsDevelopment, "IsDevelopment"},
124 {13, nullptr, "DecryptDeviceUniqueData"}, 124 {13, nullptr, "DecryptDeviceUniqueData"},
125 {14, nullptr, "DecryptAesKey"}, 125 {14, nullptr, "DecryptAesKey"},
126 {15, nullptr, "CryptAesCtr"}, 126 {15, nullptr, "CryptAesCtr"},
@@ -131,8 +131,8 @@ SPL_ES::SPL_ES(Core::System& system_, std::shared_ptr<Module> module_)
131 {21, nullptr, "AllocateAesKeyslot"}, 131 {21, nullptr, "AllocateAesKeyslot"},
132 {22, nullptr, "DeallocateAesKeySlot"}, 132 {22, nullptr, "DeallocateAesKeySlot"},
133 {23, nullptr, "GetAesKeyslotAvailableEvent"}, 133 {23, nullptr, "GetAesKeyslotAvailableEvent"},
134 {24, nullptr, "SetBootReason"}, 134 {24, &SPL::SetBootReason, "SetBootReason"},
135 {25, nullptr, "GetBootReason"}, 135 {25, &SPL::GetBootReason, "GetBootReason"},
136 {28, nullptr, "DecryptAndStoreDrmDeviceCertKey"}, 136 {28, nullptr, "DecryptAndStoreDrmDeviceCertKey"},
137 {29, nullptr, "ModularExponentiateWithDrmDeviceCertKey"}, 137 {29, nullptr, "ModularExponentiateWithDrmDeviceCertKey"},
138 {31, nullptr, "PrepareEsArchiveKey"}, 138 {31, nullptr, "PrepareEsArchiveKey"},
@@ -147,14 +147,14 @@ SPL_MANU::SPL_MANU(Core::System& system_, std::shared_ptr<Module> module_)
147 : Interface(system_, std::move(module_), "spl:manu") { 147 : Interface(system_, std::move(module_), "spl:manu") {
148 // clang-format off 148 // clang-format off
149 static const FunctionInfo functions[] = { 149 static const FunctionInfo functions[] = {
150 {0, nullptr, "GetConfig"}, 150 {0, &SPL::GetConfig, "GetConfig"},
151 {1, nullptr, "ModularExponentiate"}, 151 {1, &SPL::ModularExponentiate, "ModularExponentiate"},
152 {2, nullptr, "GenerateAesKek"}, 152 {2, nullptr, "GenerateAesKek"},
153 {3, nullptr, "LoadAesKey"}, 153 {3, nullptr, "LoadAesKey"},
154 {4, nullptr, "GenerateAesKey"}, 154 {4, nullptr, "GenerateAesKey"},
155 {5, nullptr, "SetConfig"}, 155 {5, &SPL::SetConfig, "SetConfig"},
156 {7, &SPL::GetRandomBytes, "GetRandomBytes"}, 156 {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"},
157 {11, nullptr, "IsDevelopment"}, 157 {11, &SPL::IsDevelopment, "IsDevelopment"},
158 {13, nullptr, "DecryptDeviceUniqueData"}, 158 {13, nullptr, "DecryptDeviceUniqueData"},
159 {14, nullptr, "DecryptAesKey"}, 159 {14, nullptr, "DecryptAesKey"},
160 {15, nullptr, "CryptAesCtr"}, 160 {15, nullptr, "CryptAesCtr"},
@@ -162,8 +162,8 @@ SPL_MANU::SPL_MANU(Core::System& system_, std::shared_ptr<Module> module_)
162 {21, nullptr, "AllocateAesKeyslot"}, 162 {21, nullptr, "AllocateAesKeyslot"},
163 {22, nullptr, "DeallocateAesKeySlot"}, 163 {22, nullptr, "DeallocateAesKeySlot"},
164 {23, nullptr, "GetAesKeyslotAvailableEvent"}, 164 {23, nullptr, "GetAesKeyslotAvailableEvent"},
165 {24, nullptr, "SetBootReason"}, 165 {24, &SPL::SetBootReason, "SetBootReason"},
166 {25, nullptr, "GetBootReason"}, 166 {25, &SPL::GetBootReason, "GetBootReason"},
167 {30, nullptr, "ReencryptDeviceUniqueData"}, 167 {30, nullptr, "ReencryptDeviceUniqueData"},
168 }; 168 };
169 // clang-format on 169 // clang-format on
diff --git a/src/core/hle/service/spl/spl_results.h b/src/core/hle/service/spl/spl_results.h
new file mode 100644
index 000000000..878fa91b7
--- /dev/null
+++ b/src/core/hle/service/spl/spl_results.h
@@ -0,0 +1,29 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/hle/result.h"
6
7namespace Service::SPL {
8
9// Description 0 - 99
10constexpr ResultCode ResultSecureMonitorError{ErrorModule::SPL, 0};
11constexpr ResultCode ResultSecureMonitorNotImplemented{ErrorModule::SPL, 1};
12constexpr ResultCode ResultSecureMonitorInvalidArgument{ErrorModule::SPL, 2};
13constexpr ResultCode ResultSecureMonitorBusy{ErrorModule::SPL, 3};
14constexpr ResultCode ResultSecureMonitorNoAsyncOperation{ErrorModule::SPL, 4};
15constexpr ResultCode ResultSecureMonitorInvalidAsyncOperation{ErrorModule::SPL, 5};
16constexpr ResultCode ResultSecureMonitorNotPermitted{ErrorModule::SPL, 6};
17constexpr ResultCode ResultSecureMonitorNotInitialized{ErrorModule::SPL, 7};
18
19constexpr ResultCode ResultInvalidSize{ErrorModule::SPL, 100};
20constexpr ResultCode ResultUnknownSecureMonitorError{ErrorModule::SPL, 101};
21constexpr ResultCode ResultDecryptionFailed{ErrorModule::SPL, 102};
22
23constexpr ResultCode ResultOutOfKeySlots{ErrorModule::SPL, 104};
24constexpr ResultCode ResultInvalidKeySlot{ErrorModule::SPL, 105};
25constexpr ResultCode ResultBootReasonAlreadySet{ErrorModule::SPL, 106};
26constexpr ResultCode ResultBootReasonNotSet{ErrorModule::SPL, 107};
27constexpr ResultCode ResultInvalidArgument{ErrorModule::SPL, 108};
28
29} // namespace Service::SPL
diff --git a/src/core/hle/service/spl/spl_types.h b/src/core/hle/service/spl/spl_types.h
new file mode 100644
index 000000000..23c39f7ed
--- /dev/null
+++ b/src/core/hle/service/spl/spl_types.h
@@ -0,0 +1,230 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <span>
6
7#include "common/bit_field.h"
8#include "common/common_types.h"
9
10namespace Service::SPL {
11
12constexpr size_t AES_128_KEY_SIZE = 0x10;
13
14namespace Smc {
15
16enum class FunctionId : u32 {
17 SetConfig = 0xC3000401,
18 GetConfig = 0xC3000002,
19 GetResult = 0xC3000003,
20 GetResultData = 0xC3000404,
21 ModularExponentiate = 0xC3000E05,
22 GenerateRandomBytes = 0xC3000006,
23 GenerateAesKek = 0xC3000007,
24 LoadAesKey = 0xC3000008,
25 ComputeAes = 0xC3000009,
26 GenerateSpecificAesKey = 0xC300000A,
27 ComputeCmac = 0xC300040B,
28 ReencryptDeviceUniqueData = 0xC300D60C,
29 DecryptDeviceUniqueData = 0xC300100D,
30
31 ModularExponentiateWithStorageKey = 0xC300060F,
32 PrepareEsDeviceUniqueKey = 0xC3000610,
33 LoadPreparedAesKey = 0xC3000011,
34 PrepareCommonEsTitleKey = 0xC3000012,
35
36 // Deprecated functions.
37 LoadEsDeviceKey = 0xC300100C,
38 DecryptAndStoreGcKey = 0xC300100E,
39
40 // Atmosphere functions.
41 AtmosphereIramCopy = 0xF0000201,
42 AtmosphereReadWriteRegister = 0xF0000002,
43
44 AtmosphereGetEmummcConfig = 0xF0000404,
45};
46
47enum class CipherMode {
48 CbcEncrypt = 0,
49 CbcDecrypt = 1,
50 Ctr = 2,
51};
52
53enum class DeviceUniqueDataMode {
54 DecryptDeviceUniqueData = 0,
55 DecryptAndStoreGcKey = 1,
56 DecryptAndStoreEsDeviceKey = 2,
57 DecryptAndStoreSslKey = 3,
58 DecryptAndStoreDrmDeviceCertKey = 4,
59};
60
61enum class ModularExponentiateWithStorageKeyMode {
62 Gc = 0,
63 Ssl = 1,
64 DrmDeviceCert = 2,
65};
66
67enum class EsCommonKeyType {
68 TitleKey = 0,
69 ArchiveKey = 1,
70};
71
72struct AsyncOperationKey {
73 u64 value;
74};
75
76} // namespace Smc
77
78enum class HardwareType {
79 Icosa = 0,
80 Copper = 1,
81 Hoag = 2,
82 Iowa = 3,
83 Calcio = 4,
84 Aula = 5,
85};
86
87enum class SocType {
88 Erista = 0,
89 Mariko = 1,
90};
91
92enum class HardwareState {
93 Development = 0,
94 Production = 1,
95};
96
97enum class MemoryArrangement {
98 Standard = 0,
99 StandardForAppletDev = 1,
100 StandardForSystemDev = 2,
101 Expanded = 3,
102 ExpandedForAppletDev = 4,
103
104 // Note: Dynamic is not official.
105 // Atmosphere uses it to maintain compatibility with firmwares prior to 6.0.0,
106 // which removed the explicit retrieval of memory arrangement from PM.
107 Dynamic = 5,
108 Count,
109};
110
111enum class BootReason {
112 Unknown = 0,
113 AcOk = 1,
114 OnKey = 2,
115 RtcAlarm1 = 3,
116 RtcAlarm2 = 4,
117};
118
119struct BootReasonValue {
120 union {
121 u32 value{};
122
123 BitField<0, 8, u32> power_intr;
124 BitField<8, 8, u32> rtc_intr;
125 BitField<16, 8, u32> nv_erc;
126 BitField<24, 8, u32> boot_reason;
127 };
128};
129static_assert(sizeof(BootReasonValue) == sizeof(u32), "BootReasonValue definition!");
130
131struct AesKey {
132 std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{};
133
134 std::span<u8> AsBytes() {
135 return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE};
136 }
137
138 std::span<const u8> AsBytes() const {
139 return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE};
140 }
141};
142static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "AesKey definition!");
143
144struct IvCtr {
145 std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{};
146
147 std::span<u8> AsBytes() {
148 return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE};
149 }
150
151 std::span<const u8> AsBytes() const {
152 return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE};
153 }
154};
155static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "IvCtr definition!");
156
157struct Cmac {
158 std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{};
159
160 std::span<u8> AsBytes() {
161 return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE};
162 }
163
164 std::span<const u8> AsBytes() const {
165 return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE};
166 }
167};
168static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "Cmac definition!");
169
170struct AccessKey {
171 std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{};
172
173 std::span<u8> AsBytes() {
174 return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE};
175 }
176
177 std::span<const u8> AsBytes() const {
178 return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE};
179 }
180};
181static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "AccessKey definition!");
182
183struct KeySource {
184 std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{};
185
186 std::span<u8> AsBytes() {
187 return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE};
188 }
189
190 std::span<const u8> AsBytes() const {
191 return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE};
192 }
193};
194static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "KeySource definition!");
195
196enum class ConfigItem : u32 {
197 // Standard config items.
198 DisableProgramVerification = 1,
199 DramId = 2,
200 SecurityEngineInterruptNumber = 3,
201 FuseVersion = 4,
202 HardwareType = 5,
203 HardwareState = 6,
204 IsRecoveryBoot = 7,
205 DeviceId = 8,
206 BootReason = 9,
207 MemoryMode = 10,
208 IsDevelopmentFunctionEnabled = 11,
209 KernelConfiguration = 12,
210 IsChargerHiZModeEnabled = 13,
211 QuestState = 14,
212 RegulatorType = 15,
213 DeviceUniqueKeyGeneration = 16,
214 Package2Hash = 17,
215
216 // Extension config items for exosphere.
217 ExosphereApiVersion = 65000,
218 ExosphereNeedsReboot = 65001,
219 ExosphereNeedsShutdown = 65002,
220 ExosphereGitCommitHash = 65003,
221 ExosphereHasRcmBugPatch = 65004,
222 ExosphereBlankProdInfo = 65005,
223 ExosphereAllowCalWrites = 65006,
224 ExosphereEmummcType = 65007,
225 ExospherePayloadAddress = 65008,
226 ExosphereLogConfiguration = 65009,
227 ExosphereForceEnableUsb30 = 65010,
228};
229
230} // namespace Service::SPL
diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp
index bf4402308..c634b6abd 100644
--- a/src/core/hle/service/time/time_zone_content_manager.cpp
+++ b/src/core/hle/service/time/time_zone_content_manager.cpp
@@ -125,7 +125,7 @@ ResultCode TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& locati
125 return ERROR_TIME_NOT_FOUND; 125 return ERROR_TIME_NOT_FOUND;
126 } 126 }
127 127
128 vfs_file = zoneinfo_dir->GetFile(location_name); 128 vfs_file = zoneinfo_dir->GetFileRelative(location_name);
129 if (!vfs_file) { 129 if (!vfs_file) {
130 LOG_ERROR(Service_Time, "{:016X} has no file \"{}\"! Using default timezone.", 130 LOG_ERROR(Service_Time, "{:016X} has no file \"{}\"! Using default timezone.",
131 time_zone_binary_titleid, location_name); 131 time_zone_binary_titleid, location_name);
diff --git a/src/input_common/mouse/mouse_input.cpp b/src/input_common/mouse/mouse_input.cpp
index a335e6da1..3b052ffb2 100644
--- a/src/input_common/mouse/mouse_input.cpp
+++ b/src/input_common/mouse/mouse_input.cpp
@@ -2,25 +2,23 @@
2// Licensed under GPLv2+ 2// Licensed under GPLv2+
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <stop_token>
6#include <thread>
7
5#include "common/settings.h" 8#include "common/settings.h"
6#include "input_common/mouse/mouse_input.h" 9#include "input_common/mouse/mouse_input.h"
7 10
8namespace MouseInput { 11namespace MouseInput {
9 12
10Mouse::Mouse() { 13Mouse::Mouse() {
11 update_thread = std::thread(&Mouse::UpdateThread, this); 14 update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); });
12} 15}
13 16
14Mouse::~Mouse() { 17Mouse::~Mouse() = default;
15 update_thread_running = false;
16 if (update_thread.joinable()) {
17 update_thread.join();
18 }
19}
20 18
21void Mouse::UpdateThread() { 19void Mouse::UpdateThread(std::stop_token stop_token) {
22 constexpr int update_time = 10; 20 constexpr int update_time = 10;
23 while (update_thread_running) { 21 while (!stop_token.stop_requested()) {
24 for (MouseInfo& info : mouse_info) { 22 for (MouseInfo& info : mouse_info) {
25 const Common::Vec3f angular_direction{ 23 const Common::Vec3f angular_direction{
26 -info.tilt_direction.y, 24 -info.tilt_direction.y,
diff --git a/src/input_common/mouse/mouse_input.h b/src/input_common/mouse/mouse_input.h
index 5a971ad67..c8bae99c1 100644
--- a/src/input_common/mouse/mouse_input.h
+++ b/src/input_common/mouse/mouse_input.h
@@ -6,6 +6,7 @@
6 6
7#include <array> 7#include <array>
8#include <mutex> 8#include <mutex>
9#include <stop_token>
9#include <thread> 10#include <thread>
10 11
11#include "common/common_types.h" 12#include "common/common_types.h"
@@ -85,7 +86,7 @@ public:
85 [[nodiscard]] const MouseData& GetMouseState(std::size_t button) const; 86 [[nodiscard]] const MouseData& GetMouseState(std::size_t button) const;
86 87
87private: 88private:
88 void UpdateThread(); 89 void UpdateThread(std::stop_token stop_token);
89 void UpdateYuzuSettings(); 90 void UpdateYuzuSettings();
90 void StopPanning(); 91 void StopPanning();
91 92
@@ -105,12 +106,11 @@ private:
105 u16 buttons{}; 106 u16 buttons{};
106 u16 toggle_buttons{}; 107 u16 toggle_buttons{};
107 u16 lock_buttons{}; 108 u16 lock_buttons{};
108 std::thread update_thread; 109 std::jthread update_thread;
109 MouseButton last_button{MouseButton::Undefined}; 110 MouseButton last_button{MouseButton::Undefined};
110 std::array<MouseInfo, 7> mouse_info; 111 std::array<MouseInfo, 7> mouse_info;
111 Common::SPSCQueue<MouseStatus> mouse_queue; 112 Common::SPSCQueue<MouseStatus> mouse_queue;
112 bool configuring{false}; 113 bool configuring{false};
113 bool update_thread_running{true};
114 int mouse_panning_timout{}; 114 int mouse_panning_timout{};
115}; 115};
116} // namespace MouseInput 116} // namespace MouseInput
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index f968b5b16..07939432f 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -4,10 +4,10 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <atomic>
8#include <functional> 7#include <functional>
9#include <optional> 8#include <optional>
10#include <span> 9#include <span>
10#include <stop_token>
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "video_core/engines/fermi_2d.h" 12#include "video_core/engines/fermi_2d.h"
13#include "video_core/gpu.h" 13#include "video_core/gpu.h"
@@ -123,7 +123,7 @@ public:
123 virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {} 123 virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {}
124 124
125 /// Initialize disk cached resources for the game being emulated 125 /// Initialize disk cached resources for the game being emulated
126 virtual void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading, 126 virtual void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
127 const DiskResourceLoadCallback& callback) {} 127 const DiskResourceLoadCallback& callback) {}
128 128
129 /// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver. 129 /// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver.
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index f87bb269b..eb8bdaa85 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -351,7 +351,7 @@ void RasterizerOpenGL::SetupShaders(bool is_indexed) {
351 } 351 }
352} 352}
353 353
354void RasterizerOpenGL::LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading, 354void RasterizerOpenGL::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
355 const VideoCore::DiskResourceLoadCallback& callback) { 355 const VideoCore::DiskResourceLoadCallback& callback) {
356 shader_cache.LoadDiskCache(title_id, stop_loading, callback); 356 shader_cache.LoadDiskCache(title_id, stop_loading, callback);
357} 357}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 76298517f..9995a563b 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -94,7 +94,7 @@ public:
94 const Tegra::Engines::Fermi2D::Config& copy_config) override; 94 const Tegra::Engines::Fermi2D::Config& copy_config) override;
95 bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr, 95 bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
96 u32 pixel_stride) override; 96 u32 pixel_stride) override;
97 void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading, 97 void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
98 const VideoCore::DiskResourceLoadCallback& callback) override; 98 const VideoCore::DiskResourceLoadCallback& callback) override;
99 99
100 /// Returns true when there are commands queued to the OpenGL server. 100 /// Returns true when there are commands queued to the OpenGL server.
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 5cf7cd151..5a01c59ec 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -331,7 +331,7 @@ ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer_,
331 331
332ShaderCacheOpenGL::~ShaderCacheOpenGL() = default; 332ShaderCacheOpenGL::~ShaderCacheOpenGL() = default;
333 333
334void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading, 334void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, std::stop_token stop_loading,
335 const VideoCore::DiskResourceLoadCallback& callback) { 335 const VideoCore::DiskResourceLoadCallback& callback) {
336 disk_cache.BindTitleID(title_id); 336 disk_cache.BindTitleID(title_id);
337 const std::optional transferable = disk_cache.LoadTransferable(); 337 const std::optional transferable = disk_cache.LoadTransferable();
@@ -372,7 +372,7 @@ void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop
372 const auto scope = context->Acquire(); 372 const auto scope = context->Acquire();
373 373
374 for (std::size_t i = begin; i < end; ++i) { 374 for (std::size_t i = begin; i < end; ++i) {
375 if (stop_loading) { 375 if (stop_loading.stop_requested()) {
376 return; 376 return;
377 } 377 }
378 const auto& entry = (*transferable)[i]; 378 const auto& entry = (*transferable)[i];
@@ -435,7 +435,7 @@ void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop
435 precompiled_cache_altered = true; 435 precompiled_cache_altered = true;
436 return; 436 return;
437 } 437 }
438 if (stop_loading) { 438 if (stop_loading.stop_requested()) {
439 return; 439 return;
440 } 440 }
441 441
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index 2aed0697e..b30308b6f 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -127,7 +127,7 @@ public:
127 ~ShaderCacheOpenGL() override; 127 ~ShaderCacheOpenGL() override;
128 128
129 /// Loads disk cache for the current game 129 /// Loads disk cache for the current game
130 void LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading, 130 void LoadDiskCache(u64 title_id, std::stop_token stop_loading,
131 const VideoCore::DiskResourceLoadCallback& callback); 131 const VideoCore::DiskResourceLoadCallback& callback);
132 132
133 /// Gets the current specified shader stage program 133 /// Gets the current specified shader stage program
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
index db78ce3d9..6852c11b0 100644
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
@@ -2,8 +2,7 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <atomic> 5#include <thread>
6#include <chrono>
7 6
8#include "common/settings.h" 7#include "common/settings.h"
9#include "video_core/renderer_vulkan/vk_master_semaphore.h" 8#include "video_core/renderer_vulkan/vk_master_semaphore.h"
@@ -12,8 +11,6 @@
12 11
13namespace Vulkan { 12namespace Vulkan {
14 13
15using namespace std::chrono_literals;
16
17MasterSemaphore::MasterSemaphore(const Device& device) { 14MasterSemaphore::MasterSemaphore(const Device& device) {
18 static constexpr VkSemaphoreTypeCreateInfoKHR semaphore_type_ci{ 15 static constexpr VkSemaphoreTypeCreateInfoKHR semaphore_type_ci{
19 .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR, 16 .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR,
@@ -34,9 +31,9 @@ MasterSemaphore::MasterSemaphore(const Device& device) {
34 // Validation layers have a bug where they fail to track resource usage when using timeline 31 // Validation layers have a bug where they fail to track resource usage when using timeline
35 // semaphores and synchronizing with GetSemaphoreCounterValueKHR. To workaround this issue, have 32 // semaphores and synchronizing with GetSemaphoreCounterValueKHR. To workaround this issue, have
36 // a separate thread waiting for each timeline semaphore value. 33 // a separate thread waiting for each timeline semaphore value.
37 debug_thread = std::thread([this] { 34 debug_thread = std::jthread([this](std::stop_token stop_token) {
38 u64 counter = 0; 35 u64 counter = 0;
39 while (!shutdown) { 36 while (!stop_token.stop_requested()) {
40 if (semaphore.Wait(counter, 10'000'000)) { 37 if (semaphore.Wait(counter, 10'000'000)) {
41 ++counter; 38 ++counter;
42 } 39 }
@@ -44,13 +41,6 @@ MasterSemaphore::MasterSemaphore(const Device& device) {
44 }); 41 });
45} 42}
46 43
47MasterSemaphore::~MasterSemaphore() { 44MasterSemaphore::~MasterSemaphore() = default;
48 shutdown = true;
49
50 // This thread might not be started
51 if (debug_thread.joinable()) {
52 debug_thread.join();
53 }
54}
55 45
56} // namespace Vulkan 46} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h
index 4b6d64daa..ee3cd35d0 100644
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.h
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h
@@ -65,11 +65,10 @@ public:
65 } 65 }
66 66
67private: 67private:
68 vk::Semaphore semaphore; ///< Timeline semaphore. 68 vk::Semaphore semaphore; ///< Timeline semaphore.
69 std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick. 69 std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick.
70 std::atomic<u64> current_tick{1}; ///< Current logical tick. 70 std::atomic<u64> current_tick{1}; ///< Current logical tick.
71 std::atomic<bool> shutdown{false}; ///< True when the object is being destroyed. 71 std::jthread debug_thread; ///< Debug thread to workaround validation layer bugs.
72 std::thread debug_thread; ///< Debug thread to workaround validation layer bugs.
73}; 72};
74 73
75} // namespace Vulkan 74} // namespace Vulkan
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index 9680167ee..4efe042b6 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -1098,7 +1098,15 @@ std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, const
1098 return std::nullopt; 1098 return std::nullopt;
1099 } 1099 }
1100 const ImageInfo& existing = image.info; 1100 const ImageInfo& existing = image.info;
1101 if (False(options & RelaxedOptions::Format)) { 1101 if (True(options & RelaxedOptions::Format)) {
1102 // Format checking is relaxed, but we still have to check for matching bytes per block.
1103 // This avoids creating a view for blits on UE4 titles where formats with different bytes
1104 // per block are aliased.
1105 if (BytesPerBlock(existing.format) != BytesPerBlock(candidate.format)) {
1106 return std::nullopt;
1107 }
1108 } else {
1109 // Format comaptibility is not relaxed, ensure we are creating a view on a compatible format
1102 if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) { 1110 if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) {
1103 return std::nullopt; 1111 return std::nullopt;
1104 } 1112 }
diff --git a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
index f0ee76519..758c038ba 100644
--- a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
+++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
@@ -50,7 +50,7 @@ NsightAftermathTracker::NsightAftermathTracker() {
50 } 50 }
51 dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir) / "gpucrash"; 51 dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir) / "gpucrash";
52 52
53 void(Common::FS::RemoveDirRecursively(dump_dir)); 53 Common::FS::RemoveDirRecursively(dump_dir);
54 if (!Common::FS::CreateDir(dump_dir)) { 54 if (!Common::FS::CreateDir(dump_dir)) {
55 LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory"); 55 LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory");
56 return; 56 return;
diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.cpp b/src/video_core/vulkan_common/vulkan_debug_callback.cpp
index 5c64c9bf7..0f60765bb 100644
--- a/src/video_core/vulkan_common/vulkan_debug_callback.cpp
+++ b/src/video_core/vulkan_common/vulkan_debug_callback.cpp
@@ -12,6 +12,14 @@ VkBool32 Callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
12 VkDebugUtilsMessageTypeFlagsEXT type, 12 VkDebugUtilsMessageTypeFlagsEXT type,
13 const VkDebugUtilsMessengerCallbackDataEXT* data, 13 const VkDebugUtilsMessengerCallbackDataEXT* data,
14 [[maybe_unused]] void* user_data) { 14 [[maybe_unused]] void* user_data) {
15 // Skip logging known false-positive validation errors
16 switch (static_cast<u32>(data->messageIdNumber)) {
17 case 0x682a878au: // VUID-vkCmdBindVertexBuffers2EXT-pBuffers-parameter
18 case 0x99fb7dfdu: // UNASSIGNED-RequiredParameter (vkCmdBindVertexBuffers2EXT pBuffers[0])
19 return VK_FALSE;
20 default:
21 break;
22 }
15 const std::string_view message{data->pMessage}; 23 const std::string_view message{data->pMessage};
16 if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { 24 if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
17 LOG_CRITICAL(Render_Vulkan, "{}", message); 25 LOG_CRITICAL(Render_Vulkan, "{}", message);
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index 67183e64c..e04f7dfc6 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -100,8 +100,9 @@ struct Client::Impl {
100 request.body = data; 100 request.body = data;
101 101
102 httplib::Response response; 102 httplib::Response response;
103 httplib::Error error;
103 104
104 if (!cli->send(request, response)) { 105 if (!cli->send(request, response, error)) {
105 LOG_ERROR(WebService, "{} to {} returned null", method, host + path); 106 LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
106 return WebResult{WebResult::Code::LibError, "Null response", ""}; 107 return WebResult{WebResult::Code::LibError, "Null response", ""};
107 } 108 }
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 86495803e..7524e3c40 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -51,11 +51,11 @@ void EmuThread::run() {
51 Common::SetCurrentThreadName(name.c_str()); 51 Common::SetCurrentThreadName(name.c_str());
52 52
53 auto& system = Core::System::GetInstance(); 53 auto& system = Core::System::GetInstance();
54 auto& gpu = system.GPU();
55 auto stop_token = stop_source.get_token();
54 56
55 system.RegisterHostThread(); 57 system.RegisterHostThread();
56 58
57 auto& gpu = system.GPU();
58
59 // Main process has been loaded. Make the context current to this thread and begin GPU and CPU 59 // Main process has been loaded. Make the context current to this thread and begin GPU and CPU
60 // execution. 60 // execution.
61 gpu.Start(); 61 gpu.Start();
@@ -65,7 +65,7 @@ void EmuThread::run() {
65 emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); 65 emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
66 66
67 system.Renderer().ReadRasterizer()->LoadDiskResources( 67 system.Renderer().ReadRasterizer()->LoadDiskResources(
68 system.CurrentProcess()->GetTitleID(), stop_run, 68 system.CurrentProcess()->GetTitleID(), stop_token,
69 [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { 69 [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
70 emit LoadProgress(stage, value, total); 70 emit LoadProgress(stage, value, total);
71 }); 71 });
@@ -78,7 +78,7 @@ void EmuThread::run() {
78 // so that the DebugModeLeft signal can be emitted before the 78 // so that the DebugModeLeft signal can be emitted before the
79 // next execution step 79 // next execution step
80 bool was_active = false; 80 bool was_active = false;
81 while (!stop_run) { 81 while (!stop_token.stop_requested()) {
82 if (running) { 82 if (running) {
83 if (was_active) { 83 if (was_active) {
84 emit DebugModeLeft(); 84 emit DebugModeLeft();
@@ -100,7 +100,7 @@ void EmuThread::run() {
100 } 100 }
101 running_guard = false; 101 running_guard = false;
102 102
103 if (!stop_run) { 103 if (!stop_token.stop_requested()) {
104 was_active = true; 104 was_active = true;
105 emit DebugModeEntered(); 105 emit DebugModeEntered();
106 } 106 }
@@ -108,7 +108,7 @@ void EmuThread::run() {
108 UNIMPLEMENTED(); 108 UNIMPLEMENTED();
109 } else { 109 } else {
110 std::unique_lock lock{running_mutex}; 110 std::unique_lock lock{running_mutex};
111 running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; }); 111 running_cv.wait(lock, stop_token, [this] { return IsRunning() || exec_step; });
112 } 112 }
113 } 113 }
114 114
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index acfe2bc8c..402dd2ee1 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -89,16 +89,16 @@ public:
89 * Requests for the emulation thread to stop running 89 * Requests for the emulation thread to stop running
90 */ 90 */
91 void RequestStop() { 91 void RequestStop() {
92 stop_run = true; 92 stop_source.request_stop();
93 SetRunning(false); 93 SetRunning(false);
94 } 94 }
95 95
96private: 96private:
97 bool exec_step = false; 97 bool exec_step = false;
98 bool running = false; 98 bool running = false;
99 std::atomic_bool stop_run{false}; 99 std::stop_source stop_source;
100 std::mutex running_mutex; 100 std::mutex running_mutex;
101 std::condition_variable running_cv; 101 std::condition_variable_any running_cv;
102 Common::Event running_wait{}; 102 Common::Event running_wait{};
103 std::atomic_bool running_guard{false}; 103 std::atomic_bool running_guard{false};
104 104
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 57843ac5a..62bafc453 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -221,7 +221,7 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
221// This must be in alphabetical order according to action name as it must have the same order as 221// This must be in alphabetical order according to action name as it must have the same order as
222// UISetting::values.shortcuts, which is alphabetically ordered. 222// UISetting::values.shortcuts, which is alphabetically ordered.
223// clang-format off 223// clang-format off
224const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{ 224const std::array<UISettings::Shortcut, 18> Config::default_hotkeys{{
225 {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, 225 {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
226 {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}}, 226 {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
227 {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, 227 {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
@@ -236,6 +236,7 @@ const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{
236 {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}}, 236 {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
237 {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}}, 237 {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
238 {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}}, 238 {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
239 {QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), Qt::ApplicationShortcut}},
239 {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), Qt::ApplicationShortcut}}, 240 {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), Qt::ApplicationShortcut}},
240 {QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}}, 241 {QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
241 {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}}, 242 {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
@@ -756,6 +757,8 @@ void Config::ReadCpuValues() {
756 QStringLiteral("cpuopt_unsafe_unfuse_fma"), true); 757 QStringLiteral("cpuopt_unsafe_unfuse_fma"), true);
757 ReadSettingGlobal(Settings::values.cpuopt_unsafe_reduce_fp_error, 758 ReadSettingGlobal(Settings::values.cpuopt_unsafe_reduce_fp_error,
758 QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true); 759 QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true);
760 ReadSettingGlobal(Settings::values.cpuopt_unsafe_ignore_standard_fpcr,
761 QStringLiteral("cpuopt_unsafe_ignore_standard_fpcr"), true);
759 ReadSettingGlobal(Settings::values.cpuopt_unsafe_inaccurate_nan, 762 ReadSettingGlobal(Settings::values.cpuopt_unsafe_inaccurate_nan,
760 QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true); 763 QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true);
761 ReadSettingGlobal(Settings::values.cpuopt_unsafe_fastmem_check, 764 ReadSettingGlobal(Settings::values.cpuopt_unsafe_fastmem_check,
@@ -811,6 +814,8 @@ void Config::ReadRendererValues() {
811 true); 814 true);
812 ReadSettingGlobal(Settings::values.accelerate_astc, QStringLiteral("accelerate_astc"), true); 815 ReadSettingGlobal(Settings::values.accelerate_astc, QStringLiteral("accelerate_astc"), true);
813 ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true); 816 ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true);
817 ReadSettingGlobal(Settings::values.disable_fps_limit, QStringLiteral("disable_fps_limit"),
818 false);
814 ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"), 819 ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"),
815 false); 820 false);
816 ReadSettingGlobal(Settings::values.use_asynchronous_shaders, 821 ReadSettingGlobal(Settings::values.use_asynchronous_shaders,
@@ -1340,6 +1345,8 @@ void Config::SaveCpuValues() {
1340 Settings::values.cpuopt_unsafe_unfuse_fma, true); 1345 Settings::values.cpuopt_unsafe_unfuse_fma, true);
1341 WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), 1346 WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_reduce_fp_error"),
1342 Settings::values.cpuopt_unsafe_reduce_fp_error, true); 1347 Settings::values.cpuopt_unsafe_reduce_fp_error, true);
1348 WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_ignore_standard_fpcr"),
1349 Settings::values.cpuopt_unsafe_ignore_standard_fpcr, true);
1343 WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), 1350 WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_inaccurate_nan"),
1344 Settings::values.cpuopt_unsafe_inaccurate_nan, true); 1351 Settings::values.cpuopt_unsafe_inaccurate_nan, true);
1345 WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_fastmem_check"), 1352 WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_fastmem_check"),
@@ -1396,6 +1403,8 @@ void Config::SaveRendererValues() {
1396 true); 1403 true);
1397 WriteSettingGlobal(QStringLiteral("accelerate_astc"), Settings::values.accelerate_astc, true); 1404 WriteSettingGlobal(QStringLiteral("accelerate_astc"), Settings::values.accelerate_astc, true);
1398 WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); 1405 WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true);
1406 WriteSettingGlobal(QStringLiteral("disable_fps_limit"), Settings::values.disable_fps_limit,
1407 false);
1399 WriteSettingGlobal(QStringLiteral("use_assembly_shaders"), 1408 WriteSettingGlobal(QStringLiteral("use_assembly_shaders"),
1400 Settings::values.use_assembly_shaders, false); 1409 Settings::values.use_assembly_shaders, false);
1401 WriteSettingGlobal(QStringLiteral("use_asynchronous_shaders"), 1410 WriteSettingGlobal(QStringLiteral("use_asynchronous_shaders"),
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index ce3355588..3c1de0ac9 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -42,7 +42,7 @@ public:
42 default_mouse_buttons; 42 default_mouse_buttons;
43 static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; 43 static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
44 static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods; 44 static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods;
45 static const std::array<UISettings::Shortcut, 17> default_hotkeys; 45 static const std::array<UISettings::Shortcut, 18> default_hotkeys;
46 46
47private: 47private:
48 void Initialize(const std::string& config_name); 48 void Initialize(const std::string& config_name);
diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp
index 22219cbad..13db2ba98 100644
--- a/src/yuzu/configuration/configure_cpu.cpp
+++ b/src/yuzu/configuration/configure_cpu.cpp
@@ -34,12 +34,15 @@ void ConfigureCpu::SetConfiguration() {
34 ui->accuracy->setEnabled(runtime_lock); 34 ui->accuracy->setEnabled(runtime_lock);
35 ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock); 35 ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock);
36 ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock); 36 ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock);
37 ui->cpuopt_unsafe_ignore_standard_fpcr->setEnabled(runtime_lock);
37 ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock); 38 ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock);
38 ui->cpuopt_unsafe_fastmem_check->setEnabled(runtime_lock); 39 ui->cpuopt_unsafe_fastmem_check->setEnabled(runtime_lock);
39 40
40 ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()); 41 ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma.GetValue());
41 ui->cpuopt_unsafe_reduce_fp_error->setChecked( 42 ui->cpuopt_unsafe_reduce_fp_error->setChecked(
42 Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()); 43 Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue());
44 ui->cpuopt_unsafe_ignore_standard_fpcr->setChecked(
45 Settings::values.cpuopt_unsafe_ignore_standard_fpcr.GetValue());
43 ui->cpuopt_unsafe_inaccurate_nan->setChecked( 46 ui->cpuopt_unsafe_inaccurate_nan->setChecked(
44 Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()); 47 Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue());
45 ui->cpuopt_unsafe_fastmem_check->setChecked( 48 ui->cpuopt_unsafe_fastmem_check->setChecked(
@@ -84,6 +87,9 @@ void ConfigureCpu::ApplyConfiguration() {
84 ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_reduce_fp_error, 87 ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_reduce_fp_error,
85 ui->cpuopt_unsafe_reduce_fp_error, 88 ui->cpuopt_unsafe_reduce_fp_error,
86 cpuopt_unsafe_reduce_fp_error); 89 cpuopt_unsafe_reduce_fp_error);
90 ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_ignore_standard_fpcr,
91 ui->cpuopt_unsafe_ignore_standard_fpcr,
92 cpuopt_unsafe_ignore_standard_fpcr);
87 ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_inaccurate_nan, 93 ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_inaccurate_nan,
88 ui->cpuopt_unsafe_inaccurate_nan, 94 ui->cpuopt_unsafe_inaccurate_nan,
89 cpuopt_unsafe_inaccurate_nan); 95 cpuopt_unsafe_inaccurate_nan);
@@ -137,6 +143,9 @@ void ConfigureCpu::SetupPerGameUI() {
137 ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_reduce_fp_error, 143 ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_reduce_fp_error,
138 Settings::values.cpuopt_unsafe_reduce_fp_error, 144 Settings::values.cpuopt_unsafe_reduce_fp_error,
139 cpuopt_unsafe_reduce_fp_error); 145 cpuopt_unsafe_reduce_fp_error);
146 ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_ignore_standard_fpcr,
147 Settings::values.cpuopt_unsafe_ignore_standard_fpcr,
148 cpuopt_unsafe_ignore_standard_fpcr);
140 ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_inaccurate_nan, 149 ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_inaccurate_nan,
141 Settings::values.cpuopt_unsafe_inaccurate_nan, 150 Settings::values.cpuopt_unsafe_inaccurate_nan,
142 cpuopt_unsafe_inaccurate_nan); 151 cpuopt_unsafe_inaccurate_nan);
diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h
index 57ff2772a..b2b5f1671 100644
--- a/src/yuzu/configuration/configure_cpu.h
+++ b/src/yuzu/configuration/configure_cpu.h
@@ -40,6 +40,7 @@ private:
40 40
41 ConfigurationShared::CheckState cpuopt_unsafe_unfuse_fma; 41 ConfigurationShared::CheckState cpuopt_unsafe_unfuse_fma;
42 ConfigurationShared::CheckState cpuopt_unsafe_reduce_fp_error; 42 ConfigurationShared::CheckState cpuopt_unsafe_reduce_fp_error;
43 ConfigurationShared::CheckState cpuopt_unsafe_ignore_standard_fpcr;
43 ConfigurationShared::CheckState cpuopt_unsafe_inaccurate_nan; 44 ConfigurationShared::CheckState cpuopt_unsafe_inaccurate_nan;
44 ConfigurationShared::CheckState cpuopt_unsafe_fastmem_check; 45 ConfigurationShared::CheckState cpuopt_unsafe_fastmem_check;
45}; 46};
diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui
index 31ef9e3f5..0e296d4e5 100644
--- a/src/yuzu/configuration/configure_cpu.ui
+++ b/src/yuzu/configuration/configure_cpu.ui
@@ -112,6 +112,18 @@
112 </widget> 112 </widget>
113 </item> 113 </item>
114 <item> 114 <item>
115 <widget class="QCheckBox" name="cpuopt_unsafe_ignore_standard_fpcr">
116 <property name="toolTip">
117 <string>
118 &lt;div&gt;This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes.&lt;/div&gt;
119 </string>
120 </property>
121 <property name="text">
122 <string>Faster ASIMD instructions (32 bits only)</string>
123 </property>
124 </widget>
125 </item>
126 <item>
115 <widget class="QCheckBox" name="cpuopt_unsafe_inaccurate_nan"> 127 <widget class="QCheckBox" name="cpuopt_unsafe_inaccurate_nan">
116 <property name="toolTip"> 128 <property name="toolTip">
117 <string> 129 <string>
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index a9e611125..8d13c9857 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -28,6 +28,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {
28 ui->anisotropic_filtering_combobox->setEnabled(runtime_lock); 28 ui->anisotropic_filtering_combobox->setEnabled(runtime_lock);
29 29
30 ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); 30 ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue());
31 ui->disable_fps_limit->setChecked(Settings::values.disable_fps_limit.GetValue());
31 ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders.GetValue()); 32 ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders.GetValue());
32 ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); 33 ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue());
33 ui->use_caches_gc->setChecked(Settings::values.use_caches_gc.GetValue()); 34 ui->use_caches_gc->setChecked(Settings::values.use_caches_gc.GetValue());
@@ -58,6 +59,8 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() {
58 ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, 59 ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy,
59 ui->anisotropic_filtering_combobox); 60 ui->anisotropic_filtering_combobox);
60 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, use_vsync); 61 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, use_vsync);
62 ConfigurationShared::ApplyPerGameSetting(&Settings::values.disable_fps_limit,
63 ui->disable_fps_limit, disable_fps_limit);
61 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders, 64 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders,
62 ui->use_assembly_shaders, use_assembly_shaders); 65 ui->use_assembly_shaders, use_assembly_shaders);
63 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders, 66 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders,
@@ -100,6 +103,7 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
100 if (Settings::IsConfiguringGlobal()) { 103 if (Settings::IsConfiguringGlobal()) {
101 ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal()); 104 ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal());
102 ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal()); 105 ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal());
106 ui->disable_fps_limit->setEnabled(Settings::values.disable_fps_limit.UsingGlobal());
103 ui->use_assembly_shaders->setEnabled(Settings::values.use_assembly_shaders.UsingGlobal()); 107 ui->use_assembly_shaders->setEnabled(Settings::values.use_assembly_shaders.UsingGlobal());
104 ui->use_asynchronous_shaders->setEnabled( 108 ui->use_asynchronous_shaders->setEnabled(
105 Settings::values.use_asynchronous_shaders.UsingGlobal()); 109 Settings::values.use_asynchronous_shaders.UsingGlobal());
@@ -112,6 +116,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
112 } 116 }
113 117
114 ConfigurationShared::SetColoredTristate(ui->use_vsync, Settings::values.use_vsync, use_vsync); 118 ConfigurationShared::SetColoredTristate(ui->use_vsync, Settings::values.use_vsync, use_vsync);
119 ConfigurationShared::SetColoredTristate(ui->disable_fps_limit,
120 Settings::values.disable_fps_limit, disable_fps_limit);
115 ConfigurationShared::SetColoredTristate( 121 ConfigurationShared::SetColoredTristate(
116 ui->use_assembly_shaders, Settings::values.use_assembly_shaders, use_assembly_shaders); 122 ui->use_assembly_shaders, Settings::values.use_assembly_shaders, use_assembly_shaders);
117 ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders, 123 ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders,
diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h
index 9148aacf2..6ac5f20ec 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.h
+++ b/src/yuzu/configuration/configure_graphics_advanced.h
@@ -35,6 +35,7 @@ private:
35 std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui; 35 std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui;
36 36
37 ConfigurationShared::CheckState use_vsync; 37 ConfigurationShared::CheckState use_vsync;
38 ConfigurationShared::CheckState disable_fps_limit;
38 ConfigurationShared::CheckState use_assembly_shaders; 39 ConfigurationShared::CheckState use_assembly_shaders;
39 ConfigurationShared::CheckState use_asynchronous_shaders; 40 ConfigurationShared::CheckState use_asynchronous_shaders;
40 ConfigurationShared::CheckState use_fast_gpu_time; 41 ConfigurationShared::CheckState use_fast_gpu_time;
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui
index ad0840355..18c43629e 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.ui
+++ b/src/yuzu/configuration/configure_graphics_advanced.ui
@@ -77,6 +77,24 @@
77 </widget> 77 </widget>
78 </item> 78 </item>
79 <item> 79 <item>
80 <widget class="QCheckBox" name="disable_fps_limit">
81 <property name="enabled">
82 <bool>true</bool>
83 </property>
84 <property name="toolTip">
85 <string>
86 &lt;html&gt;&lt;head/&gt;&lt;body&gt;
87 &lt;p&gt;Presents guest frames as they become available, disabling the FPS limit in most titles.&lt;/p&gt;
88 &lt;p&gt;NOTE: Will cause instabilities.&lt;/p&gt;
89 &lt;/body&gt;&lt;/html&gt;
90 </string>
91 </property>
92 <property name="text">
93 <string>Disable framerate limit (experimental)</string>
94 </property>
95 </widget>
96 </item>
97 <item>
80 <widget class="QCheckBox" name="use_assembly_shaders"> 98 <widget class="QCheckBox" name="use_assembly_shaders">
81 <property name="toolTip"> 99 <property name="toolTip">
82 <string>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</string> 100 <string>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</string>
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index a1d434aca..8c00eec59 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -47,6 +47,8 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id, const std::str
47 ui->setupUi(this); 47 ui->setupUi(this);
48 setFocusPolicy(Qt::ClickFocus); 48 setFocusPolicy(Qt::ClickFocus);
49 setWindowTitle(tr("Properties")); 49 setWindowTitle(tr("Properties"));
50 // remove Help question mark button from the title bar
51 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
50 52
51 ui->addonsTab->SetTitleId(title_id); 53 ui->addonsTab->SetTitleId(title_id);
52 54
diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui
index adf6d0b39..7da14146b 100644
--- a/src/yuzu/configuration/configure_per_game.ui
+++ b/src/yuzu/configuration/configure_per_game.ui
@@ -6,10 +6,15 @@
6 <rect> 6 <rect>
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>800</width> 9 <width>900</width>
10 <height>600</height> 10 <height>600</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
13 <property name="minimumSize">
14 <size>
15 <width>900</width>
16 </size>
17 </property>
13 <property name="windowTitle"> 18 <property name="windowTitle">
14 <string>Dialog</string> 19 <string>Dialog</string>
15 </property> 20 </property>
diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp
index 9b709d405..ebb0f411c 100644
--- a/src/yuzu/configuration/configure_per_game_addons.cpp
+++ b/src/yuzu/configuration/configure_per_game_addons.cpp
@@ -79,8 +79,8 @@ void ConfigurePerGameAddons::ApplyConfiguration() {
79 std::sort(disabled_addons.begin(), disabled_addons.end()); 79 std::sort(disabled_addons.begin(), disabled_addons.end());
80 std::sort(current.begin(), current.end()); 80 std::sort(current.begin(), current.end());
81 if (disabled_addons != current) { 81 if (disabled_addons != current) {
82 void(Common::FS::RemoveFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / 82 Common::FS::RemoveFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) /
83 "game_list" / fmt::format("{:016X}.pv.txt", title_id))); 83 "game_list" / fmt::format("{:016X}.pv.txt", title_id));
84 } 84 }
85 85
86 Settings::values.disabled_addons[title_id] = disabled_addons; 86 Settings::values.disabled_addons[title_id] = disabled_addons;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index be8933c5c..75ab5ef44 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -194,10 +194,10 @@ static void RemoveCachedContents() {
194 const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information"; 194 const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information";
195 const auto offline_system_data = cache_dir / "offline_web_applet_system_data"; 195 const auto offline_system_data = cache_dir / "offline_web_applet_system_data";
196 196
197 void(Common::FS::RemoveDirRecursively(offline_fonts)); 197 Common::FS::RemoveDirRecursively(offline_fonts);
198 void(Common::FS::RemoveDirRecursively(offline_manual)); 198 Common::FS::RemoveDirRecursively(offline_manual);
199 void(Common::FS::RemoveDirRecursively(offline_legal_information)); 199 Common::FS::RemoveDirRecursively(offline_legal_information);
200 void(Common::FS::RemoveDirRecursively(offline_system_data)); 200 Common::FS::RemoveDirRecursively(offline_system_data);
201} 201}
202 202
203GMainWindow::GMainWindow() 203GMainWindow::GMainWindow()
@@ -1025,7 +1025,11 @@ void GMainWindow::InitializeHotkeys() {
1025 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Mute Audio"), this), 1025 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Mute Audio"), this),
1026 &QShortcut::activated, this, 1026 &QShortcut::activated, this,
1027 [] { Settings::values.audio_muted = !Settings::values.audio_muted; }); 1027 [] { Settings::values.audio_muted = !Settings::values.audio_muted; });
1028 1028 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Framerate Limit"), this),
1029 &QShortcut::activated, this, [] {
1030 Settings::values.disable_fps_limit.SetValue(
1031 !Settings::values.disable_fps_limit.GetValue());
1032 });
1029 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Mouse Panning"), this), 1033 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Mouse Panning"), this),
1030 &QShortcut::activated, this, [&] { 1034 &QShortcut::activated, this, [&] {
1031 Settings::values.mouse_panning = !Settings::values.mouse_panning; 1035 Settings::values.mouse_panning = !Settings::values.mouse_panning;
@@ -1739,8 +1743,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
1739 RemoveAddOnContent(program_id, entry_type); 1743 RemoveAddOnContent(program_id, entry_type);
1740 break; 1744 break;
1741 } 1745 }
1742 void(Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / 1746 Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) /
1743 "game_list")); 1747 "game_list");
1744 game_list->PopulateAsync(UISettings::values.game_dirs); 1748 game_list->PopulateAsync(UISettings::values.game_dirs);
1745} 1749}
1746 1750
@@ -2209,8 +2213,8 @@ void GMainWindow::OnMenuInstallToNAND() {
2209 : tr("%n file(s) failed to install\n", "", failed_files.size())); 2213 : tr("%n file(s) failed to install\n", "", failed_files.size()));
2210 2214
2211 QMessageBox::information(this, tr("Install Results"), install_results); 2215 QMessageBox::information(this, tr("Install Results"), install_results);
2212 void(Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / 2216 Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) /
2213 "game_list")); 2217 "game_list");
2214 game_list->PopulateAsync(UISettings::values.game_dirs); 2218 game_list->PopulateAsync(UISettings::values.game_dirs);
2215 ui.action_Install_File_NAND->setEnabled(true); 2219 ui.action_Install_File_NAND->setEnabled(true);
2216} 2220}
@@ -2842,7 +2846,7 @@ void GMainWindow::MigrateConfigFiles() {
2842 LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination); 2846 LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination);
2843 if (!Common::FS::RenameFile(origin, destination)) { 2847 if (!Common::FS::RenameFile(origin, destination)) {
2844 // Delete the old config file if one already exists in the new location. 2848 // Delete the old config file if one already exists in the new location.
2845 void(Common::FS::RemoveFile(origin)); 2849 Common::FS::RemoveFile(origin);
2846 } 2850 }
2847 } 2851 }
2848} 2852}
@@ -3036,9 +3040,9 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
3036 3040
3037 const auto keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); 3041 const auto keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
3038 3042
3039 void(Common::FS::RemoveFile(keys_dir / "prod.keys_autogenerated")); 3043 Common::FS::RemoveFile(keys_dir / "prod.keys_autogenerated");
3040 void(Common::FS::RemoveFile(keys_dir / "console.keys_autogenerated")); 3044 Common::FS::RemoveFile(keys_dir / "console.keys_autogenerated");
3041 void(Common::FS::RemoveFile(keys_dir / "title.keys_autogenerated")); 3045 Common::FS::RemoveFile(keys_dir / "title.keys_autogenerated");
3042 } 3046 }
3043 3047
3044 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); 3048 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 621b31571..60bf66ec0 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -443,6 +443,8 @@ void Config::ReadValues() {
443 sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", true)); 443 sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", true));
444 Settings::values.use_vsync.SetValue( 444 Settings::values.use_vsync.SetValue(
445 static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1))); 445 static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1)));
446 Settings::values.disable_fps_limit.SetValue(
447 sdl2_config->GetBoolean("Renderer", "disable_fps_limit", false));
446 Settings::values.use_assembly_shaders.SetValue( 448 Settings::values.use_assembly_shaders.SetValue(
447 sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", true)); 449 sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", true));
448 Settings::values.use_asynchronous_shaders.SetValue( 450 Settings::values.use_asynchronous_shaders.SetValue(
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index f0a0ec398..cc9850aad 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -264,7 +264,10 @@ swap_screen =
264 264
265[Audio] 265[Audio]
266# Which audio output engine to use. 266# Which audio output engine to use.
267# auto (default): Auto-select, null: No audio output, cubeb: Cubeb audio engine (if available) 267# auto (default): Auto-select
268# cubeb: Cubeb audio engine (if available)
269# sdl2: SDL2 audio engine (if available)
270# null: No audio output
268output_engine = 271output_engine =
269 272
270# Whether or not to enable the audio-stretching post-processing effect. 273# Whether or not to enable the audio-stretching post-processing effect.
@@ -363,6 +366,9 @@ use_debug_asserts =
363use_auto_stub = 366use_auto_stub =
364# Enables/Disables the macro JIT compiler 367# Enables/Disables the macro JIT compiler
365disable_macro_jit=false 368disable_macro_jit=false
369# Presents guest frames as they become available. Experimental.
370# false: Disabled (default), true: Enabled
371disable_fps_limit=false
366 372
367[WebService] 373[WebService]
368# Whether or not to enable telemetry 374# Whether or not to enable telemetry
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 584967f5c..50e388312 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -219,7 +219,7 @@ int main(int argc, char** argv) {
219 system.GPU().Start(); 219 system.GPU().Start();
220 220
221 system.Renderer().ReadRasterizer()->LoadDiskResources( 221 system.Renderer().ReadRasterizer()->LoadDiskResources(
222 system.CurrentProcess()->GetTitleID(), false, 222 system.CurrentProcess()->GetTitleID(), std::stop_token{},
223 [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); 223 [](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
224 224
225 void(system.Run()); 225 void(system.Run());