diff --git a/.gitattributes b/.gitattributes index 644b9e2573..3c09b54e1e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -23,8 +23,6 @@ *.gn text eol=lf *.gni text eol=lf *.go text eol=lf -*.gyp text eol=lf -*.gypi text eol=lf *.h text eol=lf *.m text eol=lf *.md text eol=lf diff --git a/.gitignore b/.gitignore index aa7162e52b..dfef5497a4 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ .cache .gdb_history .gdbinit +/.vscode/ /Makefile /build/fuchsia /out @@ -35,11 +36,16 @@ /third_party/fuchsia/qemu /third_party/fuchsia/sdk /third_party/googletest/googletest -/third_party/gyp/gyp /third_party/libfuzzer /third_party/linux/.cipd /third_party/linux/clang /third_party/linux/sysroot -/third_party/gyp/gyp +/third_party/lss/lss +/third_party/mini_chromium/mini_chromium +/third_party/ninja/linux +/third_party/ninja/mac* +/third_party/ninja/ninja.exe +/third_party/windows/clang/win-amd64 +/third_party/zlib/zlib /xcodebuild tags diff --git a/BUILD.gn b/BUILD.gn index 8be9eee759..692f9e4750 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -33,12 +33,20 @@ if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { "test:test_test", "util:util_test", ] + + data_deps = [] + + if (crashpad_is_in_chromium) { + data_deps += [ "//testing/buildbot/filters:crashpad_tests_filters" ] + } + if (!crashpad_is_ios && !crashpad_is_fuchsia) { deps += [ "handler:handler_test" ] } if (crashpad_is_in_fuchsia) { # TODO(fuchsia:46559): Fix the leaks and remove this. deps += [ "//build/config/sanitizers:suppress-lsan.DO-NOT-USE-THIS" ] + # TODO(fxbug.dev/42059784): Remove this once the underlying issue is # addressed. exclude_toolchain_tags = [ "hwasan" ] @@ -46,6 +54,16 @@ if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { if (crashpad_is_android) { use_raw_android_executable = true + # crbug.com/418874703 - This is a workaround to propagate the data deps to + # //:crashpad_tests__dist for Android build. + data_deps += [ + "snapshot:crashpad_snapshot_test_both_dt_hash_styles", + "snapshot:crashpad_snapshot_test_module", + "snapshot:crashpad_snapshot_test_module_large", + "snapshot:crashpad_snapshot_test_module_small", + "test:crashpad_test_test_multiprocess_exec_test_child", + ] + copy("crashpad_test_data") { testonly = true sources = [ diff --git a/CMakeLists.txt b/CMakeLists.txt index 841dba312e..c113b4abe9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,7 @@ if(MSVC) $<$,$>:-Wno-deprecated-declarations> # same as /wd4996 $<$,$>:-Wno-format> # only Clang-CL $<$,$>:-Wno-cast-function-type-mismatch> # only Clang-CL + $<$,$>:-Wno-return-type> ) elseif(MINGW) # redirect to wmain @@ -143,6 +144,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_compile_options(crashpad_interface INTERFACE $<$:-Wno-multichar> $<$:-Wno-attributes> + $<$:-Wno-return-type> ) endif() diff --git a/DEPS b/DEPS index b4fe4fff5b..69f4fe28e0 100644 --- a/DEPS +++ b/DEPS @@ -29,25 +29,25 @@ vars = { deps = { 'buildtools': Var('chromium_git') + '/chromium/src/buildtools.git@' + - '8919328651a559f8a974641d40fe712062cc6718', + 'efa920ce144e4dc1c1841e73179cd7e23b9f0d5e', 'buildtools/clang_format/script': Var('chromium_git') + '/external/github.com/llvm/llvm-project/clang/tools/clang-format.git@' + 'c912837e0d82b5ca4b6e790b573b3956d3744c1c', 'crashpad/third_party/edo/edo': { 'url': Var('chromium_git') + '/external/github.com/google/eDistantObject.git@' + - '727e556705278598fce683522beedbb9946bfda0', + '38e71ff183d76f702db6966fa7236c98831acd80', 'condition': 'checkout_ios', }, 'crashpad/third_party/googletest/googletest': Var('chromium_git') + '/external/github.com/google/googletest@' + - 'af29db7ec28d6df1c7f0f745186884091e602e07', + '3983f67e32fb3e9294487b9d4f9586efa6e5d088', 'crashpad/third_party/lss/lss': Var('chromium_git') + '/linux-syscall-support.git@' + '9719c1e1e676814c456b55f5f070eabad6709d31', 'crashpad/third_party/mini_chromium/mini_chromium': Var('chromium_git') + '/chromium/mini_chromium@' + - 'bd56f6933f2fa021a44766ced638a18f477ef1c1', + 'eef885b0bb80dab8e9d79b9dc0c050a1c8e50c0b', 'crashpad/third_party/libfuzzer/src': Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' + 'fda403cf93ecb8792cb1d061564d89a6553ca020', @@ -110,7 +110,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/third_party/clang/mac-amd64', - 'version': 'latest', + 'version': 'integration', }, ], 'condition': 'checkout_fuchsia and host_os == "mac"', @@ -120,12 +120,25 @@ deps = { 'packages': [ { 'package': 'fuchsia/third_party/clang/linux-amd64', - 'version': 'latest', + 'version': 'integration', }, ], 'condition': 'checkout_fuchsia and host_os == "linux"', 'dep_type': 'cipd' }, + 'crashpad/third_party/windows/clang/win-amd64': { + 'bucket': 'chromium-browser-clang', + 'objects': [ + { + 'object_name': 'Win/clang-llvmorg-20-init-17108-g29ed6000-2.tar.xz', + 'sha256sum': '1c71efd923a91480480d4f31c2fd5f1369e01e14f15776a9454abbce0bc13548', + 'size_bytes': 46357580, + 'generation': 1737590897363452, + }, + ], + 'condition': 'checkout_win and host_os == "win"', + 'dep_type': 'gcs', + }, 'crashpad/third_party/fuchsia-gn-sdk': { 'packages': [ { @@ -208,47 +221,6 @@ deps = { } hooks = [ - { - 'name': 'clang_format_mac', - 'pattern': '.', - 'condition': 'host_os == "mac"', - 'action': [ - 'download_from_google_storage', - '--no_resume', - '--no_auth', - '--bucket=chromium-clang-format', - '--sha1_file', - 'buildtools/mac/clang-format.{host_cpu}.sha1', - '--output', - 'buildtools/mac/clang-format', - ], - }, - { - 'name': 'clang_format_linux', - 'pattern': '.', - 'condition': 'host_os == "linux"', - 'action': [ - 'download_from_google_storage', - '--no_resume', - '--no_auth', - '--bucket=chromium-clang-format', - '--sha1_file', - 'buildtools/linux64/clang-format.sha1', - ], - }, - { - 'name': 'clang_format_win', - 'pattern': '.', - 'condition': 'host_os == "win"', - 'action': [ - 'download_from_google_storage', - '--no_resume', - '--no_auth', - '--bucket=chromium-clang-format', - '--sha1_file', - 'buildtools/win/clang-format.exe.sha1', - ], - }, { # If using a local clang ("pull_linux_clang" above), also pull down a # sysroot. diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000000..f5d56bf7d0 --- /dev/null +++ b/OWNERS @@ -0,0 +1,20 @@ +# Copyright 2025 The Crashpad Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +jperaza@chromium.org +justincohen@chromium.org +lgrey@chromium.org +mark@chromium.org +pbos@chromium.org +wfh@chromium.org diff --git a/build/config/fuchsia/gn_configs.gni b/build/config/fuchsia/gn_configs.gni index 6cbf4ef34f..e38a6ae7d9 100644 --- a/build/config/fuchsia/gn_configs.gni +++ b/build/config/fuchsia/gn_configs.gni @@ -20,36 +20,6 @@ # rules to reference the contents of the fuchsia SDK. fuchsia_sdk = "//third_party/fuchsia/sdk/linux-amd64" -# ID uniquely identifying the Fuchsia IDK build. This is exposed as a -# property so it can be used to locate images and packages on GCS and -# as a marker to indicate the "version" of the IDK. -# Defaults to the id found in the manifest.json file of the SDK. -fuchsia_sdk_id = "" - -# The target API level for this repository. Embedders should override this -# value to specify the API level the packages produced from this repository -# should be targeting, e.g. in their top-level //.gn file. A value of -1 -# means that no API level will be passed to the tools that consumes it. -fuchsia_target_api_level = 18 - -# The SDK manifest file. This is useful to include as a dependency -# for some targets in order to cause a rebuild when the version of the -# SDK is changed. -fuchsia_sdk_manifest_file = "${fuchsia_sdk}/meta/manifest.json" - -# fuchsia_tool_dir is used to specify the directory in the SDK to locate -# tools for the host cpu architecture. If the host_cpu is not recognized, -# then tool dir defaults to x64. -fuchsia_tool_dir = "${fuchsia_sdk}/tools/${host_cpu}" - -if (fuchsia_sdk_id == "") { - # Note: If we need to expose more than just the id in the future, - # we should consider exposing the entire json object for the metadata vs. - # adding a bunch of variables. - _meta = read_file(fuchsia_sdk_manifest_file, "json") - fuchsia_sdk_id = _meta.id -} - declare_args() { # Specify a readelf_exec path to use. If not specified, the host's system # executable will be used. Passed to populate_build_id_dir.py and diff --git a/build/crashpad_buildconfig.gni b/build/crashpad_buildconfig.gni index 96e81b3bd2..80992a1b99 100644 --- a/build/crashpad_buildconfig.gni +++ b/build/crashpad_buildconfig.gni @@ -57,8 +57,14 @@ if (crashpad_is_external || crashpad_is_in_dart) { } if (crashpad_is_in_chromium) { + if (is_ios) { + # For `target_platform`. + import("//build/config/apple/mobile_config.gni") + } + crashpad_is_mac = is_mac crashpad_is_ios = is_ios + crashpad_is_tvos = is_ios && target_platform == "tvos" crashpad_is_apple = is_apple crashpad_is_win = is_win crashpad_is_linux = is_linux || is_chromeos @@ -72,8 +78,14 @@ if (crashpad_is_in_chromium) { import("$mini_chromium_import_root/build/compiler.gni") import("$mini_chromium_import_root/build/platform.gni") + if (mini_chromium_is_ios) { + # For `target_platform`. + import("$mini_chromium_import_root/build/ios/ios_sdk.gni") + } + crashpad_is_mac = mini_chromium_is_mac crashpad_is_ios = mini_chromium_is_ios + crashpad_is_tvos = crashpad_is_ios && target_platform == "tvos" crashpad_is_apple = mini_chromium_is_apple crashpad_is_win = mini_chromium_is_win crashpad_is_linux = mini_chromium_is_linux diff --git a/build/fuchsia_envs.py b/build/fuchsia_envs.py index 8d95b78beb..753c322070 100755 --- a/build/fuchsia_envs.py +++ b/build/fuchsia_envs.py @@ -37,6 +37,8 @@ def main(args): os.environ['SRC_ROOT'], 'third_party/fuchsia/sdk/linux-amd64/') os.environ['FUCHSIA_GN_SDK_ROOT'] = os.path.join( os.environ['SRC_ROOT'], 'third_party/fuchsia-gn-sdk/src') + os.environ['FUCHSIA_READELF'] = os.path.join(os.environ['SRC_ROOT'], + 'third_party/fuchsia/clang/linux-amd64/bin/llvm-readelf') return subprocess.run(args).returncode diff --git a/build/ios/setup_ios_gn.py b/build/ios/setup_ios_gn.py index 7043f8377b..b6c2dfed12 100755 --- a/build/ios/setup_ios_gn.py +++ b/build/ios/setup_ios_gn.py @@ -28,7 +28,8 @@ import tempfile -SUPPORTED_TARGETS = ('iphoneos', 'iphonesimulator', 'maccatalyst') +SUPPORTED_TARGETS = ('appletvos', 'appletvsimulator', 'iphoneos', + 'iphonesimulator', 'maccatalyst') SUPPORTED_CONFIGS = ('Debug', 'Release', 'Profile', 'Official') # Pattern matching lines from ~/.lldbinit that must not be copied to the @@ -92,17 +93,29 @@ class GnGenerator(object): FAT_BUILD_DEFAULT_ARCH = '64-bit' TARGET_CPU_VALUES = { + 'appletvos': '"arm64"', + 'appletvsimulator': HostCpuArch(), 'iphoneos': '"arm64"', 'iphonesimulator': HostCpuArch(), 'maccatalyst': HostCpuArch(), } TARGET_ENVIRONMENT_VALUES = { + 'appletvos': '"device"', + 'appletvsimulator': '"simulator"', 'iphoneos': '"device"', 'iphonesimulator': '"simulator"', 'maccatalyst': '"catalyst"' } + TARGET_PLATFORM_VALUES = { + 'appletvos': '"tvos"', + 'appletvsimulator': '"tvos"', + 'iphoneos': '"iphoneos"', + 'iphonesimulator': '"iphoneos"', + 'maccatalyst': '"iphoneos"' + } + def __init__(self, settings, config, target): assert target in SUPPORTED_TARGETS assert config in SUPPORTED_CONFIGS @@ -130,9 +143,9 @@ def _GetGnArgs(self): args.append(('use_system_xcode', False)) args.append(('target_cpu', self.TARGET_CPU_VALUES[self._target])) - args.append(( - 'target_environment', - self.TARGET_ENVIRONMENT_VALUES[self._target])) + args.append( + ('target_environment', self.TARGET_ENVIRONMENT_VALUES[self._target])) + args.append(('target_platform', self.TARGET_PLATFORM_VALUES[self._target])) # Add user overrides after the other configurations so that they can # refer to them and override them. diff --git a/build/run_tests.py b/build/run_tests.py index 9079da40bd..3f15a9cd3e 100755 --- a/build/run_tests.py +++ b/build/run_tests.py @@ -16,9 +16,9 @@ import argparse import os -import pipes import posixpath import re +import shlex import subprocess import sys import tempfile @@ -61,17 +61,16 @@ def _FindGNFromBinaryDir(binary_dir): return None -def _BinaryDirTargetOS(binary_dir): - """Returns the apparent target OS of binary_dir, or None if none appear to - be explicitly specified.""" +def _GetGNArgument(argument_name, binary_dir): + """Returns the value of a given GN argument, or None if it is not + explicitly specified.""" gn_path = _FindGNFromBinaryDir(binary_dir) - if gn_path: # Look for a GN “target_os”. popen = subprocess.Popen([ gn_path, '--root=' + CRASHPAD_DIR, 'args', binary_dir, - '--list=target_os', '--short' + '--list=%s' % argument_name, '--short' ], shell=IS_WINDOWS_HOST, stdout=subprocess.PIPE, @@ -79,21 +78,9 @@ def _BinaryDirTargetOS(binary_dir): text=True) value = popen.communicate()[0] if popen.returncode == 0: - match = re.match('target_os = "(.*)"$', value) + match = re.match(r'%s = "(.*)"$' % argument_name, value) if match: return match.group(1) - - # For GYP with Ninja, look for the appearance of “linux-android” in the path - # to ar. This path is configured by gyp_crashpad_android.py. - build_ninja_path = os.path.join(binary_dir, 'build.ninja') - if os.path.exists(build_ninja_path): - with open(build_ninja_path) as build_ninja_file: - build_ninja_content = build_ninja_file.read() - match = re.search('-linux-android(eabi)?-ar$', build_ninja_content, - re.MULTILINE) - if match: - return 'android' - return None @@ -185,9 +172,9 @@ def _adb_shell(command_args, env={}): script_commands = [] for k, v in env.items(): script_commands.append('export %s=%s' % - (pipes.quote(k), pipes.quote(v))) + (shlex.quote(k), shlex.quote(v))) script_commands.extend([ - ' '.join(pipes.quote(x) for x in command_args), 'status=${?}', + ' '.join(shlex.quote(x) for x in command_args), 'status=${?}', 'echo "status=${status}"', 'exit ${status}' ]) adb_command.append('; '.join(script_commands)) @@ -197,7 +184,7 @@ def _adb_shell(command_args, env={}): stdout=subprocess.PIPE, text=True) - FINAL_LINE_RE = re.compile('status=(\d+)$') + FINAL_LINE_RE = re.compile(r'status=(\d+)$') final_line = None while True: # Use readline so that the test output appears “live” when running. @@ -312,9 +299,32 @@ def _adb_shell(command_args, env={}): _adb_shell(['rm', '-rf', device_temp_dir]) -def _RunOnIOSTarget(binary_dir, test, is_xcuitest=False, gtest_filter=None): +def _RunOnIOSTarget(binary_dir, + test, + target_platform, + is_xcuitest=False, + gtest_filter=None): """Runs the given iOS |test| app on a simulator with the default OS version.""" + target_platform = target_platform or 'iphoneos' + if target_platform == 'iphoneos': + dyld_insert_libraries = ( + '__PLATFORMS__/iPhoneSimulator.platform/Developer/usr/lib/' + 'libXCTestBundleInject.dylib') + xcodebuild_platform = 'iOS Simulator' + xcodebuild_device_name = 'iPhone 15' + elif target_platform == 'tvos': + dyld_insert_libraries = ( + '__PLATFORMS__/AppleTVSimulator.platform/Developer/usr/lib/' + 'libXCTestBundleInject.dylib') + xcodebuild_platform = 'tvOS Simulator' + xcodebuild_device_name = 'Apple TV 4K (3rd generation)' + else: + raise ValueError(f'Unexpected target_platform: {target_platform}') + + # E.g. __TESTROOT__/Debug-iphonesimulator. + dyld_framework_path = '__TESTROOT__/%s' % os.path.basename(binary_dir) + def xctest(binary_dir, test, gtest_filter=None): """Returns a dict containing the xctestrun data needed to run an XCTest-based test app.""" @@ -323,17 +333,17 @@ def xctest(binary_dir, test, gtest_filter=None): 'TestBundlePath': os.path.join(test_path, test + '_module.xctest'), 'TestHostPath': os.path.join(test_path, test + '.app'), 'TestingEnvironmentVariables': { - 'DYLD_FRAMEWORK_PATH': '__TESTROOT__/Debug-iphonesimulator:', - 'DYLD_INSERT_LIBRARIES': - ('__PLATFORMS__/iPhoneSimulator.platform/Developer/' - 'usr/lib/libXCTestBundleInject.dylib'), - 'DYLD_LIBRARY_PATH': '__TESTROOT__/Debug-iphonesimulator', + 'DYLD_FRAMEWORK_PATH': dyld_framework_path + ':', + 'DYLD_INSERT_LIBRARIES': dyld_insert_libraries, + 'DYLD_LIBRARY_PATH': dyld_framework_path, 'IDEiPhoneInternalTestBundleName': test + '.app', 'XCInjectBundleInto': '__TESTHOST__/' + test, } } if gtest_filter: - module_data['CommandLineArguments'] = ['--gtest_filter='+gtest_filter] + module_data['CommandLineArguments'] = [ + '--gtest_filter=' + gtest_filter + ] return {test: module_data} def xcuitest(binary_dir, test): @@ -347,6 +357,7 @@ def xcuitest(binary_dir, test): target_app_path = os.path.join(test_path, test + '.app') module_data = { 'IsUITestBundle': True, + 'SystemAttachmentLifetime': 'deleteOnSuccess', 'IsXCTRunnerHostedTestBundle': True, 'TestBundlePath': bundle_path, 'TestHostPath': runner_path, @@ -355,11 +366,9 @@ def xcuitest(binary_dir, test): bundle_path, runner_path, target_app_path ], 'TestingEnvironmentVariables': { - 'DYLD_FRAMEWORK_PATH': '__TESTROOT__/Debug-iphonesimulator:', - 'DYLD_INSERT_LIBRARIES': - ('__PLATFORMS__/iPhoneSimulator.platform/Developer/' - 'usr/lib/libXCTestBundleInject.dylib'), - 'DYLD_LIBRARY_PATH': '__TESTROOT__/Debug-iphonesimulator', + 'DYLD_FRAMEWORK_PATH': dyld_framework_path + ':', + 'DYLD_INSERT_LIBRARIES': dyld_insert_libraries, + 'DYLD_LIBRARY_PATH': dyld_framework_path, 'XCInjectBundleInto': '__TESTHOST__/' + test + '_module-Runner', }, } @@ -371,8 +380,12 @@ def xcuitest(binary_dir, test): xctestrun_path = f.name + ".xctestrun" print(xctestrun_path) command = [ - 'xcodebuild', 'test-without-building', '-xctestrun', xctestrun_path, - '-destination', 'platform=iOS Simulator,OS=15.5,name=iPhone 13', + 'xcodebuild', + 'test-without-building', + '-xctestrun', + xctestrun_path, + '-destination', + f'platform={xcodebuild_platform},name={xcodebuild_device_name}', ] with open(xctestrun_path, 'wb') as fp: if is_xcuitest: @@ -408,9 +421,11 @@ def main(args): if os.path.isdir(binary_dir_32): os.environ['CRASHPAD_TEST_32_BIT_OUTPUT'] = binary_dir_32 - target_os = _BinaryDirTargetOS(args.binary_dir) + target_os = _GetGNArgument('target_os', args.binary_dir) is_android = target_os == 'android' is_ios = target_os == 'ios' + # |target_platform| is only set for iOS-based platforms. + target_platform = _GetGNArgument('target_platform', args.binary_dir) tests = [ 'crashpad_client_test', @@ -431,7 +446,7 @@ def main(args): for line in adb_devices.splitlines(): line = line if (line == 'List of devices attached' or - re.match('^\* daemon .+ \*$', line) or line == ''): + re.match(r'^\* daemon .+ \*$', line) or line == ''): continue (device, ignore) = line.split('\t') devices.append(device) @@ -472,6 +487,7 @@ def main(args): elif is_ios: _RunOnIOSTarget(args.binary_dir, test, + target_platform, is_xcuitest=test.startswith('ios'), gtest_filter=args.gtest_filter) else: diff --git a/client/BUILD.gn b/client/BUILD.gn index bd150ab94e..e57e0e0c6f 100644 --- a/client/BUILD.gn +++ b/client/BUILD.gn @@ -32,6 +32,8 @@ crashpad_static_library("client") { if (crashpad_is_ios) { sources += [ + "crash_handler_base_ios.cc", + "crash_handler_base_ios.h", "crashpad_client_ios.cc", "ios_handler/exception_processor.h", "ios_handler/exception_processor.mm", @@ -44,6 +46,18 @@ crashpad_static_library("client") { "simulate_crash_ios.h", "upload_behavior_ios.h", ] + + if (!crashpad_is_tvos) { + sources += [ + "crash_handler_ios.cc", + "crash_handler_ios.h", + ] + } else { + sources += [ + "crash_handler_tvos.cc", + "crash_handler_tvos.h", + ] + } } if (crashpad_is_linux || crashpad_is_android) { @@ -79,10 +93,7 @@ crashpad_static_library("client") { "../util", ] - deps = [ - ":common", - "$mini_chromium_source_parent:chromeos_buildflags", - ] + deps = [ ":common" ] if (crashpad_is_win) { libs = [ "rpcrt4.lib" ] @@ -210,8 +221,8 @@ source_set("client_test") { "../compat", "../snapshot", "../test", + "../third_party/googletest", "../third_party/googletest:googlemock", - "../third_party/googletest:googletest", "../util", ] @@ -229,6 +240,10 @@ source_set("client_test") { "../handler/win/wer:crashpad_wer_handler", ] } + + if (crashpad_is_ios) { + deps += [ "../minidump" ] + } } if (crashpad_is_linux || crashpad_is_android) { diff --git a/client/annotation.h b/client/annotation.h index a5d14f0d90..5effc776dd 100644 --- a/client/annotation.h +++ b/client/annotation.h @@ -15,17 +15,18 @@ #ifndef CRASHPAD_CLIENT_ANNOTATION_H_ #define CRASHPAD_CLIENT_ANNOTATION_H_ -#include -#include -#include - #include #include #include +#include +#include +#include +#include +#include + #include "base/check.h" #include "base/numerics/safe_conversions.h" -#include "base/strings/string_piece.h" #include "build/build_config.h" #include "util/synchronization/scoped_spin_guard.h" @@ -328,19 +329,19 @@ class StringAnnotation : public Annotation { //! \brief Sets the Annotation's string value. //! //! \param[in] string The string value. - void Set(base::StringPiece string) { + void Set(std::string_view string) { Annotation::ValueSizeType size = std::min(MaxSize, base::saturated_cast(string.size())); string = string.substr(0, size); std::copy(string.begin(), string.end(), value_); // Check for no embedded `NUL` characters. - DCHECK(string.find('\0', /*pos=*/0) == base::StringPiece::npos) + DCHECK(string.find('\0', /*pos=*/0) == std::string_view::npos) << "embedded NUL"; SetSize(size); } - const base::StringPiece value() const { - return base::StringPiece(value_, size()); + const std::string_view value() const { + return std::string_view(value_, size()); } private: diff --git a/client/annotation_list.h b/client/annotation_list.h index ce1f861397..6df33c46d7 100644 --- a/client/annotation_list.h +++ b/client/annotation_list.h @@ -46,7 +46,7 @@ class AnnotationList { //! CrashapdInfo structure. static AnnotationList* Get(); - //! \brief Returns the instace of the list, creating and registering + //! \brief Returns the instance of the list, creating and registering //! it if one is not already set on the CrashapdInfo structure. static AnnotationList* Register(); diff --git a/client/crash_handler_base_ios.cc b/client/crash_handler_base_ios.cc new file mode 100644 index 0000000000..6b349ca2bf --- /dev/null +++ b/client/crash_handler_base_ios.cc @@ -0,0 +1,127 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "client/crash_handler_base_ios.h" + +#include "base/logging.h" +#include "util/posix/signals.h" + +namespace crashpad { + +CrashHandlerBase::CrashHandlerBase() = default; +CrashHandlerBase::~CrashHandlerBase() = default; + +bool CrashHandlerBase::Initialize( + const base::FilePath& database, + const std::string& url, + const std::map& annotations, + internal::InProcessHandler::ProcessPendingReportsObservationCallback + callback) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + if (!in_process_handler().Initialize(database, url, annotations, callback)) { + LOG(ERROR) << "Unable to initialize Crashpad."; + return false; + } + if (!DoInitialize()) { + return false; + } + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +void CrashHandlerBase::ProcessIntermediateDumps( + const std::map& annotations, + const UserStreamDataSources* user_stream_sources) { + in_process_handler_.ProcessIntermediateDumps(annotations, + user_stream_sources); +} + +void CrashHandlerBase::ProcessIntermediateDump( + const base::FilePath& file, + const std::map& annotations) { + in_process_handler_.ProcessIntermediateDump(file, annotations); +} + +void CrashHandlerBase::DumpWithoutCrash(NativeCPUContext* context, + bool process_dump) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + base::FilePath path; + if (!in_process_handler_.DumpExceptionFromSimulatedMachException( + context, kMachExceptionSimulated, &path)) { + return; + } + + if (process_dump) { + in_process_handler_.ProcessIntermediateDump(path); + } +} + +void CrashHandlerBase::DumpWithoutCrashAtPath(NativeCPUContext* context, + const base::FilePath& path) { + in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath( + context, kMachExceptionSimulated, path); +} + +void CrashHandlerBase::StartProcessingPendingReports( + UploadBehavior upload_behavior) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + in_process_handler_.StartProcessingPendingReports(upload_behavior); +} + +void CrashHandlerBase::SetExceptionCallbackForTesting(void (*callback)()) { + in_process_handler_.SetExceptionCallbackForTesting(callback); +} + +void CrashHandlerBase::HandleAndReraiseSignal(int signo, + siginfo_t* siginfo, + ucontext_t* context, + struct sigaction* old_action) { + in_process_handler_.DumpExceptionFromSignal(siginfo, context); + + // Always call system handler. + Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, old_action); +} + +void CrashHandlerBase::HandleUncaughtNSException(const uint64_t* frames, + const size_t num_frames) { + in_process_handler_.DumpExceptionFromNSExceptionWithFrames(frames, + num_frames); + // After uncaught exceptions are reported, the system immediately triggers a + // call to std::terminate()/abort(). Remove the abort handler so a second + // dump isn't generated. + CHECK(Signals::InstallDefaultHandler(SIGABRT)); +} + +void CrashHandlerBase::HandleUncaughtNSExceptionWithContext( + NativeCPUContext* context) { + base::FilePath path; + in_process_handler_.DumpExceptionFromSimulatedMachException( + context, kMachExceptionFromNSException, &path); + + // After uncaught exceptions are reported, the system immediately triggers a + // call to std::terminate()/abort(). Remove the abort handler so a second + // dump isn't generated. + CHECK(Signals::InstallDefaultHandler(SIGABRT)); +} + +void CrashHandlerBase::HandleUncaughtNSExceptionWithContextAtPath( + NativeCPUContext* context, + const base::FilePath& path) { + in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath( + context, kMachExceptionFromNSException, path); +} + +bool CrashHandlerBase::MoveIntermediateDumpAtPathToPending( + const base::FilePath& path) { + if (in_process_handler_.MoveIntermediateDumpAtPathToPending(path)) { + // After uncaught exceptions are reported, the system immediately triggers + // a call to std::terminate()/abort(). Remove the abort handler so a + // second dump isn't generated. + CHECK(Signals::InstallDefaultHandler(SIGABRT)); + return true; + } + return false; +} + +} // namespace crashpad diff --git a/client/crash_handler_base_ios.h b/client/crash_handler_base_ios.h new file mode 100644 index 0000000000..0aea192553 --- /dev/null +++ b/client/crash_handler_base_ios.h @@ -0,0 +1,89 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRASHPAD_CLIENT_CRASH_HANDLER_BASE_IOS_H_ +#define CRASHPAD_CLIENT_CRASH_HANDLER_BASE_IOS_H_ + +#include +#include + +#include +#include + +#include "base/files/file_path.h" +#include "client/ios_handler/exception_processor.h" +#include "client/ios_handler/in_process_handler.h" +#include "client/upload_behavior_ios.h" +#include "util/misc/capture_context.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { + +// Base class shared by the iOS and tvOS CrashHandler implementations. +class CrashHandlerBase : public ObjcExceptionDelegate { + public: + CrashHandlerBase(const CrashHandlerBase&) = delete; + CrashHandlerBase& operator=(const CrashHandlerBase&) = delete; + + bool Initialize( + const base::FilePath& database, + const std::string& url, + const std::map& annotations, + internal::InProcessHandler::ProcessPendingReportsObservationCallback + callback); + + void ProcessIntermediateDumps( + const std::map& annotations, + const UserStreamDataSources* user_stream_sources); + + void ProcessIntermediateDump( + const base::FilePath& file, + const std::map& annotations); + + void DumpWithoutCrash(NativeCPUContext* context, bool process_dump); + + void DumpWithoutCrashAtPath(NativeCPUContext* context, + const base::FilePath& path); + + void StartProcessingPendingReports(UploadBehavior upload_behavior); + + void SetExceptionCallbackForTesting(void (*callback)()); + + protected: + CrashHandlerBase(); + virtual ~CrashHandlerBase(); + + // Subclasses are expected to install signal handlers and set up Mach ports in + // this function. + virtual bool DoInitialize() = 0; + + void HandleAndReraiseSignal(int signo, + siginfo_t* siginfo, + ucontext_t* context, + struct sigaction* old_action); + + internal::InProcessHandler& in_process_handler() { + return in_process_handler_; + } + + private: + // ObjcExceptionDelegate overrides: + void HandleUncaughtNSException(const uint64_t* frames, + const size_t num_frames) override; + + void HandleUncaughtNSExceptionWithContext(NativeCPUContext* context) override; + + void HandleUncaughtNSExceptionWithContextAtPath( + NativeCPUContext* context, + const base::FilePath& path) override; + + bool MoveIntermediateDumpAtPathToPending(const base::FilePath& path) override; + + internal::InProcessHandler in_process_handler_; + InitializationStateDcheck initialized_; +}; + +} // namespace crashpad + +#endif // CRASHPAD_CLIENT_CRASH_HANDLER_BASE_IOS_H_ diff --git a/client/crash_handler_ios.cc b/client/crash_handler_ios.cc new file mode 100644 index 0000000000..f149ae7164 --- /dev/null +++ b/client/crash_handler_ios.cc @@ -0,0 +1,307 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "client/crash_handler_ios.h" + +#include +#include + +#include + +#include "base/apple/mach_logging.h" +#include "util/ios/raw_logging.h" +#include "util/mach/mach_extensions.h" +#include "util/mach/mach_message.h" +#include "util/mach/mach_message_server.h" +#include "util/posix/signals.h" + +namespace { + +bool IsBeingDebugged() { + int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()}; + size_t len = sizeof(kinfo_proc); + kinfo_proc kern_proc_info; + if (sysctl(mib, std::size(mib), &kern_proc_info, &len, nullptr, 0) == 0) { + return kern_proc_info.kp_proc.p_flag & P_TRACED; + } + return false; +} + +} // namespace + +namespace crashpad { + +CrashHandler::ThreadSafeScopedMachPortWithReceiveRight:: + ThreadSafeScopedMachPortWithReceiveRight() + : port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)) {} + +CrashHandler::ThreadSafeScopedMachPortWithReceiveRight:: + ~ThreadSafeScopedMachPortWithReceiveRight() { + reset(); +} + +mach_port_t CrashHandler::ThreadSafeScopedMachPortWithReceiveRight::get() { + return port_.load(); +} +void CrashHandler::ThreadSafeScopedMachPortWithReceiveRight::reset() { + mach_port_t old_port = port_.exchange(MACH_PORT_NULL); + if (old_port == MACH_PORT_NULL) { + // Already reset, nothing to do. + return; + } + kern_return_t kr = mach_port_mod_refs( + mach_task_self(), old_port, MACH_PORT_RIGHT_RECEIVE, -1); + MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) + << "ThreadSafeScopedMachPortWithReceiveRight mach_port_mod_refs"; + kr = mach_port_deallocate(mach_task_self(), old_port); + MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) + << "ThreadSafeScopedMachPortWithReceiveRight mach_port_deallocate"; +} + +CrashHandler* CrashHandler::instance_ = nullptr; + +CrashHandler::CrashHandler() = default; + +CrashHandler::~CrashHandler() { + UninstallObjcExceptionPreprocessor(); + // Reset the SIGPIPE handler only if the current handler is the + // one installed by DoInitialize(). In other words, if an + // application has set its own SIGPIPE handler after initializing + // Crashpad, there is no need to change the signal handler here. + struct sigaction sa; + if (sigaction(SIGPIPE, nullptr, &sa) == 0 && + sa.sa_sigaction == CatchAndReraiseSignalDefaultAction) { + Signals::InstallDefaultHandler(SIGPIPE); + } + Signals::InstallDefaultHandler(SIGABRT); + UninstallMachExceptionHandler(); +} + +// static +CrashHandler* CrashHandler::Get() { + if (!instance_) + instance_ = new CrashHandler(); + return instance_; +} + +// static +void CrashHandler::ResetForTesting() { + delete instance_; + instance_ = nullptr; +} + +bool CrashHandler::DoInitialize() { + if (!InstallMachExceptionHandler() || + // xnu turns hardware faults into Mach exceptions, so the only signal + // left to register is SIGABRT, which never starts off as a hardware + // fault. Installing a handler for other signals would lead to + // recording exceptions twice. As a consequence, Crashpad will not + // generate intermediate dumps for anything manually calling + // raise(SIG*). In practice, this doesn’t actually happen for crash + // signals that originate as hardware faults. + !Signals::InstallHandler( + SIGABRT, CatchAndReraiseSignal, 0, &old_action_)) { + return false; + } + + // For applications that haven't ignored or set a handler for SIGPIPE: + // It’s OK for an application to set its own SIGPIPE handler (including + // SIG_IGN) before initializing Crashpad, because Crashpad will discover the + // existing handler and not install its own. + // It’s OK for Crashpad to install its own SIGPIPE handler and for the + // application to subsequently install its own (including SIG_IGN) + // afterwards, because its handler will replace Crashpad’s. + // This is useful to cover the default situation where nobody installs a + // SIGPIPE handler and the disposition is at SIG_DFL, because SIGPIPE is a + // “kill” signal (bsd/sys/signalvar.h sigprop). In that case, without + // Crashpad, SIGPIPE results in a silent and unreported kill (and not even + // ReportCrash will record it), but developers probably want to be alerted to + // the condition. + struct sigaction sa; + if (sigaction(SIGPIPE, nullptr, &sa) == 0 && sa.sa_handler == SIG_DFL) { + Signals::InstallHandler( + SIGPIPE, CatchAndReraiseSignalDefaultAction, 0, nullptr); + } + + InstallObjcExceptionPreprocessor(this); + return true; +} + +bool CrashHandler::InstallMachExceptionHandler() { + mach_port_t exception_port = exception_port_.get(); + if (exception_port == MACH_PORT_NULL) { + return false; + } + + kern_return_t kr = mach_port_insert_right(mach_task_self(), + exception_port, + exception_port, + MACH_MSG_TYPE_MAKE_SEND); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "mach_port_insert_right"; + return false; + } + + // TODO: Use SwapExceptionPort instead and put back EXC_MASK_BREAKPOINT. + // Until then, remove |EXC_MASK_BREAKPOINT| while attached to a debugger. + exception_mask_t mask = + ExcMaskAll() & + ~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_RPC_ALERT | + EXC_MASK_GUARD | (IsBeingDebugged() ? EXC_MASK_BREAKPOINT : 0)); + + ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); + if (!exception_ports.GetExceptionPorts(mask, &original_handlers_) || + !exception_ports.SetExceptionPort( + mask, + exception_port, + EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, + MACHINE_THREAD_STATE)) { + return false; + } + + mach_handler_running_ = true; + Start(); + return true; +} + +void CrashHandler::UninstallMachExceptionHandler() { + mach_handler_running_ = false; + exception_port_.reset(); + Join(); +} + +// Thread: + +void CrashHandler::ThreadMain() { + UniversalMachExcServer universal_mach_exc_server(this); + while (mach_handler_running_) { + mach_msg_return_t mr = + MachMessageServer::Run(&universal_mach_exc_server, + exception_port_.get(), + MACH_MSG_OPTION_NONE, + MachMessageServer::kPersistent, + MachMessageServer::kReceiveLargeIgnore, + kMachMessageTimeoutWaitIndefinitely); + MACH_CHECK( + mach_handler_running_ + ? mr == MACH_SEND_INVALID_DEST // This shouldn't happen for + // exception messages that come + // from the kernel itself, but if + // something else in-process sends + // exception messages and breaks, + // handle that case. + : (mr == MACH_RCV_PORT_CHANGED || // Port was closed while the + // thread was listening. + mr == MACH_RCV_INVALID_NAME), // Port was closed before the + // thread started listening. + mr) + << "MachMessageServer::Run"; + } +} + +// UniversalMachExcServer::Interface: + +kern_return_t CrashHandler::CatchMachException( + exception_behavior_t behavior, + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const mach_exception_data_type_t* code, + mach_msg_type_number_t code_count, + thread_state_flavor_t* flavor, + ConstThreadState old_state, + mach_msg_type_number_t old_state_count, + thread_state_t new_state, + mach_msg_type_number_t* new_state_count, + const mach_msg_trailer_t* trailer, + bool* destroy_complex_request) { + *destroy_complex_request = true; + + // TODO(justincohen): Forward exceptions to original_handlers_ with + // UniversalExceptionRaise. + + // iOS shouldn't have any child processes, but just in case, those will + // inherit the task exception ports, and this process isn’t prepared to + // handle them + if (task != mach_task_self()) { + CRASHPAD_RAW_LOG("MachException task != mach_task_self()"); + return KERN_FAILURE; + } + + HandleMachException(behavior, + thread, + exception, + code, + code_count, + *flavor, + old_state, + old_state_count); + + // Respond with KERN_FAILURE so the system will continue to handle this + // exception. xnu will turn this Mach exception into a signal and take the + // default action to terminate the process. However, if sigprocmask is + // called before this Mach exception returns (such as by another thread + // calling abort, see: Libc-1506.40.4/stdlib/FreeBSD/abort.c), the Mach + // exception will be converted into a signal but delivery will be blocked. + // Since concurrent exceptions lead to the losing thread sleeping + // indefinitely, if the abort thread never returns, the thread that + // triggered this Mach exception will repeatedly trap and the process will + // never terminate. If the abort thread didn’t have a user-space signal + // handler that slept forever, the abort would terminate the process even if + // all other signals had been blocked. Instead, unblock all signals + // corresponding to all Mach exceptions Crashpad is registered for before + // returning KERN_FAILURE. There is still racy behavior possible with this + // call to sigprocmask, but the repeated calls to CatchMachException here + // will eventually lead to termination. + sigset_t unblock_set; + sigemptyset(&unblock_set); + sigaddset(&unblock_set, SIGILL); // EXC_BAD_INSTRUCTION + sigaddset(&unblock_set, SIGTRAP); // EXC_BREAKPOINT + sigaddset(&unblock_set, SIGFPE); // EXC_ARITHMETIC + sigaddset(&unblock_set, SIGBUS); // EXC_BAD_ACCESS + sigaddset(&unblock_set, SIGSEGV); // EXC_BAD_ACCESS + if (sigprocmask(SIG_UNBLOCK, &unblock_set, nullptr) != 0) { + CRASHPAD_RAW_LOG("sigprocmask"); + } + return KERN_FAILURE; +} + +void CrashHandler::HandleMachException(exception_behavior_t behavior, + thread_t thread, + exception_type_t exception, + const mach_exception_data_type_t* code, + mach_msg_type_number_t code_count, + thread_state_flavor_t flavor, + ConstThreadState old_state, + mach_msg_type_number_t old_state_count) { + in_process_handler().DumpExceptionFromMachException(behavior, + thread, + exception, + code, + code_count, + flavor, + old_state, + old_state_count); +} + +// static +void CrashHandler::CatchAndReraiseSignal(int signo, + siginfo_t* siginfo, + void* context) { + Get()->HandleAndReraiseSignal(signo, + siginfo, + reinterpret_cast(context), + &(Get()->old_action_)); +} + +// static +void CrashHandler::CatchAndReraiseSignalDefaultAction(int signo, + siginfo_t* siginfo, + void* context) { + Get()->HandleAndReraiseSignal( + signo, siginfo, reinterpret_cast(context), nullptr); +} + +} // namespace crashpad diff --git a/client/crash_handler_ios.h b/client/crash_handler_ios.h new file mode 100644 index 0000000000..35e1753df6 --- /dev/null +++ b/client/crash_handler_ios.h @@ -0,0 +1,109 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRASHPAD_CLIENT_CRASH_HANDLER_IOS_H_ +#define CRASHPAD_CLIENT_CRASH_HANDLER_IOS_H_ + +#include + +#include "client/crash_handler_base_ios.h" +#include "util/mach/exc_server_variants.h" +#include "util/mach/exception_ports.h" +#include "util/thread/thread.h" + +namespace crashpad { + +// A base class for signal handler and Mach exception server. +class CrashHandler : public Thread, + public UniversalMachExcServer::Interface, + public CrashHandlerBase { + public: + CrashHandler(const CrashHandler&) = delete; + CrashHandler& operator=(const CrashHandler&) = delete; + + static CrashHandler* Get(); + + static void ResetForTesting(); + + private: + // Thread-safe version of `base::apple::ScopedMachReceiveRight` which + // allocates the Mach port upon construction and deallocates it upon + // destruction. + class ThreadSafeScopedMachPortWithReceiveRight { + public: + ThreadSafeScopedMachPortWithReceiveRight(); + + ThreadSafeScopedMachPortWithReceiveRight( + const ThreadSafeScopedMachPortWithReceiveRight&) = delete; + ThreadSafeScopedMachPortWithReceiveRight& operator=( + const ThreadSafeScopedMachPortWithReceiveRight&) = delete; + + ~ThreadSafeScopedMachPortWithReceiveRight(); + + mach_port_t get(); + void reset(); + + private: + std::atomic port_; + }; + + CrashHandler(); + + ~CrashHandler() override; + + bool DoInitialize() override; + + bool InstallMachExceptionHandler(); + + void UninstallMachExceptionHandler(); + + // Thread: + + void ThreadMain() override; + + // UniversalMachExcServer::Interface: + + kern_return_t CatchMachException(exception_behavior_t behavior, + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const mach_exception_data_type_t* code, + mach_msg_type_number_t code_count, + thread_state_flavor_t* flavor, + ConstThreadState old_state, + mach_msg_type_number_t old_state_count, + thread_state_t new_state, + mach_msg_type_number_t* new_state_count, + const mach_msg_trailer_t* trailer, + bool* destroy_complex_request) override; + + void HandleMachException(exception_behavior_t behavior, + thread_t thread, + exception_type_t exception, + const mach_exception_data_type_t* code, + mach_msg_type_number_t code_count, + thread_state_flavor_t flavor, + ConstThreadState old_state, + mach_msg_type_number_t old_state_count); + + // The signal handler installed at OS-level. + static void CatchAndReraiseSignal(int signo, + siginfo_t* siginfo, + void* context); + + static void CatchAndReraiseSignalDefaultAction(int signo, + siginfo_t* siginfo, + void* context); + + ThreadSafeScopedMachPortWithReceiveRight exception_port_; + ExceptionPorts::ExceptionHandlerVector original_handlers_; + struct sigaction old_action_ = {}; + static CrashHandler* instance_; + std::atomic mach_handler_running_ = false; +}; + +} // namespace crashpad + +#endif // CRASHPAD_CLIENT_CRASH_HANDLER_IOS_H_ diff --git a/client/crash_handler_tvos.cc b/client/crash_handler_tvos.cc new file mode 100644 index 0000000000..ab42b44398 --- /dev/null +++ b/client/crash_handler_tvos.cc @@ -0,0 +1,96 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "client/crash_handler_tvos.h" + +#include + +#include "util/posix/signals.h" +#include "util/thread/thread.h" + +namespace crashpad { + +CrashHandler* CrashHandler::instance_ = nullptr; + +CrashHandler::CrashHandler() = default; + +CrashHandler::~CrashHandler() { + UninstallObjcExceptionPreprocessor(); + for (int signo = 1; signo < NSIG; ++signo) { + if (!Signals::IsCrashSignal(signo)) { + Signals::RestoreOrResetHandler(signo, + old_actions_.ActionForSignal(signo)); + } else if (signo == SIGPIPE) { + // Reset the SIGPIPE handler only if the current handler is the one + // installed by DoInitialize(). In other words, if an application has set + // its own SIGPIPE handler after initializing Crashpad, there is no need + // to change the signal handler here. + struct sigaction sa; + if (sigaction(SIGPIPE, nullptr, &sa) == 0 && + sa.sa_sigaction == CatchAndReraiseSignal) { + Signals::InstallDefaultHandler(SIGPIPE); + } + } + } +} + +// static +CrashHandler* CrashHandler::Get() { + if (!instance_) { + instance_ = new CrashHandler; + } + return instance_; +} + +// static +void CrashHandler::ResetForTesting() { + delete instance_; + instance_ = nullptr; +} + +uint64_t CrashHandler::GetThreadIdForTesting() { + return Thread::GetThreadIdForTesting(); +} + +bool CrashHandler::DoInitialize() { + if (!Signals::InstallCrashHandlers(CatchAndReraiseSignal, + /*flags=*/0, + &old_actions_)) { + return false; + } + + // For applications that haven't ignored or set a handler for SIGPIPE: + // It’s OK for an application to set its own SIGPIPE handler (including + // SIG_IGN) before initializing Crashpad, because Crashpad will discover the + // existing handler and not install its own. + // It’s OK for Crashpad to install its own SIGPIPE handler and for the + // application to subsequently install its own (including SIG_IGN) + // afterwards, because its handler will replace Crashpad’s. + // This is useful to cover the default situation where nobody installs a + // SIGPIPE handler and the disposition is at SIG_DFL, because SIGPIPE is a + // “kill” signal (bsd/sys/signalvar.h sigprop). In that case, without + // Crashpad, SIGPIPE results in a silent and unreported kill (and not even + // ReportCrash will record it), but developers probably want to be alerted to + // the condition. + struct sigaction sa; + if (sigaction(SIGPIPE, nullptr, &sa) == 0 && sa.sa_handler == SIG_DFL) { + Signals::InstallHandler(SIGPIPE, CatchAndReraiseSignal, 0, nullptr); + } + + InstallObjcExceptionPreprocessor(this); + return true; +} + +// static +void CrashHandler::CatchAndReraiseSignal(int signo, + siginfo_t* siginfo, + void* context) { + CrashHandler* self = Get(); + self->HandleAndReraiseSignal(signo, + siginfo, + reinterpret_cast(context), + self->old_actions_.ActionForSignal(signo)); +} + +} // namespace crashpad diff --git a/client/crash_handler_tvos.h b/client/crash_handler_tvos.h new file mode 100644 index 0000000000..61e2718920 --- /dev/null +++ b/client/crash_handler_tvos.h @@ -0,0 +1,44 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRASHPAD_CLIENT_CRASH_HANDLER_TVOS_H_ +#define CRASHPAD_CLIENT_CRASH_HANDLER_TVOS_H_ + +#include "client/crash_handler_base_ios.h" +#include "util/posix/signals.h" + +namespace crashpad { + +// A crash handler based on POSIX signals. +// The APIs to handle Mach exceptions are not available to third-party +// applications on tvOS. +class CrashHandler final : public CrashHandlerBase { + public: + CrashHandler(const CrashHandler&) = delete; + CrashHandler& operator=(const CrashHandler&) = delete; + + static CrashHandler* Get(); + + static void ResetForTesting(); + + uint64_t GetThreadIdForTesting(); + + private: + CrashHandler(); + ~CrashHandler() override; + + bool DoInitialize() override; + + // The signal handler installed at OS-level. + static void CatchAndReraiseSignal(int signo, + siginfo_t* siginfo, + void* context); + + Signals::OldActions old_actions_ = {}; + static CrashHandler* instance_; +}; + +} // namespace crashpad + +#endif // CRASHPAD_CLIENT_CRASH_HANDLER_TVOS_H_ diff --git a/client/crash_report_database.h b/client/crash_report_database.h index a1050a8837..0d01c60ae0 100644 --- a/client/crash_report_database.h +++ b/client/crash_report_database.h @@ -34,6 +34,7 @@ namespace crashpad { class Settings; +class SettingsReader; //! \brief An interface for managing a collection of crash report files and //! metadata associated with the crash reports. @@ -303,6 +304,16 @@ class CrashReportDatabase { static std::unique_ptr InitializeWithoutCreating( const base::FilePath& path); + //! \brief Given a database path, return a read-only view of its settings. + //! + //! \param[in] path A path to the database. If the database does not exist, or + //! the settings file does not exist, the returned reader will fail its + //! read methods. + //! + //! \return A SettingsReader. + static std::unique_ptr GetSettingsReaderForDatabasePath( + const base::FilePath& path); + //! \brief Returns the Settings object for this database. //! //! \return A weak pointer to the Settings object, which is owned by the @@ -469,7 +480,7 @@ class CrashReportDatabase { const base::FilePath& crash_envelope) {} protected: - CrashReportDatabase() {} + CrashReportDatabase() = default; //! \brief The path to the database passed to Initialize. //! diff --git a/client/crash_report_database_generic.cc b/client/crash_report_database_generic.cc index 70dfda088e..5317ae44b0 100644 --- a/client/crash_report_database_generic.cc +++ b/client/crash_report_database_generic.cc @@ -163,7 +163,7 @@ class ScopedLockFile { class CrashReportDatabaseGeneric : public CrashReportDatabase { public: - CrashReportDatabaseGeneric(); + explicit CrashReportDatabaseGeneric(const base::FilePath& path); CrashReportDatabaseGeneric(const CrashReportDatabaseGeneric&) = delete; CrashReportDatabaseGeneric& operator=(const CrashReportDatabaseGeneric&) = @@ -171,7 +171,7 @@ class CrashReportDatabaseGeneric : public CrashReportDatabase { ~CrashReportDatabaseGeneric() override; - bool Initialize(const base::FilePath& path, bool may_create); + bool Initialize(bool may_create); // CrashReportDatabase: Settings* GetSettings() override; @@ -265,26 +265,24 @@ class CrashReportDatabaseGeneric : public CrashReportDatabase { static bool WriteMetadata(const base::FilePath& path, const Report& report); Settings& SettingsInternal() { - std::call_once(settings_init_, [this]() { - settings_.Initialize(base_dir_.Append(kSettings)); - }); + std::call_once(settings_init_, [this]() { settings_.Initialize(); }); return settings_; } - base::FilePath base_dir_; + const base::FilePath base_dir_; Settings settings_; std::once_flag settings_init_; InitializationStateDcheck initialized_; }; -CrashReportDatabaseGeneric::CrashReportDatabaseGeneric() = default; +CrashReportDatabaseGeneric::CrashReportDatabaseGeneric( + const base::FilePath& path) + : base_dir_(path), settings_(path.Append(kSettings)) {} CrashReportDatabaseGeneric::~CrashReportDatabaseGeneric() = default; -bool CrashReportDatabaseGeneric::Initialize(const base::FilePath& path, - bool may_create) { +bool CrashReportDatabaseGeneric::Initialize(bool may_create) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); - base_dir_ = path; if (!IsDirectory(base_dir_, true) && !(may_create && @@ -308,20 +306,6 @@ bool CrashReportDatabaseGeneric::Initialize(const base::FilePath& path, return true; } -// static -std::unique_ptr CrashReportDatabase::Initialize( - const base::FilePath& path) { - auto database = std::make_unique(); - return database->Initialize(path, true) ? std::move(database) : nullptr; -} - -// static -std::unique_ptr -CrashReportDatabase::InitializeWithoutCreating(const base::FilePath& path) { - auto database = std::make_unique(); - return database->Initialize(path, false) ? std::move(database) : nullptr; -} - base::FilePath CrashReportDatabaseGeneric::DatabasePath() { return base_dir_; } @@ -965,4 +949,25 @@ bool CrashReportDatabaseGeneric::WriteMetadata(const base::FilePath& path, LoggingWriteFile(handle.get(), report.id.c_str(), report.id.size()); } +// static +std::unique_ptr CrashReportDatabase::Initialize( + const base::FilePath& path) { + auto database = std::make_unique(path); + return database->Initialize(true) ? std::move(database) : nullptr; +} + +// static +std::unique_ptr +CrashReportDatabase::InitializeWithoutCreating(const base::FilePath& path) { + auto database = std::make_unique(path); + return database->Initialize(false) ? std::move(database) : nullptr; +} + +// static +std::unique_ptr +CrashReportDatabase::GetSettingsReaderForDatabasePath( + const base::FilePath& path) { + return std::make_unique(path.Append(kSettings)); +} + } // namespace crashpad diff --git a/client/crash_report_database_mac.mm b/client/crash_report_database_mac.mm index b27123fd29..58ef9e16cc 100644 --- a/client/crash_report_database_mac.mm +++ b/client/crash_report_database_mac.mm @@ -29,6 +29,7 @@ #include #include #include +#include #include #include "base/apple/scoped_nsautorelease_pool.h" @@ -36,7 +37,6 @@ #include "base/posix/eintr_wrapper.h" #include "base/scoped_generic.h" #include "base/strings/strcat.h" -#include "base/strings/string_piece.h" #include "base/strings/sys_string_conversions.h" #include "client/settings.h" #include "util/file/directory_reader.h" @@ -116,7 +116,7 @@ bool CreateOrEnsureDirectoryExists(const base::FilePath& path) { // Creates a long database xattr name from the short constant name. These names // have changed, and new_name determines whether the returned xattr name will be // the old name or its new equivalent. -std::string XattrNameInternal(const base::StringPiece& name, bool new_name) { +std::string XattrNameInternal(std::string_view name, bool new_name) { return base::StrCat({new_name ? "org.chromium.crashpad.database." : "com.googlecode.crashpad.", name}); @@ -253,7 +253,7 @@ OperationStatus ReportsInDirectory(const base::FilePath& path, //! \param[in] name The short name of the extended attribute. //! //! \return The long name of the extended attribute. - std::string XattrName(const base::StringPiece& name); + std::string XattrName(std::string_view name); //! \brief Marks a report with a given path as completed. //! @@ -271,13 +271,11 @@ OperationStatus ReportsInDirectory(const base::FilePath& path, void CleanOrphanedAttachments(); Settings& SettingsInternal() { - std::call_once(settings_init_, [this]() { - settings_.Initialize(base_dir_.Append(kSettings)); - }); + std::call_once(settings_init_, [this]() { settings_.Initialize(); }); return settings_; } - base::FilePath base_dir_; + const base::FilePath base_dir_; Settings settings_; std::once_flag settings_init_; bool xattr_new_names_; @@ -287,7 +285,7 @@ OperationStatus ReportsInDirectory(const base::FilePath& path, CrashReportDatabaseMac::CrashReportDatabaseMac(const base::FilePath& path) : CrashReportDatabase(), base_dir_(path), - settings_(), + settings_(path.Append(kSettings)), settings_init_(), xattr_new_names_(false), initialized_() {} @@ -871,7 +869,7 @@ OperationStatus ReportsInDirectory(const base::FilePath& path, return kNoError; } -std::string CrashReportDatabaseMac::XattrName(const base::StringPiece& name) { +std::string CrashReportDatabaseMac::XattrName(std::string_view name) { return XattrNameInternal(name, xattr_new_names_); } @@ -938,6 +936,8 @@ OperationStatus ReportsInDirectory(const base::FilePath& path, } } +namespace { + std::unique_ptr InitializeInternal( const base::FilePath& path, bool may_create) { @@ -949,6 +949,8 @@ OperationStatus ReportsInDirectory(const base::FilePath& path, return std::unique_ptr(database_mac.release()); } +} // namespace + // static std::unique_ptr CrashReportDatabase::Initialize( const base::FilePath& path) { @@ -961,4 +963,11 @@ OperationStatus ReportsInDirectory(const base::FilePath& path, return InitializeInternal(path, false); } +// static +std::unique_ptr +CrashReportDatabase::GetSettingsReaderForDatabasePath( + const base::FilePath& path) { + return std::make_unique(path.Append(kSettings)); +} + } // namespace crashpad diff --git a/client/crash_report_database_test.cc b/client/crash_report_database_test.cc index a5425fdb10..6d8acaf00e 100644 --- a/client/crash_report_database_test.cc +++ b/client/crash_report_database_test.cc @@ -810,10 +810,27 @@ TEST_F(CrashReportDatabaseTest, OrphanedAttachments) { CrashReportDatabase::kNoError); EXPECT_EQ(uuid, expect_uuid); +#if BUILDFLAG(IS_WIN) + const std::wstring uuid_string = uuid.ToWString(); +#else + const std::string uuid_string = uuid.ToString(); +#endif + const base::FilePath report_attachments_dir( + path().Append(FILE_PATH_LITERAL("attachments")).Append(uuid_string)); + const base::FilePath file_path1( + report_attachments_dir.Append(FILE_PATH_LITERAL("file1"))); + const base::FilePath file_path2( + report_attachments_dir.Append(FILE_PATH_LITERAL("file2"))); + CrashReportDatabase::Report report; ASSERT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); + // Cleaning a consistent database does not remove the attachements. + EXPECT_EQ(db()->CleanDatabase(0), 0); + EXPECT_TRUE(FileExists(file_path1)); + EXPECT_TRUE(FileExists(file_path1)); + ASSERT_TRUE(LoggingRemoveFile(report.file_path)); #if !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_WIN) @@ -828,17 +845,6 @@ TEST_F(CrashReportDatabaseTest, OrphanedAttachments) { ASSERT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kReportNotFound); -#if BUILDFLAG(IS_WIN) - const std::wstring uuid_string = uuid.ToWString(); -#else - const std::string uuid_string = uuid.ToString(); -#endif - base::FilePath report_attachments_dir( - path().Append(FILE_PATH_LITERAL("attachments")).Append(uuid_string)); - base::FilePath file_path1( - report_attachments_dir.Append(FILE_PATH_LITERAL("file1"))); - base::FilePath file_path2( - report_attachments_dir.Append(FILE_PATH_LITERAL("file2"))); EXPECT_TRUE(FileExists(file_path1)); EXPECT_TRUE(FileExists(file_path1)); @@ -979,6 +985,50 @@ TEST_F(CrashReportDatabaseTest, GetReportSize_RightSizeWithAttachments) { sizeof(attachment_2_data)); } +TEST_F(CrashReportDatabaseTest, InitializeFromLargerFileRetainsClientId) { + // Initialize the database for the first time, creating it. + ASSERT_TRUE(db()); + + const base::FilePath settings_path = + path().Append(FILE_PATH_LITERAL("settings.dat")); + EXPECT_FALSE(FileExists(settings_path)); + + Settings* settings = db()->GetSettings(); + ASSERT_TRUE(settings); + EXPECT_TRUE(FileExists(settings_path)); + + UUID client_id; + ASSERT_TRUE(settings->GetClientID(&client_id)); + EXPECT_NE(client_id, UUID()); + + // Close and reopen the database at the same path. + ResetDatabase(); + EXPECT_FALSE(db()); + EXPECT_TRUE(FileExists(settings_path)); + + // Append some data, to ensure that we can open settings even if a future + // version has added additional field to Settings (forwards compatible). + FileWriter settings_writer; + ASSERT_TRUE(settings_writer.Open( + settings_path, FileWriteMode::kReuseOrFail, FilePermissions::kOwnerOnly)); + ASSERT_NE(settings_writer.Seek(0, SEEK_END), 0); + constexpr uint64_t extra_garbage = 0xBADF00D; + ASSERT_TRUE(settings_writer.Write(&extra_garbage, sizeof(extra_garbage))); + settings_writer.Close(); + + auto db = CrashReportDatabase::InitializeWithoutCreating(path()); + ASSERT_TRUE(db); + + settings = db->GetSettings(); + ASSERT_TRUE(settings); + + // Make sure that the reopened settings retained the original client id and + // wasn't recreated. + UUID reopened_client_id; + ASSERT_TRUE(settings->GetClientID(&reopened_client_id)); + EXPECT_EQ(client_id, reopened_client_id); +} + } // namespace } // namespace test } // namespace crashpad diff --git a/client/crash_report_database_win.cc b/client/crash_report_database_win.cc index f9e42636c0..52fae26740 100644 --- a/client/crash_report_database_win.cc +++ b/client/crash_report_database_win.cc @@ -666,13 +666,11 @@ class CrashReportDatabaseWin : public CrashReportDatabase { std::unique_ptr AcquireMetadata(); Settings& SettingsInternal() { - std::call_once(settings_init_, [this]() { - settings_.Initialize(base_dir_.Append(kSettings)); - }); + std::call_once(settings_init_, [this]() { settings_.Initialize(); }); return settings_; } - base::FilePath base_dir_; + const base::FilePath base_dir_; Settings settings_; std::once_flag settings_init_; InitializationStateDcheck initialized_; @@ -681,7 +679,7 @@ class CrashReportDatabaseWin : public CrashReportDatabase { CrashReportDatabaseWin::CrashReportDatabaseWin(const base::FilePath& path) : CrashReportDatabase(), base_dir_(path), - settings_(), + settings_(path.Append(kSettings)), settings_init_(), initialized_() {} @@ -946,16 +944,6 @@ std::unique_ptr CrashReportDatabaseWin::AcquireMetadata() { AttachmentsRootPath()); } -std::unique_ptr InitializeInternal( - const base::FilePath& path, - bool may_create) { - std::unique_ptr database_win( - new CrashReportDatabaseWin(path)); - return database_win->Initialize(may_create) - ? std::move(database_win) - : std::unique_ptr(); -} - OperationStatus CrashReportDatabaseWin::RequestUpload(const UUID& uuid) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); @@ -1060,9 +1048,9 @@ void CrashReportDatabaseWin::CleanOrphanedAttachments() { continue; } - // Remove attachments if corresponding report doen't exist. - base::FilePath report_path = - reports_dir.Append(uuid.ToWString() + kCrashReportFileExtension); + // Remove attachments if corresponding report doesn't exist. + base::FilePath report_path = reports_dir.Append( + uuid.ToWString() + L"." + kCrashReportFileExtension); if (!IsRegularFile(report_path)) { RemoveAttachmentsByUUID(uuid); } @@ -1070,6 +1058,20 @@ void CrashReportDatabaseWin::CleanOrphanedAttachments() { } } +namespace { + +std::unique_ptr InitializeInternal( + const base::FilePath& path, + bool may_create) { + std::unique_ptr database_win( + new CrashReportDatabaseWin(path)); + return database_win->Initialize(may_create) + ? std::move(database_win) + : std::unique_ptr(); +} + +} // namespace + // static std::unique_ptr CrashReportDatabase::Initialize( const base::FilePath& path) { @@ -1082,4 +1084,11 @@ CrashReportDatabase::InitializeWithoutCreating(const base::FilePath& path) { return InitializeInternal(path, false); } +// static +std::unique_ptr +CrashReportDatabase::GetSettingsReaderForDatabasePath( + const base::FilePath& path) { + return std::make_unique(path.Append(kSettings)); +} + } // namespace crashpad diff --git a/client/crashpad_client.h b/client/crashpad_client.h index f1a2020471..d26ca04dad 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -25,7 +25,6 @@ #include "base/files/file_path.h" #include "build/build_config.h" -#include "build/chromeos_buildflags.h" #include "util/file/file_io.h" #if !BUILDFLAG(IS_FUCHSIA) @@ -44,6 +43,7 @@ #if BUILDFLAG(IS_IOS) #include "client/upload_behavior_ios.h" +#include "handler/user_stream_data_source.h" // nogncheck #endif namespace crashpad { @@ -388,6 +388,9 @@ class CrashpadClient { //! Arguments passed in other parameters and arguments required to perform //! the handshake are the responsibility of this method, and must not be //! specified in this parameter. + //! \param[in] attachments Attachment paths to pass to the Crashpad handler. + //! The handler will be started with an `--attachment` argument for each + //! path in this vector. //! //! \return `true` on success, `false` on failure with a message logged. bool StartHandlerAtCrash( @@ -452,7 +455,7 @@ class CrashpadClient { //! FirstChanceHandler and crashes the current process. //! //! \param[in] message A message to be logged before crashing. - static void CrashWithoutDump(const std::string& message); + [[noreturn]] static void CrashWithoutDump(const std::string& message); //! \brief The type for custom handlers installed by clients. using FirstChanceHandler = bool (*)(int, siginfo_t*, ucontext_t*); @@ -548,8 +551,13 @@ class CrashpadClient { //! \param[in] annotations Process annotations to set in each crash report. //! Useful when adding crash annotations detected on the next run after a //! crash but before upload. + //! \param[in] user_stream_sources An optional vector containing the + //! extensibility data sources to call on crash. Each time a minidump is + //! created, the sources are called in turn. Any streams returned are + //! added to the minidump. static void ProcessIntermediateDumps( - const std::map& annotations = {}); + const std::map& annotations = {}, + const UserStreamDataSources* user_stream_sources = nullptr); //! \brief Requests that the handler convert a single intermediate dump at \a //! file generated by DumpWithoutCrashAndDeferProcessingAtPath into a @@ -630,9 +638,10 @@ class CrashpadClient { //! be used in a shipping application. static void ResetForTesting(); - //! \brief Inject a callback into Mach handling. Intended to be used by - //! tests to trigger a reentrant exception. - static void SetMachExceptionCallbackForTesting(void (*callback)()); + //! \brief Inject a callback into the Mach exception and signal handling + //! mechanisms. Intended to be used by tests to trigger a reentrant + // exception. + static void SetExceptionCallbackForTesting(void (*callback)()); //! \brief Returns the thread id of the Mach exception thread, used by tests. static uint64_t GetThreadIdForTesting(); @@ -836,7 +845,7 @@ class CrashpadClient { static void UseSystemDefaultHandler(); #endif -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) //! \brief Sets a timestamp on the signal handler to be passed on to //! crashpad_handler and then eventually Chrome OS's crash_reporter. //! @@ -861,7 +870,7 @@ class CrashpadClient { #endif private: -#if BUILDFLAG(IS_WIN) +#if BUILDFLAG(IS_WIN) || DOXYGEN //! \brief Registers process handlers for the client. void RegisterHandlers(); #endif diff --git a/client/crashpad_client_ios.cc b/client/crashpad_client_ios.cc index 663a17c432..fbff1a304f 100644 --- a/client/crashpad_client_ios.cc +++ b/client/crashpad_client_ios.cc @@ -14,432 +14,17 @@ #include "client/crashpad_client.h" -#include -#include - -#include -#include -#include - -#include "base/apple/mach_logging.h" -#include "base/apple/scoped_mach_port.h" -#include "base/logging.h" -#include "client/ios_handler/exception_processor.h" +#include "build/buildflag.h" #include "client/ios_handler/in_process_handler.h" -#include "util/ios/raw_logging.h" -#include "util/mach/exc_server_variants.h" -#include "util/mach/exception_ports.h" -#include "util/mach/mach_extensions.h" -#include "util/mach/mach_message.h" -#include "util/mach/mach_message_server.h" -#include "util/misc/initialization_state_dcheck.h" -#include "util/posix/signals.h" -#include "util/thread/thread.h" -namespace { - -bool IsBeingDebugged() { - kinfo_proc kern_proc_info; - int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()}; - size_t len = sizeof(kern_proc_info); - if (sysctl(mib, std::size(mib), &kern_proc_info, &len, nullptr, 0) == 0) - return kern_proc_info.kp_proc.p_flag & P_TRACED; - return false; -} - -} // namespace +#if !BUILDFLAG(IS_IOS_TVOS) +#include "client/crash_handler_ios.h" +#else +#include "client/crash_handler_tvos.h" +#endif namespace crashpad { -namespace { - -// Thread-safe version of `base::apple::ScopedMachReceiveRight` which allocates -// the Mach port upon construction and deallocates it upon destruction. -class ThreadSafeScopedMachPortWithReceiveRight { - public: - ThreadSafeScopedMachPortWithReceiveRight() - : port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)) {} - - ThreadSafeScopedMachPortWithReceiveRight( - const ThreadSafeScopedMachPortWithReceiveRight&) = delete; - ThreadSafeScopedMachPortWithReceiveRight& operator=( - const ThreadSafeScopedMachPortWithReceiveRight&) = delete; - - ~ThreadSafeScopedMachPortWithReceiveRight() { reset(); } - - mach_port_t get() { return port_.load(); } - void reset() { - mach_port_t old_port = port_.exchange(MACH_PORT_NULL); - if (old_port == MACH_PORT_NULL) { - // Already reset, nothing to do. - return; - } - kern_return_t kr = mach_port_mod_refs( - mach_task_self(), old_port, MACH_PORT_RIGHT_RECEIVE, -1); - MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) - << "ThreadSafeScopedMachPortWithReceiveRight mach_port_mod_refs"; - kr = mach_port_deallocate(mach_task_self(), old_port); - MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) - << "ThreadSafeScopedMachPortWithReceiveRight mach_port_deallocate"; - } - - private: - std::atomic port_; -}; - -// A base class for signal handler and Mach exception server. -class CrashHandler : public Thread, - public UniversalMachExcServer::Interface, - public ObjcExceptionDelegate { - public: - CrashHandler(const CrashHandler&) = delete; - CrashHandler& operator=(const CrashHandler&) = delete; - - static CrashHandler* Get() { - if (!instance_) - instance_ = new CrashHandler(); - return instance_; - } - - static void ResetForTesting() { - delete instance_; - instance_ = nullptr; - } - - bool Initialize( - const base::FilePath& database, - const std::string& url, - const std::map& annotations, - internal::InProcessHandler::ProcessPendingReportsObservationCallback - callback) { - INITIALIZATION_STATE_SET_INITIALIZING(initialized_); - if (!in_process_handler_.Initialize(database, url, annotations, callback) || - !InstallMachExceptionHandler() || - // xnu turns hardware faults into Mach exceptions, so the only signal - // left to register is SIGABRT, which never starts off as a hardware - // fault. Installing a handler for other signals would lead to - // recording exceptions twice. As a consequence, Crashpad will not - // generate intermediate dumps for anything manually calling - // raise(SIG*). In practice, this doesn’t actually happen for crash - // signals that originate as hardware faults. - !Signals::InstallHandler( - SIGABRT, CatchAndReraiseSignal, 0, &old_action_)) { - LOG(ERROR) << "Unable to initialize Crashpad."; - return false; - } - - // For applications that haven't ignored or set a handler for SIGPIPE: - // It’s OK for an application to set its own SIGPIPE handler (including - // SIG_IGN) before initializing Crashpad, because Crashpad will discover the - // existing handler and not install its own. - // It’s OK for Crashpad to install its own SIGPIPE handler and for the - // application to subsequently install its own (including SIG_IGN) - // afterwards, because its handler will replace Crashpad’s. - // This is useful to cover the default situation where nobody installs a - // SIGPIPE handler and the disposition is at SIG_DFL, because SIGPIPE is a - // “kill” signal (bsd/sys/signalvar.h sigprop). In that case, without - // Crashpad, SIGPIPE results in a silent and unreported kill (and not even - // ReportCrash will record it), but developers probably want to be alerted - // to the conditon. - struct sigaction sa; - if (sigaction(SIGPIPE, nullptr, &sa) == 0 && sa.sa_handler == SIG_DFL) { - Signals::InstallHandler( - SIGPIPE, CatchAndReraiseSignalDefaultAction, 0, nullptr); - } - - InstallObjcExceptionPreprocessor(this); - INITIALIZATION_STATE_SET_VALID(initialized_); - return true; - } - - void ProcessIntermediateDumps( - const std::map& annotations) { - in_process_handler_.ProcessIntermediateDumps(annotations); - } - - void ProcessIntermediateDump( - const base::FilePath& file, - const std::map& annotations) { - in_process_handler_.ProcessIntermediateDump(file, annotations); - } - - void DumpWithoutCrash(NativeCPUContext* context, bool process_dump) { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - base::FilePath path; - if (!in_process_handler_.DumpExceptionFromSimulatedMachException( - context, kMachExceptionSimulated, &path)) { - return; - } - - if (process_dump) { - in_process_handler_.ProcessIntermediateDump(path); - } - } - - void DumpWithoutCrashAtPath(NativeCPUContext* context, - const base::FilePath& path) { - in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath( - context, kMachExceptionSimulated, path); - } - - void StartProcessingPendingReports(UploadBehavior upload_behavior) { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - in_process_handler_.StartProcessingPendingReports(upload_behavior); - } - - void SetMachExceptionCallbackForTesting(void (*callback)()) { - in_process_handler_.SetMachExceptionCallbackForTesting(callback); - } - - uint64_t GetThreadIdForTesting() { return Thread::GetThreadIdForTesting(); } - - private: - CrashHandler() = default; - - ~CrashHandler() { - UninstallObjcExceptionPreprocessor(); - Signals::InstallDefaultHandler(SIGABRT); - UninstallMachExceptionHandler(); - } - - bool InstallMachExceptionHandler() { - mach_port_t exception_port = exception_port_.get(); - if (exception_port == MACH_PORT_NULL) { - return false; - } - - kern_return_t kr = mach_port_insert_right(mach_task_self(), - exception_port, - exception_port, - MACH_MSG_TYPE_MAKE_SEND); - if (kr != KERN_SUCCESS) { - MACH_LOG(ERROR, kr) << "mach_port_insert_right"; - return false; - } - - // TODO: Use SwapExceptionPort instead and put back EXC_MASK_BREAKPOINT. - // Until then, remove |EXC_MASK_BREAKPOINT| while attached to a debugger. - exception_mask_t mask = - ExcMaskAll() & - ~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_RPC_ALERT | - EXC_MASK_GUARD | (IsBeingDebugged() ? EXC_MASK_BREAKPOINT : 0)); - - ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); - if (!exception_ports.GetExceptionPorts(mask, &original_handlers_) || - !exception_ports.SetExceptionPort( - mask, - exception_port, - EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, - MACHINE_THREAD_STATE)) { - return false; - } - - mach_handler_running_ = true; - Start(); - return true; - } - - void UninstallMachExceptionHandler() { - mach_handler_running_ = false; - exception_port_.reset(); - Join(); - } - - // Thread: - - void ThreadMain() override { - UniversalMachExcServer universal_mach_exc_server(this); - while (mach_handler_running_) { - mach_msg_return_t mr = - MachMessageServer::Run(&universal_mach_exc_server, - exception_port_.get(), - MACH_MSG_OPTION_NONE, - MachMessageServer::kPersistent, - MachMessageServer::kReceiveLargeIgnore, - kMachMessageTimeoutWaitIndefinitely); - MACH_CHECK( - mach_handler_running_ - ? mr == MACH_SEND_INVALID_DEST // This shouldn't happen for - // exception messages that come - // from the kernel itself, but if - // something else in-process sends - // exception messages and breaks, - // handle that case. - : (mr == MACH_RCV_PORT_CHANGED || // Port was closed while the - // thread was listening. - mr == MACH_RCV_INVALID_NAME), // Port was closed before the - // thread started listening. - mr) - << "MachMessageServer::Run"; - } - } - - // UniversalMachExcServer::Interface: - - kern_return_t CatchMachException(exception_behavior_t behavior, - exception_handler_t exception_port, - thread_t thread, - task_t task, - exception_type_t exception, - const mach_exception_data_type_t* code, - mach_msg_type_number_t code_count, - thread_state_flavor_t* flavor, - ConstThreadState old_state, - mach_msg_type_number_t old_state_count, - thread_state_t new_state, - mach_msg_type_number_t* new_state_count, - const mach_msg_trailer_t* trailer, - bool* destroy_complex_request) override { - *destroy_complex_request = true; - - // TODO(justincohen): Forward exceptions to original_handlers_ with - // UniversalExceptionRaise. - - // iOS shouldn't have any child processes, but just in case, those will - // inherit the task exception ports, and this process isn’t prepared to - // handle them - if (task != mach_task_self()) { - CRASHPAD_RAW_LOG("MachException task != mach_task_self()"); - return KERN_FAILURE; - } - - HandleMachException(behavior, - thread, - exception, - code, - code_count, - *flavor, - old_state, - old_state_count); - - // Respond with KERN_FAILURE so the system will continue to handle this - // exception. xnu will turn this Mach exception into a signal and take the - // default action to terminate the process. However, if sigprocmask is - // called before this Mach exception returns (such as by another thread - // calling abort, see: Libc-1506.40.4/stdlib/FreeBSD/abort.c), the Mach - // exception will be converted into a signal but delivery will be blocked. - // Since concurrent exceptions lead to the losing thread sleeping - // indefinitely, if the abort thread never returns, the thread that - // triggered this Mach exception will repeatedly trap and the process will - // never terminate. If the abort thread didn’t have a user-space signal - // handler that slept forever, the abort would terminate the process even if - // all other signals had been blocked. Instead, unblock all signals - // corresponding to all Mach exceptions Crashpad is registered for before - // returning KERN_FAILURE. There is still racy behavior possible with this - // call to sigprocmask, but the repeated calls to CatchMachException here - // will eventually lead to termination. - sigset_t unblock_set; - sigemptyset(&unblock_set); - sigaddset(&unblock_set, SIGILL); // EXC_BAD_INSTRUCTION - sigaddset(&unblock_set, SIGTRAP); // EXC_BREAKPOINT - sigaddset(&unblock_set, SIGFPE); // EXC_ARITHMETIC - sigaddset(&unblock_set, SIGBUS); // EXC_BAD_ACCESS - sigaddset(&unblock_set, SIGSEGV); // EXC_BAD_ACCESS - if (sigprocmask(SIG_UNBLOCK, &unblock_set, nullptr) != 0) { - CRASHPAD_RAW_LOG("sigprocmask"); - } - return KERN_FAILURE; - } - - void HandleMachException(exception_behavior_t behavior, - thread_t thread, - exception_type_t exception, - const mach_exception_data_type_t* code, - mach_msg_type_number_t code_count, - thread_state_flavor_t flavor, - ConstThreadState old_state, - mach_msg_type_number_t old_state_count) { - in_process_handler_.DumpExceptionFromMachException(behavior, - thread, - exception, - code, - code_count, - flavor, - old_state, - old_state_count); - } - - void HandleUncaughtNSException(const uint64_t* frames, - const size_t num_frames) override { - in_process_handler_.DumpExceptionFromNSExceptionWithFrames(frames, - num_frames); - // After uncaught exceptions are reported, the system immediately triggers a - // call to std::terminate()/abort(). Remove the abort handler so a second - // dump isn't generated. - CHECK(Signals::InstallDefaultHandler(SIGABRT)); - } - - void HandleUncaughtNSExceptionWithContext( - NativeCPUContext* context) override { - base::FilePath path; - in_process_handler_.DumpExceptionFromSimulatedMachException( - context, kMachExceptionFromNSException, &path); - - // After uncaught exceptions are reported, the system immediately triggers a - // call to std::terminate()/abort(). Remove the abort handler so a second - // dump isn't generated. - CHECK(Signals::InstallDefaultHandler(SIGABRT)); - } - - void HandleUncaughtNSExceptionWithContextAtPath( - NativeCPUContext* context, - const base::FilePath& path) override { - in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath( - context, kMachExceptionFromNSException, path); - } - - bool MoveIntermediateDumpAtPathToPending( - const base::FilePath& path) override { - if (in_process_handler_.MoveIntermediateDumpAtPathToPending(path)) { - // After uncaught exceptions are reported, the system immediately triggers - // a call to std::terminate()/abort(). Remove the abort handler so a - // second dump isn't generated. - CHECK(Signals::InstallDefaultHandler(SIGABRT)); - return true; - } - return false; - } - - // The signal handler installed at OS-level. - static void CatchAndReraiseSignal(int signo, - siginfo_t* siginfo, - void* context) { - Get()->HandleAndReraiseSignal(signo, - siginfo, - reinterpret_cast(context), - &(Get()->old_action_)); - } - - static void CatchAndReraiseSignalDefaultAction(int signo, - siginfo_t* siginfo, - void* context) { - Get()->HandleAndReraiseSignal( - signo, siginfo, reinterpret_cast(context), nullptr); - } - - void HandleAndReraiseSignal(int signo, - siginfo_t* siginfo, - ucontext_t* context, - struct sigaction* old_action) { - in_process_handler_.DumpExceptionFromSignal(siginfo, context); - - // Always call system handler. - Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, old_action); - } - - ThreadSafeScopedMachPortWithReceiveRight exception_port_; - ExceptionPorts::ExceptionHandlerVector original_handlers_; - struct sigaction old_action_ = {}; - internal::InProcessHandler in_process_handler_; - static CrashHandler* instance_; - std::atomic mach_handler_running_ = false; - InitializationStateDcheck initialized_; -}; - -CrashHandler* CrashHandler::instance_ = nullptr; - -} // namespace - CrashpadClient::CrashpadClient() {} CrashpadClient::~CrashpadClient() {} @@ -457,10 +42,11 @@ bool CrashpadClient::StartCrashpadInProcessHandler( // static void CrashpadClient::ProcessIntermediateDumps( - const std::map& annotations) { + const std::map& annotations, + const UserStreamDataSources* user_stream_sources) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); - crash_handler->ProcessIntermediateDumps(annotations); + crash_handler->ProcessIntermediateDumps(annotations, user_stream_sources); } // static @@ -510,10 +96,10 @@ void CrashpadClient::ResetForTesting() { crash_handler->ResetForTesting(); } -void CrashpadClient::SetMachExceptionCallbackForTesting(void (*callback)()) { +void CrashpadClient::SetExceptionCallbackForTesting(void (*callback)()) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); - crash_handler->SetMachExceptionCallbackForTesting(callback); + crash_handler->SetExceptionCallbackForTesting(callback); } uint64_t CrashpadClient::GetThreadIdForTesting() { diff --git a/client/crashpad_client_ios_test.mm b/client/crashpad_client_ios_test.mm index 29f3df6522..6d275911a4 100644 --- a/client/crashpad_client_ios_test.mm +++ b/client/crashpad_client_ios_test.mm @@ -16,6 +16,7 @@ #import +#include #include #include "base/strings/sys_string_conversions.h" @@ -149,7 +150,7 @@ void ThreadMain() override { // during development only. TEST_F(CrashpadIOSClient, DISABLED_ThrowException) { std::vector empty_vector; - empty_vector.at(42); + std::ignore = empty_vector.at(42); } } // namespace diff --git a/client/crashpad_client_linux.cc b/client/crashpad_client_linux.cc index e416f4f950..9cff50b6c0 100644 --- a/client/crashpad_client_linux.cc +++ b/client/crashpad_client_linux.cc @@ -34,7 +34,6 @@ #include "base/logging.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" -#include "build/chromeos_buildflags.h" #include "client/client_argv_handling.h" #include "third_party/lss/lss.h" #include "util/file/file_io.h" @@ -422,7 +421,7 @@ class RequestCrashDumpHandler : public SignalHandler { ExceptionHandlerProtocol::ClientInformation info = {}; info.exception_information_address = FromPointerCast(&GetExceptionInfo()); -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) info.crash_loop_before_time = crash_loop_before_time_; #endif @@ -430,7 +429,7 @@ class RequestCrashDumpHandler : public SignalHandler { client.RequestCrashDump(info); } -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) void SetCrashLoopBefore(uint64_t crash_loop_before_time) { crash_loop_before_time_ = crash_loop_before_time; } @@ -462,7 +461,7 @@ class RequestCrashDumpHandler : public SignalHandler { ScopedFileHandle sock_to_handler_; pid_t handler_pid_ = -1; -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) // An optional UNIX timestamp passed to us from Chrome. // This will pass to crashpad_handler and then to Chrome OS crash_reporter. // This should really be a time_t, but it's basically an opaque value (we @@ -813,7 +812,7 @@ void CrashpadClient::SetUnhandledSignals(const std::set& signals) { unhandled_signals_ = signals; } -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) // static void CrashpadClient::SetCrashLoopBefore(uint64_t crash_loop_before_time) { auto request_crash_dump_handler = RequestCrashDumpHandler::Get(); diff --git a/client/crashpad_client_linux_test.cc b/client/crashpad_client_linux_test.cc index ae76a7e4bf..c0bba4036d 100644 --- a/client/crashpad_client_linux_test.cc +++ b/client/crashpad_client_linux_test.cc @@ -24,7 +24,6 @@ #include #include "base/check_op.h" -#include "base/notreached.h" #include "build/build_config.h" #include "client/annotation.h" #include "client/annotation_list.h" @@ -760,12 +759,10 @@ class StartHandlerForChildTest : public Multiprocess { test_state_.ExpectReport(); } - void MultiprocessChild() { + [[noreturn]] void MultiprocessChild() { CHECK(test_state_.InstallHandler()); __builtin_trap(); - - NOTREACHED_IN_MIGRATION(); } StartHandlerForClientTest test_state_; diff --git a/client/crashpad_client_mac.cc b/client/crashpad_client_mac.cc index db15473c31..d040484d79 100644 --- a/client/crashpad_client_mac.cc +++ b/client/crashpad_client_mac.cc @@ -64,13 +64,13 @@ std::string FormatArgumentInt(const std::string& name, int value) { // reasons, an EXC_CRASH exception will be sent. See 10.9.5 // xnu-2422.115.4/bsd/kern/kern_exit.c proc_prepareexit(). // -// EXC_RESOURCE and EXC_GUARD do not become signals or EXC_CRASH exceptions. The -// host-level exception handler in the kernel does not receive these exception -// types, and even if it did, it would not map them to signals. Instead, the -// first Mach service loaded by the root (process ID 1) launchd with a boolean -// “ExceptionServer” property in its job dictionary (regardless of its value) or -// with any subdictionary property will become the host-level exception handler -// for EXC_CRASH, EXC_RESOURCE, and EXC_GUARD. See 10.9.5 +// EXC_RESOURCE and EXC_GUARD (pre-macOS 13) do not become signals or EXC_CRASH +// exceptions. The host-level exception handler in the kernel does not receive +// these exception types, and even if it did, it would not map them to signals. +// Instead, the first Mach service loaded by the root (process ID 1) launchd +// with a boolean “ExceptionServer” property in its job dictionary (regardless +// of its value) or with any subdictionary property will become the host-level +// exception handler for EXC_CRASH, EXC_RESOURCE, and EXC_GUARD. See 10.9.5 // launchd-842.92.1/src/core.c job_setup_exception_port(). Normally, this job is // com.apple.ReportCrash.Root, the systemwide Apple Crash Reporter. Since it is // impossible to receive EXC_RESOURCE and EXC_GUARD exceptions through the @@ -83,8 +83,15 @@ std::string FormatArgumentInt(const std::string& name, int value) { // so AND them with ExcMaskValid(). EXC_MASK_CRASH is always supported. bool SetCrashExceptionPorts(exception_handler_t exception_handler) { ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); + + exception_mask_t mask = EXC_MASK_CRASH | EXC_MASK_RESOURCE; + if (MacOSVersionNumber() < 13'00'00) { + // EXC_GUARD is delivered as an EXC_CRASH macOS 13 and later. + mask |= EXC_MASK_GUARD; + } + return exception_ports.SetExceptionPort( - (EXC_MASK_CRASH | EXC_MASK_RESOURCE | EXC_MASK_GUARD) & ExcMaskValid(), + mask & ExcMaskValid(), exception_handler, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, MACHINE_THREAD_STATE); diff --git a/client/crashpad_client_win.cc b/client/crashpad_client_win.cc index abe60a032d..94a685e976 100644 --- a/client/crashpad_client_win.cc +++ b/client/crashpad_client_win.cc @@ -158,7 +158,7 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) { // Otherwise, we know the handler startup has succeeded, and we can continue. // Tracks whether a thread has already entered UnhandledExceptionHandler. - static base::subtle::AtomicWord have_crashed; + static std::atomic have_crashed{0}; // This is a per-process handler. While this handler is being invoked, other // threads are still executing as usual, so multiple threads could enter at @@ -172,7 +172,7 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) { // that we won't save the exception pointers from the second and further // crashes, but contention here is very unlikely, and we'll still have a stack // that's blocked at this location. - if (base::subtle::Barrier_AtomicIncrement(&have_crashed, 1) > 1) { + if (std::atomic_fetch_add(&have_crashed, 1) > 0) { SleepEx(INFINITE, false); } @@ -500,7 +500,8 @@ bool StartHandlerProcess( BOOL rv; DWORD creation_flags; STARTUPINFOEX startup_info = {}; - startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES; + startup_info.StartupInfo.dwFlags = + STARTF_USESTDHANDLES | STARTF_FORCEOFFFEEDBACK; startup_info.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startup_info.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); startup_info.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); diff --git a/client/crashpad_info.cc b/client/crashpad_info.cc index 781c0e3825..78a40d8957 100644 --- a/client/crashpad_info.cc +++ b/client/crashpad_info.cc @@ -137,7 +137,13 @@ CrashpadInfo::CrashpadInfo() extra_memory_ranges_(nullptr), simple_annotations_(nullptr), user_data_minidump_stream_head_(nullptr), - annotations_list_(nullptr) {} + annotations_list_(nullptr) +#if BUILDFLAG(IS_IOS) + , + intermediate_dump_extra_memory_ranges_(nullptr) +#endif +{ +} UserDataMinidumpStreamHandle* CrashpadInfo::AddUserDataMinidumpStream( uint32_t stream_type, diff --git a/client/crashpad_info.h b/client/crashpad_info.h index 7d894cb0c3..b777a81f05 100644 --- a/client/crashpad_info.h +++ b/client/crashpad_info.h @@ -81,16 +81,54 @@ struct CrashpadInfo { //! this method is called, or they may be added, removed, or modified in \a //! address_range_bag after this method is called. //! - //! TODO(scottmg) This is currently only supported on Windows. + //! TODO(scottmg) This is currently only supported on Windows and iOS. //! //! \param[in] address_range_bag A bag of address ranges. The CrashpadInfo //! object does not take ownership of the SimpleAddressRangeBag object. //! It is the caller’s responsibility to ensure that this pointer remains //! valid while it is in effect for a CrashpadInfo object. + //! + //! \sa extra_memory_ranges() void set_extra_memory_ranges(SimpleAddressRangeBag* address_range_bag) { extra_memory_ranges_ = address_range_bag; } + //! \return The simple extra memory ranges SimpleAddressRangeBag object. + //! + //! \sa set_extra_memory_ranges() + SimpleAddressRangeBag* extra_memory_ranges() const { + return extra_memory_ranges_; + } + +#if BUILDFLAG(IS_IOS) + //! \brief Sets the bag of extra memory ranges to be included in the iOS + //! intermediate dump. This memory is not included in the minidump. + //! + //! Extra memory ranges may exist in \a address_range_bag at the time that + //! this method is called, or they may be added, removed, or modified in \a + //! address_range_bag after this method is called. + //! + //! This is only supported on iOS. + //! + //! \param[in] address_range_bag A bag of address ranges. The CrashpadInfo + //! object does not take ownership of the SimpleAddressRangeBag object. + //! It is the caller’s responsibility to ensure that this pointer remains + //! valid while it is in effect for a CrashpadInfo object. + //! + //! \sa extra_memory_ranges() + void set_intermediate_dump_extra_memory_ranges( + SimpleAddressRangeBag* address_range_bag) { + intermediate_dump_extra_memory_ranges_ = address_range_bag; + } + + //! \return The simple extra memory ranges SimpleAddressRangeBag object. + //! + //! \sa set_extra_memory_ranges() + SimpleAddressRangeBag* intermediate_dump_extra_memory_ranges() const { + return intermediate_dump_extra_memory_ranges_; + } +#endif + //! \brief Sets the simple annotations dictionary. //! //! Simple annotations set on a CrashpadInfo structure are interpreted by @@ -296,6 +334,9 @@ struct CrashpadInfo { SimpleStringDictionary* simple_annotations_; // weak internal::UserDataMinidumpStreamListEntry* user_data_minidump_stream_head_; AnnotationList* annotations_list_; // weak +#if BUILDFLAG(IS_IOS) + SimpleAddressRangeBag* intermediate_dump_extra_memory_ranges_; // weak +#endif // It’s generally safe to add new fields without changing // kCrashpadInfoVersion, because readers should check size_ and ignore fields diff --git a/client/ios_handler/exception_processor.mm b/client/ios_handler/exception_processor.mm index 0268b3a128..5ed0eb2661 100644 --- a/client/ios_handler/exception_processor.mm +++ b/client/ios_handler/exception_processor.mm @@ -42,6 +42,7 @@ #include #include #include +#include #include "base/format_macros.h" #include "base/logging.h" @@ -51,7 +52,6 @@ #include "base/strings/sys_string_conversions.h" #include "build/build_config.h" #include "client/annotation.h" -#include "client/simulate_crash_ios.h" namespace crashpad { diff --git a/client/ios_handler/in_process_handler.cc b/client/ios_handler/in_process_handler.cc index be12692098..55cb71852b 100644 --- a/client/ios_handler/in_process_handler.cc +++ b/client/ios_handler/in_process_handler.cc @@ -147,6 +147,9 @@ void InProcessHandler::DumpExceptionFromSignal(siginfo_t* siginfo, CRASHPAD_RAW_LOG("Cannot DumpExceptionFromSignal without writer"); return; } + if (exception_callback_for_testing_) { + exception_callback_for_testing_(); + } ScopedReport report(writer.GetWriter(), system_data_, annotations_); InProcessIntermediateDumpHandler::WriteExceptionFromSignal( writer.GetWriter(), system_data_, siginfo, context); @@ -170,8 +173,8 @@ void InProcessHandler::DumpExceptionFromMachException( return; } - if (mach_exception_callback_for_testing_) { - mach_exception_callback_for_testing_(); + if (exception_callback_for_testing_) { + exception_callback_for_testing_(); } ScopedReport report(writer.GetWriter(), system_data_, annotations_); @@ -254,21 +257,23 @@ bool InProcessHandler::MoveIntermediateDumpAtPathToPending( return MoveFileOrDirectory(path, new_path_unlocked); } void InProcessHandler::ProcessIntermediateDumps( - const std::map& annotations) { + const std::map& annotations, + const UserStreamDataSources* user_stream_sources) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); for (auto& file : PendingFiles()) - ProcessIntermediateDump(file, annotations); + ProcessIntermediateDump(file, annotations, user_stream_sources); } void InProcessHandler::ProcessIntermediateDump( const base::FilePath& file, - const std::map& annotations) { + const std::map& annotations, + const UserStreamDataSources* user_stream_sources) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); ProcessSnapshotIOSIntermediateDump process_snapshot; if (process_snapshot.InitializeWithFilePath(file, annotations)) { - SaveSnapshot(process_snapshot); + SaveSnapshot(process_snapshot, user_stream_sources); } } @@ -320,7 +325,8 @@ void InProcessHandler::UpdatePruneAndUploadThreads( } void InProcessHandler::SaveSnapshot( - ProcessSnapshotIOSIntermediateDump& process_snapshot) { + ProcessSnapshotIOSIntermediateDump& process_snapshot, + const UserStreamDataSources* user_stream_sources) { std::unique_ptr new_report; CrashReportDatabase::OperationStatus database_status = database_->PrepareNewCrashReport(&new_report); @@ -339,6 +345,8 @@ void InProcessHandler::SaveSnapshot( MinidumpFileWriter minidump; minidump.InitializeFromSnapshot(&process_snapshot); + AddUserExtensionStreams(user_stream_sources, &process_snapshot, &minidump); + if (!minidump.WriteEverything(new_report->Writer())) { Metrics::ExceptionCaptureResult( Metrics::CaptureResult::kMinidumpWriteFailed); diff --git a/client/ios_handler/in_process_handler.h b/client/ios_handler/in_process_handler.h index 13c18cb8ef..f65f7e405a 100644 --- a/client/ios_handler/in_process_handler.h +++ b/client/ios_handler/in_process_handler.h @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#ifndef CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_IN_PROCESS_HANDLER_H_ +#define CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_IN_PROCESS_HANDLER_H_ + #include #include @@ -26,9 +29,11 @@ #include "client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h" #include "client/upload_behavior_ios.h" #include "handler/crash_report_upload_thread.h" +#include "handler/user_stream_data_source.h" #include "snapshot/ios/process_snapshot_ios_intermediate_dump.h" #include "util/ios/ios_intermediate_dump_writer.h" #include "util/ios/ios_system_data_collector.h" +#include "util/mach/mach_extensions.h" #include "util/misc/capture_context.h" #include "util/misc/initialization_state_dcheck.h" @@ -159,17 +164,27 @@ class InProcessHandler { //! minidumps and trigger an upload if possible. //! //! \param[in] annotations Process annotations to set in each crash report. + //! \param[in] user_stream_sources An optional vector containing the + //! extensibility data sources to call on crash. Each time a minidump is + //! created, the sources are called in turn. Any streams returned are + //! added to the minidump. void ProcessIntermediateDumps( - const std::map& annotations); + const std::map& annotations, + const UserStreamDataSources* user_stream_sources); //! \brief Requests that the handler convert a specific intermediate dump into //! a minidump and trigger an upload if possible. //! //! \param[in] path Path to the specific intermediate dump. //! \param[in] annotations Process annotations to set in each crash report. + //! \param[in] user_stream_sources An optional vector containing the + //! extensibility data sources to call on crash. Each time a minidump is + //! created, the sources are called in turn. Any streams returned are + //! added to the minidump. void ProcessIntermediateDump( const base::FilePath& path, - const std::map& annotations = {}); + const std::map& annotations = {}, + const UserStreamDataSources* user_stream_sources = {}); //! \brief Requests that the handler begin in-process uploading of any //! pending reports. @@ -180,10 +195,11 @@ class InProcessHandler { void StartProcessingPendingReports( UploadBehavior upload_behavior = UploadBehavior::kUploadWhenAppIsActive); - //! \brief Inject a callback into Mach handling. Intended to be used by - //! tests to trigger a reentrant exception. - void SetMachExceptionCallbackForTesting(void (*callback)()) { - mach_exception_callback_for_testing_ = callback; + //! \brief Inject a callback into the Mach exception and signal handling + //! mechanisms. Intended to be used by tests to trigger a reentrant + // exception. + void SetExceptionCallbackForTesting(void (*callback)()) { + exception_callback_for_testing_ = callback; } private: @@ -239,7 +255,12 @@ class InProcessHandler { //! \brief Writes a minidump to the Crashpad database from the //! \a process_snapshot, and triggers the upload_thread_ if started. - void SaveSnapshot(ProcessSnapshotIOSIntermediateDump& process_snapshot); + //! \param[in] user_stream_sources An optional vector containing the + //! extensibility data sources to call on crash. Each time a minidump is + //! created, the sources are called in turn. Any streams returned are + //! added to the minidump. + void SaveSnapshot(ProcessSnapshotIOSIntermediateDump& process_snapshot, + const UserStreamDataSources* user_stream_sources = {}); //! \brief Process a maximum of 20 pending intermediate dumps. Dumps named //! with our bundle id get first priority to prevent spamming. @@ -262,8 +283,9 @@ class InProcessHandler { const base::FilePath NewLockedFilePath(); // Intended to be used by tests triggering a reentrant exception. Called - // in DumpExceptionFromMachException after aquiring the cached_writer_. - void (*mach_exception_callback_for_testing_)() = nullptr; + // in DumpExceptionFromMachException and DumpExceptionFromSignal after + // acquiring the cached_writer_. + void (*exception_callback_for_testing_)() = nullptr; // Used to synchronize access to UpdatePruneAndUploadThreads(). base::Lock prune_and_upload_lock_; @@ -284,3 +306,5 @@ class InProcessHandler { } // namespace internal } // namespace crashpad + +#endif // CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_IN_PROCESS_HANDLER_H_ diff --git a/client/ios_handler/in_process_handler_test.cc b/client/ios_handler/in_process_handler_test.cc index 7d69d34f98..43c9db68b1 100644 --- a/client/ios_handler/in_process_handler_test.cc +++ b/client/ios_handler/in_process_handler_test.cc @@ -110,35 +110,35 @@ TEST_F(InProcessHandlerTest, TestPendingFileLimit) { // Only process other app files. CreateFiles(0, 20); - handler().ProcessIntermediateDumps({}); + handler().ProcessIntermediateDumps({}, nullptr); VerifyRemainingFileCount(0, 0); ClearFiles(); // Only process our app files. CreateFiles(20, 20); - handler().ProcessIntermediateDumps({}); + handler().ProcessIntermediateDumps({}, nullptr); VerifyRemainingFileCount(0, 20); ClearFiles(); // Process all of our files and 10 remaining. CreateFiles(10, 30); - handler().ProcessIntermediateDumps({}); + handler().ProcessIntermediateDumps({}, nullptr); VerifyRemainingFileCount(0, 20); ClearFiles(); // Process 20 our files, leaving 10 remaining, and all other files remaining. CreateFiles(30, 10); - handler().ProcessIntermediateDumps({}); + handler().ProcessIntermediateDumps({}, nullptr); VerifyRemainingFileCount(10, 10); ClearFiles(); CreateFiles(0, 0); - handler().ProcessIntermediateDumps({}); + handler().ProcessIntermediateDumps({}, nullptr); VerifyRemainingFileCount(0, 0); ClearFiles(); CreateFiles(10, 0); - handler().ProcessIntermediateDumps({}); + handler().ProcessIntermediateDumps({}, nullptr); VerifyRemainingFileCount(0, 0); ClearFiles(); } diff --git a/client/ios_handler/in_process_intermediate_dump_handler.cc b/client/ios_handler/in_process_intermediate_dump_handler.cc index 5e6c42df61..6900d4ed4e 100644 --- a/client/ios_handler/in_process_intermediate_dump_handler.cc +++ b/client/ios_handler/in_process_intermediate_dump_handler.cc @@ -52,6 +52,7 @@ using thread_state_type = arm_thread_state64_t; // From snapshot/mac/process_types/crashreporterclient.proctype struct crashreporter_annotations_t { + // Version 4 uint64_t version; uint64_t message; uint64_t signature_string; @@ -59,7 +60,23 @@ struct crashreporter_annotations_t { uint64_t message2; uint64_t thread; uint64_t dialog_mode; + + // Version 5 uint64_t abort_cause; + + // The structure here is only defined through version 5, although version 7 is + // also known, and contains an additional 264 bytes from this point. Version 7 + // was first used in iOS 26. The version 7 extension is not defined here + // because it would cause the reader to attempt to read the full length of a + // version 7 structure, even in cases where only a version 5 structure is + // present at runtime, as it will be in iOS versions before 26. + // + // If it ever becomes necessary to read fields beyond version 5 (or whatever + // the minimum structure version that would be encountered at runtime is at + // that time in the future), the reader will need to take additional care to + // consult the `version` field and only read as much of the structure as is + // appropriate for the indicated version. The macOS implementation implements + // this logic. }; //! \brief Manage memory and ports after calling `task_threads`. @@ -91,7 +108,7 @@ class ScopedTaskThreads { void WriteError(IntermediateDumpKey key) { CRASHPAD_RAW_LOG("Unable to write key"); switch (key) { -// clang-format off + // clang-format off #define CASE_KEY(Name, Value) \ case IntermediateDumpKey::Name: \ CRASHPAD_RAW_LOG(#Name); \ @@ -493,9 +510,14 @@ void WriteCrashpadSimpleAnnotationsDictionary(IOSIntermediateDumpWriter* writer, void WriteAppleCrashReporterAnnotations( IOSIntermediateDumpWriter* writer, crashreporter_annotations_t* crash_info) { - // This number was totally made up out of nowhere, but it seems prudent to - // enforce some limit. - constexpr size_t kMaxMessageSize = 1024; + // It seems prudent to enforce some limit. Different users of + // CRSetCrashLogMessage and CRSetCrashLogMessage2, apparently the private + // functions used to set message and message2, use + // different buffer lengths. dyld-1231.3 libdyld/dyld_process_info.cpp has + // `static char sCrashReporterInfo[4096]`, which seems like a reasonable + // limit. + constexpr size_t kMaxMessageSize = 4096; + IOSIntermediateDumpWriter::ScopedMap annotation_map( writer, IntermediateDumpKey::kAnnotationsCrashInfo); if (crash_info->message) { @@ -1096,7 +1118,9 @@ void InProcessIntermediateDumpHandler::WriteModuleInfoAtAddress( WriteProperty( writer, IntermediateDumpKey::kSize, &segment_vm_read_ptr->vmsize); slide = address - segment_vm_read_ptr->vmaddr; - } else if (strcmp(segment_vm_read_ptr->segname, SEG_DATA) == 0) { + } else if (strcmp(segment_vm_read_ptr->segname, SEG_DATA) == 0 || + // dyld puts __crash_info in __DATA_DIRTY. + strcmp(segment_vm_read_ptr->segname, "__DATA_DIRTY") == 0) { WriteDataSegmentAnnotations(writer, segment_vm_read_ptr, slide); } } else if (command_vm_read_ptr->cmd == LC_ID_DYLIB) { @@ -1137,21 +1161,29 @@ void InProcessIntermediateDumpHandler::WriteDataSegmentAnnotations( for (uint32_t sect_index = 0; sect_index <= segment_vm_read_ptr->nsects; ++sect_index) { if (strcmp(section_vm_read_ptr->sectname, "crashpad_info") == 0) { - ScopedVMRead crashpad_info; - if (crashpad_info.Read(section_vm_read_ptr->addr + slide) && - crashpad_info->size() == sizeof(CrashpadInfo) && - crashpad_info->signature() == CrashpadInfo::kSignature && - crashpad_info->version() == 1) { - WriteCrashpadAnnotationsList(writer, crashpad_info.get()); - WriteCrashpadSimpleAnnotationsDictionary(writer, crashpad_info.get()); + if (section_vm_read_ptr->size >= sizeof(CrashpadInfo)) { + ScopedVMRead crashpad_info; + if (crashpad_info.Read(section_vm_read_ptr->addr + slide) && + crashpad_info->size() <= section_vm_read_ptr->size && + crashpad_info->size() >= sizeof(CrashpadInfo) && + crashpad_info->signature() == CrashpadInfo::kSignature && + crashpad_info->version() == 1) { + WriteCrashpadAnnotationsList(writer, crashpad_info.get()); + WriteCrashpadSimpleAnnotationsDictionary(writer, crashpad_info.get()); + WriteCrashpadExtraMemoryRanges(writer, crashpad_info.get()); + WriteCrashpadIntermediateDumpExtraMemoryRanges(writer, + crashpad_info.get()); + } } } else if (strcmp(section_vm_read_ptr->sectname, "__crash_info") == 0) { - ScopedVMRead crash_info; - if (!crash_info.Read(section_vm_read_ptr->addr + slide) || - (crash_info->version != 4 && crash_info->version != 5)) { - continue; + if (section_vm_read_ptr->size >= sizeof(crashreporter_annotations_t)) { + ScopedVMRead crash_info; + if (crash_info.Read(section_vm_read_ptr->addr + slide) && + (crash_info->version == 4 || crash_info->version == 5 || + crash_info->version == 7)) { + WriteAppleCrashReporterAnnotations(writer, crash_info.get()); + } } - WriteAppleCrashReporterAnnotations(writer, crash_info.get()); } section_vm_read_ptr = reinterpret_cast( reinterpret_cast(section_vm_read_ptr) + sizeof(section_64)); @@ -1257,5 +1289,72 @@ void InProcessIntermediateDumpHandler::WriteCrashpadAnnotationsList( } } +void InProcessIntermediateDumpHandler::WriteCrashpadExtraMemoryRanges( + IOSIntermediateDumpWriter* writer, + CrashpadInfo* crashpad_info) { + if (!crashpad_info->extra_memory_ranges()) { + return; + } + + ScopedVMRead extra_memory_ranges; + if (!extra_memory_ranges.Read(crashpad_info->extra_memory_ranges())) { + CRASHPAD_RAW_LOG("Unable to read extra memory ranges object"); + return; + } + + IOSIntermediateDumpWriter::ScopedArray module_extra_memory_regions_array( + writer, IntermediateDumpKey::kModuleExtraMemoryRegions); + + SimpleAddressRangeBag::Iterator iterator(*(extra_memory_ranges.get())); + while (const SimpleAddressRangeBag::Entry* entry = iterator.Next()) { + const uint64_t& address = entry->base; + const uint64_t& size = entry->size; + IOSIntermediateDumpWriter::ScopedArrayMap memory_region_map(writer); + WriteProperty( + writer, IntermediateDumpKey::kModuleExtraMemoryRegionAddress, &address); + WritePropertyBytes(writer, + IntermediateDumpKey::kModuleExtraMemoryRegionData, + reinterpret_cast(address), + size); + } +} + +void InProcessIntermediateDumpHandler:: + WriteCrashpadIntermediateDumpExtraMemoryRanges( + IOSIntermediateDumpWriter* writer, + CrashpadInfo* crashpad_info) { + if (!crashpad_info->intermediate_dump_extra_memory_ranges()) { + return; + } + + ScopedVMRead intermediate_dump_extra_memory; + if (!intermediate_dump_extra_memory.Read( + crashpad_info->intermediate_dump_extra_memory_ranges())) { + CRASHPAD_RAW_LOG( + "Unable to read intermediate dump extra memory ranges object"); + return; + } + + IOSIntermediateDumpWriter::ScopedArray module_extra_memory_regions_array( + writer, IntermediateDumpKey::kModuleIntermediateDumpExtraMemoryRegions); + + SimpleAddressRangeBag::Iterator iterator( + *(intermediate_dump_extra_memory.get())); + while (const SimpleAddressRangeBag::Entry* entry = iterator.Next()) { + const uint64_t& address = entry->base; + const uint64_t& size = entry->size; + IOSIntermediateDumpWriter::ScopedArrayMap memory_region_map(writer); + WriteProperty( + writer, + IntermediateDumpKey::kModuleIntermediateDumpExtraMemoryRegionAddress, + &address); + WritePropertyBytes( + writer, + IntermediateDumpKey::kModuleIntermediateDumpExtraMemoryRegionData, + reinterpret_cast(address), + size); + } +} + } // namespace internal } // namespace crashpad diff --git a/client/ios_handler/in_process_intermediate_dump_handler.h b/client/ios_handler/in_process_intermediate_dump_handler.h index 1cf9180c74..0fa43b6ff0 100644 --- a/client/ios_handler/in_process_intermediate_dump_handler.h +++ b/client/ios_handler/in_process_intermediate_dump_handler.h @@ -17,7 +17,6 @@ #include #include -#include #include #include @@ -57,8 +56,8 @@ class InProcessIntermediateDumpHandler final { //! //! \param[in] writer The dump writer //! \param[in] system_data An object containing various system data points. - //! \param[in] report_time Report creation time in nanoseconds as returned by - //! ClockMonotonicNanoseconds(). + //! \param[in] report_time_nanos Report creation time in nanoseconds as + //! returned by ClockMonotonicNanoseconds(). static void WriteSystemInfo(IOSIntermediateDumpWriter* writer, const IOSSystemDataCollector& system_data, uint64_t report_time_nanos); @@ -103,11 +102,10 @@ class InProcessIntermediateDumpHandler final { //! \brief Write an ExceptionSnapshot from a mach exception to the //! intermediate dump. //! - //! Only one of the WriteExceptionFromSignal, WriteExceptionFromMachException - //! and WriteExceptionFromNSException should be called per intermediate dump. + //! Only one of the WriteExceptionFromSignal, WriteExceptionFromMachException + //! and WriteExceptionFromNSException should be called per intermediate dump. //! //! \param[in] writer The dump writer - //! \param[in] system_data An object containing various system data points. //! \param[in] behavior //! \param[in] thread //! \param[in] exception @@ -153,6 +151,15 @@ class InProcessIntermediateDumpHandler final { //! \brief Write Crashpad annotations list. static void WriteCrashpadAnnotationsList(IOSIntermediateDumpWriter* writer, CrashpadInfo* crashpad_info); + + //! \brief Write Crashpad extra memory data. + static void WriteCrashpadExtraMemoryRanges(IOSIntermediateDumpWriter* writer, + CrashpadInfo* crashpad_info); + + //! \brief Write Crashpad intermediate dump extra memory data. + static void WriteCrashpadIntermediateDumpExtraMemoryRanges( + IOSIntermediateDumpWriter* writer, + CrashpadInfo* crashpad_info); }; } // namespace internal diff --git a/client/ios_handler/in_process_intermediate_dump_handler_test.cc b/client/ios_handler/in_process_intermediate_dump_handler_test.cc index 27179bb3af..44bc428aeb 100644 --- a/client/ios_handler/in_process_intermediate_dump_handler_test.cc +++ b/client/ios_handler/in_process_intermediate_dump_handler_test.cc @@ -25,7 +25,9 @@ #include "client/crashpad_info.h" #include "client/simple_string_dictionary.h" #include "gtest/gtest.h" +#include "minidump/minidump_file_writer.h" #include "snapshot/ios/process_snapshot_ios_intermediate_dump.h" +#include "snapshot/minidump/process_snapshot_minidump.h" #include "test/scoped_set_thread_name.h" #include "test/scoped_temp_dir.h" #include "test/test_paths.h" @@ -38,6 +40,16 @@ namespace { using internal::InProcessIntermediateDumpHandler; +class ReadToString : public crashpad::MemorySnapshot::Delegate { + public: + std::string result; + + bool MemorySnapshotDelegateRead(void* data, size_t size) override { + result = std::string(reinterpret_cast(data), size); + return true; + } +}; + class InProcessIntermediateDumpHandlerTest : public testing::Test { protected: // testing::Test: @@ -93,7 +105,14 @@ class InProcessIntermediateDumpHandlerTest : public testing::Test { // macOS 14.0 is 23A344, macOS 13.6.5 is 22G621, so if the first two // characters in the kern.osversion are > 22, this build will reproduce the // simulator bug in crbug.com/328282286 - bool IsMacOSVersion143OrGreaterAndiOS16OrLess() { + // This now reproduces on macOS 15.4 24E248 as well for iOS17 simulators. + bool HasMacOSBrokeDYLDTaskInfo() { + if (__builtin_available(iOS 18, *)) { + return false; + } + if (std::stoi(system_data_.Build().substr(0, 2)) >= 24) { + return true; + } if (__builtin_available(iOS 17, *)) { return false; } @@ -118,7 +137,6 @@ TEST_F(InProcessIntermediateDumpHandlerTest, TestSystem) { ASSERT_NE(system, nullptr); #if defined(ARCH_CPU_X86_64) EXPECT_EQ(system->GetCPUArchitecture(), kCPUArchitectureX86_64); - EXPECT_STREQ(system->CPUVendor().c_str(), "GenuineIntel"); #elif defined(ARCH_CPU_ARM64) EXPECT_EQ(system->GetCPUArchitecture(), kCPUArchitectureARM64); #else @@ -138,9 +156,10 @@ TEST_F(InProcessIntermediateDumpHandlerTest, TestSystem) { TEST_F(InProcessIntermediateDumpHandlerTest, TestAnnotations) { #if TARGET_OS_SIMULATOR - // This test will fail on older (=14.3 or + // =15.4 due to a bug in Simulator. + // crbug.com/328282286 + if (HasMacOSBrokeDYLDTaskInfo()) { // For TearDown. ASSERT_TRUE(LoggingRemoveFile(path())); return; @@ -231,6 +250,108 @@ TEST_F(InProcessIntermediateDumpHandlerTest, TestAnnotations) { } } +TEST_F(InProcessIntermediateDumpHandlerTest, TestExtraMemoryRanges) { +#if TARGET_OS_SIMULATOR + // This test will fail on =14.3 or + // =15.4 due to a bug in Simulator. + // crbug.com/328282286 + if (HasMacOSBrokeDYLDTaskInfo()) { + // For TearDown. + ASSERT_TRUE(LoggingRemoveFile(path())); + return; + } +#endif + + // Put the string on the heap so the memory doesn't coalesce with the stack. + std::unique_ptr someExtraMemoryString( + new std::string("extra memory range")); + crashpad::SimpleAddressRangeBag* ios_extra_ranges = + new crashpad::SimpleAddressRangeBag(); + crashpad::CrashpadInfo::GetCrashpadInfo()->set_extra_memory_ranges( + ios_extra_ranges); + ios_extra_ranges->Insert((void*)someExtraMemoryString->c_str(), 18); + WriteReportAndCloseWriter(); + crashpad::CrashpadInfo::GetCrashpadInfo()->set_extra_memory_ranges(nullptr); + internal::ProcessSnapshotIOSIntermediateDump process_snapshot; + ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {})); + ASSERT_EQ(process_snapshot.ExtraMemory().size(), 1LU); + auto memory = process_snapshot.ExtraMemory()[0]; + EXPECT_EQ(memory->Address(), + reinterpret_cast(someExtraMemoryString->c_str())); + EXPECT_EQ(memory->Size(), 18LU); + ReadToString delegate; + ASSERT_TRUE(memory->Read(&delegate)); + EXPECT_EQ(delegate.result, someExtraMemoryString->c_str()); + + StringFile string_file; + MinidumpFileWriter minidump_file_writer; + minidump_file_writer.InitializeFromSnapshot(&process_snapshot); + ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); + + ProcessSnapshotMinidump process_snapshot_minidump; + EXPECT_TRUE(process_snapshot_minidump.Initialize(&string_file)); + bool found; + for (auto minidump_memory : process_snapshot_minidump.ExtraMemory()) { + if (minidump_memory->Address() == + reinterpret_cast(someExtraMemoryString->c_str()) && + minidump_memory->Size() == 18LU) { + found = true; + break; + } + } + EXPECT_TRUE(found); +} + +TEST_F(InProcessIntermediateDumpHandlerTest, + TestIntermediateDumpExtraMemoryRanges) { +#if TARGET_OS_SIMULATOR + // This test will fail on =14.3 or + // =15.4 due to a bug in Simulator. + // crbug.com/328282286 + if (HasMacOSBrokeDYLDTaskInfo()) { + // For TearDown. + ASSERT_TRUE(LoggingRemoveFile(path())); + return; + } +#endif + + // Put the string on the heap so the memory doesn't coalesce with the stack. + std::unique_ptr someExtraMemoryString( + new std::string("extra memory range")); + crashpad::SimpleAddressRangeBag* ios_extra_ranges = + new crashpad::SimpleAddressRangeBag(); + crashpad::CrashpadInfo::GetCrashpadInfo() + ->set_intermediate_dump_extra_memory_ranges(ios_extra_ranges); + ios_extra_ranges->Insert((void*)someExtraMemoryString->c_str(), 18); + WriteReportAndCloseWriter(); + crashpad::CrashpadInfo::GetCrashpadInfo() + ->set_intermediate_dump_extra_memory_ranges(nullptr); + internal::ProcessSnapshotIOSIntermediateDump process_snapshot; + ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {})); + ASSERT_EQ(process_snapshot.IntermediateDumpExtraMemory().size(), 1LU); + auto memory = process_snapshot.IntermediateDumpExtraMemory()[0]; + EXPECT_EQ(memory->Address(), + reinterpret_cast(someExtraMemoryString->c_str())); + EXPECT_EQ(memory->Size(), 18LU); + ReadToString delegate; + ASSERT_TRUE(memory->Read(&delegate)); + EXPECT_EQ(delegate.result, someExtraMemoryString->c_str()); + + StringFile string_file; + MinidumpFileWriter minidump_file_writer; + minidump_file_writer.InitializeFromSnapshot(&process_snapshot); + ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); + + ProcessSnapshotMinidump process_snapshot_minidump; + EXPECT_TRUE(process_snapshot_minidump.Initialize(&string_file)); + for (auto minidump_memory : process_snapshot_minidump.ExtraMemory()) { + EXPECT_FALSE( + minidump_memory->Address() == + reinterpret_cast(someExtraMemoryString->c_str()) && + minidump_memory->Size() == 18LU); + } +} + TEST_F(InProcessIntermediateDumpHandlerTest, TestThreads) { const ScopedSetThreadName scoped_set_thread_name("TestThreads"); diff --git a/client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h b/client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h index 613b398d08..670ee00fb7 100644 --- a/client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h +++ b/client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h @@ -31,8 +31,8 @@ class PruneCondition; //! dumps. //! //! After the thread is started, the database is pruned using the condition -//! every 24 hours. Upon calling Start(), the thread waits 5 seconds before -//! performing the initial prune operation. +//! every 24 hours. Upon calling Start(), the thread waits before performing +//! the initial prune operation. //! //! Locked intermediate dump files are unlocked only once, not periodically. //! Locked dumps that match this bundle id can be unlocked if they are over a @@ -48,8 +48,11 @@ class PruneIntermediateDumpsAndCrashReportsThread //! pruning. //! \param[in] pending_path The path to any locked intermediate dump files. //! \param[in] bundle_identifier_and_seperator The identifier for this client, - //! used to determine when locked files are considered stale, with a - //! seperator at the end to allow for substring searches. + //! used to determine when locked files are considered stale, with a + //! seperator at the end to allow for substring searches. + //! \param[in] is_extension Whether the process is an app extension. If + //! `true`, the inital prune will occur after a 5-second delay. If + //! `false`, the initial prune will occur after a 60-second delay. PruneIntermediateDumpsAndCrashReportsThread( CrashReportDatabase* database, std::unique_ptr condition, diff --git a/client/prune_crash_reports.cc b/client/prune_crash_reports.cc index 6e4c7b9c65..ae71aee249 100644 --- a/client/prune_crash_reports.cc +++ b/client/prune_crash_reports.cc @@ -122,10 +122,8 @@ bool BinaryPruneCondition::ShouldPruneReport( return lhs_->ShouldPruneReport(report) && rhs_->ShouldPruneReport(report); case OR: return lhs_->ShouldPruneReport(report) || rhs_->ShouldPruneReport(report); - default: - NOTREACHED_IN_MIGRATION(); - return false; } + NOTREACHED(); } } // namespace crashpad diff --git a/client/ring_buffer_annotation_load_test_main.cc b/client/ring_buffer_annotation_load_test_main.cc index 2a767be584..50ca9eb18d 100644 --- a/client/ring_buffer_annotation_load_test_main.cc +++ b/client/ring_buffer_annotation_load_test_main.cc @@ -26,10 +26,10 @@ #include #include #include +#include #include "base/notreached.h" #include "base/strings/string_number_conversions.h" -#include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "client/annotation.h" @@ -313,8 +313,8 @@ class RingBufferAnnotationSnapshot final { std::vector bytes; while (ring_buffer_reader.Pop(bytes)) { int next_value; - base::StringPiece str(reinterpret_cast(&bytes[0]), - bytes.size()); + std::string_view str(reinterpret_cast(&bytes[0]), + bytes.size()); if (!base::HexStringToInt(str, &next_value)) { fprintf(stderr, "Couldn't parse value: [%.*s]\n", diff --git a/client/settings.cc b/client/settings.cc index 420fbfc958..38da08b8b5 100644 --- a/client/settings.cc +++ b/client/settings.cc @@ -15,6 +15,7 @@ #include "client/settings.h" #include +#include #include @@ -115,8 +116,12 @@ void ScopedLockedFileHandleTraits::Free(FileHandle handle) { #endif // BUILDFLAG(IS_FUCHSIA) -struct Settings::Data { +struct SettingsReader::Data { static constexpr uint32_t kSettingsMagic = 'CPds'; + + // Version number only used for incompatible changes to Data. Do not change + // this when adding additional fields at the end. Modifying `kSettingsVersion` + // will wipe away the entire struct when reading from other versions. static constexpr uint32_t kSettingsVersion = 1; enum Options : uint32_t { @@ -138,47 +143,117 @@ struct Settings::Data { UUID client_id; }; -Settings::Settings() = default; +SettingsReader::SettingsReader(const base::FilePath& file_path) + : SettingsReader(file_path, InitializationState::kStateValid) {} -Settings::~Settings() = default; +SettingsReader::SettingsReader(const base::FilePath& file_path, + InitializationState::State state) + : file_path_(file_path), initialized_(state) {} + +SettingsReader::~SettingsReader() = default; -bool Settings::Initialize(const base::FilePath& file_path) { - DCHECK(initialized_.is_uninitialized()); - initialized_.set_invalid(); - file_path_ = file_path; +bool SettingsReader::GetClientID(UUID* client_id) { + DCHECK(initialized().is_valid()); Data settings; - if (!OpenForWritingAndReadSettings(&settings).is_valid()) + if (!OpenAndReadSettings(&settings)) return false; - initialized_.set_valid(); + *client_id = settings.client_id; return true; } -bool Settings::GetClientID(UUID* client_id) { - DCHECK(initialized_.is_valid()); +bool SettingsReader::GetUploadsEnabled(bool* enabled) { + DCHECK(initialized().is_valid()); Data settings; if (!OpenAndReadSettings(&settings)) return false; - *client_id = settings.client_id; + *enabled = (settings.options & Data::Options::kUploadsEnabled) != 0; return true; } -bool Settings::GetUploadsEnabled(bool* enabled) { - DCHECK(initialized_.is_valid()); +bool SettingsReader::GetLastUploadAttemptTime(time_t* time) { + DCHECK(initialized().is_valid()); Data settings; if (!OpenAndReadSettings(&settings)) return false; - *enabled = (settings.options & Data::Options::kUploadsEnabled) != 0; + *time = InRangeCast(settings.last_upload_attempt_time, + std::numeric_limits::max()); + return true; +} + +bool SettingsReader::OpenAndReadSettings(Data* out_data) { + ScopedFileHandle handle(LoggingOpenFileForRead(file_path_)); + return handle.is_valid() && ReadSettings(handle.get(), out_data, true); +} + +bool SettingsReader::ReadSettings(FileHandle handle, + Data* out_data, + bool log_read_error) { + if (LoggingSeekFile(handle, 0, SEEK_SET) != 0) { + return false; + } + + // This clears `out_data` so that any bytes not read from disk are zero + // initialized. This is expected when reading from an older settings file with + // fewer fields. + memset(out_data, 0, sizeof(*out_data)); + + const FileOperationResult read_result = + log_read_error ? LoggingReadFileUntil(handle, out_data, sizeof(*out_data)) + : ReadFileUntil(handle, out_data, sizeof(*out_data)); + + if (read_result <= 0) { + return false; + } + + // Newer versions of crashpad may add fields to Data, but all versions have + // the data members up to `client_id`. Do not attempt to understand a smaller + // struct read. + const size_t min_size = + offsetof(Data, client_id) + sizeof(out_data->client_id); + if (static_cast(read_result) < min_size) { + LOG(ERROR) << "Settings file too small: minimum " << min_size + << ", observed " << read_result; + return false; + } + + if (out_data->magic != Data::kSettingsMagic) { + LOG(ERROR) << "Settings magic is not " << Data::kSettingsMagic; + return false; + } + + if (out_data->version != Data::kSettingsVersion) { + LOG(ERROR) << "Settings version is not " << Data::kSettingsVersion; + return false; + } + + return true; +} + +Settings::Settings(const base::FilePath& path) + : SettingsReader(path, InitializationState::kStateUninitialized) {} + +Settings::~Settings() = default; + +bool Settings::Initialize() { + DCHECK(initialized().is_uninitialized()); + initialized().set_invalid(); + + Data settings; + if (!OpenForWritingAndReadSettings(&settings).is_valid()) + return false; + + initialized().set_valid(); return true; } bool Settings::SetUploadsEnabled(bool enabled) { - DCHECK(initialized_.is_valid()); + DCHECK(initialized().is_valid()); Data settings; ScopedLockedFileHandle handle = OpenForWritingAndReadSettings(&settings); @@ -193,20 +268,8 @@ bool Settings::SetUploadsEnabled(bool enabled) { return WriteSettings(handle.get(), settings); } -bool Settings::GetLastUploadAttemptTime(time_t* time) { - DCHECK(initialized_.is_valid()); - - Data settings; - if (!OpenAndReadSettings(&settings)) - return false; - - *time = InRangeCast(settings.last_upload_attempt_time, - std::numeric_limits::max()); - return true; -} - bool Settings::SetLastUploadAttemptTime(time_t time) { - DCHECK(initialized_.is_valid()); + DCHECK(initialized().is_valid()); Data settings; ScopedLockedFileHandle handle = OpenForWritingAndReadSettings(&settings); @@ -292,11 +355,10 @@ FileHandle Settings::GetHandleFromOptions( return OpenFileForReadAndWrite( file_path, options.mode, options.permissions); } - NOTREACHED_IN_MIGRATION(); - return kInvalidFileHandle; + NOTREACHED(); } -Settings::ScopedLockedFileHandle Settings::OpenForReading() { +Settings::ScopedLockedFileHandle Settings::OpenForReading() const { internal::MakeScopedLockedFileHandleOptions options; options.function_enum = internal::FileOpenFunction::kLoggingOpenFileForRead; return MakeScopedLockedFileHandle(options, FileLocking::kShared, file_path()); @@ -322,6 +384,9 @@ Settings::ScopedLockedFileHandle Settings::OpenForReadingAndWriting( } bool Settings::OpenAndReadSettings(Data* out_data) { + // Because this implementation distinguishes between a failure to open and a + // failure to read the settings file, it cannot simply invoke + // SettingsReader::OpenAndReadSettings. ScopedLockedFileHandle handle = OpenForReading(); if (!handle.is_valid()) return false; @@ -340,7 +405,7 @@ Settings::ScopedLockedFileHandle Settings::OpenForWritingAndReadSettings( Data* out_data) { ScopedLockedFileHandle handle; bool created = false; - if (!initialized_.is_valid()) { + if (!initialized().is_valid()) { // If this object is initializing, it hasn’t seen a settings file already, // so go easy on errors. Creating a new settings file for the first time // shouldn’t spew log messages. @@ -387,33 +452,6 @@ Settings::ScopedLockedFileHandle Settings::OpenForWritingAndReadSettings( return handle; } -bool Settings::ReadSettings(FileHandle handle, - Data* out_data, - bool log_read_error) { - if (LoggingSeekFile(handle, 0, SEEK_SET) != 0) - return false; - - bool read_result = - log_read_error - ? LoggingReadFileExactly(handle, out_data, sizeof(*out_data)) - : ReadFileExactly(handle, out_data, sizeof(*out_data)); - - if (!read_result) - return false; - - if (out_data->magic != Data::kSettingsMagic) { - LOG(ERROR) << "Settings magic is not " << Data::kSettingsMagic; - return false; - } - - if (out_data->version != Data::kSettingsVersion) { - LOG(ERROR) << "Settings version is not " << Data::kSettingsVersion; - return false; - } - - return true; -} - bool Settings::WriteSettings(FileHandle handle, const Data& data) { if (LoggingSeekFile(handle, 0, SEEK_SET) != 0) return false; diff --git a/client/settings.h b/client/settings.h index 39b2de869d..7da179de2e 100644 --- a/client/settings.h +++ b/client/settings.h @@ -59,38 +59,26 @@ constexpr double kUploadReportTimeoutSeconds = 60; } // namespace internal -//! \brief An interface for accessing and modifying the settings of a -//! CrashReportDatabase. +//! \brief A class for accessing the settings of a CrashReportDatabase. +//! +//! SettingsReader does not participate in file locking. //! //! This class must not be instantiated directly, but rather an instance of it -//! should be retrieved via CrashReportDatabase::GetSettings(). -class Settings { +//! should be retrieved via +//! CrashReportDatabase::GetSettingsReaderForDatabasePath. +class SettingsReader { public: - static inline constexpr char kLockfileExtension[] = ".__lock__"; - - Settings(); + explicit SettingsReader(const base::FilePath& path); - Settings(const Settings&) = delete; - Settings& operator=(const Settings&) = delete; + SettingsReader(const SettingsReader&) = delete; + SettingsReader& operator=(const SettingsReader&) = delete; - ~Settings(); - - //! \brief Initializes the settings data store. - //! - //! This method must be called only once, and must be successfully called - //! before any other method in this class may be called. - //! - //! \param[in] path The location to store the settings data. - //! \return `true` if the data store was initialized successfully, otherwise - //! `false` with an error logged. - bool Initialize(const base::FilePath& path); + virtual ~SettingsReader(); //! \brief Retrieves the immutable identifier for this client, which is used //! on a server to locate all crash reports from a specific Crashpad //! database. //! - //! This is automatically initialized when the database is created. - //! //! \param[out] client_id The unique client identifier. //! //! \return On success, returns `true`, otherwise returns `false` with an @@ -112,15 +100,6 @@ class Settings { //! error logged. bool GetUploadsEnabled(bool* enabled); - //! \brief Sets the user’s preference for submitting crash reports to a - //! collection server. - //! - //! \param[in] enabled Whether crash reports should be uploaded. - //! - //! \return On success, returns `true`, otherwise returns `false` with an - //! error logged. - bool SetUploadsEnabled(bool enabled); - //! \brief Retrieves the last time at which a report was attempted to be //! uploaded. //! @@ -132,6 +111,67 @@ class Settings { //! error logged. bool GetLastUploadAttemptTime(time_t* time); + protected: + struct Data; + + SettingsReader(const base::FilePath& path, InitializationState::State state); + + // Opens the settings file and reads the data. If that fails, an error will + // be logged and the function will return false. + virtual bool OpenAndReadSettings(Data* out_data); + + // Reads the settings from |handle|. Logs an error and returns false on + // failure. + // + // If |log_read_error| is false, nothing will be logged for a read error, but + // this method will still return false. This is intended to be used to + // suppress error messages when attempting to read a newly created settings + // file. + bool ReadSettings(FileHandle handle, Data* out_data, bool log_read_error); + + const base::FilePath& file_path() const { return file_path_; } + + InitializationState& initialized() { return initialized_; } + + private: + const base::FilePath file_path_; + InitializationState initialized_; +}; + +//! \brief An interface for accessing and modifying the settings of a +//! CrashReportDatabase. +//! +//! This class must not be instantiated directly, but rather an instance of it +//! should be retrieved via CrashReportDatabase::GetSettings(). +//! +//! Settings will lock files prior to reading or writing them, and respect +//! existing locks. +class Settings final : public SettingsReader { + public: + static inline constexpr char kLockfileExtension[] = ".__lock__"; + + explicit Settings(const base::FilePath& file_path); + + ~Settings(); + + //! \brief Initializes the settings data store. + //! + //! This method must be called only once, and must be successfully called + //! before any other method in this class may be called. + //! + //! \return `true` if the data store was initialized successfully, otherwise + //! `false` with an error logged. + bool Initialize(); + + //! \brief Sets the user’s preference for submitting crash reports to a + //! collection server. + //! + //! \param[in] enabled Whether crash reports should be uploaded. + //! + //! \return On success, returns `true`, otherwise returns `false` with an + //! error logged. + bool SetUploadsEnabled(bool enabled); + //! \brief Sets the last time at which a report was attempted to be uploaded. //! //! This is only meant to be used internally by the CrashReportDatabase. @@ -157,8 +197,6 @@ class Settings { #endif // !CRASHPAD_FLOCK_ALWAYS_SUPPORTED private: - struct Data; - // This must be constructed with MakeScopedLockedFileHandle(). It both unlocks // and closes the file on destruction. Note that on Fuchsia, this handle DOES // NOT offer correct operation, only an attempt to DCHECK if racy behavior is @@ -227,7 +265,7 @@ class Settings { // Opens the settings file for reading. On error, logs a message and returns // the invalid handle. - ScopedLockedFileHandle OpenForReading(); + ScopedLockedFileHandle OpenForReading() const; // Opens the settings file for reading and writing. On error, logs a message // and returns the invalid handle. |mode| determines how the file will be @@ -243,25 +281,13 @@ class Settings { // Opens the settings file and reads the data. If that fails, an error will // be logged and the settings will be recovered and re-initialized. If that // also fails, returns false with additional log data from recovery. - bool OpenAndReadSettings(Data* out_data); + bool OpenAndReadSettings(Data* out_data) override; // Opens the settings file for writing and reads the data. If reading fails, // recovery is attempted. Returns the opened file handle on success, or the // invalid file handle on failure, with an error logged. ScopedLockedFileHandle OpenForWritingAndReadSettings(Data* out_data); - // Reads the settings from |handle|. Logs an error and returns false on - // failure. This does not perform recovery. - // - // |handle| must be the result of OpenForReading() or - // OpenForReadingAndWriting(). - // - // If |log_read_error| is false, nothing will be logged for a read error, but - // this method will still return false. This is intended to be used to - // suppress error messages when attempting to read a newly created settings - // file. - bool ReadSettings(FileHandle handle, Data* out_data, bool log_read_error); - // Writes the settings to |handle|. Logs an error and returns false on // failure. This does not perform recovery. // @@ -281,12 +307,6 @@ class Settings { // // |handle| must be the result of OpenForReadingAndWriting(). bool InitializeSettings(FileHandle handle); - - const base::FilePath& file_path() const { return file_path_; } - - base::FilePath file_path_; - - InitializationState initialized_; }; } // namespace crashpad diff --git a/client/settings_test.cc b/client/settings_test.cc index c955116667..941031474e 100644 --- a/client/settings_test.cc +++ b/client/settings_test.cc @@ -26,14 +26,15 @@ namespace { class SettingsTest : public testing::Test { public: - SettingsTest() = default; + SettingsTest() + : temp_dir_(), + settings_path_(temp_dir_.path().Append(FILE_PATH_LITERAL("settings"))), + settings_(settings_path_) {} SettingsTest(const SettingsTest&) = delete; SettingsTest& operator=(const SettingsTest&) = delete; - base::FilePath settings_path() { - return temp_dir_.path().Append(FILE_PATH_LITERAL("settings")); - } + base::FilePath settings_path() { return settings_path_; } Settings* settings() { return &settings_; } @@ -51,12 +52,11 @@ class SettingsTest : public testing::Test { protected: // testing::Test: - void SetUp() override { - ASSERT_TRUE(settings()->Initialize(settings_path())); - } + void SetUp() override { ASSERT_TRUE(settings()->Initialize()); } private: - ScopedTempDir temp_dir_; + const ScopedTempDir temp_dir_; + const base::FilePath settings_path_; Settings settings_; }; @@ -65,8 +65,8 @@ TEST_F(SettingsTest, ClientID) { EXPECT_TRUE(settings()->GetClientID(&client_id)); EXPECT_NE(client_id, UUID()); - Settings local_settings; - EXPECT_TRUE(local_settings.Initialize(settings_path())); + Settings local_settings(settings_path()); + EXPECT_TRUE(local_settings.Initialize()); UUID actual; EXPECT_TRUE(local_settings.GetClientID(&actual)); EXPECT_EQ(actual, client_id); @@ -82,8 +82,8 @@ TEST_F(SettingsTest, UploadsEnabled) { EXPECT_TRUE(settings()->GetUploadsEnabled(&enabled)); EXPECT_TRUE(enabled); - Settings local_settings; - EXPECT_TRUE(local_settings.Initialize(settings_path())); + Settings local_settings(settings_path()); + EXPECT_TRUE(local_settings.Initialize()); enabled = false; EXPECT_TRUE(local_settings.GetUploadsEnabled(&enabled)); EXPECT_TRUE(enabled); @@ -108,21 +108,43 @@ TEST_F(SettingsTest, LastUploadAttemptTime) { EXPECT_TRUE(settings()->GetLastUploadAttemptTime(&actual)); EXPECT_EQ(actual, expected); - Settings local_settings; - EXPECT_TRUE(local_settings.Initialize(settings_path())); + Settings local_settings(settings_path()); + EXPECT_TRUE(local_settings.Initialize()); actual = -1; EXPECT_TRUE(local_settings.GetLastUploadAttemptTime(&actual)); EXPECT_EQ(actual, expected); } +TEST_F(SettingsTest, ReadOnly) { + { // A SettingsReader can read an existing setting... + SettingsReader reader(settings_path()); + bool enabled = true; + // Default value is false. + EXPECT_TRUE(reader.GetUploadsEnabled(&enabled)); + EXPECT_FALSE(enabled); + + settings()->SetUploadsEnabled(true); + EXPECT_TRUE(reader.GetUploadsEnabled(&enabled)); + EXPECT_TRUE(enabled); + } + + { // ...but not one that doesn't exist. + base::FilePath bad_path = + settings_path().DirName().Append(FILE_PATH_LITERAL("does_not_exist")); + SettingsReader reader(bad_path); + bool enabled = true; + EXPECT_FALSE(reader.GetUploadsEnabled(&enabled)); + } +} + // The following tests write a corrupt settings file and test the recovery // operation. TEST_F(SettingsTest, BadFileOnInitialize) { InitializeBadFile(); - Settings settings; - EXPECT_TRUE(settings.Initialize(settings_path())); + Settings settings(settings_path()); + EXPECT_TRUE(settings.Initialize()); } TEST_F(SettingsTest, BadFileOnGet) { @@ -132,8 +154,8 @@ TEST_F(SettingsTest, BadFileOnGet) { EXPECT_TRUE(settings()->GetClientID(&client_id)); EXPECT_NE(client_id, UUID()); - Settings local_settings; - EXPECT_TRUE(local_settings.Initialize(settings_path())); + Settings local_settings(settings_path()); + EXPECT_TRUE(local_settings.Initialize()); UUID actual; EXPECT_TRUE(local_settings.GetClientID(&actual)); EXPECT_EQ(actual, client_id); @@ -162,8 +184,8 @@ TEST_F(SettingsTest, UnlinkFile) { << ErrnoMessage("unlink"); #endif - Settings local_settings; - EXPECT_TRUE(local_settings.Initialize(settings_path())); + Settings local_settings(settings_path()); + EXPECT_TRUE(local_settings.Initialize()); UUID new_client_id; EXPECT_TRUE(local_settings.GetClientID(&new_client_id)); EXPECT_NE(new_client_id, client_id); diff --git a/client/simple_string_dictionary.h b/client/simple_string_dictionary.h index 441f0541d8..f4a2f8ffeb 100644 --- a/client/simple_string_dictionary.h +++ b/client/simple_string_dictionary.h @@ -19,10 +19,10 @@ #include #include +#include #include #include "base/check_op.h" -#include "base/strings/string_piece.h" #include "util/misc/implicit_cast.h" namespace crashpad { @@ -133,10 +133,10 @@ class TSimpleStringDictionary { //! //! \return The corresponding value for \a key, or if \a key is not found, //! `nullptr`. - const char* GetValueForKey(base::StringPiece key) const { + const char* GetValueForKey(std::string_view key) const { DCHECK(key.data()); DCHECK(key.size()); - DCHECK_EQ(key.find('\0', 0), base::StringPiece::npos); + DCHECK_EQ(key.find('\0', 0), std::string_view::npos); if (!key.data() || !key.size()) { return nullptr; } @@ -159,7 +159,7 @@ class TSimpleStringDictionary { //! string. It must not contain embedded `NUL`s. //! \param[in] value The value to store. If `nullptr`, \a key is removed from //! the map. Must not contain embedded `NUL`s. - void SetKeyValue(base::StringPiece key, base::StringPiece value) { + void SetKeyValue(std::string_view key, std::string_view value) { if (!value.data()) { RemoveKey(key); return; @@ -167,7 +167,7 @@ class TSimpleStringDictionary { DCHECK(key.data()); DCHECK(key.size()); - DCHECK_EQ(key.find('\0', 0), base::StringPiece::npos); + DCHECK_EQ(key.find('\0', 0), std::string_view::npos); if (!key.data() || !key.size()) { return; } @@ -179,7 +179,7 @@ class TSimpleStringDictionary { } // |value| must not contain embedded NULs. - DCHECK_EQ(value.find('\0', 0), base::StringPiece::npos); + DCHECK_EQ(value.find('\0', 0), std::string_view::npos); Entry* entry = GetEntryForKey(key); @@ -188,7 +188,7 @@ class TSimpleStringDictionary { for (size_t i = 0; i < num_entries; ++i) { if (!entries_[i].is_active()) { entry = &entries_[i]; - SetFromStringPiece(key, entry->key, key_size); + SetFromStringView(key, entry->key, key_size); break; } } @@ -210,7 +210,7 @@ class TSimpleStringDictionary { DCHECK_EQ(count, 1); #endif - SetFromStringPiece(value, entry->value, value_size); + SetFromStringView(value, entry->value, value_size); } //! \brief Removes \a key from the map. @@ -219,10 +219,10 @@ class TSimpleStringDictionary { //! //! \param[in] key The key of the entry to remove. This must not be `nullptr`, //! nor an empty string. It must not contain embedded `NUL`s. - void RemoveKey(base::StringPiece key) { + void RemoveKey(std::string_view key) { DCHECK(key.data()); DCHECK(key.size()); - DCHECK_EQ(key.find('\0', 0), base::StringPiece::npos); + DCHECK_EQ(key.find('\0', 0), std::string_view::npos); if (!key.data() || !key.size()) { return; } @@ -237,15 +237,15 @@ class TSimpleStringDictionary { } private: - static void SetFromStringPiece(base::StringPiece src, - char* dst, - size_t dst_size) { + static void SetFromStringView(std::string_view src, + char* dst, + size_t dst_size) { size_t copy_len = std::min(dst_size - 1, src.size()); src.copy(dst, copy_len); dst[copy_len] = '\0'; } - static bool EntryKeyEquals(base::StringPiece key, const Entry& entry) { + static bool EntryKeyEquals(std::string_view key, const Entry& entry) { if (key.size() >= KeySize) return false; @@ -258,7 +258,7 @@ class TSimpleStringDictionary { return strncmp(key.data(), entry.key, key.size()) == 0; } - const Entry* GetConstEntryForKey(base::StringPiece key) const { + const Entry* GetConstEntryForKey(std::string_view key) const { for (size_t i = 0; i < num_entries; ++i) { if (EntryKeyEquals(key, entries_[i])) { return &entries_[i]; @@ -267,7 +267,7 @@ class TSimpleStringDictionary { return nullptr; } - Entry* GetEntryForKey(base::StringPiece key) { + Entry* GetEntryForKey(std::string_view key) { return const_cast(GetConstEntryForKey(key)); } diff --git a/client/simple_string_dictionary_test.cc b/client/simple_string_dictionary_test.cc index 6ef94c9ace..c32594c34b 100644 --- a/client/simple_string_dictionary_test.cc +++ b/client/simple_string_dictionary_test.cc @@ -14,6 +14,8 @@ #include "client/simple_string_dictionary.h" +#include + #include "base/check_op.h" #include "gtest/gtest.h" #include "test/gtest_death.h" @@ -73,7 +75,7 @@ TEST(SimpleStringDictionary, SimpleStringDictionary) { EXPECT_FALSE(dict.GetValueForKey("key3")); // Remove by setting value to nullptr - dict.SetKeyValue("key2", base::StringPiece(nullptr, 0)); + dict.SetKeyValue("key2", std::string_view(nullptr, 0)); // Now make sure it's not there anymore EXPECT_FALSE(dict.GetValueForKey("key2")); @@ -258,14 +260,14 @@ TEST(SimpleStringDictionary, OutOfSpace) { TEST(SimpleStringDictionaryDeathTest, SetKeyValueWithNullKey) { TSimpleStringDictionary<4, 6, 6> map; - ASSERT_DEATH_CHECK(map.SetKeyValue(base::StringPiece(nullptr, 0), "hello"), + ASSERT_DEATH_CHECK(map.SetKeyValue(std::string_view(nullptr, 0), "hello"), "key"); } TEST(SimpleStringDictionaryDeathTest, GetValueForKeyWithNullKey) { TSimpleStringDictionary<4, 6, 6> map; map.SetKeyValue("hi", "there"); - ASSERT_DEATH_CHECK(map.GetValueForKey(base::StringPiece(nullptr, 0)), "key"); + ASSERT_DEATH_CHECK(map.GetValueForKey(std::string_view(nullptr, 0)), "key"); EXPECT_STREQ("there", map.GetValueForKey("hi")); } diff --git a/client/simulate_crash.h b/client/simulate_crash.h index 036e24f6a9..36477914fd 100644 --- a/client/simulate_crash.h +++ b/client/simulate_crash.h @@ -17,6 +17,7 @@ #include "build/build_config.h" +// IWYU pragma: begin_exports #if BUILDFLAG(IS_MAC) #include "client/simulate_crash_mac.h" #elif BUILDFLAG(IS_IOS) @@ -26,5 +27,6 @@ #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) #include "client/simulate_crash_linux.h" #endif +// IWYU pragma: end_exports #endif // CRASHPAD_CLIENT_SIMULATE_CRASH_H_ diff --git a/codereview.settings b/codereview.settings index ac1a735cfa..e31258ce82 100644 --- a/codereview.settings +++ b/codereview.settings @@ -16,4 +16,3 @@ GERRIT_HOST: True CODE_REVIEW_SERVER: https://chromium-review.googlesource.com/ VIEW_VC: https://chromium.googlesource.com/crashpad/crashpad/+/ PROJECT: crashpad -BUG_PREFIX: crashpad: diff --git a/doc/appengine/src/crashpad-home/main.go b/doc/appengine/src/crashpad-home/main.go index dced1e8001..e7995596d3 100644 --- a/doc/appengine/src/crashpad-home/main.go +++ b/doc/appengine/src/crashpad-home/main.go @@ -146,11 +146,12 @@ func handler(w http.ResponseWriter, r *http.Request) { // contentType returns the appropriate content type header for file. func contentType(file string) string { contentTypes := map[string]string{ - ".html": "text/html; charset=UTF-8", ".css": "text/css; charset=UTF-8", + ".html": "text/html; charset=UTF-8", + ".ico": "image/x-icon", ".js": "text/javascript; charset=UTF-8", ".png": "image/png", - ".ico": "image/x-icon", + ".svg": "image/svg+xml", } for suffix, typ := range contentTypes { if strings.HasSuffix(file, suffix) { diff --git a/doc/ios_overview_design.md b/doc/ios_overview_design.md index 9ed01b7b3a..3fca252833 100644 --- a/doc/ios_overview_design.md +++ b/doc/ios_overview_design.md @@ -175,3 +175,17 @@ pending reports, such as when ideal network conditions exist. By default, clients start with uploading disabled. Applications should call this API when it is determined that it is appropriate to do so (such as on a few seconds after startup, or when network connectivity is appropriate). + +## tvOS considerations + +tvOS, an operating system based on iOS, shares the constraints described above +and uses the same architecture. Additionally, tvOS does not allow the use of +the `mach_msg()` APIs so using Mach exceptions is impossible. + +Consequently, the Mach exception handler is _not_ installed once the in-process +handler is initialized; the tvOS handler only uses the POSIX signals and +Objective-C exception preprocessors. Furthermore, the POSIX signal handler is +unable to handle all crashes that the Mach exception handler can (e.g. stack +overflows, as `sigaltstack()` cannot be used either). In other words, the tvOS +in-process handler can only handle a subset of all exceptions that the iOS +in-process handler does. diff --git a/doc/support/crashpad.doxy b/doc/support/crashpad.doxy index ca1ea25178..ab96856083 100644 --- a/doc/support/crashpad.doxy +++ b/doc/support/crashpad.doxy @@ -1,7 +1,7 @@ -# Doxyfile 1.8.18 +# Doxyfile 1.13.2 # This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. +# Doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. @@ -12,6 +12,16 @@ # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use Doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use Doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options @@ -32,7 +42,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "Crashpad" +PROJECT_NAME = Crashpad # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -41,7 +51,7 @@ PROJECT_NAME = "Crashpad" PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a +# for a project that appears at the top of each page and should give viewers a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = @@ -53,24 +63,42 @@ PROJECT_BRIEF = PROJECT_LOGO = +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = + # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If +# entered, it will be relative to the location where Doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = out/doc/doxygen -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. +# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding Doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise cause +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. @@ -79,36 +107,28 @@ CREATE_SUBDIRS = NO ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this +# documentation generated by Doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English -# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all generated output in the proper direction. -# Possible values are: None, LTR, RTL and Context. -# The default value is: None. - -OUTPUT_TEXT_DIRECTION = None - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# If the BRIEF_MEMBER_DESC tag is set to YES, Doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# If the REPEAT_BRIEF tag is set to YES, Doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the @@ -129,13 +149,13 @@ REPEAT_BRIEF = YES ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief +# Doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# If the INLINE_INHERITED_MEMB tag is set to YES, Doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. @@ -143,7 +163,7 @@ ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# If the FULL_PATH_NAMES tag is set to YES, Doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. @@ -153,11 +173,11 @@ FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to +# If left blank the directory from which Doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. +# will be relative from the directory where Doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = @@ -173,41 +193,42 @@ STRIP_FROM_INC_PATH = . \ compat/non_mac \ compat/non_win -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't +# If the SHORT_NAMES tag is set to YES, Doxygen will generate much shorter (but +# less readable) file names. This can be useful if your file system doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the +# first line (until the first dot, question mark or exclamation mark) of a +# Javadoc-style comment as the brief description. If set to NO, the Javadoc- +# style will behave just like regular Qt-style comments (thus requiring an +# explicit @brief command for a brief description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO -# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be -# interpreted by doxygen. +# interpreted by Doxygen. # The default value is: NO. JAVADOC_BANNER = NO -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will interpret the first +# line (until the first dot, question mark or exclamation mark) of a Qt-style +# comment as the brief description. If set to NO, the Qt-style will behave just +# like regular Qt-style comments (thus requiring an explicit \brief command for +# a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this @@ -219,13 +240,21 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and Doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# Doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as Doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# If the SEPARATE_MEMBER_PAGES tag is set to YES then Doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. @@ -242,16 +271,16 @@ TAB_SIZE = 4 # the documentation. An alias has the form: # name=value # For example adding -# "sideeffect=@par Side Effects:\n" +# "sideeffect=@par Side Effects:^^" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. -# When you need a literal { or } or , in the value part of an alias you have to -# escape them by means of a backslash (\), this can lead to conflicts with the -# commands \{ and \} for these it is advised to use the version @{ and @} or use -# a double escape (\\{ and \\}) +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) ALIASES = @@ -295,27 +324,30 @@ OPTIMIZE_OUTPUT_SLICE = NO # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, -# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files). For instance to make doxygen treat .inc files +# default for Fortran type files). For instance to make Doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by Doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. EXTENSION_MAPPING = -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# The output of markdown processing is further processed by Doxygen, so you can +# mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. @@ -325,25 +357,45 @@ MARKDOWN_SUPPORT = YES # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 5. +# Minimum value: 0, maximum value: 99, default value: 6. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 0 -# When enabled doxygen tries to link words that correspond to documented +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled Doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. +# globally by setting AUTOLINK_SUPPORT to NO. Words listed in the +# AUTOLINK_IGNORE_WORDS tag are excluded from automatic linking. # The default value is: YES. AUTOLINK_SUPPORT = YES +# This tag specifies a list of words that, when matching the start of a word in +# the documentation, will suppress auto links generation, if it is enabled via +# AUTOLINK_SUPPORT. This list does not affect affect links explicitly created +# using \# or the \link or commands. +# This tag requires that the tag AUTOLINK_SUPPORT is set to YES. + +AUTOLINK_IGNORE_WORDS = + # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and +# tag to YES in order to let Doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. +# versus func(std::string) {}). This also makes the inheritance and +# collaboration diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO @@ -355,16 +407,16 @@ BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. +# https://www.riverbankcomputing.com/software) sources only. Doxygen will parse +# them like normal C++ but will assume all classes use public instead of private +# inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. +# Doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. @@ -373,7 +425,7 @@ SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first +# tag is set to YES then Doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. @@ -431,21 +483,42 @@ TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The +# code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# Doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest +# symbols. At the end of a run Doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use +# during processing. When set to 0 Doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# If the EXTRACT_ALL tag is set to YES, Doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. @@ -504,7 +577,14 @@ EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. @@ -512,22 +592,31 @@ EXTRACT_ANON_NSPACES = NO HIDE_UNDOC_MEMBERS = NO -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# If the HIDE_UNDOC_NAMESPACES tag is set to YES, Doxygen will hide all +# undocumented namespaces that are normally visible in the namespace hierarchy. +# If set to NO, these namespaces will be included in the various overviews. This +# option has no effect if EXTRACT_ALL is enabled. +# The default value is: YES. + +HIDE_UNDOC_NAMESPACES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. @@ -541,30 +630,44 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# (including Cygwin) ands Mac users are advised to set this option to NO. -# The default value is: system dependent. +# With the correct setting of option CASE_SENSE_NAMES Doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and macOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. CASE_SENSE_NAMES = YES -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. @@ -577,7 +680,7 @@ SHOW_INCLUDE_FILES = YES SHOW_GROUPED_MEMB_INC = NO -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. @@ -589,14 +692,14 @@ FORCE_LOCAL_INCLUDES = YES INLINE_INFO = YES -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# If the SORT_MEMBER_DOCS tag is set to YES then Doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. @@ -604,7 +707,7 @@ SORT_MEMBER_DOCS = YES SORT_BRIEF_DOCS = NO -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then Doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. @@ -616,7 +719,7 @@ SORT_BRIEF_DOCS = NO SORT_MEMBERS_CTORS_1ST = NO -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# If the SORT_GROUP_NAMES tag is set to YES then Doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. @@ -633,11 +736,11 @@ SORT_GROUP_NAMES = NO SORT_BY_SCOPE_NAME = NO -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. @@ -647,26 +750,26 @@ STRICT_PROTO_MATCHING = NO # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. -GENERATE_TODOLIST = YES +GENERATE_TODOLIST = NO # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. -GENERATE_TESTLIST = YES +GENERATE_TESTLIST = NO # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. -GENERATE_BUGLIST = YES +GENERATE_BUGLIST = NO # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. -GENERATE_DEPRECATEDLIST= YES +GENERATE_DEPRECATEDLIST= NO # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond @@ -707,24 +810,25 @@ SHOW_FILES = YES SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from +# Doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file +# by Doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated +# by Doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can +# that represents Doxygen's defaults, run Doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. # -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# Note that if you run Doxygen from a directory containing a file called +# DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = @@ -739,19 +843,35 @@ LAYOUT_FILE = CITE_BIB_FILES = +# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH +# environment variable) so that external tools such as latex and gs can be +# found. +# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the +# path already specified by the PATH variable, and are added in the order +# specified. +# Note: This option is particularly useful for macOS version 14 (Sonoma) and +# higher, when running Doxygen from Doxywizard, because in this case any user- +# defined changes to the PATH are ignored. A typical example on macOS is to set +# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin +# together with the standard path, the full search path used by doxygen when +# launching external tools will then become +# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + +EXTERNAL_TOOL_PATH = + #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the +# standard output by Doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. @@ -759,49 +879,97 @@ QUIET = YES WARNINGS = YES -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# If the WARN_IF_UNDOCUMENTED tag is set to YES then Doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = NO -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. +# If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES +# If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete +# function parameter documentation. If set to NO, Doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. If -# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# value. If set to NO, Doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = NO -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about +# undocumented enumeration values. If set to NO, Doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If WARN_LAYOUT_FILE option is set to YES, Doxygen will warn about issues found +# while parsing the user defined layout file, such as missing or wrong elements. +# See also LAYOUT_FILE for details. If set to NO, problems with the layout file +# will be suppressed. +# The default value is: YES. + +WARN_LAYOUT_FILE = YES + +# If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the Doxygen process Doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO -# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# The WARN_FORMAT tag determines the format of the warning messages that Doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of Doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard -# error (stderr). +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). WARN_LOGFILE = @@ -818,29 +986,42 @@ WARN_LOGFILE = INPUT = # This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: https://www.gnu.org/software/libiconv/) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 +# This tag can be used to specify the character encoding of the source files +# that Doxygen parses. The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). +# See also: INPUT_ENCODING for further information on supported encodings. + +INPUT_FILE_ENCODING = + # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. +# read by Doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. # -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), -# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen -# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -858,11 +1039,13 @@ RECURSIVE = YES # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # -# Note that relative paths are relative to the directory from which doxygen is +# Note that relative paths are relative to the directory from which Doxygen is # run. -EXCLUDE = compat/mac \ - compat/non_cxx11_lib \ +EXCLUDE = compat/android \ + compat/ios \ + compat/linux \ + compat/mac \ compat/win \ third_party @@ -886,10 +1069,7 @@ EXCLUDE_PATTERNS = out* # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* +# ANamespace::AClass, ANamespace::*Test EXCLUDE_SYMBOLS = @@ -904,7 +1084,7 @@ EXAMPLE_PATH = # *.h) to filter out the source-files in the directories. If left blank all # files are included. -EXAMPLE_PATTERNS = +EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands @@ -919,7 +1099,7 @@ EXAMPLE_RECURSIVE = NO IMAGE_PATH = -# The INPUT_FILTER tag can be used to specify a program that doxygen should +# The INPUT_FILTER tag can be used to specify a program that Doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # @@ -934,9 +1114,14 @@ IMAGE_PATH = # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # +# Note that Doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. +# properly processed by Doxygen. INPUT_FILTER = @@ -949,7 +1134,7 @@ INPUT_FILTER = # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. +# properly processed by Doxygen. FILTER_PATTERNS = @@ -971,10 +1156,28 @@ FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. +# and want to reuse the introduction page also for the Doxygen output. USE_MDFILE_AS_MAINPAGE = +# If the IMPLICIT_DIR_DOCS tag is set to YES, any README.md file found in sub- +# directories of the project's root, is used as the documentation for that sub- +# directory, except when the README.md starts with a \dir, \page or \mainpage +# command. If set to NO, the README.md file needs to start with an explicit \dir +# command in order to be used as directory documentation. +# The default value is: YES. + +IMPLICIT_DIR_DOCS = YES + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- @@ -989,12 +1192,13 @@ USE_MDFILE_AS_MAINPAGE = SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. +# multi-line macros, enums or list initialized variables directly into the +# documentation. # The default value is: NO. INLINE_SOURCES = NO -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. @@ -1032,7 +1236,7 @@ REFERENCES_LINK_SOURCE = YES SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# point to the HTML generated by the htags(1) tool instead of Doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. @@ -1046,14 +1250,14 @@ SOURCE_TOOLTIPS = YES # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # -# The result: instead of the source browser generated by doxygen, the links to +# The result: instead of the source browser generated by Doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# If the VERBATIM_HEADERS tag is set the YES then Doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. @@ -1072,17 +1276,11 @@ VERBATIM_HEADERS = NO ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = @@ -1091,7 +1289,7 @@ IGNORE_PREFIX = # Configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES @@ -1112,40 +1310,40 @@ HTML_OUTPUT = html HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a +# each generated HTML page. If the tag is left blank Doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. +# that Doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally +# for information on how to generate the default header that Doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description +# default header when upgrading to a newer version of Doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard +# generated HTML page. If the tag is left blank Doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. +# that Doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. +# the HTML output. If left blank Doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. +# sheet that Doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. @@ -1155,13 +1353,18 @@ HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. +# created by Doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = doc/support/crashpad_doxygen.css @@ -1176,9 +1379,22 @@ HTML_EXTRA_STYLESHEET = doc/support/crashpad_doxygen.css HTML_EXTRA_FILES = +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generates light mode output, DARK always +# generates dark mode output, AUTO_LIGHT automatically sets the mode according +# to the user preference, uses light mode if no preference is set (the default), +# AUTO_DARK automatically sets the mode according to the user preference, uses +# dark mode if no preference is set and TOGGLE allows a user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see +# this color. Hue is specified as an angle on a color-wheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. @@ -1188,7 +1404,7 @@ HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A +# in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1206,15 +1422,6 @@ HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will @@ -1234,6 +1441,33 @@ HTML_DYNAMIC_MENUS = YES HTML_DYNAMIC_SECTIONS = NO +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1249,10 +1483,11 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: https://developer.apple.com/xcode/), introduced with OSX -# 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, Doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. @@ -1269,6 +1504,13 @@ GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. @@ -1291,14 +1533,18 @@ DOCSET_PUBLISHER_ID = org.doxygen.Publisher DOCSET_PUBLISHER_NAME = Publisher -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline (the HTML help workshop was already many +# years in maintenance mode). You can download the HTML help workshop from the +# web archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for @@ -1318,14 +1564,14 @@ CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. +# Doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1352,6 +1598,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help @@ -1370,7 +1626,8 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1378,8 +1635,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1387,16 +1644,16 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = @@ -1408,9 +1665,9 @@ QHP_CUST_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = @@ -1441,10 +1698,10 @@ ECLIPSE_DOC_ID = org.doxygen.Project # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. +# The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. -DISABLE_INDEX = NO +DISABLE_INDEX = YES # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag @@ -1453,18 +1710,30 @@ DISABLE_INDEX = NO # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by Doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -GENERATE_TREEVIEW = NO +FULL_SIDEBAR = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. +# Doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. @@ -1473,6 +1742,12 @@ GENERATE_TREEVIEW = NO ENUM_VALUES_PER_LINE = 0 +# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified +# enumeration values besides the enumeration mnemonics. +# The default value is: NO. + +SHOW_ENUM_VALUES = NO + # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. @@ -1480,19 +1755,26 @@ ENUM_VALUES_PER_LINE = 0 TREEVIEW_WIDTH = 250 -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# If the EXT_LINKS_IN_WINDOW option is set to YES, Doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO -# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. -# Possible values are: png The default and svg Looks nicer but requires the -# pdf2svg tool. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). # The default value is: png. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1500,24 +1782,13 @@ HTML_FORMULA_FORMAT = png # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML +# Doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. @@ -1535,11 +1806,29 @@ FORMULA_MACROFILE = USE_MATHJAX = NO +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + # When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1552,33 +1841,40 @@ MATHJAX_FORMAT = HTML-CSS # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +MATHJAX_RELPATH = # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and +# When the SEARCHENGINE tag is enabled Doxygen will generate a search box for +# the HTML output. The underlying search engine uses JavaScript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then +# For large projects the JavaScript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically @@ -1597,7 +1893,7 @@ SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH -# setting. When disabled, doxygen will generate a PHP script for searching and +# setting. When disabled, Doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing # and searching needs to be provided by external tools. See the section # "External Indexing and Searching" for details. @@ -1606,7 +1902,7 @@ SEARCHENGINE = YES SERVER_BASED_SEARCH = NO -# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# When EXTERNAL_SEARCH tag is enabled Doxygen will no longer generate the PHP # script for searching. Instead the search results are written to an XML file # which needs to be processed by an external indexer. Doxygen will invoke an # external search engine pointed to by the SEARCHENGINE_URL option to obtain the @@ -1614,7 +1910,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1627,8 +1924,9 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. SEARCHENGINE_URL = @@ -1649,7 +1947,7 @@ SEARCHDATA_FILE = searchdata.xml EXTERNAL_SEARCH_ID = -# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through Doxygen # projects other than the one defined by this configuration file, but that are # all added to the same external search index. Each project needs to have a # unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of @@ -1663,7 +1961,7 @@ EXTRA_SEARCH_MAPPINGS = # Configuration options related to the LaTeX output #--------------------------------------------------------------------------- -# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# If the GENERATE_LATEX tag is set to YES, Doxygen will generate LaTeX output. # The default value is: YES. GENERATE_LATEX = NO @@ -1686,7 +1984,7 @@ LATEX_OUTPUT = latex # the output language. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_CMD_NAME = latex +LATEX_CMD_NAME = # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate # index for LaTeX. @@ -1708,7 +2006,7 @@ MAKEINDEX_CMD_NAME = makeindex LATEX_MAKEINDEX_CMD = makeindex -# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# If the COMPACT_LATEX tag is set to YES, Doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -1737,36 +2035,38 @@ PAPER_TYPE = a4 EXTRA_PACKAGES = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank Doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that Doxygen normally uses. # -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of Doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HEADER = -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank Doxygen will generate a standard footer. See # LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that Doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_FOOTER = # The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined # LaTeX style sheets that are included after the standard style sheets created -# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# by Doxygen. Using this option one can overrule certain style aspects. Doxygen # will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the @@ -1792,55 +2092,45 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, Doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. This option is also used -# when generating formulas in HTML. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BATCHMODE = NO -# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# If the LATEX_HIDE_INDICES tag is set to YES then Doxygen will not include the # index chapters (such as File Index, Compound Index, etc.) in the output. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HIDE_INDICES = NO -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See # https://en.wikipedia.org/wiki/BibTeX and \cite for more info. -# The default value is: plain. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_BIB_STYLE = plain - -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. +# The default value is: plainnat. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_TIMESTAMP = NO +LATEX_BIB_STYLE = plainnat # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, @@ -1854,7 +2144,7 @@ LATEX_EMOJI_DIRECTORY = # Configuration options related to the RTF output #--------------------------------------------------------------------------- -# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# If the GENERATE_RTF tag is set to YES, Doxygen will generate RTF output. The # RTF output is optimized for Word 97 and may not look too pretty with other RTF # readers/editors. # The default value is: NO. @@ -1869,7 +2159,7 @@ GENERATE_RTF = NO RTF_OUTPUT = rtf -# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# If the COMPACT_RTF tag is set to YES, Doxygen generates more compact RTF # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -1889,38 +2179,36 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's +# Load stylesheet definitions from file. Syntax is similar to Doxygen's # configuration file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. # # See also section "Doxygen usage" for information on how to generate the -# default style sheet that doxygen normally uses. +# default style sheet that Doxygen normally uses. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's configuration file. A template extensions file can be +# similar to Doxygen's configuration file. A template extensions file can be # generated using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_EXTENSIONS_FILE = -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. +# The RTF_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the RTF_OUTPUT output directory. +# Note that the files will be copied as-is; there are no commands or markers +# available. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_SOURCE_CODE = NO +RTF_EXTRA_FILES = #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- -# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# If the GENERATE_MAN tag is set to YES, Doxygen will generate man pages for # classes and files. # The default value is: NO. @@ -1951,7 +2239,7 @@ MAN_EXTENSION = .3 MAN_SUBDIR = -# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, then it # will generate one additional man file for each entity documented in the real # man page(s). These additional files only source the real man page, but without # them the man command would be unable to find the correct page. @@ -1964,7 +2252,7 @@ MAN_LINKS = NO # Configuration options related to the XML output #--------------------------------------------------------------------------- -# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# If the GENERATE_XML tag is set to YES, Doxygen will generate an XML file that # captures the structure of the code including all documentation. # The default value is: NO. @@ -1978,7 +2266,7 @@ GENERATE_XML = NO XML_OUTPUT = xml -# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# If the XML_PROGRAMLISTING tag is set to YES, Doxygen will dump the program # listings (including syntax highlighting and cross-referencing information) to # the XML output. Note that enabling this will significantly increase the size # of the XML output. @@ -1987,7 +2275,7 @@ XML_OUTPUT = xml XML_PROGRAMLISTING = YES -# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, Doxygen will include # namespace members in file scope as well, matching the HTML output. # The default value is: NO. # This tag requires that the tag GENERATE_XML is set to YES. @@ -1998,7 +2286,7 @@ XML_NS_MEMB_FILE_SCOPE = NO # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- -# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# If the GENERATE_DOCBOOK tag is set to YES, Doxygen will generate Docbook files # that can be used to generate PDF. # The default value is: NO. @@ -2012,32 +2300,49 @@ GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- -# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# If the GENERATE_AUTOGEN_DEF tag is set to YES, Doxygen will generate an +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + +# If the GENERATE_SQLITE3 tag is set to YES Doxygen will generate a Sqlite3 +# database with symbols found by Doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each Doxygen run. If set to NO, Doxygen +# will warn if a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- -# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# If the GENERATE_PERLMOD tag is set to YES, Doxygen will generate a Perl module # file that captures the structure of the code including all documentation. # # Note that this feature is still experimental and incomplete at the moment. @@ -2045,7 +2350,7 @@ GENERATE_AUTOGEN_DEF = NO GENERATE_PERLMOD = NO -# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# If the PERLMOD_LATEX tag is set to YES, Doxygen will generate the necessary # Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI # output from the Perl module output. # The default value is: NO. @@ -2075,13 +2380,13 @@ PERLMOD_MAKEVAR_PREFIX = # Configuration options related to the preprocessor #--------------------------------------------------------------------------- -# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# If the ENABLE_PREPROCESSING tag is set to YES, Doxygen will evaluate all # C-preprocessor directives found in the sources and include files. # The default value is: YES. ENABLE_PREPROCESSING = YES -# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# If the MACRO_EXPANSION tag is set to YES, Doxygen will expand all macro names # in the source code. If set to NO, only conditional compilation will be # performed. Macro expansion can be done in a controlled way by setting # EXPAND_ONLY_PREDEF to YES. @@ -2107,7 +2412,8 @@ SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by the -# preprocessor. +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. INCLUDE_PATH = @@ -2143,7 +2449,7 @@ PREDEFINED = ALIGNAS(x)= \ EXPAND_AS_DEFINED = -# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# If the SKIP_FUNCTION_MACROS tag is set to YES then Doxygen's preprocessor will # remove all references to function-like macros that are alone on a line, have # an all uppercase name, and do not end with a semicolon. Such function macros # are typically used for boiler-plate code, and will confuse the parser if not @@ -2167,26 +2473,26 @@ SKIP_FUNCTION_MACROS = YES # section "Linking to external documentation" for more information about the use # of tag files. # Note: Each tag file must have a unique name (where the name does NOT include -# the path). If a tag file is not located in the directory in which doxygen is +# the path). If a tag file is not located in the directory in which Doxygen is # run, you must also specify the path to the tagfile here. TAGFILES = -# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# When a file name is specified after GENERATE_TAGFILE, Doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2200,42 +2506,26 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. HIDE_UNDOC_RELATIONS = YES -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# If you set the HAVE_DOT tag to YES then Doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: NO. -HAVE_DOT = NO +HAVE_DOT = YES -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed -# to run in parallel. When set to 0 doxygen will base this on the number of +# The DOT_NUM_THREADS specifies the number of dot invocations Doxygen is allowed +# to run in parallel. When set to 0 Doxygen will base this on the number of # processors available in the system. You can set it explicitly to a value # larger than 0 to get control over the balance between CPU load and processing # speed. @@ -2244,55 +2534,83 @@ HAVE_DOT = NO DOT_NUM_THREADS = 0 -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# Doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see Node, +# Edge and Graph Attributes specification You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_COMMON_ATTR = "fontname=\"Noto Sans, Lucida Grande, Lucida Sans Unicode, Helvetica, Arial, sans-serif\",fontsize=10" + +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = Helvetica +DOT_EDGE_ATTR = "labelfontname=\"Noto Sans, Lucida Grande, Lucida Sans Unicode, Helvetica, Arial, sans-serif\",labelfontsize=10" -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTSIZE = 10 +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. # This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for -# each documented class showing the direct and indirect inheritance relations. -# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then Doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. Explicit enabling an inheritance +# graph or choosing a different representation for an inheritance graph of a +# specific class, can be accomplished by means of the command \inheritancegraph. +# Disabling an inheritance graph can be accomplished by means of the command +# \hideinheritancegraph. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. CLASS_GRAPH = YES -# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# If the COLLABORATION_GRAPH tag is set to YES then Doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES -# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. +# If the GROUP_GRAPHS tag is set to YES then Doxygen will generate a graph for +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GROUP_GRAPHS = YES -# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# If the UML_LOOK tag is set to YES, Doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. # The default value is: NO. @@ -2309,10 +2627,32 @@ UML_LOOK = NO # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, Doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, Doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, Doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will be wrapped across multiple lines. Some heuristics are +# applied to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2322,24 +2662,29 @@ UML_LIMIT_NUM_FIELDS = 10 TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to -# YES then doxygen will generate a graph for each documented file showing the +# YES then Doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are -# set to YES then doxygen will generate a graph for each documented file showing +# set to YES then Doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. INCLUDED_BY_GRAPH = YES -# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# If the CALL_GRAPH tag is set to YES then Doxygen will generate a call # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. @@ -2351,7 +2696,7 @@ INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO -# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# If the CALLER_GRAPH tag is set to YES then Doxygen will generate a caller # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. @@ -2363,44 +2708,59 @@ CALL_GRAPH = NO CALLER_GRAPH = NO -# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# If the GRAPHICAL_HIERARCHY tag is set to YES then Doxygen will graphical # hierarchy of all classes instead of a textual one. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GRAPHICAL_HIERARCHY = YES -# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# If the DIRECTORY_GRAPH tag is set to YES then Doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the -# files in the directories. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. DIRECTORY_GRAPH = YES +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). -# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order -# to make the SVG files visible in IE 9+ (other browsers do not have this -# requirement). +# https://www.graphviz.org/)). +# +# Note the formats svg:cairo and svg:cairo:cairo cannot be used in combination +# with INTERACTIVE_SVG (the INTERACTIVE_SVG will be set to NO). # Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, -# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and -# png:gdiplus:gdiplus. +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus, +# png:gdiplus:gdiplus, svg:cairo, svg:cairo:cairo, svg:svg, svg:svg:core, +# gif:cairo, gif:cairo:gd, gif:cairo:gdiplus, gif:gdiplus, gif:gdiplus:gdiplus, +# gif:gd, gif:gd:gd, jpg:cairo, jpg:cairo:gd, jpg:cairo:gdiplus, jpg:gd, +# jpg:gd:gd, jpg:gdiplus and jpg:gdiplus:gdiplus. # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_IMAGE_FORMAT = png +DOT_IMAGE_FORMAT = svg -# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to -# enable generation of interactive SVG images that allow zooming and panning. +# If DOT_IMAGE_FORMAT is set to svg or svg:svg or svg:svg:core, then this option +# can be set to YES to enable generation of interactive SVG images that allow +# zooming and panning. # # Note that this requires a modern browser other than Internet Explorer. Tested # and working are Firefox, Chrome, Safari, and Opera. -# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make -# the SVG files visible. Older versions of IE do not have SVG support. +# +# Note This option will be automatically disabled when DOT_IMAGE_FORMAT is set +# to svg:cairo or svg:cairo:cairo. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2419,11 +2779,12 @@ DOT_PATH = DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in Doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2431,28 +2792,34 @@ MSCFILE_DIRS = DIAFILE_DIRS = -# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. +# When using PlantUML, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. PLANTUML_JAR_PATH = -# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a -# configuration file for plantuml. +# When using PlantUML, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for PlantUML. PLANTUML_CFG_FILE = -# When using plantuml, the specified paths are searched for files specified by -# the !include statement in a plantuml block. +# When using PlantUML, the specified paths are searched for files specified by +# the !include statement in a PlantUML block. PLANTUML_INCLUDE_PATH = +# The PLANTUMLFILE_DIRS tag can be used to specify one or more directories that +# contain PlantUml files that are included in the documentation (see the +# \plantumlfile command). + +PLANTUMLFILE_DIRS = + # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes -# larger than this value, doxygen will truncate the graph, which is visualized -# by representing a node as a red box. Note that doxygen if the number of direct +# larger than this value, Doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that if the number of direct # children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that # the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. @@ -2473,18 +2840,6 @@ DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support @@ -2494,17 +2849,37 @@ DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = NO -# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# If the GENERATE_LEGEND tag is set to YES Doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the Doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, Doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES + +# You can define message sequence charts within Doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then Doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, Doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = diff --git a/doc/support/crashpad_doxygen.css b/doc/support/crashpad_doxygen.css index 1e3366a644..13ce3854ca 100644 --- a/doc/support/crashpad_doxygen.css +++ b/doc/support/crashpad_doxygen.css @@ -12,47 +12,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import "https://fonts.googleapis.com/css?family=Open+Sans:300,400,700&subset=latin,cyrillic-ext,greek-ext,cyrillic,greek,vietnamese,latin-ext"; -@import "https://fonts.googleapis.com/css?family=Source+Code+Pro"; +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap'); -body, -table, -div, -p, -dl, -.title, -.icon, -table.directory, -.navpath li.navelem a, -#projectname, -#projectbrief, -#projectnumber, -div.toc li, -div.toc h3, -#powerTip div, -.sm-dox a, -.sm-dox a:focus, -.sm-dox a:hover, -.sm-dox a:active { - font-family: 'Open Sans', - 'Lucida Grande', - 'Lucida Sans Unicode', - Helvetica, - Arial, - sans-serif; -} +:root { + --font-family-normal: 'Noto Sans', + 'Lucida Grande', + 'Lucida Sans Unicode', + Helvetica, + Arial, + sans-serif; + --font-family-nav: var(--font-family-normal); + --font-family-title: var(--font-family-normal); + --font-family-toc: var(--font-family-normal); + --font-family-search: var(--font-family-normal); + --font-family-icon: var(--font-family-normal); + --font-family-tooltip: var(--font-family-normal); -code, -tt, -pre.fragment, -div.line, -.overload, -.params .paramdir, -.sm-dox a span.sub-arrow { - font-family: 'Source Code Pro', - Monaco, - 'Lucida Console', - monospace; + --font-family-monospace: 'Source Code Pro', + Monaco, + 'Lucida Console', + monospace; } .icon { diff --git a/doc/support/generate.sh b/doc/support/generate.sh index df3c1d7c07..18b505ad34 100755 --- a/doc/support/generate.sh +++ b/doc/support/generate.sh @@ -28,7 +28,7 @@ cd "$(dirname "${0}")/../.." source doc/support/compat.sh -python doc/support/generate_doxygen.py +doc/support/generate_doxygen.py output_dir=doc/generated maybe_mkdir "${output_dir}" diff --git a/handler/BUILD.gn b/handler/BUILD.gn index a2d6f7fb48..1427ce5f88 100644 --- a/handler/BUILD.gn +++ b/handler/BUILD.gn @@ -20,8 +20,6 @@ static_library("handler") { "handler_main.h", "prune_crash_reports_thread.cc", "prune_crash_reports_thread.h", - "user_stream_data_source.cc", - "user_stream_data_source.h", ] if (crashpad_is_mac) { @@ -74,7 +72,6 @@ static_library("handler") { ":common", "../minidump", "../snapshot", - "../third_party/mini_chromium:chromeos_buildflags", "../tools:tool_support", ] @@ -95,10 +92,14 @@ if (crashpad_is_android) { static_library("common") { sources = [ + "crash_report_upload_rate_limit.cc", + "crash_report_upload_rate_limit.h", "crash_report_upload_thread.cc", "crash_report_upload_thread.h", "minidump_to_upload_parameters.cc", "minidump_to_upload_parameters.h", + "user_stream_data_source.cc", + "user_stream_data_source.h", ] if (crashpad_is_apple) { sources += [ @@ -113,6 +114,7 @@ static_library("common") { ] deps = [ "../client:common", + "../minidump", "../snapshot", "../util", "../util:net", @@ -125,7 +127,10 @@ static_library("common") { source_set("handler_test") { testonly = true - sources = [ "minidump_to_upload_parameters_test.cc" ] + sources = [ + "crash_report_upload_rate_limit_test.cc", + "minidump_to_upload_parameters_test.cc", + ] if (crashpad_is_linux || crashpad_is_android) { sources += [ "linux/exception_handler_server_test.cc" ] @@ -142,7 +147,7 @@ source_set("handler_test") { "../snapshot", "../snapshot:test_support", "../test", - "../third_party/googletest:googletest", + "../third_party/googletest", "../third_party/mini_chromium:base", "../util", ] @@ -222,9 +227,14 @@ if (crashpad_is_android) { if (crashpad_is_in_chromium) { # Chromium's sanitizer runtime libraries do not include an unwinder, # so add Chromium's standard dependencies to link against the in-tree - # libunwind. + # libunwind. The coverage wrapper similarly requires an unwinder, as + # well as a few other bits from libc++abi. There are some issues with pgo + # as well. + import("//build/config/compiler/pgo/pgo.gni") + import("//build/config/coverage/coverage.gni") import("//build/config/sanitizers/sanitizers.gni") - no_default_deps = !using_sanitizer + no_default_deps = + !use_clang_coverage && !using_sanitizer && chrome_pgo_phase != 1 remove_configs = [ "//build/config/android:default_orderfile_instrumentation" ] } @@ -280,7 +290,7 @@ if (crashpad_is_win) { deps = [ "../client", "../test", - "../third_party/googletest:googletest", + "../third_party/googletest", "../third_party/mini_chromium:base", ] } diff --git a/handler/CMakeLists.txt b/handler/CMakeLists.txt index 2247e2a72f..ae0b2e4996 100644 --- a/handler/CMakeLists.txt +++ b/handler/CMakeLists.txt @@ -1,6 +1,8 @@ add_library(crashpad_handler_lib STATIC crash_report_upload_thread.cc crash_report_upload_thread.h + crash_report_upload_rate_limit.cc + crash_report_upload_rate_limit.h handler_main.cc handler_main.h minidump_to_upload_parameters.cc diff --git a/handler/crash_report_upload_rate_limit.cc b/handler/crash_report_upload_rate_limit.cc new file mode 100644 index 0000000000..10cb196dbd --- /dev/null +++ b/handler/crash_report_upload_rate_limit.cc @@ -0,0 +1,47 @@ +// Copyright 2025 The Crashpad Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "handler/crash_report_upload_rate_limit.h" + +#include + +#include + +#include "util/misc/metrics.h" + +namespace crashpad { + +ShouldRateLimitResult ShouldRateLimit(time_t now, + time_t last_upload_attempt_time, + time_t interval_seconds) { + if (now >= last_upload_attempt_time) { + // If the most recent upload attempt occurred within the past interval, + // don’t attempt to upload the new report. If it happened longer ago, + // attempt to upload the report. + if (now - last_upload_attempt_time < interval_seconds) { + return {1, Metrics::CrashSkippedReason::kUploadThrottled}; + } + } else if (last_upload_attempt_time - now < kBackwardsClockTolerance) { + // The most recent upload attempt purportedly occurred in the future. If + // it “happened” at least `kBackwardsClockTolerance` in the future, assume + // that the last upload attempt time is bogus, and attempt to upload the + // report. If the most recent upload time is in the future but within + // `kBackwardsClockTolerance`, accept it and don’t attempt to upload the + // report. + return {1, Metrics::CrashSkippedReason::kUnexpectedTime}; + } + return {0, std::nullopt}; +} + +} // namespace crashpad diff --git a/handler/crash_report_upload_rate_limit.h b/handler/crash_report_upload_rate_limit.h new file mode 100644 index 0000000000..bd046c5fc4 --- /dev/null +++ b/handler/crash_report_upload_rate_limit.h @@ -0,0 +1,60 @@ +// Copyright 2025 The Crashpad Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_HANDLER_CRASH_REPORT_UPLOAD_RATE_LIMIT_H_ +#define CRASHPAD_HANDLER_CRASH_REPORT_UPLOAD_RATE_LIMIT_H_ + +#include + +#include + +#include "util/misc/metrics.h" + +namespace crashpad { + +//! \brief How far into the future an upload-attempt timestamp can be and still +//! be treated as valid. Beyond this tolerance, timestamps are disregarded. +//! Exposed for testing. +inline constexpr int kBackwardsClockTolerance = 60 * 60 * 24; // 1 day + +//! \brief The return type of `ShouldRateLimit()`. Describes the number of +//! crash-report upload attempts in the past interval, and if upload +//! should be skipped, the reason why. +struct ShouldRateLimitResult { + //! \brief The number of upload attempts in the past interval. + unsigned int attempts_in_interval; + + //! \brief If the crash should be skipped, the reason why. `std::nullopt` + //! otherwise. + std::optional skip_reason; +}; + +//! \brief Determines whether a crash report can be uploaded now, or whether +//! uploads should be rate limited at this time. +//! +//! \param[in] now The current time. +//! \param[in] last_upload_attempt_time The timestamp of the last crash-report +//! upload attempt. +//! \param[in] interval_seconds The unit of time, in seconds, over which to +//! perform rate-limiting calculations (an "interval"). +//! +//! \return The number of upload attempts in the past interval, and if the +//! current crash report should be skipped, the reason why. +ShouldRateLimitResult ShouldRateLimit(time_t now, + time_t last_upload_attempt_time, + time_t interval_seconds); + +} // namespace crashpad + +#endif // CRASHPAD_HANDLER_CRASH_REPORT_UPLOAD_RATE_LIMIT_H_ diff --git a/handler/crash_report_upload_rate_limit_test.cc b/handler/crash_report_upload_rate_limit_test.cc new file mode 100644 index 0000000000..8cdced5327 --- /dev/null +++ b/handler/crash_report_upload_rate_limit_test.cc @@ -0,0 +1,76 @@ +// Copyright 2025 The Crashpad Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "handler/crash_report_upload_rate_limit.h" + +#include + +#include + +#include "gtest/gtest.h" +#include "util/misc/metrics.h" + +namespace crashpad::test { +namespace { + +using Metrics::CrashSkippedReason::kUnexpectedTime; +using Metrics::CrashSkippedReason::kUploadThrottled; + +constexpr time_t kOneHour = 60 * 60; + +TEST(CrashReportUploadRateLimitTest, ShouldRateLimit_OnePerHour) { + const time_t now = time(nullptr); + + // Allow upload if the last upload attempt time is in the future, at or beyond + // `kBackwardsClockTolerance`. + auto should_rate_limit = + ShouldRateLimit(now, now + kBackwardsClockTolerance + 1, kOneHour); + EXPECT_EQ(should_rate_limit.skip_reason, std::nullopt); + + should_rate_limit = + ShouldRateLimit(now, now + kBackwardsClockTolerance, kOneHour); + EXPECT_EQ(should_rate_limit.skip_reason, std::nullopt); + + // Skip upload if the last attempt time is in the future but within + // `kBackwardsClockTolerance`. + should_rate_limit = + ShouldRateLimit(now, now + kBackwardsClockTolerance - 1, kOneHour); + EXPECT_EQ(should_rate_limit.skip_reason, kUnexpectedTime); + + should_rate_limit = ShouldRateLimit(now, now + 1, kOneHour); + EXPECT_EQ(should_rate_limit.skip_reason, kUnexpectedTime); + + // Skip upload if the last attempt was under one interval ago. + should_rate_limit = ShouldRateLimit(now, now, kOneHour); + EXPECT_EQ(should_rate_limit.skip_reason, kUploadThrottled); + + should_rate_limit = ShouldRateLimit(now, now - 1, kOneHour); + EXPECT_EQ(should_rate_limit.skip_reason, kUploadThrottled); + + should_rate_limit = ShouldRateLimit(now, now - kOneHour + 1, kOneHour); + EXPECT_EQ(should_rate_limit.skip_reason, kUploadThrottled); + + // Allow upload if the last attempt was one interval or more ago. + should_rate_limit = ShouldRateLimit(now, now - kOneHour, kOneHour); + EXPECT_EQ(should_rate_limit.skip_reason, std::nullopt); + + should_rate_limit = ShouldRateLimit(now, now - kOneHour - 1, kOneHour); + EXPECT_EQ(should_rate_limit.skip_reason, std::nullopt); + + should_rate_limit = ShouldRateLimit(now, 0, kOneHour); + EXPECT_EQ(should_rate_limit.skip_reason, std::nullopt); +} + +} // namespace +} // namespace crashpad::test diff --git a/handler/crash_report_upload_thread.cc b/handler/crash_report_upload_thread.cc index caa329c945..c4eeaf46f9 100644 --- a/handler/crash_report_upload_thread.cc +++ b/handler/crash_report_upload_thread.cc @@ -29,6 +29,7 @@ #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "client/settings.h" +#include "handler/crash_report_upload_rate_limit.h" #include "handler/minidump_to_upload_parameters.h" #include "snapshot/minidump/process_snapshot_minidump.h" #include "snapshot/module_snapshot.h" @@ -240,8 +241,7 @@ void CrashReportUploadThread::ProcessPendingReport( return; case CrashReportDatabase::kCannotRequestUpload: - NOTREACHED_IN_MIGRATION(); - return; + NOTREACHED(); } std::string response_body; @@ -380,29 +380,13 @@ bool CrashReportUploadThread::ShouldRateLimitUpload( Settings* const settings = database_->GetSettings(); time_t last_upload_attempt_time; if (settings->GetLastUploadAttemptTime(&last_upload_attempt_time)) { - time_t now = time(nullptr); - if (now >= last_upload_attempt_time) { - // If the most recent upload attempt occurred within the past hour, - // don’t attempt to upload the new report. If it happened longer ago, - // attempt to upload the report. - constexpr int kUploadAttemptIntervalSeconds = 60 * 60; // 1 hour - if (now - last_upload_attempt_time < kUploadAttemptIntervalSeconds) { - database_->SkipReportUpload( - report.uuid, Metrics::CrashSkippedReason::kUploadThrottled); - return true; - } - } else { - // The most recent upload attempt purportedly occurred in the future. If - // it “happened” at least one day in the future, assume that the last - // upload attempt time is bogus, and attempt to upload the report. If - // the most recent upload time is in the future but within one day, - // accept it and don’t attempt to upload the report. - constexpr int kBackwardsClockTolerance = 60 * 60 * 24; // 1 day - if (last_upload_attempt_time - now < kBackwardsClockTolerance) { - database_->SkipReportUpload( - report.uuid, Metrics::CrashSkippedReason::kUnexpectedTime); - return true; - } + const time_t now = time(nullptr); + constexpr int kUploadAttemptIntervalSeconds = 60 * 60; // 1 hour + const auto should_rate_limit = ShouldRateLimit( + now, last_upload_attempt_time, kUploadAttemptIntervalSeconds); + if (should_rate_limit.skip_reason.has_value()) { + database_->SkipReportUpload(report.uuid, *should_rate_limit.skip_reason); + return true; } } return false; diff --git a/handler/handler_main.cc b/handler/handler_main.cc index 1883f9d78d..51568a5f3e 100644 --- a/handler/handler_main.cc +++ b/handler/handler_main.cc @@ -38,7 +38,6 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" -#include "build/chromeos_buildflags.h" #include "client/crash_report_database.h" #include "client/crashpad_client.h" #include "client/crashpad_info.h" @@ -57,7 +56,7 @@ #include "util/string/split_string.h" #include "util/synchronization/semaphore.h" -#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +#if BUILDFLAG(IS_CHROMEOS) #include "handler/linux/cros_crash_report_exception_handler.h" #endif @@ -201,7 +200,7 @@ void Usage(const base::FilePath& me) { " --url=URL send crash reports to this Breakpad server URL,\n" " only if uploads are enabled for the database\n" // clang-format on -#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +#if BUILDFLAG(IS_CHROMEOS) // clang-format off " --use-cros-crash-reporter\n" " pass crash reports to /sbin/crash_reporter\n" @@ -214,7 +213,7 @@ void Usage(const base::FilePath& me) { " crash_reporter, thus skipping metrics consent\n" " checks\n" // clang-format on -#endif // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +#endif // BUILDFLAG(IS_CHROMEOS) #if BUILDFLAG(IS_ANDROID) // clang-format off " --write-minidump-to-log write minidump to log\n" @@ -258,11 +257,11 @@ struct Options { bool periodic_tasks; bool rate_limit; bool upload_gzip; -#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +#if BUILDFLAG(IS_CHROMEOS) bool use_cros_crash_reporter = false; base::FilePath minidump_dir_for_tests; bool always_allow_feedback = false; -#endif // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +#endif // BUILDFLAG(IS_CHROMEOS) #if defined(ATTACHMENTS_SUPPORTED) std::vector attachments; #endif // ATTACHMENTS_SUPPORTED @@ -570,7 +569,7 @@ class ScopedStoppable { void InitCrashpadLogging() { logging::LoggingSettings settings; -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) settings.logging_dest = logging::LOG_TO_FILE; settings.log_file_path = "/var/log/chrome/chrome"; #elif BUILDFLAG(IS_WIN) @@ -644,11 +643,11 @@ int HandlerMain(int argc, #endif kOptionURL, kOptionHttpProxy, -#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +#if BUILDFLAG(IS_CHROMEOS) kOptionUseCrosCrashReporter, kOptionMinidumpDirForTests, kOptionAlwaysAllowFeedback, -#endif // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +#endif // BUILDFLAG(IS_CHROMEOS) #if BUILDFLAG(IS_ANDROID) kOptionWriteMinidumpToLog, #endif // BUILDFLAG(IS_ANDROID) @@ -737,7 +736,7 @@ int HandlerMain(int argc, // BUILDFLAG(IS_ANDROID) {"url", required_argument, nullptr, kOptionURL}, {"http-proxy", optional_argument, nullptr, kOptionHttpProxy}, -#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +#if BUILDFLAG(IS_CHROMEOS) {"use-cros-crash-reporter", no_argument, nullptr, @@ -747,7 +746,7 @@ int HandlerMain(int argc, nullptr, kOptionMinidumpDirForTests}, {"always-allow-feedback", no_argument, nullptr, kOptionAlwaysAllowFeedback}, -#endif // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +#endif // BUILDFLAG(IS_CHROMEOS) #if BUILDFLAG(IS_ANDROID) {"write-minidump-to-log", no_argument, nullptr, kOptionWriteMinidumpToLog}, #endif // BUILDFLAG(IS_ANDROID) @@ -926,7 +925,7 @@ int HandlerMain(int argc, options.http_proxy = optarg; break; } -#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +#if BUILDFLAG(IS_CHROMEOS) case kOptionUseCrosCrashReporter: { options.use_cros_crash_reporter = true; break; @@ -940,7 +939,7 @@ int HandlerMain(int argc, options.always_allow_feedback = true; break; } -#endif // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +#endif // BUILDFLAG(IS_CHROMEOS) #if BUILDFLAG(IS_ANDROID) case kOptionWriteMinidumpToLog: { options.write_minidump_to_log = true; @@ -1106,7 +1105,7 @@ int HandlerMain(int argc, std::unique_ptr exception_handler; #endif -#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +#if BUILDFLAG(IS_CHROMEOS) if (options.use_cros_crash_reporter) { auto cros_handler = std::make_unique( database.get(), @@ -1158,7 +1157,7 @@ int HandlerMain(int argc, ,&options.crash_reporter ,&options.crash_envelope ); -#endif // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +#endif // BUILDFLAG(IS_CHROMEOS) #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) if (options.exception_information_address) { diff --git a/handler/linux/crash_report_exception_handler.cc b/handler/linux/crash_report_exception_handler.cc index ec56035801..90b13e5190 100644 --- a/handler/linux/crash_report_exception_handler.cc +++ b/handler/linux/crash_report_exception_handler.cc @@ -53,9 +53,9 @@ class Logger final : public LogOutputStream::Delegate { ~Logger() override = default; #if BUILDFLAG(IS_ANDROID) - int Log(const char* buf) override { + bool Log(const char* buf) override { return __android_log_buf_write( - LOG_ID_CRASH, ANDROID_LOG_FATAL, "crashpad", buf); + LOG_ID_CRASH, ANDROID_LOG_FATAL, "crashpad", buf) > 0; } size_t OutputCap() override { @@ -72,7 +72,7 @@ class Logger final : public LogOutputStream::Delegate { } #else // TODO(jperaza): Log to an appropriate location on Linux. - int Log(const char* buf) override { return -ENOTCONN; } + bool Log(const char* buf) override { return false; } size_t OutputCap() override { return 0; } size_t LineWidth() override { return 0; } #endif diff --git a/handler/linux/cros_crash_report_exception_handler.cc b/handler/linux/cros_crash_report_exception_handler.cc index 25637408df..51486a1077 100644 --- a/handler/linux/cros_crash_report_exception_handler.cc +++ b/handler/linux/cros_crash_report_exception_handler.cc @@ -257,8 +257,6 @@ bool CrosCrashReportExceptionHandler::HandleExceptionWithConnection( constexpr int32_t kFixedVersion = 15363; // TODO(https://crbug.com/1420445): Remove this check (and the // CRASHPAD_IS_IN_CHROMIUM defines) when M115 branches. - // (Lacros is guaranteed not to be more than 2 milestones ahead of ash, and - // M113 on ash has the relevant crash_reporter change.) if (major_version >= kFixedVersion) { // Used to distinguish between non-fatal and fatal crashes. const ExceptionSnapshot* const exception_snapshot = snapshot->Exception(); diff --git a/handler/mac/crash_report_exception_handler.cc b/handler/mac/crash_report_exception_handler.cc index 3a136f2002..3225030aaa 100644 --- a/handler/mac/crash_report_exception_handler.cc +++ b/handler/mac/crash_report_exception_handler.cc @@ -32,6 +32,7 @@ #include "util/mach/bootstrap.h" #include "util/mach/exc_client_variants.h" #include "util/mach/exception_behaviors.h" +#include "util/mach/exception_ports.h" #include "util/mach/exception_types.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" @@ -230,51 +231,71 @@ kern_return_t CrashReportExceptionHandler::CatchMachException( } } - if (client_options.system_crash_reporter_forwarding != TriState::kDisabled && - (exception == EXC_CRASH || - exception == EXC_RESOURCE || - exception == EXC_GUARD)) { - // Don’t forward simulated exceptions such as kMachExceptionSimulated to the - // system crash reporter. Only forward the types of exceptions that it would - // receive under normal conditions. Although the system crash reporter is - // able to deal with other exceptions including simulated ones, forwarding - // them to the system crash reporter could present the system’s crash UI for - // processes that haven’t actually crashed, and could result in reports not - // actually associated with crashes being sent to the operating system - // vendor. - base::apple::ScopedMachSendRight system_crash_reporter_handler( - SystemCrashReporterHandler()); - if (system_crash_reporter_handler.get()) { - // Make copies of mutable out parameters so that the system crash reporter - // can’t influence the state returned by this method. - thread_state_flavor_t flavor_forward = *flavor; - mach_msg_type_number_t new_state_forward_count = *new_state_count; - std::vector new_state_forward( - new_state, new_state + new_state_forward_count); - - // The system crash reporter requires the behavior to be - // EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES. It uses the identity - // parameters but doesn’t appear to use the state parameters, including - // |flavor|, and doesn’t care if they are 0 or invalid. As long as an - // identity is available (checked above), any other exception behavior is - // converted to what the system crash reporter wants, with the caveat that - // problems may arise if the state wasn’t available and the system crash - // reporter changes in the future to use it. However, normally, the state - // will be available. - kern_return_t kr = UniversalExceptionRaise( - EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, - system_crash_reporter_handler.get(), - thread, - task, - exception, - code, - code_count, - &flavor_forward, - old_state, - old_state_count, - new_state_forward_count ? &new_state_forward[0] : nullptr, - &new_state_forward_count); - MACH_LOG_IF(WARNING, kr != KERN_SUCCESS, kr) << "UniversalExceptionRaise"; + if (client_options.system_crash_reporter_forwarding != TriState::kDisabled) { + if (exception == EXC_CRASH) { + // For exception handlers that respond to state-carrying behaviors, when + // the handler is called by the kernel (as it is normally), the kernel + // will attempt to set a new thread state when the exception handler + // returns successfully. Other code that mimics the kernel’s + // exception-delivery semantics may implement the same or similar + // behavior. In some situations, it is undesirable to set a new thread + // state. If the exception handler were to return unsuccessfully, however, + // the kernel would continue searching for an exception handler at a wider + // (task or host) scope. This may also be undesirable. + // + // If such exception handlers return `MACH_RCV_PORT_DIED`, the kernel will + // not set a new thread state and will also not search for another + // exception handler. See 15.3 xnu-11215.84.4/osfmk/kern/exception.c. + // `exception_deliver()` will only set a new thread state if the handler’s + // return code was `MACH_MSG_SUCCESS` (a synonym for `KERN_SUCCESS`), and + // subsequently, `exception_triage()` will not search for a new handler if + // the handler’s return code was `KERN_SUCCESS` or `MACH_RCV_PORT_DIED`. + // + // Another effect of returning `MACH_RCV_PORT_DIED` for `EXC_CRASH` is + // that an `EXC_CORPSE_NOTIFY` exception is generated. Starting with macOS + // 10.15, for the system crash reporter to generate a report, + // `EXC_CORPSE_NOTIFY` *must* be generated and forwarding `EXC_CRASH` (as + // we do below with `EXC_RESOURCE` and pre-macOS 13 `EXC_GUARD`) is not + // sufficient. Between macOS 10.11 and macOS 10.14 (inclusive), both + // forwarding as below, and causing `EXC_CORPSE_NOTIFY` to be generated + // are sufficient (and in fact, if we do both, two crash reports are + // generated). + return MACH_RCV_PORT_DIED; + } + if (exception == EXC_RESOURCE || exception == EXC_GUARD) { + // Only forward the types of exceptions that the crash reporter would + // receive under normal conditions. Otherwise, system crash reporter could + // present the system’s crash UI for processes that haven’t actually + // crashed, and could result in reports not actually associated with + // crashes being sent to the operating system vendor. + base::apple::ScopedMachSendRight system_crash_reporter_handler( + SystemCrashReporterHandler()); + if (system_crash_reporter_handler.get()) { + // Make copies of mutable out parameters so that the system crash + // reporter can’t influence the state returned by this method. + thread_state_flavor_t flavor_forward = *flavor; + mach_msg_type_number_t new_state_forward_count = *new_state_count; + std::vector new_state_forward; + if (new_state_forward_count) { + new_state_forward.assign(new_state, + new_state + new_state_forward_count); + } + kern_return_t kr = UniversalExceptionRaise( + EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, + system_crash_reporter_handler.get(), + thread, + task, + exception, + code, + code_count, + &flavor_forward, + old_state, + old_state_count, + new_state_forward_count ? &new_state_forward[0] : nullptr, + &new_state_forward_count); + MACH_LOG_IF(WARNING, kr != KERN_SUCCESS, kr) + << "UniversalExceptionRaise"; + } } } @@ -282,7 +303,7 @@ kern_return_t CrashReportExceptionHandler::CatchMachException( behavior, old_state, old_state_count, new_state, new_state_count); Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess); - return ExcServerSuccessfulReturnValue(exception, behavior, false); + return KERN_SUCCESS; } } // namespace crashpad diff --git a/handler/main.cc b/handler/main.cc index 61f6444758..90e0d8d7d6 100644 --- a/handler/main.cc +++ b/handler/main.cc @@ -18,6 +18,7 @@ #include "tools/tool_support.h" #if BUILDFLAG(IS_WIN) +#include #include #endif diff --git a/handler/win/crashy_test_program.cc b/handler/win/crashy_test_program.cc index 0929f3375f..a1449798aa 100644 --- a/handler/win/crashy_test_program.cc +++ b/handler/win/crashy_test_program.cc @@ -133,7 +133,7 @@ bool CreateThreadWithRegisterPointingToTestMemory() { return true; } -void SomeCrashyFunction() { +__declspec(noinline) void SomeCrashyFunction() { // SetLastError and NTSTATUS so that we have something to view in !gle in // windbg. RtlNtStatusToDosError() stores STATUS_NO_SUCH_FILE into the // LastStatusError of the TEB as a side-effect, and we'll be setting diff --git a/handler/win/fastfail_test_program.cc b/handler/win/fastfail_test_program.cc index f654119f8c..5e27c50ff1 100644 --- a/handler/win/fastfail_test_program.cc +++ b/handler/win/fastfail_test_program.cc @@ -17,6 +17,7 @@ #include "base/check.h" #include "base/files/file_path.h" #include "base/logging.h" +#include "base/notreached.h" #include "client/crashpad_client.h" #include "util/misc/paths.h" @@ -54,7 +55,7 @@ void CfgCrash() { IndirectCall(&func); } __except (EXCEPTION_EXECUTE_HANDLER) { // CFG fast fail should never be caught. - CHECK(false); + NOTREACHED(); } // Should only reach here if CFG is disabled. abort(); diff --git a/handler/win/hanging_program.cc b/handler/win/hanging_program.cc index 00eaf17162..371eadb93b 100644 --- a/handler/win/hanging_program.cc +++ b/handler/win/hanging_program.cc @@ -28,7 +28,7 @@ namespace { -DWORD WINAPI Thread1(LPVOID context) { +[[noreturn]] DWORD WINAPI Thread1(LPVOID context) { HANDLE event = context; // Increase the thread priority as a hacky way to signal to @@ -40,17 +40,15 @@ DWORD WINAPI Thread1(LPVOID context) { Sleep(INFINITE); - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); } -DWORD WINAPI Thread2(LPVOID dummy) { +[[noreturn]] DWORD WINAPI Thread2(LPVOID dummy) { Sleep(INFINITE); - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); } -DWORD WINAPI Thread3(LPVOID context) { +[[noreturn]] DWORD WINAPI Thread3(LPVOID context) { // This is a convenient way to pass the event handle to loader_lock_dll.dll. HANDLE event = context; PCHECK(SetEnvironmentVariable( @@ -58,15 +56,12 @@ DWORD WINAPI Thread3(LPVOID context) { base::UTF8ToWide(base::StringPrintf("%p", event)).c_str())); HMODULE dll = LoadLibrary(L"loader_lock_dll.dll"); - if (!dll) - PLOG(FATAL) << "LoadLibrary"; + PCHECK(dll) << "LoadLibrary"; // This call is not expected to return. - if (!FreeLibrary(dll)) - PLOG(FATAL) << "FreeLibrary"; + PCHECK(FreeLibrary(dll)); - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); } } // namespace diff --git a/handler/win/heap_corrupting_program.cc b/handler/win/heap_corrupting_program.cc index 8fe62f6efc..7516d4a263 100644 --- a/handler/win/heap_corrupting_program.cc +++ b/handler/win/heap_corrupting_program.cc @@ -17,6 +17,7 @@ #include "base/check.h" #include "base/files/file_path.h" #include "base/logging.h" +#include "base/notreached.h" #include "client/crashpad_client.h" #include "util/misc/paths.h" @@ -43,7 +44,7 @@ void HeapCorruptionCrash() { HeapDestroy(heap); } __except (EXCEPTION_EXECUTE_HANDLER) { // Heap corruption exception should never be caught. - CHECK(false); + NOTREACHED(); } // Should never reach here. abort(); diff --git a/handler/win/self_destroying_test_program.cc b/handler/win/self_destroying_test_program.cc index fbcc35104f..1e98a2f704 100644 --- a/handler/win/self_destroying_test_program.cc +++ b/handler/win/self_destroying_test_program.cc @@ -24,11 +24,17 @@ namespace crashpad { namespace { +// Without this, clang optimizes away the _alloca below, which in turn +// makes the VirtualFree() crash with an access violation. +#if defined(__clang__) +#pragma clang optimize off +#endif + // We VirtualFree a region in ourselves (the stack) to confirm that the // exception reporter captures as much as possible in the minidump and doesn't // abort. __debugbreak() immediately after doing so because the process is // clearly in a very broken state at this point. -bool FreeOwnStackAndBreak() { +__declspec(noinline) bool FreeOwnStackAndBreak() { ProcessReaderWin process_reader; if (!process_reader.Initialize(GetCurrentProcess(), ProcessSuspensionState::kRunning)) { @@ -45,7 +51,7 @@ bool FreeOwnStackAndBreak() { // Push the stack up a bit so that hopefully the crash handler can succeed, // but won't be able to read the base of the stack. - _alloca(16384); + [[maybe_unused]] volatile void* do_not_optimize_away = _alloca(16384); // We can't succeed at MEM_RELEASEing this memory, but MEM_DECOMMIT is good // enough to make it inaccessible. @@ -63,7 +69,7 @@ bool FreeOwnStackAndBreak() { return true; } -int SelfDestroyingMain(int argc, wchar_t* argv[]) { +__declspec(noinline) int SelfDestroyingMain(int argc, wchar_t* argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %ls \n", argv[0]); return EXIT_FAILURE; @@ -83,6 +89,10 @@ int SelfDestroyingMain(int argc, wchar_t* argv[]) { return EXIT_SUCCESS; } +#if defined(__clang__) +#pragma clang optimize on +#endif + } // namespace } // namespace crashpad diff --git a/handler/win/wer/BUILD.gn b/handler/win/wer/BUILD.gn index 71c0126ca3..084d209c12 100644 --- a/handler/win/wer/BUILD.gn +++ b/handler/win/wer/BUILD.gn @@ -40,9 +40,9 @@ source_set("crashpad_wer_test") { deps = [ ":crashpad_wer", ":crashpad_wer_handler", - "../../../client:client", - "../../../test:test", - "../../../third_party/googletest:googletest", + "../../../client", + "../../../test", + "../../../third_party/googletest", "../../../util:util_registration_protocol", ] } diff --git a/infra/config/generated/commit-queue.cfg b/infra/config/generated/commit-queue.cfg index 1a17e5c964..154c6b0c8f 100644 --- a/infra/config/generated/commit-queue.cfg +++ b/infra/config/generated/commit-queue.cfg @@ -56,6 +56,12 @@ config_groups { builders { name: "crashpad/try/crashpad_linux_x64_rel" } + builders { + name: "crashpad/try/crashpad_mac_arm64_dbg" + } + builders { + name: "crashpad/try/crashpad_mac_arm64_rel" + } builders { name: "crashpad/try/crashpad_mac_x64_dbg" } diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg index 0ab25244f6..e4d981d705 100644 --- a/infra/config/generated/cr-buildbucket.cfg +++ b/infra/config/generated/cr-buildbucket.cfg @@ -364,6 +364,80 @@ buckets { value: 100 } } + builders { + name: "crashpad_mac_arm64_dbg" + swarming_host: "chromium-swarm.appspot.com" + dimensions: "cpu:arm64" + dimensions: "os:Mac-15" + dimensions: "pool:luci.flex.ci" + exe { + cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" + cipd_version: "refs/heads/main" + cmd: "luciexe" + } + properties: + '{' + ' "$gatekeeper": {' + ' "group": "client.crashpad"' + ' },' + ' "$kitchen": {' + ' "devshell": true,' + ' "git_auth": true' + ' },' + ' "config": "Debug",' + ' "recipe": "crashpad/build",' + ' "target_cpu": "arm64",' + ' "target_os": "mac"' + '}' + execution_timeout_secs: 10800 + caches { + name: "osx_sdk_mac" + path: "osx_sdk" + } + build_numbers: YES + service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + experiments { + key: "luci.recipes.use_python3" + value: 100 + } + } + builders { + name: "crashpad_mac_arm64_rel" + swarming_host: "chromium-swarm.appspot.com" + dimensions: "cpu:arm64" + dimensions: "os:Mac-15" + dimensions: "pool:luci.flex.ci" + exe { + cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" + cipd_version: "refs/heads/main" + cmd: "luciexe" + } + properties: + '{' + ' "$gatekeeper": {' + ' "group": "client.crashpad"' + ' },' + ' "$kitchen": {' + ' "devshell": true,' + ' "git_auth": true' + ' },' + ' "config": "Release",' + ' "recipe": "crashpad/build",' + ' "target_cpu": "arm64",' + ' "target_os": "mac"' + '}' + execution_timeout_secs: 10800 + caches { + name: "osx_sdk_mac" + path: "osx_sdk" + } + build_numbers: YES + service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + experiments { + key: "luci.recipes.use_python3" + value: 100 + } + } builders { name: "crashpad_mac_x64_dbg" swarming_host: "chromium-swarm.appspot.com" @@ -848,6 +922,74 @@ buckets { value: 100 } } + builders { + name: "crashpad_mac_arm64_dbg" + swarming_host: "chromium-swarm.appspot.com" + dimensions: "cpu:arm64" + dimensions: "os:Mac-15" + dimensions: "pool:luci.flex.try" + exe { + cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" + cipd_version: "refs/heads/main" + cmd: "luciexe" + } + properties: + '{' + ' "$kitchen": {' + ' "devshell": true,' + ' "git_auth": true' + ' },' + ' "config": "Debug",' + ' "recipe": "crashpad/build",' + ' "target_cpu": "arm64",' + ' "target_os": "mac"' + '}' + execution_timeout_secs: 10800 + caches { + name: "osx_sdk_mac" + path: "osx_sdk" + } + build_numbers: YES + service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com" + experiments { + key: "luci.recipes.use_python3" + value: 100 + } + } + builders { + name: "crashpad_mac_arm64_rel" + swarming_host: "chromium-swarm.appspot.com" + dimensions: "cpu:arm64" + dimensions: "os:Mac-15" + dimensions: "pool:luci.flex.try" + exe { + cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" + cipd_version: "refs/heads/main" + cmd: "luciexe" + } + properties: + '{' + ' "$kitchen": {' + ' "devshell": true,' + ' "git_auth": true' + ' },' + ' "config": "Release",' + ' "recipe": "crashpad/build",' + ' "target_cpu": "arm64",' + ' "target_os": "mac"' + '}' + execution_timeout_secs: 10800 + caches { + name: "osx_sdk_mac" + path: "osx_sdk" + } + build_numbers: YES + service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com" + experiments { + key: "luci.recipes.use_python3" + value: 100 + } + } builders { name: "crashpad_mac_x64_dbg" swarming_host: "chromium-swarm.appspot.com" diff --git a/infra/config/generated/luci-milo.cfg b/infra/config/generated/luci-milo.cfg index 9a78f93fb9..9ff20ab344 100644 --- a/infra/config/generated/luci-milo.cfg +++ b/infra/config/generated/luci-milo.cfg @@ -70,6 +70,16 @@ consoles { category: "mac|x64" short_name: "rel" } + builders { + name: "buildbucket/luci.crashpad.ci/crashpad_mac_arm64_dbg" + category: "mac|arm64" + short_name: "dbg" + } + builders { + name: "buildbucket/luci.crashpad.ci/crashpad_mac_arm64_rel" + category: "mac|arm64" + short_name: "rel" + } builders { name: "buildbucket/luci.crashpad.ci/crashpad_win_x64_dbg" category: "win|x64" @@ -120,6 +130,12 @@ consoles { builders { name: "buildbucket/luci.crashpad.try/crashpad_mac_x64_rel" } + builders { + name: "buildbucket/luci.crashpad.try/crashpad_mac_arm64_dbg" + } + builders { + name: "buildbucket/luci.crashpad.try/crashpad_mac_arm64_rel" + } builders { name: "buildbucket/luci.crashpad.try/crashpad_win_x64_dbg" } diff --git a/infra/config/generated/luci-scheduler.cfg b/infra/config/generated/luci-scheduler.cfg index 6e6e8e19cf..5ac9433a13 100644 --- a/infra/config/generated/luci-scheduler.cfg +++ b/infra/config/generated/luci-scheduler.cfg @@ -104,6 +104,26 @@ job { builder: "crashpad_linux_x64_rel" } } +job { + id: "crashpad_mac_arm64_dbg" + realm: "ci" + acl_sets: "ci" + buildbucket { + server: "cr-buildbucket.appspot.com" + bucket: "ci" + builder: "crashpad_mac_arm64_dbg" + } +} +job { + id: "crashpad_mac_arm64_rel" + realm: "ci" + acl_sets: "ci" + buildbucket { + server: "cr-buildbucket.appspot.com" + bucket: "ci" + builder: "crashpad_mac_arm64_rel" + } +} job { id: "crashpad_mac_x64_dbg" realm: "ci" @@ -158,6 +178,8 @@ trigger { triggers: "crashpad_ios_x64_rel" triggers: "crashpad_linux_x64_dbg" triggers: "crashpad_linux_x64_rel" + triggers: "crashpad_mac_arm64_dbg" + triggers: "crashpad_mac_arm64_rel" triggers: "crashpad_mac_x64_dbg" triggers: "crashpad_mac_x64_rel" triggers: "crashpad_win_x64_dbg" diff --git a/infra/config/generated/project.cfg b/infra/config/generated/project.cfg index 59369e1839..75a34e33a9 100644 --- a/infra/config/generated/project.cfg +++ b/infra/config/generated/project.cfg @@ -7,7 +7,7 @@ name: "crashpad" access: "group:all" lucicfg { - version: "1.43.6" + version: "1.45.6" package_dir: ".." config_dir: "generated" entry_point: "main.star" diff --git a/infra/config/main.star b/infra/config/main.star index 3c719e9294..29cc642a64 100755 --- a/infra/config/main.star +++ b/infra/config/main.star @@ -1,4 +1,5 @@ #!/usr/bin/env lucicfg + # Copyright 2021 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -150,7 +151,7 @@ def crashpad_caches(platform): elif platform == "mac": return [swarming.cache("osx_sdk", name = "osx_sdk_mac")] -def crashpad_dimensions(platform, bucket): +def crashpad_dimensions(platform, cpu, bucket): dimensions = {} dimensions["cpu"] = "x86-64" dimensions["pool"] = "luci.flex." + bucket @@ -162,7 +163,11 @@ def crashpad_dimensions(platform, bucket): elif platform == "linux": dimensions["os"] = "Ubuntu-22.04" elif platform == "mac": - dimensions["os"] = "Mac-13|Mac-14" + if cpu == "x64": + dimensions["os"] = "Mac-13|Mac-14" + elif cpu == "arm64": + dimensions["cpu"] = cpu + dimensions["os"] = "Mac-15" elif platform == "win": dimensions["os"] = "Windows-10" @@ -227,7 +232,7 @@ def crashpad_builder(platform, cpu, config, bucket): executable = crashpad_recipe(), build_numbers = True, caches = crashpad_caches(platform), - dimensions = crashpad_dimensions(platform, bucket), + dimensions = crashpad_dimensions(platform, cpu, bucket), execution_timeout = 3 * time.hour, properties = crashpad_properties(platform, cpu, config, bucket), service_account = "crashpad-" + bucket + "-builder@chops-service-accounts.iam.gserviceaccount.com", @@ -246,6 +251,8 @@ crashpad_builder("linux", "x64", "dbg", "ci") crashpad_builder("linux", "x64", "rel", "ci") crashpad_builder("mac", "x64", "dbg", "ci") crashpad_builder("mac", "x64", "rel", "ci") +crashpad_builder("mac", "arm64", "dbg", "ci") +crashpad_builder("mac", "arm64", "rel", "ci") crashpad_builder("win", "x64", "dbg", "ci") crashpad_builder("win", "x64", "rel", "ci") @@ -261,5 +268,7 @@ crashpad_builder("linux", "x64", "dbg", "try") crashpad_builder("linux", "x64", "rel", "try") crashpad_builder("mac", "x64", "dbg", "try") crashpad_builder("mac", "x64", "rel", "try") +crashpad_builder("mac", "arm64", "dbg", "try") +crashpad_builder("mac", "arm64", "rel", "try") crashpad_builder("win", "x64", "dbg", "try") crashpad_builder("win", "x64", "rel", "try") diff --git a/minidump/BUILD.gn b/minidump/BUILD.gn index f2395469c0..5b235aa5b6 100644 --- a/minidump/BUILD.gn +++ b/minidump/BUILD.gn @@ -76,8 +76,8 @@ crashpad_static_library("minidump") { ] deps = [ - "../snapshot", "$mini_chromium_source_parent:base", + "../snapshot", "../util", ] @@ -102,8 +102,8 @@ static_library("format") { public_deps = [ "../compat" ] deps = [ - "../snapshot:context", "$mini_chromium_source_parent:base", + "../snapshot:context", "../util", ] } @@ -135,10 +135,10 @@ static_library("test_support") { public_deps = [ ":minidump" ] deps = [ + "$mini_chromium_source_parent:base", "../snapshot:test_support", "../test", - "../third_party/googletest:googletest", - "$mini_chromium_source_parent:base", + "../third_party/googletest", "../util", ] @@ -179,10 +179,10 @@ source_set("minidump_test") { deps = [ ":test_support", + "$mini_chromium_source_parent:base", "../snapshot:test_support", "../test", - "../third_party/googletest:googletest", - "$mini_chromium_source_parent:base", + "../third_party/googletest", "../util", ] diff --git a/minidump/minidump_context.h b/minidump/minidump_context.h index 12413de02b..9dcbcb468c 100644 --- a/minidump/minidump_context.h +++ b/minidump/minidump_context.h @@ -80,7 +80,7 @@ enum MinidumpContextX86Flags : uint32_t { //! \brief Indicates the validity of non-control integer registers //! (`CONTEXT_INTEGER`). //! - //! The `edi`, `esi`, `ebx`, `edx`, `ecx, and `eax` fields are valid. + //! The `edi`, `esi`, `ebx`, `edx`, `ecx`, and `eax` fields are valid. kMinidumpContextX86Integer = kMinidumpContextX86 | 0x00000002, //! \brief Indicates the validity of non-control segment registers @@ -122,10 +122,9 @@ enum MinidumpContextX86Flags : uint32_t { //! \brief Indicates the validity of all registers except `xsave` data. //! (`CONTEXT_ALL`). - kMinidumpContextX86All = kMinidumpContextX86Full | - kMinidumpContextX86FloatingPoint | - kMinidumpContextX86Debug | - kMinidumpContextX86Extended, + kMinidumpContextX86All = + kMinidumpContextX86Full | kMinidumpContextX86FloatingPoint | + kMinidumpContextX86Debug | kMinidumpContextX86Extended, }; //! \brief A 32-bit x86 CPU context (register state) carried in a minidump file. @@ -411,7 +410,7 @@ struct MinidumpXSaveAreaHeader { //! This is used to calculate the final size of the extended context, and //! can be validated by calling InitializeContext2 with one XSTATE feature, //! and LocateXStateFeature to determine the first offset. -//! Also see "MANAGING STATE USING THE XSAVE FEATURE SET", Ch. 13, Intel SDM. +//! Also see “MANAGING STATE USING THE XSAVE FEATURE SET”, Ch. 13, Intel SDM. constexpr uint32_t kMinidumpAMD64XSaveOffset = 0x550; //! \brief Offset of first xsave feature within the extended context area. @@ -420,9 +419,9 @@ constexpr uint32_t kMinidumpAMD64XSaveOffset = 0x550; //! Intel SDM 13.4.1. This is not where the item is in the extended compacted //! context, but is the offset recorded in the minidump. It needs to be correct //! there. See https://windows-internals.com/cet-on-windows/ for some discussion -//! "CONTEXT_XSTATE: Extended processor state chunk. The state is stored in the +//! “CONTEXT_XSTATE: Extended processor state chunk. The state is stored in the //! same format the XSAVE operation stores it with exception of the first 512 -//! bytes, i.e. starting from XSAVE_AREA_HEADER." This may vary by cpuid. +//! bytes, i.e. starting from XSAVE_AREA_HEADER.” This may vary by cpuid. constexpr uint32_t kXSaveAreaFirstOffset = 0x240; //! \brief XSAVE_CET_U_FORMAT diff --git a/minidump/minidump_context_writer_test.cc b/minidump/minidump_context_writer_test.cc index 8eaae83148..277bd4d347 100644 --- a/minidump/minidump_context_writer_test.cc +++ b/minidump/minidump_context_writer_test.cc @@ -50,14 +50,8 @@ class TestTypeNames { public: template static std::string GetName(int) { - if (std::is_same()) { - return "RVA"; - } - if (std::is_same()) { - return "RVA64"; - } - NOTREACHED_IN_MIGRATION(); - return ""; + static_assert(std::is_same() || std::is_same()); + return std::is_same() ? "RVA" : "RVA64"; } }; diff --git a/minidump/minidump_extensions.h b/minidump/minidump_extensions.h index 2ca262f326..1b00b6269b 100644 --- a/minidump/minidump_extensions.h +++ b/minidump/minidump_extensions.h @@ -124,7 +124,7 @@ enum MinidumpStreamType : uint32_t { //! file. //! //! \sa MINIDUMP_STRING -struct ALIGNAS(4) PACKED MinidumpUTF8String { +struct alignas(4) MinidumpUTF8String { // The field names do not conform to typical style, they match the names used // in MINIDUMP_STRING. This makes it easier to operate on MINIDUMP_STRING (for // UTF-16 strings) and MinidumpUTF8String using templates. @@ -138,18 +138,18 @@ struct ALIGNAS(4) PACKED MinidumpUTF8String { //! \brief The string, encoded in UTF-8, and terminated with a `NUL` byte. uint8_t Buffer[0]; -}; +} PACKED; //! \brief A variable-length array of bytes carried within a minidump file. //! The data have no intrinsic type and should be interpreted according //! to their referencing context. -struct ALIGNAS(4) PACKED MinidumpByteArray { +struct alignas(4) MinidumpByteArray { //! \brief The length of the #data field. uint32_t length; //! \brief The bytes of data. uint8_t data[0]; -}; +} PACKED; //! \brief CPU type values for MINIDUMP_SYSTEM_INFO::ProcessorArchitecture. //! @@ -281,16 +281,16 @@ enum MinidumpOS : uint32_t { }; //! \brief A list of ::RVA pointers. -struct ALIGNAS(4) PACKED MinidumpRVAList { +struct alignas(4) MinidumpRVAList { //! \brief The number of children present in the #children array. uint32_t count; //! \brief Pointers to other structures in the minidump file. RVA children[0]; -}; +} PACKED; //! \brief A key-value pair. -struct ALIGNAS(4) PACKED MinidumpSimpleStringDictionaryEntry { +struct alignas(4) MinidumpSimpleStringDictionaryEntry { //! \brief ::RVA of a MinidumpUTF8String containing the key of a key-value //! pair. RVA key; @@ -298,19 +298,19 @@ struct ALIGNAS(4) PACKED MinidumpSimpleStringDictionaryEntry { //! \brief ::RVA of a MinidumpUTF8String containing the value of a key-value //! pair. RVA value; -}; +} PACKED; //! \brief A list of key-value pairs. -struct ALIGNAS(4) PACKED MinidumpSimpleStringDictionary { +struct alignas(4) MinidumpSimpleStringDictionary { //! \brief The number of key-value pairs present. uint32_t count; //! \brief A list of MinidumpSimpleStringDictionaryEntry entries. MinidumpSimpleStringDictionaryEntry entries[0]; -}; +} PACKED; //! \brief A typed annotation object. -struct ALIGNAS(4) PACKED MinidumpAnnotation { +struct alignas(4) MinidumpAnnotation { //! \brief ::RVA of a MinidumpUTF8String containing the name of the //! annotation. RVA name; @@ -324,16 +324,16 @@ struct ALIGNAS(4) PACKED MinidumpAnnotation { //! \brief ::RVA of a MinidumpByteArray to the data for the annotation. RVA value; -}; +} PACKED; //! \brief A list of annotation objects. -struct ALIGNAS(4) PACKED MinidumpAnnotationList { +struct alignas(4) MinidumpAnnotationList { //! \brief The number of annotation objects present. uint32_t count; //! \brief A list of MinidumpAnnotation objects. MinidumpAnnotation objects[0]; -}; +} PACKED; //! \brief Additional Crashpad-specific information about a module carried //! within a minidump file. @@ -350,7 +350,7 @@ struct ALIGNAS(4) PACKED MinidumpAnnotationList { //! fields are valid or not. //! //! \sa MinidumpModuleCrashpadInfoList -struct ALIGNAS(4) PACKED MinidumpModuleCrashpadInfo { +struct alignas(4) MinidumpModuleCrashpadInfo { //! \brief The structure’s currently-defined version number. //! //! \sa version @@ -395,12 +395,12 @@ struct ALIGNAS(4) PACKED MinidumpModuleCrashpadInfo { //! //! This field may be present when #version is at least `1`. MINIDUMP_LOCATION_DESCRIPTOR annotation_objects; -}; +} PACKED; //! \brief A link between a MINIDUMP_MODULE structure and additional //! Crashpad-specific information about a module carried within a minidump //! file. -struct ALIGNAS(4) PACKED MinidumpModuleCrashpadInfoLink { +struct alignas(4) MinidumpModuleCrashpadInfoLink { //! \brief A link to a MINIDUMP_MODULE structure in the module list stream. //! //! This field is an index into MINIDUMP_MODULE_LIST::Modules. This field’s @@ -413,7 +413,7 @@ struct ALIGNAS(4) PACKED MinidumpModuleCrashpadInfoLink { //! MINIDUMP_LOCATION_DESCRIPTOR pointers to allow for future growth of the //! MinidumpModuleCrashpadInfo structure. MINIDUMP_LOCATION_DESCRIPTOR location; -}; +} PACKED; //! \brief Additional Crashpad-specific information about modules carried within //! a minidump file. @@ -427,7 +427,7 @@ struct ALIGNAS(4) PACKED MinidumpModuleCrashpadInfoLink { //! structure carried within the minidump file will necessarily have //! Crashpad-specific information provided by a MinidumpModuleCrashpadInfo //! structure. -struct ALIGNAS(4) PACKED MinidumpModuleCrashpadInfoList { +struct alignas(4) MinidumpModuleCrashpadInfoList { //! \brief The number of children present in the #modules array. uint32_t count; @@ -435,7 +435,7 @@ struct ALIGNAS(4) PACKED MinidumpModuleCrashpadInfoList { //! MINIDUMP_MODULE structures that contain module information //! traditionally carried within minidump files. MinidumpModuleCrashpadInfoLink modules[0]; -}; +} PACKED; //! \brief Additional Crashpad-specific information carried within a minidump //! file. @@ -446,7 +446,7 @@ struct ALIGNAS(4) PACKED MinidumpModuleCrashpadInfoList { //! structure. Revise #kVersion and document each field’s validity based on //! #version, so that newer parsers will be able to determine whether the added //! fields are valid or not. -struct ALIGNAS(4) PACKED MinidumpCrashpadInfo { +struct alignas(4) MinidumpCrashpadInfo { // UUID has a constructor, which makes it non-POD, which makes this structure // non-POD. In order for the default constructor to zero-initialize other // members, an explicit constructor must be provided. @@ -533,7 +533,7 @@ struct ALIGNAS(4) PACKED MinidumpCrashpadInfo { //! This field is present when #version is at least `1`, if the size of the //! structure is large enough to accommodate it. uint64_t address_mask; -}; +} PACKED; #if defined(COMPILER_MSVC) #pragma pack(pop) @@ -543,4 +543,4 @@ struct ALIGNAS(4) PACKED MinidumpCrashpadInfo { } // namespace crashpad -#endif // CRASHPAD_MINIDUMP_MINIDUMP_EXTENSIONS_H_ +#endif // CRASHPAD_MINIDUMP_MINIDUMP_EXTENSIONS_H_ \ No newline at end of file diff --git a/minidump/minidump_string_writer_test.cc b/minidump/minidump_string_writer_test.cc index 573eac6996..e72832b46d 100644 --- a/minidump/minidump_string_writer_test.cc +++ b/minidump/minidump_string_writer_test.cc @@ -36,14 +36,8 @@ class TestTypeNames { public: template static std::string GetName(int) { - if (std::is_same()) { - return "RVA"; - } - if (std::is_same()) { - return "RVA64"; - } - NOTREACHED_IN_MIGRATION(); - return ""; + static_assert(std::is_same() || std::is_same()); + return std::is_same() ? "RVA" : "RVA64"; } }; diff --git a/minidump/minidump_system_info_writer.cc b/minidump/minidump_system_info_writer.cc index 9fe3ac0e6b..e9e05124aa 100644 --- a/minidump/minidump_system_info_writer.cc +++ b/minidump/minidump_system_info_writer.cc @@ -136,9 +136,7 @@ void MinidumpSystemInfoWriter::InitializeFromSnapshot( cpu_architecture = kMinidumpCPUArchitectureRISCV64Breakpad; break; default: - NOTREACHED_IN_MIGRATION(); - cpu_architecture = kMinidumpCPUArchitectureUnknown; - break; + NOTREACHED(); } SetCPUArchitecture(cpu_architecture); @@ -185,9 +183,7 @@ void MinidumpSystemInfoWriter::InitializeFromSnapshot( operating_system = kMinidumpOSIOS; break; default: - NOTREACHED_IN_MIGRATION(); - operating_system = kMinidumpOSUnknown; - break; + NOTREACHED(); } SetOS(operating_system); diff --git a/snapshot/BUILD.gn b/snapshot/BUILD.gn index 2ae944c3e8..5ae9c1018f 100644 --- a/snapshot/BUILD.gn +++ b/snapshot/BUILD.gn @@ -286,7 +286,7 @@ if (crashpad_is_linux) { deps = [ ":snapshot", "$mini_chromium_source_parent:base", - "../util:util", + "../util", ] seed_corpus = "elf/elf_image_reader_fuzzer_corpus" } @@ -436,8 +436,8 @@ source_set("snapshot_test") { "../compat", "../minidump:format", "../test", + "../third_party/googletest", "../third_party/googletest:googlemock", - "../third_party/googletest:googletest", "../util", ] diff --git a/snapshot/cpu_context.cc b/snapshot/cpu_context.cc index 71bf4ace4c..6b69edefd5 100644 --- a/snapshot/cpu_context.cc +++ b/snapshot/cpu_context.cc @@ -174,8 +174,7 @@ uint64_t CPUContext::InstructionPointer() const { case kCPUArchitectureRISCV64: return riscv64->pc; default: - NOTREACHED_IN_MIGRATION(); - return ~0ull; + NOTREACHED(); } } @@ -192,8 +191,7 @@ uint64_t CPUContext::StackPointer() const { case kCPUArchitectureRISCV64: return riscv64->regs[1]; default: - NOTREACHED_IN_MIGRATION(); - return ~0ull; + NOTREACHED(); } } @@ -202,13 +200,11 @@ uint64_t CPUContext::ShadowStackPointer() const { case kCPUArchitectureX86: case kCPUArchitectureARM: case kCPUArchitectureARM64: - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); case kCPUArchitectureX86_64: return x86_64->xstate.cet_u.ssp; default: - NOTREACHED_IN_MIGRATION(); - return ~0ull; + NOTREACHED(); } } @@ -221,8 +217,7 @@ bool CPUContext::HasShadowStack() const { case kCPUArchitectureX86_64: return x86_64->xstate.cet_u.cetmsr != 0; default: - NOTREACHED_IN_MIGRATION(); - return false; + NOTREACHED(); } } @@ -238,8 +233,7 @@ bool CPUContext::Is64Bit() const { case kCPUArchitectureMIPSEL: return false; default: - NOTREACHED_IN_MIGRATION(); - return false; + NOTREACHED(); } } diff --git a/snapshot/crashpad_info_size_test_module.cc b/snapshot/crashpad_info_size_test_module.cc index 3318702f66..9ec736ef3b 100644 --- a/snapshot/crashpad_info_size_test_module.cc +++ b/snapshot/crashpad_info_size_test_module.cc @@ -53,6 +53,9 @@ struct TestCrashpadInfo { #if !defined(CRASHPAD_INFO_SIZE_TEST_MODULE_SMALL) void* user_data_minidump_stream_head_; void* annotations_list_; +#if BUILDFLAG(IS_IOS) + void* intermediate_dump_extra_memory_ranges_; +#endif // BUILDFLAG(IS_IOS) #endif // CRASHPAD_INFO_SIZE_TEST_MODULE_SMALL #if defined(CRASHPAD_INFO_SIZE_TEST_MODULE_LARGE) uint8_t trailer_[64 * 1024]; @@ -95,6 +98,10 @@ TestCrashpadInfo g_test_crashpad_info = {'CPad', #if !defined(CRASHPAD_INFO_SIZE_TEST_MODULE_SMALL) nullptr, nullptr, +#if BUILDFLAG(IS_IOS) + nullptr, +#endif // BUILDFLAG(IS_IOS) + #endif // CRASHPAD_INFO_SIZE_TEST_MODULE_SMALL #if defined(CRASHPAD_INFO_SIZE_TEST_MODULE_LARGE) {} diff --git a/snapshot/fuchsia/process_reader_fuchsia.cc b/snapshot/fuchsia/process_reader_fuchsia.cc index 8b10e2cccc..36aedb3aa5 100644 --- a/snapshot/fuchsia/process_reader_fuchsia.cc +++ b/snapshot/fuchsia/process_reader_fuchsia.cc @@ -16,6 +16,7 @@ #include #include +#include #include #include "base/check_op.h" @@ -176,7 +177,8 @@ void ProcessReaderFuchsia::InitializeModules() { zx_status_t status = process_->get_property( ZX_PROP_PROCESS_DEBUG_ADDR, &debug_address, sizeof(debug_address)); if (status != ZX_OK || debug_address == 0) { - LOG(ERROR) << "zx_object_get_property ZX_PROP_PROCESS_DEBUG_ADDR"; + ZX_LOG(ERROR, status) + << "zx_object_get_property ZX_PROP_PROCESS_DEBUG_ADDR"; return; } diff --git a/snapshot/fuchsia/process_reader_fuchsia_test.cc b/snapshot/fuchsia/process_reader_fuchsia_test.cc index 05d1a14257..ed36019d4f 100644 --- a/snapshot/fuchsia/process_reader_fuchsia_test.cc +++ b/snapshot/fuchsia/process_reader_fuchsia_test.cc @@ -147,7 +147,7 @@ CRASHPAD_CHILD_TEST_MAIN(ProcessReaderChildThreadsTestMain) { EXPECT_EQ(status, ZX_OK); constexpr size_t kNumThreads = 5; - struct ThreadData thread_data[kNumThreads] = {{0, 0}}; + struct ThreadData thread_data[kNumThreads] = {{0, ""}}; for (size_t i = 0; i < kNumThreads; ++i) { thread_data[i] = { diff --git a/snapshot/fuchsia/process_snapshot_fuchsia.cc b/snapshot/fuchsia/process_snapshot_fuchsia.cc index 1f51a4c900..bb144cba95 100644 --- a/snapshot/fuchsia/process_snapshot_fuchsia.cc +++ b/snapshot/fuchsia/process_snapshot_fuchsia.cc @@ -115,8 +115,7 @@ crashpad::ProcessID ProcessSnapshotFuchsia::ProcessID() const { crashpad::ProcessID ProcessSnapshotFuchsia::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); // TODO(scottmg): https://crashpad.chromium.org/bug/196 - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); } void ProcessSnapshotFuchsia::SnapshotTime(timeval* snapshot_time) const { diff --git a/snapshot/fuchsia/system_snapshot_fuchsia.cc b/snapshot/fuchsia/system_snapshot_fuchsia.cc index a764e855a0..b95bf0d061 100644 --- a/snapshot/fuchsia/system_snapshot_fuchsia.cc +++ b/snapshot/fuchsia/system_snapshot_fuchsia.cc @@ -108,8 +108,7 @@ uint32_t SystemSnapshotFuchsia::CPUX86Signature() const { #if defined(ARCH_CPU_X86_64) return cpuid_.Signature(); #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -118,8 +117,7 @@ uint64_t SystemSnapshotFuchsia::CPUX86Features() const { #if defined(ARCH_CPU_X86_64) return cpuid_.Features(); #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -128,8 +126,7 @@ uint64_t SystemSnapshotFuchsia::CPUX86ExtendedFeatures() const { #if defined(ARCH_CPU_X86_64) return cpuid_.ExtendedFeatures(); #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -137,8 +134,7 @@ uint32_t SystemSnapshotFuchsia::CPUX86Leaf7Features() const { #if defined(ARCH_CPU_X86_64) return cpuid_.Leaf7Features(); #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -147,8 +143,7 @@ bool SystemSnapshotFuchsia::CPUX86SupportsDAZ() const { #if defined(ARCH_CPU_X86_64) return cpuid_.SupportsDAZ(); #else - NOTREACHED_IN_MIGRATION(); - return false; + NOTREACHED(); #endif } diff --git a/snapshot/ios/exception_snapshot_ios_intermediate_dump.h b/snapshot/ios/exception_snapshot_ios_intermediate_dump.h index dc9d5954da..71ad9c471c 100644 --- a/snapshot/ios/exception_snapshot_ios_intermediate_dump.h +++ b/snapshot/ios/exception_snapshot_ios_intermediate_dump.h @@ -25,7 +25,6 @@ #include "snapshot/exception_snapshot.h" #include "snapshot/ios/memory_snapshot_ios_intermediate_dump.h" #include "util/ios/ios_intermediate_dump_map.h" -#include "util/mach/mach_extensions.h" #include "util/misc/initialization_state_dcheck.h" namespace crashpad { diff --git a/snapshot/ios/intermediate_dump_reader_util.cc b/snapshot/ios/intermediate_dump_reader_util.cc index 4e701ce2bf..a4ecfb85a9 100644 --- a/snapshot/ios/intermediate_dump_reader_util.cc +++ b/snapshot/ios/intermediate_dump_reader_util.cc @@ -14,6 +14,7 @@ #include "snapshot/ios/intermediate_dump_reader_util.h" +#include "base/logging.h" #include "util/ios/ios_intermediate_dump_data.h" #include "util/ios/ios_intermediate_dump_map.h" #include "util/misc/metrics.h" diff --git a/snapshot/ios/intermediate_dump_reader_util.h b/snapshot/ios/intermediate_dump_reader_util.h index 480f36fcc9..c96c8e7bbb 100644 --- a/snapshot/ios/intermediate_dump_reader_util.h +++ b/snapshot/ios/intermediate_dump_reader_util.h @@ -14,7 +14,6 @@ #include -#include "base/logging.h" #include "util/ios/ios_intermediate_dump_data.h" #include "util/ios/ios_intermediate_dump_map.h" @@ -73,9 +72,10 @@ const IOSIntermediateDumpList* GetListFromMap(const IOSIntermediateDumpMap* map, //! //! \param[in] map The map to load from. //! \param[in] key The key to load from \a map. +//! \param[out] value The loaded string. //! -//! \return Returns `true` if the string could be loaded, otherwise returns -//! `false` and logs an error. +//! \return Returns `true` with \a value set accordingly if the string could be +//! loaded, otherwise returns `false` and logs an error. bool GetDataStringFromMap(const IOSIntermediateDumpMap* map, const IntermediateDumpKey& key, std::string* value); diff --git a/snapshot/ios/memory_snapshot_ios_intermediate_dump.h b/snapshot/ios/memory_snapshot_ios_intermediate_dump.h index 742d8e040b..a3fe81743f 100644 --- a/snapshot/ios/memory_snapshot_ios_intermediate_dump.h +++ b/snapshot/ios/memory_snapshot_ios_intermediate_dump.h @@ -15,10 +15,11 @@ #ifndef CRASHPAD_SNAPSHOT_IOS_INTERMEDIATE_DUMP_MEMORY_SNAPSHOT_IOS_INTERMEDIATEDUMP_H_ #define CRASHPAD_SNAPSHOT_IOS_INTERMEDIATE_DUMP_MEMORY_SNAPSHOT_IOS_INTERMEDIATEDUMP_H_ +#include + #include #include "snapshot/memory_snapshot.h" -#include "util/misc/address_types.h" #include "util/misc/initialization_state_dcheck.h" namespace crashpad { @@ -39,6 +40,7 @@ class MemorySnapshotIOSIntermediateDump final : public MemorySnapshot { //! \brief Initializes the object. //! //! \param[in] address The base address of the memory region to snapshot. + //! \param[in] data The destination address where the snapshot will be stored. //! \param[in] size The size of the memory region to snapshot. void Initialize(vm_address_t address, vm_address_t data, vm_size_t size); diff --git a/snapshot/ios/module_snapshot_ios_intermediate_dump.cc b/snapshot/ios/module_snapshot_ios_intermediate_dump.cc index 09498f148a..16d2282c81 100644 --- a/snapshot/ios/module_snapshot_ios_intermediate_dump.cc +++ b/snapshot/ios/module_snapshot_ios_intermediate_dump.cc @@ -127,6 +127,63 @@ bool ModuleSnapshotIOSIntermediateDump::Initialize( } } + const IOSIntermediateDumpList* extra_memory_regions_array = + image_data->GetAsList(IntermediateDumpKey::kModuleExtraMemoryRegions); + if (extra_memory_regions_array) { + for (auto& region : *extra_memory_regions_array) { + vm_address_t address; + const IOSIntermediateDumpData* region_data = + region->GetAsData(Key::kModuleExtraMemoryRegionData); + if (!region_data) + continue; + if (GetDataValueFromMap( + region.get(), Key::kModuleExtraMemoryRegionAddress, &address)) { + const std::vector& bytes = region_data->bytes(); + vm_size_t data_size = bytes.size(); + if (data_size == 0) + continue; + + const vm_address_t data = + reinterpret_cast(bytes.data()); + + auto memory = + std::make_unique(); + memory->Initialize(address, data, data_size); + extra_memory_.push_back(std::move(memory)); + } + } + } + + const IOSIntermediateDumpList* intermediate_dump_extra_memory_regions_array = + image_data->GetAsList( + IntermediateDumpKey::kModuleIntermediateDumpExtraMemoryRegions); + if (intermediate_dump_extra_memory_regions_array) { + for (auto& region : *intermediate_dump_extra_memory_regions_array) { + vm_address_t address; + const IOSIntermediateDumpData* region_data = + region->GetAsData(Key::kModuleIntermediateDumpExtraMemoryRegionData); + if (!region_data) + continue; + if (GetDataValueFromMap( + region.get(), + Key::kModuleIntermediateDumpExtraMemoryRegionAddress, + &address)) { + const std::vector& bytes = region_data->bytes(); + vm_size_t data_size = bytes.size(); + if (data_size == 0) + continue; + + const vm_address_t data = + reinterpret_cast(bytes.data()); + + auto memory = + std::make_unique(); + memory->Initialize(address, data, data_size); + intermediate_dump_extra_memory_.push_back(std::move(memory)); + } + } + } + const IOSIntermediateDumpMap* crash_info_dump = image_data->GetAsMap(IntermediateDumpKey::kAnnotationsCrashInfo); if (crash_info_dump) { @@ -266,6 +323,24 @@ ModuleSnapshotIOSIntermediateDump::ExtraMemoryRanges() const { return std::set>(); } +std::vector +ModuleSnapshotIOSIntermediateDump::ExtraMemory() const { + std::vector extra_memory; + for (const auto& memory : extra_memory_) { + extra_memory.push_back(memory.get()); + } + return extra_memory; +} + +std::vector +ModuleSnapshotIOSIntermediateDump::IntermediateDumpExtraMemory() const { + std::vector intermediate_dump_extra_memory; + for (const auto& memory : intermediate_dump_extra_memory_) { + intermediate_dump_extra_memory.push_back(memory.get()); + } + return intermediate_dump_extra_memory; +} + std::vector ModuleSnapshotIOSIntermediateDump::CustomMinidumpStreams() const { return std::vector(); diff --git a/snapshot/ios/module_snapshot_ios_intermediate_dump.h b/snapshot/ios/module_snapshot_ios_intermediate_dump.h index b018d2d2fe..ec7c9602a4 100644 --- a/snapshot/ios/module_snapshot_ios_intermediate_dump.h +++ b/snapshot/ios/module_snapshot_ios_intermediate_dump.h @@ -23,7 +23,7 @@ #include #include -#include "snapshot/crashpad_info_client_options.h" +#include "snapshot/ios/memory_snapshot_ios_intermediate_dump.h" #include "snapshot/module_snapshot.h" #include "util/ios/ios_intermediate_dump_map.h" #include "util/misc/initialization_state_dcheck.h" @@ -46,8 +46,8 @@ class ModuleSnapshotIOSIntermediateDump final : public ModuleSnapshot { //! \brief Initialize the snapshot //! - //! \param[in] exception_data The intermediate dump map used to initialize - //! this object. + //! \param[in] image_data The intermediate dump map used to initialize this + //! object. //! //! \return `true` if the snapshot could be created. bool Initialize(const IOSIntermediateDumpMap* image_data); @@ -75,6 +75,10 @@ class ModuleSnapshotIOSIntermediateDump final : public ModuleSnapshot { std::set> ExtraMemoryRanges() const override; std::vector CustomMinidumpStreams() const override; + // Used by ProcessSnapshot + std::vector ExtraMemory() const; + std::vector IntermediateDumpExtraMemory() const; + private: std::string name_; uint64_t address_; @@ -87,7 +91,10 @@ class ModuleSnapshotIOSIntermediateDump final : public ModuleSnapshot { std::vector annotations_vector_; std::map annotations_simple_map_; std::vector annotation_objects_; - + std::vector> + extra_memory_; + std::vector> + intermediate_dump_extra_memory_; InitializationStateDcheck initialized_; }; diff --git a/snapshot/ios/process_snapshot_ios_intermediate_dump.cc b/snapshot/ios/process_snapshot_ios_intermediate_dump.cc index 609ea654cd..de9caebe04 100644 --- a/snapshot/ios/process_snapshot_ios_intermediate_dump.cc +++ b/snapshot/ios/process_snapshot_ios_intermediate_dump.cc @@ -304,7 +304,25 @@ std::vector ProcessSnapshotIOSIntermediateDump::Handles() std::vector ProcessSnapshotIOSIntermediateDump::ExtraMemory() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return std::vector(); + std::vector extra_memory; + for (const auto& module : modules_) { + for (const auto& memory : module->ExtraMemory()) { + extra_memory.push_back(memory); + } + } + return extra_memory; +} + +std::vector +ProcessSnapshotIOSIntermediateDump::IntermediateDumpExtraMemory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + std::vector intermediate_dump_extra_memory; + for (const auto& module : modules_) { + for (const auto& memory : module->IntermediateDumpExtraMemory()) { + intermediate_dump_extra_memory.push_back(memory); + } + } + return intermediate_dump_extra_memory; } const ProcessMemory* ProcessSnapshotIOSIntermediateDump::Memory() const { diff --git a/snapshot/ios/process_snapshot_ios_intermediate_dump.h b/snapshot/ios/process_snapshot_ios_intermediate_dump.h index 8d5d048d17..1bdedcf10c 100644 --- a/snapshot/ios/process_snapshot_ios_intermediate_dump.h +++ b/snapshot/ios/process_snapshot_ios_intermediate_dump.h @@ -99,6 +99,11 @@ class ProcessSnapshotIOSIntermediateDump final : public ProcessSnapshot { std::vector ExtraMemory() const override; const ProcessMemory* Memory() const override; + // Returns ModuleSnapshotIOSIntermediateDump::IntermediateDumpExtraMemory + // stored in the intermediate dump. This is used by UserExtensionStreams for + // processing the snapshot. This memory will not be written to a minidump + std::vector IntermediateDumpExtraMemory() const; + private: // Retain the reader for the lifetime of the ProcessSnapshot so large chunks // of data do not need to be copied around (such as MemorySnapshot @@ -121,6 +126,8 @@ class ProcessSnapshotIOSIntermediateDump final : public ProcessSnapshot { UUID client_id_; std::map annotations_simple_map_; timeval snapshot_time_; + std::vector> + extra_memory_; InitializationStateDcheck initialized_; }; diff --git a/snapshot/ios/system_snapshot_ios_intermediate_dump.cc b/snapshot/ios/system_snapshot_ios_intermediate_dump.cc index 6a9f2c1c52..7dae6772ea 100644 --- a/snapshot/ios/system_snapshot_ios_intermediate_dump.cc +++ b/snapshot/ios/system_snapshot_ios_intermediate_dump.cc @@ -15,23 +15,13 @@ #include "snapshot/ios/system_snapshot_ios_intermediate_dump.h" #include -#include #include #include #include -#include - -#include "base/apple/mach_logging.h" -#include "base/logging.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" -#include "snapshot/cpu_context.h" #include "snapshot/ios/intermediate_dump_reader_util.h" -#include "snapshot/posix/timezone.h" -#include "util/ios/ios_intermediate_dump_data.h" -#include "util/mac/mac_util.h" -#include "util/numeric/in_range_cast.h" namespace crashpad { diff --git a/snapshot/ios/system_snapshot_ios_intermediate_dump.h b/snapshot/ios/system_snapshot_ios_intermediate_dump.h index 339139b7c7..80cc6ea386 100644 --- a/snapshot/ios/system_snapshot_ios_intermediate_dump.h +++ b/snapshot/ios/system_snapshot_ios_intermediate_dump.h @@ -21,7 +21,6 @@ #include "snapshot/system_snapshot.h" #include "util/ios/ios_intermediate_dump_map.h" -#include "util/ios/ios_system_data_collector.h" #include "util/misc/initialization_state_dcheck.h" namespace crashpad { @@ -44,7 +43,6 @@ class SystemSnapshotIOSIntermediateDump final : public SystemSnapshot { //! //! \param[in] system_data An intermediate dump map containing various system //! data points. - //! \return `true` if the snapshot could be created. void Initialize(const IOSIntermediateDumpMap* system_data); // SystemSnapshot: diff --git a/snapshot/ios/thread_snapshot_ios_intermediate_dump.cc b/snapshot/ios/thread_snapshot_ios_intermediate_dump.cc index d37ccef3bf..af9dd2b872 100644 --- a/snapshot/ios/thread_snapshot_ios_intermediate_dump.cc +++ b/snapshot/ios/thread_snapshot_ios_intermediate_dump.cc @@ -242,6 +242,7 @@ uint64_t ThreadSnapshotIOSIntermediateDump::ThreadSpecificDataAddress() const { std::vector ThreadSnapshotIOSIntermediateDump::ExtraMemory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::vector extra_memory; for (const auto& memory : extra_memory_) { extra_memory.push_back(memory.get()); diff --git a/snapshot/linux/debug_rendezvous_test.cc b/snapshot/linux/debug_rendezvous_test.cc index 62c5d4e85d..2efa32befc 100644 --- a/snapshot/linux/debug_rendezvous_test.cc +++ b/snapshot/linux/debug_rendezvous_test.cc @@ -22,7 +22,6 @@ #include "base/format_macros.h" #include "base/strings/string_number_conversions.h" -#include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" diff --git a/snapshot/linux/exception_snapshot_linux.h b/snapshot/linux/exception_snapshot_linux.h index f931d33002..e08fce59a1 100644 --- a/snapshot/linux/exception_snapshot_linux.h +++ b/snapshot/linux/exception_snapshot_linux.h @@ -53,6 +53,9 @@ class ExceptionSnapshotLinux final : public ExceptionSnapshot { //! \param[in] context_address The address in the target process' address //! space of the ucontext_t passed to the signal handler. //! \param[in] thread_id The thread ID of the thread that received the signal. + //! \param[inout] gather_indirectly_referenced_memory_cap The remaining budget + //! for indirectly referenced memory, honored on entry and updated on + //! return. //! //! \return `true` if the snapshot could be created, `false` otherwise with //! an appropriate message logged. diff --git a/snapshot/linux/signal_context.h b/snapshot/linux/signal_context.h index 89e697a08b..a76852e20e 100644 --- a/snapshot/linux/signal_context.h +++ b/snapshot/linux/signal_context.h @@ -16,11 +16,11 @@ #define CRASHPAD_SNAPSHOT_LINUX_SNAPSHOT_SIGNAL_CONTEXT_H_ #include +#include #include #include #include -#include #include #include "build/build_config.h" diff --git a/snapshot/linux/system_snapshot_linux.cc b/snapshot/linux/system_snapshot_linux.cc index 3eb91b4f40..99c35b3aaf 100644 --- a/snapshot/linux/system_snapshot_linux.cc +++ b/snapshot/linux/system_snapshot_linux.cc @@ -19,13 +19,13 @@ #include #include +#include #include "base/check_op.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/notreached.h" #include "base/strings/string_number_conversions.h" -#include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "snapshot/cpu_context.h" @@ -63,8 +63,8 @@ bool ReadCPUsOnline(uint32_t* first_cpu, uint8_t* cpu_count) { std::string left, right; if (SplitStringFirst(range, '-', &left, &right)) { unsigned int start, end; - if (!base::StringToUint(base::StringPiece(left), &start) || - !base::StringToUint(base::StringPiece(right), &end) || end <= start) { + if (!base::StringToUint(left, &start) || + !base::StringToUint(right, &end) || end <= start) { LOG(ERROR) << "format error: " << range; return false; } @@ -79,7 +79,7 @@ bool ReadCPUsOnline(uint32_t* first_cpu, uint8_t* cpu_count) { } } else { unsigned int cpuno; - if (!base::StringToUint(base::StringPiece(range), &cpuno)) { + if (!base::StringToUint(range, &cpuno)) { LOG(ERROR) << "format error"; return false; } @@ -111,7 +111,7 @@ bool ReadFreqFile(const std::string& filename, uint64_t* hz) { contents.pop_back(); uint64_t khz; - if (!base::StringToUint64(base::StringPiece(contents), &khz)) { + if (!base::StringToUint64(contents, &khz)) { LOG(ERROR) << "format error"; return false; } @@ -121,14 +121,35 @@ bool ReadFreqFile(const std::string& filename, uint64_t* hz) { } #if BUILDFLAG(IS_ANDROID) +struct ReadPropertyData { + std::string* value; + bool read = false; +}; + +void ReadPropertyCallback(void* cookie, + const char* name, + const char* value, + uint32_t serial) { + auto* data = static_cast(cookie); + data->value->assign(value); + data->read = true; +} + bool ReadProperty(const char* property, std::string* value) { - char value_buffer[PROP_VALUE_MAX]; - int length = __system_property_get(property, value_buffer); - if (length <= 0) { + const prop_info* prop = __system_property_find(property); + if (!prop) { + LOG(ERROR) << "Couldn't read property " << property; + return false; + } + + ReadPropertyData data; + data.value = value; + __system_property_read_callback(prop, ReadPropertyCallback, &data); + + if (!data.read) { LOG(ERROR) << "Couldn't read property " << property; return false; } - *value = value_buffer; return true; } #endif // BUILDFLAG(IS_ANDROID) @@ -276,8 +297,7 @@ uint32_t SystemSnapshotLinux::CPUX86Signature() const { #if defined(ARCH_CPU_X86_FAMILY) return cpuid_.Signature(); #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -286,8 +306,7 @@ uint64_t SystemSnapshotLinux::CPUX86Features() const { #if defined(ARCH_CPU_X86_FAMILY) return cpuid_.Features(); #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -296,8 +315,7 @@ uint64_t SystemSnapshotLinux::CPUX86ExtendedFeatures() const { #if defined(ARCH_CPU_X86_FAMILY) return cpuid_.ExtendedFeatures(); #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -306,8 +324,7 @@ uint32_t SystemSnapshotLinux::CPUX86Leaf7Features() const { #if defined(ARCH_CPU_X86_FAMILY) return cpuid_.Leaf7Features(); #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -316,8 +333,7 @@ bool SystemSnapshotLinux::CPUX86SupportsDAZ() const { #if defined(ARCH_CPU_X86_FAMILY) return cpuid_.SupportsDAZ(); #else - NOTREACHED_IN_MIGRATION(); - return false; + NOTREACHED(); #endif // ARCH_CPU_X86_FMAILY } @@ -411,13 +427,13 @@ void SystemSnapshotLinux::ReadKernelVersion(const std::string& version_string) { return; } - if (!base::StringToInt(base::StringPiece(versions[0]), &os_version_major_)) { + if (!base::StringToInt(versions[0], &os_version_major_)) { LOG(WARNING) << "no kernel version"; return; } DCHECK_GE(os_version_major_, 3); - if (!base::StringToInt(base::StringPiece(versions[1]), &os_version_minor_)) { + if (!base::StringToInt(versions[1], &os_version_minor_)) { LOG(WARNING) << "no major revision"; return; } @@ -427,7 +443,7 @@ void SystemSnapshotLinux::ReadKernelVersion(const std::string& version_string) { if (minor_rev_end == std::string::npos) { minor_rev_end = versions[2].size(); } - if (!base::StringToInt(base::StringPiece(versions[2].c_str(), minor_rev_end), + if (!base::StringToInt(std::string_view(versions[2].c_str(), minor_rev_end), &os_version_bugfix_)) { LOG(WARNING) << "no minor revision"; return; diff --git a/snapshot/linux/thread_snapshot_linux.h b/snapshot/linux/thread_snapshot_linux.h index 2229f7a9ad..e89f90c928 100644 --- a/snapshot/linux/thread_snapshot_linux.h +++ b/snapshot/linux/thread_snapshot_linux.h @@ -44,6 +44,9 @@ class ThreadSnapshotLinux final : public ThreadSnapshot { //! the thread. //! \param[in] thread The thread within the ProcessReaderLinux for //! which the snapshot should be created. + //! \param[inout] gather_indirectly_referenced_memory_bytes_remaining The + //! remaining budget for indirectly referenced memory, honored on entry + //! and updated on return. //! //! \return `true` if the snapshot could be created, `false` otherwise with //! a message logged. diff --git a/snapshot/mac/cpu_context_mac.cc b/snapshot/mac/cpu_context_mac.cc index c8eddfcd3d..c8db26e422 100644 --- a/snapshot/mac/cpu_context_mac.cc +++ b/snapshot/mac/cpu_context_mac.cc @@ -196,8 +196,7 @@ thread_state_flavor_t InitializeCPUContextX86Flavor( } default: { - NOTREACHED_IN_MIGRATION(); - return THREAD_STATE_NONE; + NOTREACHED(); } } } @@ -377,8 +376,7 @@ thread_state_flavor_t InitializeCPUContextX86_64Flavor( } default: { - NOTREACHED_IN_MIGRATION(); - return THREAD_STATE_NONE; + NOTREACHED(); } } } @@ -553,8 +551,7 @@ thread_state_flavor_t InitializeCPUContextARM64Flavor( } default: { - NOTREACHED_IN_MIGRATION(); - return THREAD_STATE_NONE; + NOTREACHED(); } } } diff --git a/snapshot/mac/mach_o_image_annotations_reader.cc b/snapshot/mac/mach_o_image_annotations_reader.cc index ec33779b98..eda96b5056 100644 --- a/snapshot/mac/mach_o_image_annotations_reader.cc +++ b/snapshot/mac/mach_o_image_annotations_reader.cc @@ -88,7 +88,8 @@ void MachOImageAnnotationsReader::ReadCrashReporterClientAnnotations( return; } - if (crash_info.version != 4 && crash_info.version != 5) { + if (crash_info.version != 4 && crash_info.version != 5 && + crash_info.version != 7) { LOG(WARNING) << "unexpected crash info version " << crash_info.version << " in " << name_; return; @@ -104,9 +105,13 @@ void MachOImageAnnotationsReader::ReadCrashReporterClientAnnotations( return; } - // This number was totally made up out of nowhere, but it seems prudent to - // enforce some limit. - constexpr size_t kMaxMessageSize = 1024; + // It seems prudent to enforce some limit. Different users of + // CRSetCrashLogMessage and CRSetCrashLogMessage2, apparently the private + // functions used to set message and message2, use + // different buffer lengths. 15.0 dyld-1231.3 libdyld/dyld_process_info.cpp + // has `static char sCrashReporterInfo[4096]`, which seems like a reasonable + // limit. + constexpr size_t kMaxMessageSize = 4096; if (crash_info.message) { std::string message; if (process_reader_->Memory()->ReadCStringSizeLimited( diff --git a/snapshot/mac/mach_o_image_annotations_reader.h b/snapshot/mac/mach_o_image_annotations_reader.h index 58976d3baf..c3c4f13e24 100644 --- a/snapshot/mac/mach_o_image_annotations_reader.h +++ b/snapshot/mac/mach_o_image_annotations_reader.h @@ -40,10 +40,11 @@ class ProcessReaderMac; //! used by Apple code. The `message` and `message2` fields can be recovered //! from any module with a compatible data section, and are included in the //! annotations returned by Vector(). -//! - `dyld`’s `error_string`. This format is used exclusively by dyld, -//! typically for fatal errors. This string can be recovered from any +//! - dyld’s `error_string`. This format was previously used exclusively by +//! dyld, typically for fatal errors. This string can be recovered from any //! `MH_DYLINKER`-type module with this symbol, and is included in the -//! annotations returned by Vector(). +//! annotations returned by Vector(). Newer versions of dyld use +//! `crashreporter_annotations_t`. class MachOImageAnnotationsReader { public: //! \brief Constructs an object. @@ -76,7 +77,7 @@ class MachOImageAnnotationsReader { std::vector AnnotationsList() const; private: - // Reades crashreporter_annotations_t::message and + // Reads crashreporter_annotations_t::message and // crashreporter_annotations_t::message2 on behalf of Vector(). void ReadCrashReporterClientAnnotations( std::vector* vector_annotations) const; diff --git a/snapshot/mac/process_reader_mac_test.cc b/snapshot/mac/process_reader_mac_test.cc index ed6992376d..fbc810dbe2 100644 --- a/snapshot/mac/process_reader_mac_test.cc +++ b/snapshot/mac/process_reader_mac_test.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include "base/apple/mach_logging.h" #include "base/check_op.h" +#include "base/files/file_path.h" #include "base/logging.h" #include "base/posix/eintr_wrapper.h" #include "base/strings/stringprintf.h" @@ -55,6 +57,12 @@ namespace crashpad { namespace test { namespace { +#if !defined(ARCH_CPU_64_BITS) +using MachHeader = mach_header; +#else +using MachHeader = mach_header_64; +#endif + using ModulePathAndAddress = std::pair; struct PathAndAddressHash { std::size_t operator()(const ModulePathAndAddress& pair) const { @@ -835,6 +843,19 @@ bool ExpectCLKernels() { MacOSVersionNumber() >= 10'07'00; } +// Starting in dyld-1284.13 (macOS 15.4), _dyld_get_image_name (in +// dyld4::PrebuiltLoader::path) returns an absolute path for a main executable, +// even when the image was not loaded from an absolute path. This can break test +// expectations when the test executable is not invoked from an absolute path. +// As a workaround, just use the main executable’s basename for comparison +// purposes. +std::string ComparableModuleName(const std::string& module_name, + uint32_t file_type) { + return file_type == MH_EXECUTE + ? base::FilePath(module_name).BaseName().value() + : module_name; +} + TEST(ProcessReaderMac, SelfModules) { ScopedOpenCLNoOpKernel ensure_cl_kernels; ASSERT_NO_FATAL_FAILURE(ensure_cl_kernels.SetUp()); @@ -868,12 +889,14 @@ TEST(ProcessReaderMac, SelfModules) { EXPECT_EQ(file_type, static_cast(MH_EXECUTE)); } else { EXPECT_NE(file_type, static_cast(MH_EXECUTE)); + EXPECT_NE(file_type, static_cast(MH_DYLINKER)); } if (IsMalformedCLKernelsModule(module.reader->FileType(), module.name)) { cl_kernel_names.insert(module.name); } actual_modules.insert( - std::make_pair(module.name, module.reader->Address())); + std::make_pair(ComparableModuleName(module.name, file_type), + module.reader->Address())); } EXPECT_EQ(cl_kernel_names.size() > 0, ExpectCLKernels() && ensure_cl_kernels.success()); @@ -887,8 +910,11 @@ TEST(ProcessReaderMac, SelfModules) { const char* dyld_image_name = _dyld_get_image_name(index); mach_vm_address_t dyld_image_address = FromPointerCast(_dyld_get_image_header(index)); - expect_modules.insert( - std::make_pair(std::string(dyld_image_name), dyld_image_address)); + const MachHeader* image_header = + reinterpret_cast(dyld_image_address); + expect_modules.insert(std::make_pair( + ComparableModuleName(dyld_image_name, image_header->filetype), + dyld_image_address)); if (cl_kernel_names.find(dyld_image_name) == cl_kernel_names.end()) { VerifyImageExistence(dyld_image_name); } @@ -925,7 +951,6 @@ class ProcessReaderModulesChild final : public MachMultiprocess { EXPECT_EQ(file_type, static_cast(MH_EXECUTE)); } else if (i == modules.size() - 1) { EXPECT_EQ(file_type, static_cast(MH_DYLINKER)); - } else { EXPECT_NE(file_type, static_cast(MH_EXECUTE)); EXPECT_NE(file_type, static_cast(MH_DYLINKER)); @@ -934,7 +959,8 @@ class ProcessReaderModulesChild final : public MachMultiprocess { cl_kernel_names.insert(module.name); } actual_modules.insert( - std::make_pair(module.name, module.reader->Address())); + std::make_pair(ComparableModuleName(module.name, file_type), + module.reader->Address())); } // There needs to be at least an entry for the main executable, for a dylib, @@ -962,10 +988,16 @@ class ProcessReaderModulesChild final : public MachMultiprocess { mach_vm_address_t expect_address; CheckedReadFileExactly( read_handle, &expect_address, sizeof(expect_address)); - expect_modules.insert(std::make_pair(expect_name, expect_address)); + + uint32_t file_type; + CheckedReadFileExactly(read_handle, &file_type, sizeof(file_type)); + if (cl_kernel_names.find(expect_name) == cl_kernel_names.end()) { VerifyImageExistence(expect_name.c_str()); } + + expect_modules.insert(std::make_pair( + ComparableModuleName(expect_name, file_type), expect_address)); } EXPECT_EQ(cl_kernel_names.size() > 0, ExpectCLKernels() && ensure_cl_kernels_success_); @@ -1002,6 +1034,10 @@ class ProcessReaderModulesChild final : public MachMultiprocess { dyld_image_infos->dyldImageLoadAddress); } + const MachHeader* image_header = + reinterpret_cast(dyld_image_address); + uint32_t file_type = image_header->filetype; + uint32_t dyld_image_name_length = strlen(dyld_image_name); CheckedWriteFile(write_handle, &dyld_image_name_length, @@ -1012,6 +1048,7 @@ class ProcessReaderModulesChild final : public MachMultiprocess { CheckedWriteFile( write_handle, &dyld_image_address, sizeof(dyld_image_address)); + CheckedWriteFile(write_handle, &file_type, sizeof(file_type)); } // Wait for the parent to signal that it’s OK to exit by closing its end of diff --git a/snapshot/mac/process_types/crashreporterclient.proctype b/snapshot/mac/process_types/crashreporterclient.proctype index 885984bb9c..6b69c64ea7 100644 --- a/snapshot/mac/process_types/crashreporterclient.proctype +++ b/snapshot/mac/process_types/crashreporterclient.proctype @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -// The name of this file was chosen based on -// https://llvm.org/svn/llvm-project/llvm/trunk/lib/Support/PrettyStackTrace.cpp. +// The name of this file was chosen based on an #include in +// https://github.com/llvm/llvm-project/blob/main/llvm/lib/Support/PrettyStackTrace.cpp. // The name of the structure it describes was chosen based on that file as well -// as 10.9.2 cups-372.2/cups/backend/usb-darwin.c. That file also provided the -// names and types of the fields in the structure. +// as 10.9 +// https://github.com/apple-oss-distributions/cups/blame/cups-372/cups/backend/usb-darwin.c. // // This file is intended to be included multiple times in the same translation // unit, so #include guards are intentionally absent. @@ -26,8 +26,8 @@ // accessors. This file is also used by the iOS in process handler to read both // messages in client/ios_handler/in_process_intermediate_dump_handler.cc. -// Client Mach-O images will contain a __DATA,__crash_info section formatted -// according to this structure. +// Client Mach-O images will contain a __DATA,__crash_info or +// __DATA_DIRTY,__crash_info section formatted according to this structure. // // crashreporter_annotations_t is variable-length. Its length dictated by its // |version| field which is always present. A custom implementation of the @@ -45,6 +45,9 @@ PROCESS_TYPE_STRUCT_BEGIN(crashreporter_annotations_t) PROCESS_TYPE_STRUCT_VERSIONED(crashreporter_annotations_t, version) // Version 4 (OS X 10.7) + // + // From 10.9 + // https://github.com/apple-oss-distributions/cups/blame/cups-372/cups/backend/usb-darwin.c. PROCESS_TYPE_STRUCT_MEMBER(uint64_t, message) // char* PROCESS_TYPE_STRUCT_MEMBER(uint64_t, signature_string) // char* PROCESS_TYPE_STRUCT_MEMBER(uint64_t, backtrace) // char* @@ -53,7 +56,27 @@ PROCESS_TYPE_STRUCT_BEGIN(crashreporter_annotations_t) PROCESS_TYPE_STRUCT_MEMBER(uint64_t, dialog_mode) // unsigned int // Version 5 (OS X 10.11) - PROCESS_TYPE_STRUCT_MEMBER(uint64_t, unknown_0) + // + // Empirically, this is 8 bytes longer than a version 4 structure. It being a + // single field named abort_cause is gleaned from + // https://github.com/llvm/llvm-project/commit/8c345dcb9b1d3a5b0f8b6a81c7c8531b435ff3e2. + PROCESS_TYPE_STRUCT_MEMBER(uint64_t, abort_cause) + + // Version 7 (macOS 26) + // + // Empirically, the version 7 structure is 328 bytes long. The exact meaning + // of the extended structure is unknown. + // + // TODO: Look to a future version of llvm’s + // llvm/lib/Support/PrettyStackTrace.cpp or + // https://github.com/WebKit/WebKit/blob/main/Source/WTF/wtf/spi/cocoa/CrashReporterClientSPI.h + // to aid in understanding. However, this may not be fruitful: + // https://github.com/llvm/llvm-project/commit/0b7cbd23a043ea4c14bd13ccd737049d38f64b5d + // (https://github.com/llvm/llvm-project/pull/123978) indicates that + // `CRASHREPORTER_ANNOTATIONS_INITIALIZER` may be used as the initializer, + // which would provide no additional information about the arrangement of + // structure members without . + PROCESS_TYPE_STRUCT_MEMBER(uint64_t, unknown_0, [33]) PROCESS_TYPE_STRUCT_END(crashreporter_annotations_t) #endif // ! PROCESS_TYPE_STRUCT_IMPLEMENT_INTERNAL_READ_INTO && diff --git a/snapshot/mac/process_types/custom.cc b/snapshot/mac/process_types/custom.cc index 31c5cfd1d0..b0adada9de 100644 --- a/snapshot/mac/process_types/custom.cc +++ b/snapshot/mac/process_types/custom.cc @@ -191,12 +191,15 @@ bool dyld_all_image_infos::ReadInto( template size_t crashreporter_annotations_t::ExpectedSizeForVersion( decltype(crashreporter_annotations_t::version) version) { - if (version >= 5) { + if (version >= 7) { return sizeof(crashreporter_annotations_t); } - if (version >= 4) { + if (version >= 5) { return offsetof(crashreporter_annotations_t, unknown_0); } + if (version >= 4) { + return offsetof(crashreporter_annotations_t, abort_cause); + } return offsetof(crashreporter_annotations_t, message); } diff --git a/snapshot/mac/system_snapshot_mac.cc b/snapshot/mac/system_snapshot_mac.cc index 933626fc66..0242440ef0 100644 --- a/snapshot/mac/system_snapshot_mac.cc +++ b/snapshot/mac/system_snapshot_mac.cc @@ -204,8 +204,7 @@ uint32_t SystemSnapshotMac::CPUX86Signature() const { #if defined(ARCH_CPU_X86_FAMILY) return ReadIntSysctlByName("machdep.cpu.signature", 0); #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -215,8 +214,7 @@ uint64_t SystemSnapshotMac::CPUX86Features() const { #if defined(ARCH_CPU_X86_FAMILY) return ReadIntSysctlByName("machdep.cpu.feature_bits", 0); #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -226,8 +224,7 @@ uint64_t SystemSnapshotMac::CPUX86ExtendedFeatures() const { #if defined(ARCH_CPU_X86_FAMILY) return ReadIntSysctlByName("machdep.cpu.extfeature_bits", 0); #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -251,8 +248,7 @@ uint32_t SystemSnapshotMac::CPUX86Leaf7Features() const { CallCPUID(7, &eax, &ebx, &ecx, &edx); return ebx; #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -290,8 +286,7 @@ bool SystemSnapshotMac::CPUX86SupportsDAZ() const { // Test the DAZ bit. return fxsave.mxcsr_mask & (1 << 6); #else - NOTREACHED_IN_MIGRATION(); - return false; + NOTREACHED(); #endif } diff --git a/snapshot/minidump/module_snapshot_minidump.cc b/snapshot/minidump/module_snapshot_minidump.cc index 8d461b3570..36b005213a 100644 --- a/snapshot/minidump/module_snapshot_minidump.cc +++ b/snapshot/minidump/module_snapshot_minidump.cc @@ -17,6 +17,8 @@ #include #include +#include + #include "base/check_op.h" #include "base/logging.h" #include "base/notreached.h" @@ -228,15 +230,13 @@ std::vector ModuleSnapshotMinidump::AnnotationObjects() std::set> ModuleSnapshotMinidump::ExtraMemoryRanges() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 - return std::set>(); + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } std::vector ModuleSnapshotMinidump::CustomMinidumpStreams() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 - return std::vector(); + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } bool ModuleSnapshotMinidump::InitializeModuleCrashpadInfo( diff --git a/snapshot/minidump/process_snapshot_minidump.cc b/snapshot/minidump/process_snapshot_minidump.cc index 472e025764..6ec51a0354 100644 --- a/snapshot/minidump/process_snapshot_minidump.cc +++ b/snapshot/minidump/process_snapshot_minidump.cc @@ -135,8 +135,7 @@ crashpad::ProcessID ProcessSnapshotMinidump::ProcessID() const { crashpad::ProcessID ProcessSnapshotMinidump::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 - return 0; + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } void ProcessSnapshotMinidump::SnapshotTime(timeval* snapshot_time) const { @@ -208,8 +207,7 @@ std::vector ProcessSnapshotMinidump::Modules() const { std::vector ProcessSnapshotMinidump::UnloadedModules() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 - return unloaded_modules_; + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } const ExceptionSnapshot* ProcessSnapshotMinidump::Exception() const { @@ -229,8 +227,7 @@ std::vector ProcessSnapshotMinidump::MemoryMap() std::vector ProcessSnapshotMinidump::Handles() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 - return std::vector(); + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } std::vector ProcessSnapshotMinidump::ExtraMemory() diff --git a/snapshot/minidump/system_snapshot_minidump.cc b/snapshot/minidump/system_snapshot_minidump.cc index cfdedadf2d..b7c7bfd0a8 100644 --- a/snapshot/minidump/system_snapshot_minidump.cc +++ b/snapshot/minidump/system_snapshot_minidump.cc @@ -100,37 +100,32 @@ std::string SystemSnapshotMinidump::CPUVendor() const { void SystemSnapshotMinidump::CPUFrequency(uint64_t* current_hz, uint64_t* max_hz) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } uint32_t SystemSnapshotMinidump::CPUX86Signature() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 - return 0; + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } uint64_t SystemSnapshotMinidump::CPUX86Features() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 - return 0; + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } uint64_t SystemSnapshotMinidump::CPUX86ExtendedFeatures() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 - return 0; + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } uint32_t SystemSnapshotMinidump::CPUX86Leaf7Features() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 - return 0; + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } bool SystemSnapshotMinidump::CPUX86SupportsDAZ() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 - return false; + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } SystemSnapshot::OperatingSystem SystemSnapshotMinidump::GetOperatingSystem() @@ -178,14 +173,12 @@ std::string SystemSnapshotMinidump::OSVersionFull() const { std::string SystemSnapshotMinidump::MachineDescription() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 - return std::string(); + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } bool SystemSnapshotMinidump::NXEnabled() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 - return false; + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } void SystemSnapshotMinidump::TimeZone(DaylightSavingTimeStatus* dst_status, @@ -194,13 +187,12 @@ void SystemSnapshotMinidump::TimeZone(DaylightSavingTimeStatus* dst_status, std::string* standard_name, std::string* daylight_name) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } uint64_t SystemSnapshotMinidump::AddressMask() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED_IN_MIGRATION(); // https://crashpad.chromium.org/bug/10 - return 0; + NOTREACHED(); // https://crashpad.chromium.org/bug/10 } } // namespace internal diff --git a/snapshot/sanitized/process_snapshot_sanitized_test.cc b/snapshot/sanitized/process_snapshot_sanitized_test.cc index e9a835b1fb..772eadc60b 100644 --- a/snapshot/sanitized/process_snapshot_sanitized_test.cc +++ b/snapshot/sanitized/process_snapshot_sanitized_test.cc @@ -129,8 +129,9 @@ void ChildTestFunction() { CRASHPAD_CHILD_TEST_MAIN(ChildToBeSanitized) { ChildTestFunction(); - NOTREACHED_IN_MIGRATION(); - return EXIT_SUCCESS; + + // This isn't reachable unless assertions inside ChildTestFunction fail. + NOTREACHED(); } void ExpectAnnotations(ProcessSnapshot* snapshot, bool sanitized) { diff --git a/snapshot/win/end_to_end_test.py b/snapshot/win/end_to_end_test.py index 25f661c1a6..b00b3e54be 100755 --- a/snapshot/win/end_to_end_test.py +++ b/snapshot/win/end_to_end_test.py @@ -237,7 +237,7 @@ def __init__(self, cdb_path, dump_path, command): [cdb_path, '-z', dump_path, '-c', command + ';q'], text=True) def Check(self, pattern, message, re_flags=0, must_not_match=False): - match_obj = re.search(pattern, self.out, re_flags) + match_obj = re.search(pattern, self.out, flags=re_flags) if match_obj and not must_not_match: # Matched. Consume up to end of match. self.out = self.out[match_obj.end(0):] @@ -263,7 +263,7 @@ def Check(self, pattern, message, re_flags=0, must_not_match=False): g_had_failures = True def Find(self, pattern, re_flags=0): - match_obj = re.search(pattern, self.out, re_flags) + match_obj = re.search(pattern, self.out, flags=re_flags) if match_obj: # Matched. Consume up to end of match. self.out = self.out[match_obj.end(0):] diff --git a/snapshot/win/exception_snapshot_win.h b/snapshot/win/exception_snapshot_win.h index 7c83169ff5..686e771a73 100644 --- a/snapshot/win/exception_snapshot_win.h +++ b/snapshot/win/exception_snapshot_win.h @@ -16,6 +16,7 @@ #define CRASHPAD_SNAPSHOT_WIN_EXCEPTION_SNAPSHOT_WIN_H_ #include + #include #include @@ -63,6 +64,9 @@ class ExceptionSnapshotWin final : public ExceptionSnapshot { //! \param[in] exception_pointers The address of an `EXCEPTION_POINTERS` //! record in the target process, passed through from the exception //! handler. + //! \param[inout] gather_indirectly_referenced_memory_cap The remaining budget + //! for indirectly referenced memory, honored on entry and updated on + //! return. //! //! \note If the exception was triggered by //! CrashpadClient::DumpAndCrashTargetProcess(), this has the side-effect diff --git a/snapshot/win/system_snapshot_win.cc b/snapshot/win/system_snapshot_win.cc index 65d06b27fe..160c41d801 100644 --- a/snapshot/win/system_snapshot_win.cc +++ b/snapshot/win/system_snapshot_win.cc @@ -360,8 +360,7 @@ uint32_t SystemSnapshotWin::CPUX86Signature() const { __cpuid(cpu_info, 1); return cpu_info[0]; #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -375,8 +374,7 @@ uint64_t SystemSnapshotWin::CPUX86Features() const { return (static_cast(cpu_info[2]) << 32) | static_cast(cpu_info[3]); #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -391,8 +389,7 @@ uint64_t SystemSnapshotWin::CPUX86ExtendedFeatures() const { return (static_cast(cpu_info[2]) << 32) | static_cast(cpu_info[3]); #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -410,8 +407,7 @@ uint32_t SystemSnapshotWin::CPUX86Leaf7Features() const { __cpuidex(cpu_info, 7, 0); return cpu_info[1]; #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } @@ -441,8 +437,7 @@ bool SystemSnapshotWin::CPUX86SupportsDAZ() const { // Test the DAZ bit. return (mxcsr_mask & (1 << 6)) != 0; #else - NOTREACHED_IN_MIGRATION(); - return 0; + NOTREACHED(); #endif } diff --git a/test/BUILD.gn b/test/BUILD.gn index f20c66f294..58ddc50016 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -135,7 +135,7 @@ static_library("test") { deps = [ "$mini_chromium_source_parent:base", "../compat", - "../third_party/googletest:googletest", + "../third_party/googletest", "../util", ] @@ -214,8 +214,8 @@ source_set("test_test") { ":test", "$mini_chromium_source_parent:base", "../compat", + "../third_party/googletest", "../third_party/googletest:googlemock", - "../third_party/googletest:googletest", "../util", ] @@ -243,8 +243,8 @@ static_library("googlemock_main") { ":test", "$mini_chromium_source_parent:base", "$mini_chromium_source_parent:base_test_support", + "../third_party/googletest", "../third_party/googletest:googlemock", - "../third_party/googletest:googletest", ] if (crashpad_is_android) { deps += [ "../util" ] @@ -263,7 +263,7 @@ static_library("googletest_main") { ":test", "$mini_chromium_source_parent:base", "$mini_chromium_source_parent:base_test_support", - "../third_party/googletest:googletest", + "../third_party/googletest", ] if (crashpad_is_android) { deps += [ "../util" ] diff --git a/test/fuchsia_crashpad_tests.cml b/test/fuchsia_crashpad_tests.cml index 63ec2e20d1..bfcedb30f2 100644 --- a/test/fuchsia_crashpad_tests.cml +++ b/test/fuchsia_crashpad_tests.cml @@ -1,6 +1,17 @@ -// Copyright 2022 The Fuchsia Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +// Copyright 2022 The Crashpad Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + { include: [ "//src/sys/test_runners/elf/ambient_exec.shard.cml", @@ -45,15 +56,6 @@ }, ], offer: [ - { - protocol: "fuchsia.logger.LogSink", - from: "parent", - to: [ - "#crashpad_test", - "#dns_resolver", - "#netstack", - ] - }, { protocol: "fuchsia.net.name.Lookup", from: "#dns_resolver", diff --git a/test/ios/BUILD.gn b/test/ios/BUILD.gn index 6b1deca858..ba4c527118 100644 --- a/test/ios/BUILD.gn +++ b/test/ios/BUILD.gn @@ -58,7 +58,7 @@ source_set("google_test_setup") { ":google_test_runner_shared_headers", "../$mini_chromium_source_parent:base", "../../build:apple_enable_arc", - "../../third_party/googletest:googletest", + "../../third_party/googletest", ] frameworks = [ "UIKit.framework" ] } diff --git a/test/ios/crash_type_xctest.mm b/test/ios/crash_type_xctest.mm index 646c9dfd4d..c81e8c232a 100644 --- a/test/ios/crash_type_xctest.mm +++ b/test/ios/crash_type_xctest.mm @@ -33,12 +33,21 @@ // macOS 14.0 is 23A344, macOS 13.6.5 is 22G621, so if the first two characters // in the kern.osversion are > 22, this build will reproduce the simulator bug // in crbug.com/328282286 -bool IsMacOSVersion143OrGreaterAndiOS16OrLess() { - if (__builtin_available(iOS 17, *)) { +// macOS 14.0 is 23A344, macOS 13.6.5 is 22G621, so if the first two +// characters in the kern.osversion are > 22, this build will reproduce the +// simulator bug in crbug.com/328282286 +// This now reproduces on macOS 15.4 24E248 as well for iOS17 simulators. +bool HasMacOSBrokeDYLDTaskInfo() { + if (__builtin_available(iOS 18, *)) { return false; } - std::string build = crashpad::ReadStringSysctlByName("kern.osversion", false); + if (std::stoi(build.substr(0, 2)) >= 24) { + return true; + } + if (__builtin_available(iOS 17, *)) { + return false; + } return std::stoi(build.substr(0, 2)) > 22; } #endif @@ -104,6 +113,12 @@ - (BOOL)mayTerminateOutOfBandWithoutCrashReport { - (void)setUp { app_ = [[XCUIApplication alloc] init]; + if ([self.name isEqualToString:@"-[CPTestTestCase testExtensionStreams]"]) { + app_.launchArguments = @[ @"--test-extension-streams" ]; + } else if ([self.name isEqualToString: + @"-[CPTestTestCase testCrashWithExtraMemory]"]) { + app_.launchArguments = @[ @"--test-extra_memory" ]; + } [app_ launch]; rootObject_ = [EDOClientService rootObjectWithPort:12345]; [rootObject_ clearPendingReports]; @@ -144,6 +159,7 @@ - (void)testKillAbort { - (void)testTrap { [rootObject_ crashTrap]; +#if !BUILDFLAG(IS_IOS_TVOS) #if defined(ARCH_CPU_X86_64) [self verifyCrashReportException:EXC_BAD_INSTRUCTION]; #elif defined(ARCH_CPU_ARM64) @@ -151,6 +167,12 @@ - (void)testTrap { #else #error Port to your CPU architecture #endif +#else // !BUILDFLAG(IS_IOS_TVOS) + [self verifyCrashReportException:EXC_SOFT_SIGNAL]; + NSNumber* report_exception; + XCTAssertTrue([rootObject_ pendingReportExceptionInfo:&report_exception]); + XCTAssertEqual(report_exception.intValue, SIGTRAP); +#endif } - (void)testAbort { @@ -163,7 +185,14 @@ - (void)testAbort { - (void)testBadAccess { [rootObject_ crashBadAccess]; +#if !BUILDFLAG(IS_IOS_TVOS) [self verifyCrashReportException:EXC_BAD_ACCESS]; +#else + [self verifyCrashReportException:EXC_SOFT_SIGNAL]; + NSNumber* report_exception; + XCTAssertTrue([rootObject_ pendingReportExceptionInfo:&report_exception]); + XCTAssertEqual(report_exception.intValue, SIGSEGV); +#endif } - (void)testException { @@ -235,7 +264,14 @@ - (void)testcrashUnrecognizedSelectorAfterDelay { - (void)testCatchUIGestureEnvironmentNSException { // Tap the button with the string UIGestureEnvironmentException. +#if !BUILDFLAG(IS_IOS_TVOS) [app_.buttons[@"UIGestureEnvironmentException"] tap]; +#else + // tvOS does not have [XCUIElement tap]. This version assumes there is just + // one big button with "UIGestureEnvironmentException" as title, so we can + // just press Select on the remote to activate it. + [XCUIRemote.sharedRemote pressButton:XCUIRemoteButtonSelect]; +#endif [self verifyCrashReportException:crashpad::kMachExceptionFromNSException]; NSDictionary* dict = [rootObject_ getAnnotations]; XCTAssertTrue([[dict[@"objects"][0] valueForKeyPath:@"exceptionReason"] @@ -265,10 +301,21 @@ - (void)testCrashCoreAutoLayoutSinkhole { isEqualToString:@"NSGenericException"]); } +// This test cannot run correctly on tvOS: it is impossible to catch this stack +// overflow as a Mach exception (like we do on iOS), and sigaltstack() is also +// forbidden so we cannot detect it with POSIX signals either. +// Per xnu-11215.81.4/bsd/uxkern/ux_exception.c's handle_ux_exception(), when a +// stack overflow is detected but no alternate stack is specified, the kernel +// will reset SIGSEGV to SIG_DFL before delivering the signal, so we are never +// able to capture this crash. Even if we do pass SA_ONSTACK to sigaction(), we +// will just crash a bit later, as we are still using the same stack that has +// already overflown. +#if !BUILDFLAG(IS_IOS_TVOS) - (void)testRecursion { [rootObject_ crashRecursion]; [self verifyCrashReportException:EXC_BAD_ACCESS]; } +#endif - (void)testClientAnnotations { [rootObject_ crashKillAbort]; @@ -343,9 +390,10 @@ - (void)testCrashWithDyldErrorString { - (void)testCrashWithAnnotations { #if TARGET_OS_SIMULATOR - // This test will fail on older (=14.3 or + // =15.4 due to a bug in Simulator. + // crbug.com/328282286 + if (crashpad::HasMacOSBrokeDYLDTaskInfo()) { return; } #endif @@ -397,12 +445,49 @@ - (void)testCrashWithAnnotations { XCTAssertFalse(reader.Pop(ringBufferEntry)); } +- (void)testCrashWithExtraMemory { +#if TARGET_OS_SIMULATOR + // This test will fail on =14.3 or + // =15.4 due to a bug in Simulator. + // crbug.com/328282286 + if (crashpad::HasMacOSBrokeDYLDTaskInfo()) { + return; + } +#endif + + [rootObject_ crashKillAbort]; + [self verifyCrashReportException:EXC_SOFT_SIGNAL]; + + NSDictionary* dict = [rootObject_ getExtraMemory]; + BOOL found = NO; + for (NSString* key in dict) { + if ([dict[key] isEqualToString:@"hello world"]) { + found = YES; + break; + } + } + XCTAssertTrue(found); +} + +- (void)testExtensionStreams { +#if TARGET_OS_SIMULATOR + // This test will fail on =14.3 or + // =15.4 due to a bug in Simulator. + // crbug.com/328282286 + if (crashpad::HasMacOSBrokeDYLDTaskInfo()) { + return; + } +#endif + [rootObject_ crashKillAbort]; + [self verifyCrashReportException:EXC_SOFT_SIGNAL]; + XCTAssertTrue([rootObject_ hasExtensionStream]); +} + - (void)testDumpWithoutCrash { [rootObject_ generateDumpWithoutCrash:10 threads:3]; // The app should not crash XCTAssertTrue(app_.state == XCUIApplicationStateRunningForeground); - XCTAssertEqual([rootObject_ pendingReportCount], 30); } diff --git a/test/ios/host/BUILD.gn b/test/ios/host/BUILD.gn index 7a735f34dd..1ae7fbacd3 100644 --- a/test/ios/host/BUILD.gn +++ b/test/ios/host/BUILD.gn @@ -44,6 +44,7 @@ static_library("app_host_sources") { ":app_shared_sources", "../../../build:apple_enable_arc", "../../../client", + "../../../minidump:test_support", "../../../snapshot", "../../../test", "../../../third_party/edo", diff --git a/test/ios/host/cptest_application_delegate.mm b/test/ios/host/cptest_application_delegate.mm index 531a4cde17..118f0b4ac3 100644 --- a/test/ios/host/cptest_application_delegate.mm +++ b/test/ios/host/cptest_application_delegate.mm @@ -26,6 +26,7 @@ #include #include +#include #include #import "Service/Sources/EDOHostNamingService.h" @@ -41,6 +42,7 @@ #include "client/ring_buffer_annotation.h" #include "client/simple_string_dictionary.h" #include "client/simulate_crash.h" +#include "minidump/test/minidump_user_extension_stream_util.h" #include "snapshot/minidump/process_snapshot_minidump.h" #include "test/file.h" #import "test/ios/host/cptest_crash_view_controller.h" @@ -55,6 +57,36 @@ namespace { +class ReadToString : public crashpad::MemorySnapshot::Delegate { + public: + std::string result; + + bool MemorySnapshotDelegateRead(void* data, size_t size) override { + result = std::string(reinterpret_cast(data), size); + return true; + } +}; + +static constexpr char kExpectedStreamData[] = "Injected extension stream!"; + +class TestUserStreamDataSource : public crashpad::UserStreamDataSource { + public: + TestUserStreamDataSource() {} + + TestUserStreamDataSource(const TestUserStreamDataSource&) = delete; + TestUserStreamDataSource& operator=(const TestUserStreamDataSource&) = delete; + + std::unique_ptr + ProduceStreamData(crashpad::ProcessSnapshot* process_snapshot) override; +}; + +std::unique_ptr +TestUserStreamDataSource::ProduceStreamData( + crashpad::ProcessSnapshot* process_snapshot) { + return std::make_unique( + 0xCAFEBABE, kExpectedStreamData, sizeof(kExpectedStreamData)); +} + constexpr crashpad::Annotation::Type kRingBufferType = crashpad::Annotation::UserDefinedType(42); @@ -108,7 +140,7 @@ OperationStatus GetPendingReports(std::vector* pending_reports) { #if defined(__IPHONE_15_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0 UIWindowScene* scene = reinterpret_cast( [UIApplication sharedApplication].connectedScenes.anyObject); - if (@available(iOS 15.0, *)) { + if (@available(iOS 15.0, tvOS 15.0, *)) { return scene.keyWindow; } else { return [scene.windows firstObject]; @@ -138,6 +170,8 @@ - (void)processIntermediateDumps; @implementation CPTestApplicationDelegate { crashpad::CrashpadClient client_; crashpad::ScopedFileHandle raw_logging_file_; + crashpad::SimpleAddressRangeBag extra_ranges_; + std::unique_ptr extra_memory_string_; } @synthesize window = _window; @@ -173,7 +207,19 @@ - (BOOL)application:(UIApplication*)application annotations, crashpad::CrashpadClient:: ProcessPendingReportsObservationCallback())) { - client_.ProcessIntermediateDumps(); + crashpad::UserStreamDataSources user_stream_data_sources; + if ([arguments containsObject:@"--test-extension-streams"]) { + user_stream_data_sources.push_back( + std::make_unique()); + } + client_.ProcessIntermediateDumps({}, &user_stream_data_sources); + } + + if ([arguments containsObject:@"--test-extra_memory"]) { + crashpad::CrashpadInfo::GetCrashpadInfo()->set_extra_memory_ranges( + &extra_ranges_); + extra_memory_string_ = std::make_unique("hello world"); + extra_ranges_.Insert((void*)extra_memory_string_->c_str(), 11); } self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; @@ -284,6 +330,43 @@ - (NSDictionary*)getAnnotations { return [dict passByValue]; } +- (NSDictionary*)getExtraMemory { + auto process_snapshot = GetProcessSnapshotMinidumpFromSinglePending(); + if (!process_snapshot) + return @{}; + + NSDictionary* dict = [@{} mutableCopy]; + + for (auto memory : process_snapshot->ExtraMemory()) { + ReadToString delegate; + if (memory->Size() > 0 && memory->Read(&delegate) && + !delegate.result.empty()) { + NSString* key = [@(memory->Address()) stringValue]; + NSString* value = @(delegate.result.c_str()); + if (value.length) { + [dict setValue:value forKey:key]; + } + } + } + return [dict passByValue]; +} + +- (BOOL)hasExtensionStream { + auto process_snapshot = GetProcessSnapshotMinidumpFromSinglePending(); + if (!process_snapshot) + return NO; + + auto streams = process_snapshot->CustomMinidumpStreams(); + for (const auto& stream : streams) { + if (stream->stream_type() == 0xCAFEBABE) { + return memcmp(kExpectedStreamData, + stream->data().data(), + sizeof(kExpectedStreamData)) == 0; + } + } + return NO; +} + - (NSDictionary*)getProcessAnnotations { auto process_snapshot = GetProcessSnapshotMinidumpFromSinglePending(); if (!process_snapshot) @@ -320,7 +403,7 @@ - (void)crashAbort { - (void)crashException { std::vector empty_vector = {}; - empty_vector.at(42); + std::ignore = empty_vector.at(42); } - (void)crashNSException { @@ -534,9 +617,9 @@ - (void)catchConcurrentNSException { - (void)crashInHandlerReentrant { crashpad::CrashpadClient client_; - client_.SetMachExceptionCallbackForTesting(abort); + client_.SetExceptionCallbackForTesting(abort); - // Trigger a Mach exception. + // Trigger an exception. [self crashTrap]; } diff --git a/test/ios/host/cptest_shared_object.h b/test/ios/host/cptest_shared_object.h index 21011ce424..985bd46ab4 100644 --- a/test/ios/host/cptest_shared_object.h +++ b/test/ios/host/cptest_shared_object.h @@ -43,11 +43,19 @@ - (bool)pendingReportExceptionInfo:(NSNumber**)exception_info; // Return an NSDictionary with a dictionary named "simplemap", an array named -// "vector" and an array named "objects", representing the combination of all -// modules AnnotationsSimpleMap, AnnotationsVector and AnnotationObjects -// (strings only) respectively. +// "vector" an array named "objects", and an array named "ringbuffers", +// representing the combination of all modules AnnotationsSimpleMap, +// AnnotationsVector and AnnotationObjects (String and RingBuffer type) +// respectively. - (NSDictionary*)getAnnotations; +// Return an NSDictionary with a dictionary representing all key value pairs of +// ExtraMemory MemorySnapshots where the data can be converted to an NSString. +- (NSDictionary*)getExtraMemory; + +// Returns YES if a minidump contains the expected custom stream data. +- (BOOL)hasExtensionStream; + // Return an NSDictionary representing the ProcessSnapshotMinidump // AnnotationsSimpleMap. - (NSDictionary*)getProcessAnnotations; diff --git a/test/linux/fake_ptrace_connection.cc b/test/linux/fake_ptrace_connection.cc index 8eefe6b2c2..0f8551e48a 100644 --- a/test/linux/fake_ptrace_connection.cc +++ b/test/linux/fake_ptrace_connection.cc @@ -90,15 +90,13 @@ ProcessMemoryLinux* FakePtraceConnection::Memory() { bool FakePtraceConnection::Threads(std::vector* threads) { // TODO(jperaza): Implement this if/when it's needed. - NOTREACHED_IN_MIGRATION(); - return false; + NOTREACHED(); } ssize_t FakePtraceConnection::ReadUpTo(VMAddress address, size_t size, void* buffer) { - NOTREACHED_IN_MIGRATION(); - return false; + NOTREACHED(); } } // namespace test diff --git a/test/scoped_temp_dir_win.cc b/test/scoped_temp_dir_win.cc index 750a6fdf92..6d23f88aca 100644 --- a/test/scoped_temp_dir_win.cc +++ b/test/scoped_temp_dir_win.cc @@ -20,6 +20,7 @@ #include #include "base/check.h" +#include "base/notreached.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "gtest/gtest.h" @@ -56,7 +57,7 @@ void ScopedTempDir::Rename() { } } - CHECK(false) << "Couldn't move to a new unique temp dir"; + NOTREACHED() << "Couldn't move to a new unique temp dir"; } // static @@ -70,8 +71,7 @@ base::FilePath ScopedTempDir::CreateTemporaryDirectory() { return path_to_create; } - CHECK(false) << "Couldn't create a new unique temp dir"; - return base::FilePath(); + NOTREACHED() << "Couldn't create a new unique temp dir"; } } // namespace test diff --git a/third_party/cpp-httplib/README.crashpad b/third_party/cpp-httplib/README.crashpad index 8246a7358c..b6e6597b50 100644 --- a/third_party/cpp-httplib/README.crashpad +++ b/third_party/cpp-httplib/README.crashpad @@ -1,17 +1,25 @@ Name: cpp-httplib Short Name: cpp-httplib URL: https://github.com/yhirose/cpp-httplib -Revision: 5b3187e2f9e77c672063d49a1167bbb563da023e +Version: 0.25.0 +Revision: 3f44c80fd345e859d6f61b2dda566aa3329ce35e License: MIT License File: cpp-httplib/LICENSE Security Critical: no (test only) Shipped: no Description: -A C++11 header-only HTTP library. +A C++11 single-file header-only cross-platform HTTP/HTTPS library. Local Modifications: -- Exclude test/ and example/ subdirs. -- Patch httplib.h to use #include "third_party/zlib/zlib_crashpad.h" instead of - . -- Make `this` capture explicit to avoid errors in C++20. + - Exclude all subdirectories and build-related files. + - Patch httplib.h to use #include "third_party/zlib/zlib_crashpad.h" instead of + . + - #define CPPHTTPLIB_NO_EXCEPTIONS 1. + - Revert upstream patches that drop support for 32-bit Windows: + - 52163ed9823c Fix #2148 (#2173) + - 9dbaed75efff Fix #2175 (#2177) + - 1f110b54d8b3 Chang #error to #warning for the 32-bit environment check + except 32-bit Windows + - 0b3758ec36be Fix problem with Windows version check + - fbee136dca54 Fix #2193. Allow _WIN32 diff --git a/third_party/cpp-httplib/cpp-httplib/README.md b/third_party/cpp-httplib/cpp-httplib/README.md index 84685b4a93..ff07694c22 100644 --- a/third_party/cpp-httplib/cpp-httplib/README.md +++ b/third_party/cpp-httplib/cpp-httplib/README.md @@ -1,93 +1,690 @@ cpp-httplib =========== -A C++11 header-only HTTP library. +[![](https://github.com/yhirose/cpp-httplib/workflows/test/badge.svg)](https://github.com/yhirose/cpp-httplib/actions) -It's extremely easy to setup. Just include **httplib.h** file in your code! +A C++11 single-file header-only cross platform HTTP/HTTPS library. -Inspired by [Sinatra](http://www.sinatrarb.com/) and [express](https://github.com/visionmedia/express). +It's extremely easy to set up. Just include the **httplib.h** file in your code! -Server Example --------------- +> [!IMPORTANT] +> This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want. + +Simple examples +--------------- + +#### Server (Multi-threaded) + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Server svr; + +// HTTPS +httplib::SSLServer svr; + +svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) { + res.set_content("Hello World!", "text/plain"); +}); + +svr.listen("0.0.0.0", 8080); +``` + +#### Client + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Client cli("http://yhirose.github.io"); + +// HTTPS +httplib::Client cli("https://yhirose.github.io"); + +auto res = cli.Get("/hi"); +res->status; +res->body; +``` + +SSL Support +----------- + +SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked. + +> [!NOTE] +> cpp-httplib currently supports only version 3.0 or later. Please see [this page](https://www.openssl.org/policies/releasestrat.html) to get more information. + +> [!TIP] +> For macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`. + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// Server +httplib::SSLServer svr("./cert.pem", "./key.pem"); + +// Client +httplib::Client cli("https://localhost:1234"); // scheme + host +httplib::SSLClient cli("localhost:1234"); // host +httplib::SSLClient cli("localhost", 1234); // host, port + +// Use your CA bundle +cli.set_ca_cert_path("./ca-bundle.crt"); + +// Disable cert verification +cli.enable_server_certificate_verification(false); + +// Disable host verification +cli.enable_server_hostname_verification(false); +``` + +> [!NOTE] +> When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself. + +### SSL Error Handling + +When SSL operations fail, cpp-httplib provides detailed error information through two separate error fields: + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +httplib::Client cli("https://example.com"); + +auto res = cli.Get("/"); +if (!res) { + // Check the error type + auto err = res.error(); + + switch (err) { + case httplib::Error::SSLConnection: + std::cout << "SSL connection failed, SSL error: " + << res->ssl_error() << std::endl; + break; + + case httplib::Error::SSLLoadingCerts: + std::cout << "SSL cert loading failed, OpenSSL error: " + << std::hex << res->ssl_openssl_error() << std::endl; + break; + + case httplib::Error::SSLServerVerification: + std::cout << "SSL verification failed, X509 error: " + << res->ssl_openssl_error() << std::endl; + break; + + case httplib::Error::SSLServerHostnameVerification: + std::cout << "SSL hostname verification failed, X509 error: " + << res->ssl_openssl_error() << std::endl; + break; + + default: + std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; + } + } +} +``` + +Server +------ ```c++ #include int main(void) { - using namespace httplib; + using namespace httplib; - Server svr; + Server svr; - svr.Get("/hi", [](const Request& req, Response& res) { - res.set_content("Hello World!", "text/plain"); - }); + svr.Get("/hi", [](const Request& req, Response& res) { + res.set_content("Hello World!", "text/plain"); + }); - svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) { - auto numbers = req.matches[1]; - res.set_content(numbers, "text/plain"); - }); + // Match the request path against a regular expression + // and extract its captures + svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) { + auto numbers = req.matches[1]; + res.set_content(numbers, "text/plain"); + }); - svr.listen("localhost", 1234); + // Capture the second segment of the request path as "id" path param + svr.Get("/users/:id", [&](const Request& req, Response& res) { + auto user_id = req.path_params.at("id"); + res.set_content(user_id, "text/plain"); + }); + + // Extract values from HTTP headers and URL query params + svr.Get("/body-header-param", [](const Request& req, Response& res) { + if (req.has_header("Content-Length")) { + auto val = req.get_header_value("Content-Length"); + } + if (req.has_param("key")) { + auto val = req.get_param_value("key"); + } + res.set_content(req.body, "text/plain"); + }); + + // If the handler takes time to finish, you can also poll the connection state + svr.Get("/task", [&](const Request& req, Response& res) { + const char * result = nullptr; + process.run(); // for example, starting an external process + while (result == nullptr) { + sleep(1); + if (req.is_connection_closed()) { + process.kill(); // kill the process + return; + } + result = process.stdout(); // != nullptr if the process finishes + } + res.set_content(result, "text/plain"); + }); + + svr.Get("/stop", [&](const Request& req, Response& res) { + svr.stop(); + }); + + svr.listen("localhost", 1234); } ``` `Post`, `Put`, `Delete` and `Options` methods are also supported. -### Method Chain +### Bind a socket to multiple interfaces and any available port ```cpp -svr.Get("/get", [](const auto& req, auto& res) { - res.set_content("get", "text/plain"); - }) - .Post("/post", [](const auto& req, auto& res) { - res.set_content(req.body(), "text/plain"); - }) - .listen("localhost", 1234); +int port = svr.bind_to_any_port("0.0.0.0"); +svr.listen_after_bind(); ``` ### Static File Server ```cpp -svr.set_base_dir("./www"); +// Mount / to ./www directory +auto ret = svr.set_mount_point("/", "./www"); +if (!ret) { + // The specified base directory doesn't exist... +} + +// Mount /public to ./www directory +ret = svr.set_mount_point("/public", "./www"); + +// Mount /public to ./www1 and ./www2 directories +ret = svr.set_mount_point("/public", "./www1"); // 1st order to search +ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search + +// Remove mount / +ret = svr.remove_mount_point("/"); + +// Remove mount /public +ret = svr.remove_mount_point("/public"); +``` + +```cpp +// User defined file extension and MIME type mappings +svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h"); +``` + +The following are built-in mappings: + +| Extension | MIME Type | Extension | MIME Type | +| :--------- | :-------------------------- | :--------- | :-------------------------- | +| css | text/css | mpga | audio/mpeg | +| csv | text/csv | weba | audio/webm | +| txt | text/plain | wav | audio/wave | +| vtt | text/vtt | otf | font/otf | +| html, htm | text/html | ttf | font/ttf | +| apng | image/apng | woff | font/woff | +| avif | image/avif | woff2 | font/woff2 | +| bmp | image/bmp | 7z | application/x-7z-compressed | +| gif | image/gif | atom | application/atom+xml | +| png | image/png | pdf | application/pdf | +| svg | image/svg+xml | mjs, js | text/javascript | +| webp | image/webp | json | application/json | +| ico | image/x-icon | rss | application/rss+xml | +| tif | image/tiff | tar | application/x-tar | +| tiff | image/tiff | xhtml, xht | application/xhtml+xml | +| jpeg, jpg | image/jpeg | xslt | application/xslt+xml | +| mp4 | video/mp4 | xml | application/xml | +| mpeg | video/mpeg | gz | application/gzip | +| webm | video/webm | zip | application/zip | +| mp3 | audio/mp3 | wasm | application/wasm | + +> [!WARNING] +> These static file server methods are not thread-safe. + +### File request handler + +```cpp +// The handler is called right before the response is sent to a client +svr.set_file_request_handler([](const Request &req, Response &res) { + ... +}); ``` ### Logging +cpp-httplib provides separate logging capabilities for access logs and error logs, similar to web servers like Nginx and Apache. + +#### Access Logging + +Access loggers capture successful HTTP requests and responses: + ```cpp -svr.set_logger([](const auto& req, const auto& res) { - your_logger(req, res); +svr.set_logger([](const httplib::Request& req, const httplib::Response& res) { + std::cout << req.method << " " << req.path << " -> " << res.status << std::endl; }); ``` -### Error Handler +#### Pre-compression Logging + +You can also set a pre-compression logger to capture request/response data before compression is applied: + +```cpp +svr.set_pre_compression_logger([](const httplib::Request& req, const httplib::Response& res) { + // Log before compression - res.body contains uncompressed content + // Content-Encoding header is not yet set + your_pre_compression_logger(req, res); +}); +``` + +The pre-compression logger is only called when compression would be applied. For responses without compression, only the access logger is called. + +#### Error Logging + +Error loggers capture failed requests and connection issues. Unlike access loggers, error loggers only receive the Error and Request information, as errors typically occur before a meaningful Response can be generated. + +```cpp +svr.set_error_logger([](const httplib::Error& err, const httplib::Request* req) { + std::cerr << httplib::to_string(err) << " while processing request"; + if (req) { + std::cerr << ", client: " << req->get_header_value("X-Forwarded-For") + << ", request: '" << req->method << " " << req->path << " " << req->version << "'" + << ", host: " << req->get_header_value("Host"); + } + std::cerr << std::endl; +}); +``` + +### Error handler ```cpp svr.set_error_handler([](const auto& req, auto& res) { - const char* fmt = "

Error Status: %d

"; - char buf[BUFSIZ]; - snprintf(buf, sizeof(buf), fmt, res.status); - res.set_content(buf, "text/html"); + auto fmt = "

Error Status: %d

"; + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf), fmt, res.status); + res.set_content(buf, "text/html"); +}); +``` + +### Exception handler +The exception handler gets called if a user routing handler throws an error. + +```cpp +svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) { + auto fmt = "

Error 500

%s

"; + char buf[BUFSIZ]; + try { + std::rethrow_exception(ep); + } catch (std::exception &e) { + snprintf(buf, sizeof(buf), fmt, e.what()); + } catch (...) { // See the following NOTE + snprintf(buf, sizeof(buf), fmt, "Unknown Exception"); + } + res.set_content(buf, "text/html"); + res.status = StatusCode::InternalServerError_500; +}); +``` + +> [!CAUTION] +> if you don't provide the `catch (...)` block for a rethrown exception pointer, an uncaught exception will end up causing the server crash. Be careful! + +### Pre routing handler + +```cpp +svr.set_pre_routing_handler([](const auto& req, auto& res) { + if (req.path == "/hello") { + res.set_content("world", "text/html"); + return Server::HandlerResponse::Handled; + } + return Server::HandlerResponse::Unhandled; +}); +``` + +### Post routing handler + +```cpp +svr.set_post_routing_handler([](const auto& req, auto& res) { + res.set_header("ADDITIONAL_HEADER", "value"); +}); +``` + +### Pre request handler + +```cpp +svr.set_pre_request_handler([](const auto& req, auto& res) { + if (req.matched_route == "/user/:user") { + auto user = req.path_params.at("user"); + if (user != "john") { + res.status = StatusCode::Forbidden_403; + res.set_content("error", "text/html"); + return Server::HandlerResponse::Handled; + } + } + return Server::HandlerResponse::Unhandled; }); ``` -### 'multipart/form-data' POST data +### Form data handling + +#### URL-encoded form data ('application/x-www-form-urlencoded') + +```cpp +svr.Post("/form", [&](const auto& req, auto& res) { + // URL query parameters and form-encoded data are accessible via req.params + std::string username = req.get_param_value("username"); + std::string password = req.get_param_value("password"); + + // Handle multiple values with same name + auto interests = req.get_param_values("interests"); + + // Check existence + if (req.has_param("newsletter")) { + // Handle newsletter subscription + } +}); +``` + +#### 'multipart/form-data' POST data + +```cpp +svr.Post("/multipart", [&](const Request& req, Response& res) { + // Access text fields (from form inputs without files) + std::string username = req.form.get_field("username"); + std::string bio = req.form.get_field("bio"); + + // Access uploaded files + if (req.form.has_file("avatar")) { + const auto& file = req.form.get_file("avatar"); + std::cout << "Uploaded file: " << file.filename + << " (" << file.content_type << ") - " + << file.content.size() << " bytes" << std::endl; + + // Access additional headers if needed + for (const auto& header : file.headers) { + std::cout << "Header: " << header.first << " = " << header.second << std::endl; + } + + // Save to disk + std::ofstream ofs(file.filename, std::ios::binary); + ofs << file.content; + } + + // Handle multiple values with same name + auto tags = req.form.get_fields("tags"); // e.g., multiple checkboxes + for (const auto& tag : tags) { + std::cout << "Tag: " << tag << std::endl; + } + + auto documents = req.form.get_files("documents"); // multiple file upload + for (const auto& doc : documents) { + std::cout << "Document: " << doc.filename + << " (" << doc.content.size() << " bytes)" << std::endl; + } + + // Check existence before accessing + if (req.form.has_field("newsletter")) { + std::cout << "Newsletter subscription: " << req.form.get_field("newsletter") << std::endl; + } + + // Get counts for validation + if (req.form.get_field_count("tags") > 5) { + res.status = StatusCode::BadRequest_400; + res.set_content("Too many tags", "text/plain"); + return; + } + + // Summary + std::cout << "Received " << req.form.fields.size() << " text fields and " + << req.form.files.size() << " files" << std::endl; + + res.set_content("Upload successful", "text/plain"); +}); +``` + +### Receive content with a content receiver + +```cpp +svr.Post("/content_receiver", + [&](const Request &req, Response &res, const ContentReader &content_reader) { + if (req.is_multipart_form_data()) { + // NOTE: `content_reader` is blocking until every form data field is read + // This approach allows streaming processing of large files + std::vector items; + content_reader( + [&](const FormData &item) { + items.push_back(item); + return true; + }, + [&](const char *data, size_t data_length) { + items.back().content.append(data, data_length); + return true; + }); + + // Process the received items + for (const auto& item : items) { + if (item.filename.empty()) { + // Text field + std::cout << "Field: " << item.name << " = " << item.content << std::endl; + } else { + // File + std::cout << "File: " << item.name << " (" << item.filename << ") - " + << item.content.size() << " bytes" << std::endl; + } + } + } else { + std::string body; + content_reader([&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); + } + }); +``` + +### Send content with the content provider + +```cpp +const size_t DATA_CHUNK_SIZE = 4; + +svr.Get("/stream", [&](const Request &req, Response &res) { + auto data = new std::string("abcdefg"); + + res.set_content_provider( + data->size(), // Content length + "text/plain", // Content type + [&, data](size_t offset, size_t length, DataSink &sink) { + const auto &d = *data; + sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE)); + return true; // return 'false' if you want to cancel the process. + }, + [data](bool success) { delete data; }); +}); +``` + +Without content length: + +```cpp +svr.Get("/stream", [&](const Request &req, Response &res) { + res.set_content_provider( + "text/plain", // Content type + [&](size_t offset, DataSink &sink) { + if (/* there is still data */) { + std::vector data; + // prepare data... + sink.write(data.data(), data.size()); + } else { + sink.done(); // No more data + } + return true; // return 'false' if you want to cancel the process. + }); +}); +``` + +### Chunked transfer encoding + +```cpp +svr.Get("/chunked", [&](const Request& req, Response& res) { + res.set_chunked_content_provider( + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done(); // No more data + return true; // return 'false' if you want to cancel the process. + } + ); +}); +``` + +With trailer: + +```cpp +svr.Get("/chunked", [&](const Request& req, Response& res) { + res.set_header("Trailer", "Dummy1, Dummy2"); + res.set_chunked_content_provider( + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done_with_trailer({ + {"Dummy1", "DummyVal1"}, + {"Dummy2", "DummyVal2"} + }); + return true; + } + ); +}); +``` + +### Send file content + +```cpp +svr.Get("/content", [&](const Request &req, Response &res) { + res.set_file_content("./path/to/content.html"); +}); + +svr.Get("/content", [&](const Request &req, Response &res) { + res.set_file_content("./path/to/content", "text/html"); +}); +``` + +### 'Expect: 100-continue' handler + +By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header. + +```cpp +// Send a '417 Expectation Failed' response. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return StatusCode::ExpectationFailed_417; +}); +``` + +```cpp +// Send a final status without reading the message body. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return res.status = StatusCode::Unauthorized_401; +}); +``` + +### Keep-Alive connection + +```cpp +svr.set_keep_alive_max_count(2); // Default is 100 +svr.set_keep_alive_timeout(10); // Default is 5 +``` + +### Timeout + +```c++ +svr.set_read_timeout(5, 0); // 5 seconds +svr.set_write_timeout(5, 0); // 5 seconds +svr.set_idle_interval(0, 100000); // 100 milliseconds +``` + +### Set maximum payload length for reading a request body + +```c++ +svr.set_payload_max_length(1024 * 1024 * 512); // 512MB +``` + +> [!NOTE] +> When the request body content type is 'www-form-urlencoded', the actual payload length shouldn't exceed `CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH`. + +### Server-Sent Events + +Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssesvr.cc) and [Client example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssecli.cc). + +### Default thread pool support + +`ThreadPool` is used as the **default** task queue, with a default thread count of 8 or `std::thread::hardware_concurrency() - 1`, whichever is greater. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`. + +If you want to set the thread count at runtime, there is no convenient way... But here is how. + +```cpp +svr.new_task_queue = [] { return new ThreadPool(12); }; +``` + +You can also provide an optional parameter to limit the maximum number +of pending requests, i.e. requests `accept()`ed by the listener but +still waiting to be serviced by worker threads. ```cpp -svr.Post("/multipart", [&](const auto& req, auto& res) { - auto size = req.files.size(); - auto ret = req.has_file("name1")); - const auto& file = req.get_file_value("name1"); - // file.filename; - // file.content_type; - auto body = req.body.substr(file.offset, file.length)); -}) +svr.new_task_queue = [] { return new ThreadPool(/*num_threads=*/12, /*max_queued_requests=*/18); }; ``` -Client Example --------------- +Default limit is 0 (unlimited). Once the limit is reached, the listener +will shutdown the client connection. + +### Override the default thread pool with yours + +You can supply your own thread pool implementation according to your need. + +```cpp +class YourThreadPoolTaskQueue : public TaskQueue { +public: + YourThreadPoolTaskQueue(size_t n) { + pool_.start_with_thread_count(n); + } + + virtual bool enqueue(std::function fn) override { + /* Return true if the task was actually enqueued, or false + * if the caller must drop the corresponding connection. */ + return pool_.enqueue(fn); + } + + virtual void shutdown() override { + pool_.shutdown_gracefully(); + } + +private: + YourThreadPool pool_; +}; + +svr.new_task_queue = [] { + return new YourThreadPoolTaskQueue(12); +}; +``` -### GET +Client +------ ```c++ #include @@ -95,15 +692,121 @@ Client Example int main(void) { - httplib::Client cli("localhost", 1234); + httplib::Client cli("localhost", 1234); - auto res = cli.Get("/hi"); - if (res && res->status == 200) { - std::cout << res->body << std::endl; + if (auto res = cli.Get("/hi")) { + if (res->status == StatusCode::OK_200) { + std::cout << res->body << std::endl; } + } else { + auto err = res.error(); + std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; + } } ``` +> [!TIP] +> Constructor with scheme-host-port string is now supported! + +```c++ +httplib::Client cli("localhost"); +httplib::Client cli("localhost:8080"); +httplib::Client cli("http://localhost"); +httplib::Client cli("http://localhost:8080"); +httplib::Client cli("https://localhost"); +httplib::SSLClient cli("localhost"); +``` + +### Error code + +Here is the list of errors from `Result::error()`. + +```c++ +enum Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + SSLServerHostnameVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + ProxyConnection, +}; +``` + +### Client Logging + +#### Access Logging + +```cpp +cli.set_logger([](const httplib::Request& req, const httplib::Response& res) { + auto duration = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time).count(); + std::cout << "✓ " << req.method << " " << req.path + << " -> " << res.status << " (" << res.body.size() << " bytes, " + << duration << "ms)" << std::endl; +}); +``` + +#### Error Logging + +```cpp +cli.set_error_logger([](const httplib::Error& err, const httplib::Request* req) { + std::cerr << "✗ "; + if (req) { + std::cerr << req->method << " " << req->path << " "; + } + std::cerr << "failed: " << httplib::to_string(err); + + // Add specific guidance based on error type + switch (err) { + case httplib::Error::Connection: + std::cerr << " (verify server is running and reachable)"; + break; + case httplib::Error::SSLConnection: + std::cerr << " (check SSL certificate and TLS configuration)"; + break; + case httplib::Error::ConnectionTimeout: + std::cerr << " (increase timeout or check network latency)"; + break; + case httplib::Error::Read: + std::cerr << " (server may have closed connection prematurely)"; + break; + default: + break; + } + std::cerr << std::endl; +}); +``` + +### GET with HTTP headers + +```c++ +httplib::Headers headers = { + { "Hello", "World!" } +}; +auto res = cli.Get("/hi", headers); +``` +or +```c++ +auto res = cli.Get("/hi", {{"Hello", "World!"}}); +``` +or +```c++ +cli.set_default_headers({ + { "Hello", "World!" } +}); +auto res = cli.Get("/hi"); +``` + ### POST ```c++ @@ -115,11 +818,36 @@ res = cli.Post("/person", "name=john1¬e=coder", "application/x-www-form-urlen ```c++ httplib::Params params; -params["name"] = "john"; -params["note"] = "coder"; +params.emplace("name", "john"); +params.emplace("note", "coder"); + +auto res = cli.Post("/post", params); +``` + or + +```c++ +httplib::Params params{ + { "name", "john" }, + { "note", "coder" } +}; + auto res = cli.Post("/post", params); ``` +### POST with Multipart Form Data + +```c++ +httplib::UploadFormDataItems items = { + { "text1", "text default", "", "" }, + { "text2", "aωb", "", "" }, + { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" }, + { "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" }, + { "file3", "", "", "application/octet-stream" }, +}; + +auto res = cli.Post("/multipart", items); +``` + ### PUT ```c++ @@ -139,71 +867,419 @@ res = cli.Options("*"); res = cli.Options("/resource/foo"); ``` -### Connection Timeout +### Timeout ```c++ -httplib::Client cli("localhost", 8080, 5); // timeouts in 5 seconds +cli.set_connection_timeout(0, 300000); // 300 milliseconds +cli.set_read_timeout(5, 0); // 5 seconds +cli.set_write_timeout(5, 0); // 5 seconds + +// This method works the same as curl's `--max-time` option +cli.set_max_timeout(5000); // 5 seconds +``` + +### Receive content with a content receiver + +```c++ +std::string body; + +auto res = cli.Get("/large-data", + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); +``` + +```cpp +std::string body; + +auto res = cli.Get( + "/stream", Headers(), + [&](const Response &response) { + EXPECT_EQ(StatusCode::OK_200, response.status); + return true; // return 'false' if you want to cancel the request. + }, + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; // return 'false' if you want to cancel the request. + }); ``` + +### Send content with a content provider + +```cpp +std::string body = ...; + +auto res = cli.Post( + "/stream", body.size(), + [](size_t offset, size_t length, DataSink &sink) { + sink.write(body.data() + offset, length); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + +### Chunked transfer encoding + +```cpp +auto res = cli.Post( + "/stream", + [](size_t offset, DataSink &sink) { + sink.os << "chunked data 1"; + sink.os << "chunked data 2"; + sink.os << "chunked data 3"; + sink.done(); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + ### With Progress Callback ```cpp -httplib::Client client(url, port); +httplib::Client cli(url, port); // prints: 0 / 000 bytes => 50% complete -std::shared_ptr res = - cli.Get("/", [](uint64_t len, uint64_t total) { - printf("%lld / %lld bytes => %d%% complete\n", - len, total, - (int)((len/total)*100)); - } +auto res = cli.Get("/", [](size_t len, size_t total) { + printf("%lld / %lld bytes => %d%% complete\n", + len, total, + (int)(len*100/total)); + return true; // return 'false' if you want to cancel the request. +} ); ``` ![progress](https://user-images.githubusercontent.com/236374/33138910-495c4ecc-cf86-11e7-8693-2fc6d09615c4.gif) -This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23). +### Authentication -### Range +```cpp +// Basic Authentication +cli.set_basic_auth("user", "pass"); + +// Digest Authentication +cli.set_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_bearer_token_auth("token"); +``` + +> [!NOTE] +> OpenSSL is required for Digest Authentication. + +### Proxy server support ```cpp -httplib::Client cli("httpbin.org", 80); +cli.set_proxy("host", port); -// 'Range: bytes=1-10' -httplib::Headers headers = { httplib::make_range_header(1, 10) }; +// Basic Authentication +cli.set_proxy_basic_auth("user", "pass"); + +// Digest Authentication +cli.set_proxy_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_proxy_bearer_token_auth("pass"); +``` + +> [!NOTE] +> OpenSSL is required for Digest Authentication. + +### Range -auto res = cli.Get("/range/32", headers); +```cpp +httplib::Client cli("httpbin.org"); + +auto res = cli.Get("/range/32", { + httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10' +}); // res->status should be 206. // res->body should be "bcdefghijk". ``` -OpenSSL Support ---------------- +```cpp +httplib::make_range_header({{1, 10}, {20, -1}}) // 'Range: bytes=1-10, 20-' +httplib::make_range_header({{100, 199}, {500, 599}}) // 'Range: bytes=100-199, 500-599' +httplib::make_range_header({{0, 0}, {-1, 1}}) // 'Range: bytes=0-0, -1' +``` -SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked. +### Keep-Alive connection -```c++ -#define CPPHTTPLIB_OPENSSL_SUPPORT +```cpp +httplib::Client cli("localhost", 1234); + +cli.Get("/hello"); // with "Connection: close" + +cli.set_keep_alive(true); +cli.Get("/world"); -SSLServer svr("./cert.pem", "./key.pem"); +cli.set_keep_alive(false); +cli.Get("/last-request"); // with "Connection: close" +``` + +### Redirect + +```cpp +httplib::Client cli("yahoo.com"); + +auto res = cli.Get("/"); +res->status; // 301 + +cli.set_follow_location(true); +res = cli.Get("/"); +res->status; // 200 +``` + +### Use a specific network interface + +> [!NOTE] +> This feature is not available on Windows, yet. + +```cpp +cli.set_interface("eth0"); // Interface name, IP address or host name +``` + +### Automatic Path Encoding -SSLClient cli("localhost", 8080); +The client automatically encodes special characters in URL paths by default: + +```cpp +httplib::Client cli("https://example.com"); + +// Automatic path encoding (default behavior) +cli.set_path_encode(true); +auto res = cli.Get("/path with spaces/file.txt"); // Automatically encodes spaces + +// Disable automatic path encoding +cli.set_path_encode(false); +auto res = cli.Get("/already%20encoded/path"); // Use pre-encoded paths ``` -Zlib Support ------------- +- `set_path_encode(bool on)` - Controls automatic encoding of special characters in URL paths + - `true` (default): Automatically encodes spaces, plus signs, newlines, and other special characters + - `false`: Sends paths as-is without encoding (useful for pre-encoded URLs) -'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. +### Performance Note for Local Connections -The server applies gzip compression to the following MIME type contents: +> [!WARNING] +> On Windows systems with improperly configured IPv6 settings, using "localhost" as the hostname may cause significant connection delays (up to 2 seconds per request) due to DNS resolution issues. This affects both client and server operations. For better performance when connecting to local services, use "127.0.0.1" instead of "localhost". +> +> See: https://github.com/yhirose/cpp-httplib/issues/366#issuecomment-593004264 - * all text types +```cpp +// May be slower on Windows due to DNS resolution delays +httplib::Client cli("localhost", 8080); +httplib::Server svr; +svr.listen("localhost", 8080); + +// Faster alternative for local connections +httplib::Client cli("127.0.0.1", 8080); +httplib::Server svr; +svr.listen("127.0.0.1", 8080); +``` + +Compression +----------- + +The server can apply compression to the following MIME type contents: + + * all text types except text/event-stream * image/svg+xml * application/javascript * application/json * application/xml + * application/protobuf * application/xhtml+xml +### Zlib Support + +'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. `libz` should be linked. + +### Brotli Support + +Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked. +Please see https://github.com/google/brotli for more detail. + +### Zstd Support + +Zstd compression is available with `CPPHTTPLIB_ZSTD_SUPPORT`. Necessary libraries should be linked. +Please see https://github.com/facebook/zstd for more detail. + +### Default `Accept-Encoding` value + +The default `Accept-Encoding` value contains all possible compression types. So, the following two examples are same. + +```c++ +res = cli.Get("/resource/foo"); +res = cli.Get("/resource/foo", {{"Accept-Encoding", "br, gzip, deflate, zstd"}}); +``` + +If we don't want a response without compression, we have to set `Accept-Encoding` to an empty string. This behavior is similar to curl. + +```c++ +res = cli.Get("/resource/foo", {{"Accept-Encoding", ""}}); +``` + +### Compress request body on client + +```c++ +cli.set_compress(true); +res = cli.Post("/resource/foo", "...", "text/plain"); +``` + +### Compress response body on client + +```c++ +cli.set_decompress(false); +res = cli.Get("/resource/foo"); +res->body; // Compressed data + +``` + +Unix Domain Socket Support +-------------------------- + +Unix Domain Socket support is available on Linux and macOS. + +```c++ +// Server +httplib::Server svr; +svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80); + +// Client +httplib::Client cli("./my-socket.sock"); +cli.set_address_family(AF_UNIX); +``` + +"my-socket.sock" can be a relative path or an absolute path. Your application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path. + +This library automatically sets the Host header to "localhost" for Unix socket connections, similar to curl's behavior: + + +URI Encoding/Decoding Utilities +------------------------------- + +cpp-httplib provides utility functions for URI encoding and decoding: + +```cpp +#include + +std::string url = "https://example.com/search?q=hello world"; +std::string encoded = httplib::encode_uri(url); +std::string decoded = httplib::decode_uri(encoded); + +std::string param = "hello world"; +std::string encoded_component = httplib::encode_uri_component(param); +std::string decoded_component = httplib::decode_uri_component(encoded_component); +``` + +### Functions + +- `encode_uri(const std::string &value)` - Encodes a full URI, preserving reserved characters like `://`, `?`, `&`, `=` +- `decode_uri(const std::string &value)` - Decodes a URI-encoded string +- `encode_uri_component(const std::string &value)` - Encodes a URI component (query parameter, path segment), encoding all reserved characters +- `decode_uri_component(const std::string &value)` - Decodes a URI component + +Use `encode_uri()` for full URLs and `encode_uri_component()` for individual query parameters or path segments. + + +Split httplib.h into .h and .cc +------------------------------- + +```console +$ ./split.py -h +usage: split.py [-h] [-e EXTENSION] [-o OUT] + +This script splits httplib.h into .h and .cc parts. + +optional arguments: + -h, --help show this help message and exit + -e EXTENSION, --extension EXTENSION + extension of the implementation file (default: cc) + -o OUT, --out OUT where to write the files (default: out) + +$ ./split.py +Wrote out/httplib.h and out/httplib.cc +``` + +Dockerfile for Static HTTP Server +--------------------------------- + +Dockerfile for static HTTP server is available. Port number of this HTTP server is 80, and it serves static files from `/html` directory in the container. + +```bash +> docker build -t cpp-httplib-server . +... + +> docker run --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server +Serving HTTP on 0.0.0.0 port 80 ... +192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1" +192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..." +192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..." +``` + +From Docker Hub + +```bash +> docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server +Serving HTTP on 0.0.0.0 port 80 ... +192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1" +192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..." +192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..." +``` + +NOTE +---- + +### Regular Expression Stack Overflow + +> [!CAUTION] +> When using complex regex patterns in route handlers, be aware that certain patterns may cause stack overflow during pattern matching. This is a known issue with `std::regex` implementations and affects the `dispatch_request()` method. +> +> ```cpp +> // This pattern can cause stack overflow with large input +> svr.Get(".*", handler); +> ``` +> +> Consider using simpler patterns or path parameters to avoid this issue: +> +> ```cpp +> // Safer alternatives +> svr.Get("/users/:id", handler); // Path parameters +> svr.Get(R"(/api/v\d+/.*)", handler); // More specific patterns +> ``` + +### g++ + +g++ 4.8 and below cannot build this library since `` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions). + +### Windows + +Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32_LEAN_AND_MEAN` beforehand. + +```cpp +#include +#include +``` + +```cpp +#define WIN32_LEAN_AND_MEAN +#include +#include +``` + +> [!NOTE] +> cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance. + +> [!NOTE] +> Windows 8 or lower, Visual Studio 2015 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested. + License ------- -MIT license (© 2018 Yuji Hirose) +MIT license (© 2025 Yuji Hirose) + +Special Thanks To +----------------- + +[These folks](https://github.com/yhirose/cpp-httplib/graphs/contributors) made great contributions to polish this library to totally another level from a simple toy! diff --git a/third_party/cpp-httplib/cpp-httplib/httplib.h b/third_party/cpp-httplib/cpp-httplib/httplib.h index 1165e26016..7cd665fce9 100644 --- a/third_party/cpp-httplib/cpp-httplib/httplib.h +++ b/third_party/cpp-httplib/cpp-httplib/httplib.h @@ -1,2335 +1,11879 @@ // // httplib.h // -// Copyright (c) 2017 Yuji Hirose. All rights reserved. +// Copyright (c) 2025 Yuji Hirose. All rights reserved. // MIT License // -#ifndef _CPPHTTPLIB_HTTPLIB_H_ -#define _CPPHTTPLIB_HTTPLIB_H_ +#ifndef CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_HTTPLIB_H + +#define CPPHTTPLIB_VERSION "0.25.0" +#define CPPHTTPLIB_VERSION_NUM "0x001900" + +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND +#define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 1000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_COUNT +#define CPPHTTPLIB_HEADER_MAX_COUNT 100 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_RANGE_MAX_COUNT +#define CPPHTTPLIB_RANGE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_IPV6_V6ONLY +#define CPPHTTPLIB_IPV6_V6ONLY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_SEND_BUFSIZ +#define CPPHTTPLIB_SEND_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + +#ifndef CPPHTTPLIB_MAX_LINE_LENGTH +#define CPPHTTPLIB_MAX_LINE_LENGTH 32768 +#endif + +#define CPPHTTPLIB_NO_EXCEPTIONS 1 + +/* + * Headers + */ #ifdef _WIN32 #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS -#endif +#endif //_CRT_SECURE_NO_WARNINGS + #ifndef _CRT_NONSTDC_NO_DEPRECATE #define _CRT_NONSTDC_NO_DEPRECATE +#endif //_CRT_NONSTDC_NO_DEPRECATE + +#if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported #endif -#if defined(_MSC_VER) && _MSC_VER < 1900 -#define snprintf _snprintf_s +#pragma comment(lib, "ws2_32.lib") + +#ifdef _WIN64 +using ssize_t = __int64; +#else +using ssize_t = long; #endif +#endif // _MSC_VER #ifndef S_ISREG -#define S_ISREG(m) (((m)&S_IFREG)==S_IFREG) -#endif +#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG) +#endif // S_ISREG + #ifndef S_ISDIR -#define S_ISDIR(m) (((m)&S_IFDIR)==S_IFDIR) -#endif +#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR) +#endif // S_ISDIR + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX #include #include #include -#undef min -#undef max +#if defined(__has_include) +#if __has_include() +// afunix.h uses types declared in winsock2.h, so has to be included after it. +#include +#define CPPHTTPLIB_HAVE_AFUNIX_H 1 +#endif +#endif -#ifndef strcasecmp -#define strcasecmp _stricmp +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 #endif -typedef SOCKET socket_t; -#else -#include -#include +using nfds_t = unsigned long; +using socket_t = SOCKET; +using socklen_t = int; + +#else // not _WIN32 + +#include +#if !defined(_AIX) && !defined(__MVS__) +#include +#endif +#ifdef __MVS__ +#include +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif +#endif +#include #include -#include #include -#include -#include +#ifdef __linux__ +#include +#endif +#include +#include +#include +#include +#include #include -#include +#include +#include -typedef int socket_t; +using socket_t = int; +#ifndef INVALID_SOCKET #define INVALID_SOCKET (-1) #endif +#endif //_WIN32 + +#if defined(__APPLE__) +#include +#endif -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include #include #include +#include #include +#include +#include #include -#include #include -#include -#include +#include +#include +#include +#include + +#if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) || \ + defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) +#if TARGET_OS_OSX +#include +#include +#endif +#endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO or + // CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN #ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#endif +#endif // _WIN32 + +#if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) +#if TARGET_OS_OSX +#include +#endif +#endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO + +#include +#include #include +#include + +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER < 0x1010107f +#error Please use OpenSSL or a current version of BoringSSL +#endif +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#elif OPENSSL_VERSION_NUMBER < 0x30000000L +#error Sorry, OpenSSL versions prior to 3.0.0 are not supported #endif +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + #ifdef CPPHTTPLIB_ZLIB_SUPPORT #include "third_party/zlib/zlib_crashpad.h" #endif +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + +#ifdef CPPHTTPLIB_ZSTD_SUPPORT +#include +#endif + /* - * Configuration + * Declaration */ -#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 -#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND 0 - -namespace httplib -{ +namespace httplib { namespace detail { -struct ci { - bool operator() (const std::string & s1, const std::string & s2) const { - return std::lexicographical_compare( - s1.begin(), s1.end(), - s2.begin(), s2.end(), - [](char c1, char c2) { - return ::tolower(c1) < ::tolower(c2); - }); - } -}; - -} // namespace detail +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ -enum class HttpVersion { v1_0 = 0, v1_1 }; +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} -typedef std::multimap Headers; +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} -template -std::pair make_range_header(uint64_t value, Args... args); +namespace case_ignore { + +inline unsigned char to_lower(int c) { + const static unsigned char table[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226, + 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, + 255, + }; + return table[(unsigned char)(char)c]; +} -typedef std::multimap Params; -typedef std::smatch Match; -typedef std::function Progress; +inline bool equal(const std::string &a, const std::string &b) { + return a.size() == b.size() && + std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) { + return to_lower(ca) == to_lower(cb); + }); +} -struct MultipartFile { - std::string filename; - std::string content_type; - size_t offset = 0; - size_t length = 0; +struct equal_to { + bool operator()(const std::string &a, const std::string &b) const { + return equal(a, b); + } }; -typedef std::multimap MultipartFiles; -struct Request { - std::string version; - std::string method; - std::string target; - std::string path; - Headers headers; - std::string body; - Params params; - MultipartFiles files; - Match matches; - - Progress progress; - - bool has_header(const char* key) const; - std::string get_header_value(const char* key) const; - void set_header(const char* key, const char* val); - - bool has_param(const char* key) const; - std::string get_param_value(const char* key) const; - - bool has_file(const char* key) const; - MultipartFile get_file_value(const char* key) const; +struct hash { + size_t operator()(const std::string &key) const { + return hash_core(key.data(), key.size(), 0); + } + + size_t hash_core(const char *s, size_t l, size_t h) const { + return (l == 0) ? h + : hash_core(s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no + // overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(to_lower(*s))); + } }; -struct Response { - std::string version; - int status; - Headers headers; - std::string body; +template +using unordered_set = std::unordered_set; - bool has_header(const char* key) const; - std::string get_header_value(const char* key) const; - void set_header(const char* key, const char* val); +} // namespace case_ignore - void set_redirect(const char* uri); - void set_content(const char* s, size_t n, const char* content_type); - void set_content(const std::string& s, const char* content_type); +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". - Response() : status(-1) {} -}; +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} -class Stream { -public: - virtual ~Stream() {} - virtual int read(char* ptr, size_t size) = 0; - virtual int write(const char* ptr, size_t size1) = 0; - virtual int write(const char* ptr) = 0; - virtual std::string get_remote_addr() = 0; - - template - void write_format(const char* fmt, const Args& ...args); -}; + scope_exit(scope_exit &&rhs) noexcept + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } -class SocketStream : public Stream { -public: - SocketStream(socket_t sock); - virtual ~SocketStream(); + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } - virtual int read(char* ptr, size_t size); - virtual int write(const char* ptr, size_t size); - virtual int write(const char* ptr); - virtual std::string get_remote_addr(); + void release() { this->execute_on_destruction = false; } private: - socket_t sock_; -}; + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; -class Server { -public: - typedef std::function Handler; - typedef std::function Logger; + std::function exit_function; + bool execute_on_destruction; +}; - Server(); +} // namespace detail - virtual ~Server(); +enum SSLVerifierResponse { + // no decision has been made, use the built-in certificate verifier + NoDecisionMade, + // connection certificate is verified and accepted + CertificateAccepted, + // connection certificate was processed but is rejected + CertificateRejected +}; - virtual bool is_valid() const; +enum StatusCode { + // Information responses + Continue_100 = 100, + SwitchingProtocol_101 = 101, + Processing_102 = 102, + EarlyHints_103 = 103, + + // Successful responses + OK_200 = 200, + Created_201 = 201, + Accepted_202 = 202, + NonAuthoritativeInformation_203 = 203, + NoContent_204 = 204, + ResetContent_205 = 205, + PartialContent_206 = 206, + MultiStatus_207 = 207, + AlreadyReported_208 = 208, + IMUsed_226 = 226, + + // Redirection messages + MultipleChoices_300 = 300, + MovedPermanently_301 = 301, + Found_302 = 302, + SeeOther_303 = 303, + NotModified_304 = 304, + UseProxy_305 = 305, + unused_306 = 306, + TemporaryRedirect_307 = 307, + PermanentRedirect_308 = 308, + + // Client error responses + BadRequest_400 = 400, + Unauthorized_401 = 401, + PaymentRequired_402 = 402, + Forbidden_403 = 403, + NotFound_404 = 404, + MethodNotAllowed_405 = 405, + NotAcceptable_406 = 406, + ProxyAuthenticationRequired_407 = 407, + RequestTimeout_408 = 408, + Conflict_409 = 409, + Gone_410 = 410, + LengthRequired_411 = 411, + PreconditionFailed_412 = 412, + PayloadTooLarge_413 = 413, + UriTooLong_414 = 414, + UnsupportedMediaType_415 = 415, + RangeNotSatisfiable_416 = 416, + ExpectationFailed_417 = 417, + ImATeapot_418 = 418, + MisdirectedRequest_421 = 421, + UnprocessableContent_422 = 422, + Locked_423 = 423, + FailedDependency_424 = 424, + TooEarly_425 = 425, + UpgradeRequired_426 = 426, + PreconditionRequired_428 = 428, + TooManyRequests_429 = 429, + RequestHeaderFieldsTooLarge_431 = 431, + UnavailableForLegalReasons_451 = 451, + + // Server error responses + InternalServerError_500 = 500, + NotImplemented_501 = 501, + BadGateway_502 = 502, + ServiceUnavailable_503 = 503, + GatewayTimeout_504 = 504, + HttpVersionNotSupported_505 = 505, + VariantAlsoNegotiates_506 = 506, + InsufficientStorage_507 = 507, + LoopDetected_508 = 508, + NotExtended_510 = 510, + NetworkAuthenticationRequired_511 = 511, +}; - Server& Get(const char* pattern, Handler handler); - Server& Post(const char* pattern, Handler handler); +using Headers = + std::unordered_multimap; - Server& Put(const char* pattern, Handler handler); - Server& Delete(const char* pattern, Handler handler); - Server& Options(const char* pattern, Handler handler); +using Params = std::multimap; +using Match = std::smatch; - bool set_base_dir(const char* path); +using DownloadProgress = std::function; +using UploadProgress = std::function; - void set_error_handler(Handler handler); - void set_logger(Logger logger); +struct Response; +using ResponseHandler = std::function; - void set_keep_alive_max_count(size_t count); +struct FormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; + Headers headers; +}; - int bind_to_any_port(const char* host, int socket_flags = 0); - bool listen_after_bind(); +struct FormField { + std::string name; + std::string content; + Headers headers; +}; +using FormFields = std::multimap; - bool listen(const char* host, int port, int socket_flags = 0); +using FormFiles = std::multimap; - bool is_running() const; - void stop(); +struct MultipartFormData { + FormFields fields; // Text fields from multipart + FormFiles files; // Files from multipart -protected: - bool process_request(Stream& strm, bool last_connection, bool& connection_close); + // Text field access + std::string get_field(const std::string &key, size_t id = 0) const; + std::vector get_fields(const std::string &key) const; + bool has_field(const std::string &key) const; + size_t get_field_count(const std::string &key) const; - size_t keep_alive_max_count_; + // File access + FormData get_file(const std::string &key, size_t id = 0) const; + std::vector get_files(const std::string &key) const; + bool has_file(const std::string &key) const; + size_t get_file_count(const std::string &key) const; +}; -private: - typedef std::vector> Handlers; - - socket_t create_server_socket(const char* host, int port, int socket_flags) const; - int bind_internal(const char* host, int port, int socket_flags); - bool listen_internal(); - - bool routing(Request& req, Response& res); - bool handle_file_request(Request& req, Response& res); - bool dispatch_request(Request& req, Response& res, Handlers& handlers); - - bool parse_request_line(const char* s, Request& req); - void write_response(Stream& strm, bool last_connection, const Request& req, Response& res); - - virtual bool read_and_close_socket(socket_t sock); - - bool is_running_; - socket_t svr_sock_; - std::string base_dir_; - Handlers get_handlers_; - Handlers post_handlers_; - Handlers put_handlers_; - Handlers delete_handlers_; - Handlers options_handlers_; - Handler error_handler_; - Logger logger_; - - // TODO: Use thread pool... - std::mutex running_threads_mutex_; - int running_threads_; +struct UploadFormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; }; +using UploadFormDataItems = std::vector; -class Client { +class DataSink { public: - Client( - const char* host, - int port = 80, - size_t timeout_sec = 300); + DataSink() : os(&sb_), sb_(*this) {} + + DataSink(const DataSink &) = delete; + DataSink &operator=(const DataSink &) = delete; + DataSink(DataSink &&) = delete; + DataSink &operator=(DataSink &&) = delete; + + std::function write; + std::function is_writable; + std::function done; + std::function done_with_trailer; + std::ostream os; - virtual ~Client(); +private: + class data_sink_streambuf final : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) override { + sink_.write(s, static_cast(n)); + return n; + } - virtual bool is_valid() const; + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; +}; - std::shared_ptr Get(const char* path, Progress progress = nullptr); - std::shared_ptr Get(const char* path, const Headers& headers, Progress progress = nullptr); +using ContentProvider = + std::function; - std::shared_ptr Head(const char* path); - std::shared_ptr Head(const char* path, const Headers& headers); +using ContentProviderWithoutLength = + std::function; - std::shared_ptr Post(const char* path, const std::string& body, const char* content_type); - std::shared_ptr Post(const char* path, const Headers& headers, const std::string& body, const char* content_type); +using ContentProviderResourceReleaser = std::function; - std::shared_ptr Post(const char* path, const Params& params); - std::shared_ptr Post(const char* path, const Headers& headers, const Params& params); +struct FormDataProvider { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; +}; +using FormDataProviderItems = std::vector; - std::shared_ptr Put(const char* path, const std::string& body, const char* content_type); - std::shared_ptr Put(const char* path, const Headers& headers, const std::string& body, const char* content_type); +using ContentReceiverWithProgress = std::function; - std::shared_ptr Delete(const char* path); - std::shared_ptr Delete(const char* path, const Headers& headers); +using ContentReceiver = + std::function; - std::shared_ptr Options(const char* path); - std::shared_ptr Options(const char* path, const Headers& headers); +using FormDataHeader = std::function; - bool send(Request& req, Response& res); +class ContentReader { +public: + using Reader = std::function; + using FormDataReader = + std::function; -protected: - bool process_request(Stream& strm, Request& req, Response& res, bool& connection_close); + ContentReader(Reader reader, FormDataReader multipart_reader) + : reader_(std::move(reader)), + formdata_reader_(std::move(multipart_reader)) {} - const std::string host_; - const int port_; - size_t timeout_sec_; - const std::string host_and_port_; + bool operator()(FormDataHeader header, ContentReceiver receiver) const { + return formdata_reader_(std::move(header), std::move(receiver)); + } -private: - socket_t create_client_socket() const; - bool read_response_line(Stream& strm, Response& res); - void write_request(Stream& strm, Request& req); + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } - virtual bool read_and_close_socket(socket_t sock, Request& req, Response& res); + Reader reader_; + FormDataReader formdata_reader_; }; +using Range = std::pair; +using Ranges = std::vector; + +struct Request { + std::string method; + std::string path; + std::string matched_route; + Params params; + Headers headers; + Headers trailers; + std::string body; + + std::string remote_addr; + int remote_port = -1; + std::string local_addr; + int local_port = -1; + + // for server + std::string version; + std::string target; + MultipartFormData form; + Ranges ranges; + Match matches; + std::unordered_map path_params; + std::function is_connection_closed = []() { return true; }; + + // for client + std::vector accept_content_types; + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; + DownloadProgress download_progress; + UploadProgress upload_progress; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -class SSLSocketStream : public Stream { -public: - SSLSocketStream(socket_t sock, SSL* ssl); - virtual ~SSLSocketStream(); + const SSL *ssl = nullptr; +#endif - virtual int read(char* ptr, size_t size); - virtual int write(const char* ptr, size_t size); - virtual int write(const char* ptr); - virtual std::string get_remote_addr(); + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, const char *def = "", + size_t id = 0) const; + size_t get_header_value_u64(const std::string &key, size_t def = 0, + size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + bool has_trailer(const std::string &key) const; + std::string get_trailer_value(const std::string &key, size_t id = 0) const; + size_t get_trailer_value_count(const std::string &key) const; + + bool has_param(const std::string &key) const; + std::string get_param_value(const std::string &key, size_t id = 0) const; + size_t get_param_value_count(const std::string &key) const; + + bool is_multipart_form_data() const; + + // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; + std::chrono::time_point start_time_ = + (std::chrono::steady_clock::time_point::min)(); +}; -private: - socket_t sock_; - SSL* ssl_; +struct Response { + std::string version; + int status = -1; + std::string reason; + Headers headers; + Headers trailers; + std::string body; + std::string location; // Redirect location + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, const char *def = "", + size_t id = 0) const; + size_t get_header_value_u64(const std::string &key, size_t def = 0, + size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + bool has_trailer(const std::string &key) const; + std::string get_trailer_value(const std::string &key, size_t id = 0) const; + size_t get_trailer_value_count(const std::string &key) const; + + void set_redirect(const std::string &url, int status = StatusCode::Found_302); + void set_content(const char *s, size_t n, const std::string &content_type); + void set_content(const std::string &s, const std::string &content_type); + void set_content(std::string &&s, const std::string &content_type); + + void set_content_provider( + size_t length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_file_content(const std::string &path, + const std::string &content_type); + void set_file_content(const std::string &path); + + Response() = default; + Response(const Response &) = default; + Response &operator=(const Response &) = default; + Response(Response &&) = default; + Response &operator=(Response &&) = default; + ~Response() { + if (content_provider_resource_releaser_) { + content_provider_resource_releaser_(content_provider_success_); + } + } + + // private members... + size_t content_length_ = 0; + ContentProvider content_provider_; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; + std::string file_content_path_; + std::string file_content_content_type_; }; -class SSLServer : public Server { +class Stream { public: - SSLServer( - const char* cert_path, const char* private_key_path); + virtual ~Stream() = default; - virtual ~SSLServer(); + virtual bool is_readable() const = 0; + virtual bool wait_readable() const = 0; + virtual bool wait_writable() const = 0; - virtual bool is_valid() const; + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; -private: - virtual bool read_and_close_socket(socket_t sock); + virtual time_t duration() const = 0; - SSL_CTX* ctx_; - std::mutex ctx_mutex_; + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); }; -class SSLClient : public Client { +class TaskQueue { public: - SSLClient( - const char* host, - int port = 80, - size_t timeout_sec = 300); - - virtual ~SSLClient(); + TaskQueue() = default; + virtual ~TaskQueue() = default; - virtual bool is_valid() const; + virtual bool enqueue(std::function fn) = 0; + virtual void shutdown() = 0; -private: - virtual bool read_and_close_socket(socket_t sock, Request& req, Response& res); - - SSL_CTX* ctx_; - std::mutex ctx_mutex_; + virtual void on_idle() {} }; -#endif -/* - * Implementation - */ -namespace detail { +class ThreadPool final : public TaskQueue { +public: + explicit ThreadPool(size_t n, size_t mqr = 0) + : shutdown_(false), max_queued_requests_(mqr) { + while (n) { + threads_.emplace_back(worker(*this)); + n--; + } + } -template -void split(const char* b, const char* e, char d, Fn fn) -{ - int i = 0; - int beg = 0; + ThreadPool(const ThreadPool &) = delete; + ~ThreadPool() override = default; - while (e ? (b + i != e) : (b[i] != '\0')) { - if (b[i] == d) { - fn(&b[beg], &b[i]); - beg = i + 1; - } - i++; + bool enqueue(std::function fn) override { + { + std::unique_lock lock(mutex_); + if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) { + return false; + } + jobs_.push_back(std::move(fn)); } - if (i) { - fn(&b[beg], &b[i]); - } -} + cond_.notify_one(); + return true; + } -// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` -// to store data. The call can set memory on stack for performance. -class stream_line_reader { -public: - stream_line_reader(Stream& strm, char* fixed_buffer, size_t fixed_buffer_size) - : strm_(strm) - , fixed_buffer_(fixed_buffer) - , fixed_buffer_size_(fixed_buffer_size) { + void shutdown() override { + // Stop all worker threads... + { + std::unique_lock lock(mutex_); + shutdown_ = true; } - const char* ptr() const { - if (glowable_buffer_.empty()) { - return fixed_buffer_; - } else { - return glowable_buffer_.data(); - } + cond_.notify_all(); + + // Join... + for (auto &t : threads_) { + t.join(); } + } - bool getline() { - fixed_buffer_used_size_ = 0; - glowable_buffer_.clear(); +private: + struct worker { + explicit worker(ThreadPool &pool) : pool_(pool) {} - for (size_t i = 0; ; i++) { - char byte; - auto n = strm_.read(&byte, 1); + void operator()() { + for (;;) { + std::function fn; + { + std::unique_lock lock(pool_.mutex_); - if (n < 0) { - return false; - } else if (n == 0) { - if (i == 0) { - return false; - } else { - break; - } - } + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); - append(byte); + if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } - if (byte == '\n') { - break; - } + fn = pool_.jobs_.front(); + pool_.jobs_.pop_front(); } - return true; - } + assert(true == static_cast(fn)); + fn(); + } -private: - void append(char c) { - if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { - fixed_buffer_[fixed_buffer_used_size_++] = c; - fixed_buffer_[fixed_buffer_used_size_] = '\0'; - } else { - if (glowable_buffer_.empty()) { - assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); - glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); - } - glowable_buffer_ += c; - } +#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(LIBRESSL_VERSION_NUMBER) + OPENSSL_thread_stop(); +#endif } - Stream& strm_; - char* fixed_buffer_; - const size_t fixed_buffer_size_; - size_t fixed_buffer_used_size_; - std::string glowable_buffer_; -}; + ThreadPool &pool_; + }; + friend struct worker; -inline int close_socket(socket_t sock) -{ -#ifdef _WIN32 - return closesocket(sock); -#else - return close(sock); -#endif -} + std::vector threads_; + std::list> jobs_; -inline int select_read(socket_t sock, size_t sec, size_t usec) -{ - fd_set fds; - FD_ZERO(&fds); - FD_SET(sock, &fds); + bool shutdown_; + size_t max_queued_requests_ = 0; - timeval tv; - tv.tv_sec = sec; - tv.tv_usec = usec; + std::condition_variable cond_; + std::mutex mutex_; +}; - return select(sock + 1, &fds, NULL, NULL, &tv); -} +using Logger = std::function; -inline bool wait_until_socket_is_ready(socket_t sock, size_t sec, size_t usec) -{ - fd_set fdsr; - FD_ZERO(&fdsr); - FD_SET(sock, &fdsr); +// Forward declaration for Error type +enum class Error; +using ErrorLogger = std::function; - auto fdsw = fdsr; - auto fdse = fdsr; +using SocketOptions = std::function; - timeval tv; - tv.tv_sec = sec; - tv.tv_usec = usec; +namespace detail { - if (select(sock + 1, &fdsr, &fdsw, &fdse, &tv) < 0) { - return false; - } else if (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw)) { - int error = 0; - socklen_t len = sizeof(error); - if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&error, &len) < 0 || error) { - return false; - } - } else { - return false; - } +bool set_socket_opt_impl(socket_t sock, int level, int optname, + const void *optval, socklen_t optlen); +bool set_socket_opt(socket_t sock, int level, int optname, int opt); +bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec, + time_t usec); - return true; -} +} // namespace detail -template -inline bool read_and_close_socket(socket_t sock, size_t keep_alive_max_count, T callback) -{ - bool ret = false; - - if (keep_alive_max_count > 0) { - auto count = keep_alive_max_count; - while (count > 0 && - detail::select_read(sock, - CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, - CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0) { - SocketStream strm(sock); - auto last_connection = count == 1; - auto connection_close = false; - - ret = callback(strm, last_connection, connection_close); - if (!ret || connection_close) { - break; - } +void default_socket_options(socket_t sock); - count--; - } - } else { - SocketStream strm(sock); - auto dummy_connection_close = false; - ret = callback(strm, true, dummy_connection_close); - } +const char *status_message(int status); - close_socket(sock); - return ret; -} +std::string get_bearer_token_auth(const Request &req); -inline int shutdown_socket(socket_t sock) -{ -#ifdef _WIN32 - return shutdown(sock, SD_BOTH); -#else - return shutdown(sock, SHUT_RDWR); -#endif -} +namespace detail { -template -socket_t create_socket(const char* host, int port, Fn fn, int socket_flags = 0) -{ -#ifdef _WIN32 -#define SO_SYNCHRONOUS_NONALERT 0x20 -#define SO_OPENTYPE 0x7008 +class MatcherBase { +public: + MatcherBase(std::string pattern) : pattern_(pattern) {} + virtual ~MatcherBase() = default; - int opt = SO_SYNCHRONOUS_NONALERT; - setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char*)&opt, sizeof(opt)); -#endif + const std::string &pattern() const { return pattern_; } - // Get address info - struct addrinfo hints; - struct addrinfo *result; + // Match request path and populate its matches and + virtual bool match(Request &request) const = 0; - memset(&hints, 0, sizeof(struct addrinfo)); +private: + std::string pattern_; +}; + +/** + * Captures parameters in request path and stores them in Request::path_params + * + * Capture name is a substring of a pattern from : to /. + * The rest of the pattern is matched against the request path directly + * Parameters are captured starting from the next character after + * the end of the last matched static pattern fragment until the next /. + * + * Example pattern: + * "/path/fragments/:capture/more/fragments/:second_capture" + * Static fragments: + * "/path/fragments/", "more/fragments/" + * + * Given the following request path: + * "/path/fragments/:1/more/fragments/:2" + * the resulting capture will be + * {{"capture", "1"}, {"second_capture", "2"}} + */ +class PathParamsMatcher final : public MatcherBase { +public: + PathParamsMatcher(const std::string &pattern); + + bool match(Request &request) const override; + +private: + // Treat segment separators as the end of path parameter capture + // Does not need to handle query parameters as they are parsed before path + // matching + static constexpr char separator = '/'; + + // Contains static path fragments to match against, excluding the '/' after + // path params + // Fragments are separated by path params + std::vector static_fragments_; + // Stores the names of the path parameters to be used as keys in the + // Request::path_params map + std::vector param_names_; +}; + +/** + * Performs std::regex_match on request path + * and stores the result in Request::matches + * + * Note that regex match is performed directly on the whole request. + * This means that wildcard patterns may match multiple path segments with /: + * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end". + */ +class RegexMatcher final : public MatcherBase { +public: + RegexMatcher(const std::string &pattern) + : MatcherBase(pattern), regex_(pattern) {} + + bool match(Request &request) const override; + +private: + std::regex regex_; +}; + +ssize_t write_headers(Stream &strm, const Headers &headers); + +} // namespace detail + +class Server { +public: + using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = + std::function; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server &Get(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, HandlerWithContentReader handler); + Server &Put(const std::string &pattern, Handler handler); + Server &Put(const std::string &pattern, HandlerWithContentReader handler); + Server &Patch(const std::string &pattern, Handler handler); + Server &Patch(const std::string &pattern, HandlerWithContentReader handler); + Server &Delete(const std::string &pattern, Handler handler); + Server &Delete(const std::string &pattern, HandlerWithContentReader handler); + Server &Options(const std::string &pattern, Handler handler); + + bool set_base_dir(const std::string &dir, + const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, + Headers headers = Headers()); + bool remove_mount_point(const std::string &mount_point); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime); + Server &set_default_file_mimetype(const std::string &mime); + Server &set_file_request_handler(Handler handler); + + template + Server &set_error_handler(ErrorHandlerFunc &&handler) { + return set_error_handler_core( + std::forward(handler), + std::is_convertible{}); + } + + Server &set_exception_handler(ExceptionHandler handler); + + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); + + Server &set_pre_request_handler(HandlerWithResponse handler); + + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); + Server &set_pre_compression_logger(Logger logger); + Server &set_error_logger(ErrorLogger error_logger); + + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_ipv6_v6only(bool on); + Server &set_socket_options(SocketOptions socket_options); + + Server &set_default_headers(Headers headers); + Server & + set_header_writer(std::function const &writer); + + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const std::string &host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string &host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const std::string &host, int port, int socket_flags = 0); + + bool is_running() const; + void wait_until_ready() const; + void stop(); + void decommission(); + + std::function new_task_queue; + +protected: + bool process_request(Stream &strm, const std::string &remote_addr, + int remote_port, const std::string &local_addr, + int local_port, bool close_connection, + bool &connection_closed, + const std::function &setup_request); + + std::atomic svr_sock_{INVALID_SOCKET}; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + +private: + using Handlers = + std::vector, Handler>>; + using HandlersForContentReader = + std::vector, + HandlerWithContentReader>>; + + static std::unique_ptr + make_matcher(const std::string &pattern); + + Server &set_error_handler_core(HandlerWithResponse handler, std::true_type); + Server &set_error_handler_core(Handler handler, std::false_type); + + socket_t create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const; + int bind_internal(const std::string &host, int port, int socket_flags); + bool listen_internal(); + + bool routing(Request &req, Response &res, Stream &strm); + bool handle_file_request(const Request &req, Response &res); + bool dispatch_request(Request &req, Response &res, + const Handlers &handlers) const; + bool dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) const; + + bool parse_request_line(const char *s, Request &req) const; + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary) const; + bool write_response(Stream &strm, bool close_connection, Request &req, + Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges); + bool write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type); + bool read_content(Stream &strm, Request &req, Response &res); + bool read_content_with_content_receiver(Stream &strm, Request &req, + Response &res, + ContentReceiver receiver, + FormDataHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + FormDataHeader multipart_header, + ContentReceiver multipart_receiver) const; + + virtual bool process_and_close_socket(socket_t sock); + + void output_log(const Request &req, const Response &res) const; + void output_pre_compression_log(const Request &req, + const Response &res) const; + void output_error_log(const Error &err, const Request *req) const; + + std::atomic is_running_{false}; + std::atomic is_decommissioned{false}; + + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + std::map file_extension_and_mimetype_map_; + std::string default_file_mimetype_ = "application/octet-stream"; + Handler file_request_handler_; + + Handlers get_handlers_; + Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; + Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; + Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; + Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; + Handlers options_handlers_; + + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + HandlerWithResponse pre_request_handler_; + Expect100ContinueHandler expect_100_continue_handler_; + + mutable std::mutex logger_mutex_; + Logger logger_; + Logger pre_compression_logger_; + ErrorLogger error_logger_; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; + SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; + std::function header_writer_ = + detail::write_headers; +}; + +enum class Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + SSLServerHostnameVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + ProxyConnection, + ResourceExhaustion, + TooManyFormDataFiles, + ExceedMaxPayloadSize, + ExceedUriMaxLength, + ExceedMaxSocketDescriptorCount, + InvalidRequestLine, + InvalidHTTPMethod, + InvalidHTTPVersion, + InvalidHeaders, + MultipartParsing, + OpenFile, + Listen, + GetSockName, + UnsupportedAddressFamily, + HTTPParsing, + InvalidRangeHeader, + + // For internal use only + SSLPeerCouldBeClosed_, +}; + +std::string to_string(Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + +class Result { +public: + Result() = default; + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + Result(std::unique_ptr &&res, Error err, Headers &&request_headers, + int ssl_error) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)), ssl_error_(ssl_error) {} + Result(std::unique_ptr &&res, Error err, Headers &&request_headers, + int ssl_error, unsigned long ssl_openssl_error) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)), ssl_error_(ssl_error), + ssl_openssl_error_(ssl_openssl_error) {} +#endif + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // SSL Error + int ssl_error() const { return ssl_error_; } + // OpenSSL Error + unsigned long ssl_openssl_error() const { return ssl_openssl_error_; } +#endif + + // Request Headers + bool has_request_header(const std::string &key) const; + std::string get_request_header_value(const std::string &key, + const char *def = "", + size_t id = 0) const; + size_t get_request_header_value_u64(const std::string &key, size_t def = 0, + size_t id = 0) const; + size_t get_request_header_value_count(const std::string &key) const; + +private: + std::unique_ptr res_; + Error err_ = Error::Unknown; + Headers request_headers_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + int ssl_error_ = 0; + unsigned long ssl_openssl_error_ = 0; +#endif +}; + +class ClientImpl { +public: + explicit ClientImpl(const std::string &host); + + explicit ClientImpl(const std::string &host, int port); + + explicit ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + virtual ~ClientImpl(); + + virtual bool is_valid() const; + + // clang-format off + Result Get(const std::string &path, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers); + Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Params ¶ms); + Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const Params ¶ms); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Delete(const std::string &path, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Params ¶ms, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const Params ¶ms, DownloadProgress progress = nullptr); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + // clang-format on + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void + set_header_writer(std::function const &writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_ipv6_v6only(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_max_timeout(time_t msec); + template + void set_max_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_path_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_store(X509_STORE *ca_cert_store); + X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size) const; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); + void enable_server_hostname_verification(bool enabled); + void set_server_certificate_verifier( + std::function verifier); +#endif + + void set_logger(Logger logger); + void set_error_logger(ErrorLogger error_logger); + +protected: + struct Socket { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + virtual bool create_and_connect_socket(Socket &socket, Error &error); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket) const; + void close_socket(Socket &socket); + + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error) const; + + void copy_settings(const ClientImpl &rhs); + + void output_log(const Request &req, const Response &res) const; + void output_error_log(const Error &err, const Request *req) const; + + // Socket endpoint information + const std::string host_; + const int port_; + const std::string host_and_port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname-IP map + std::map addr_map_; + + // Default headers + Headers default_headers_; + + // Header writer + std::function header_writer_ = + detail::write_headers; + + // Settings + std::string client_cert_path_; + std::string client_key_path_; + + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND; + time_t max_timeout_msec_ = CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string digest_auth_username_; + std::string digest_auth_password_; +#endif + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool path_encode_ = true; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + std::string interface_; + + std::string proxy_host_; + int proxy_port_ = -1; + + std::string proxy_basic_auth_username_; + std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + + X509_STORE *ca_cert_store_ = nullptr; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; + bool server_hostname_verification_ = true; + std::function server_certificate_verifier_; +#endif + + mutable std::mutex logger_mutex_; + Logger logger_; + ErrorLogger error_logger_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + int last_ssl_error_ = 0; + unsigned long last_openssl_error_ = 0; +#endif + +private: + bool send_(Request &req, Response &res, Error &error); + Result send_(Request &&req); + + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, + Response &res) const; + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool create_redirect_client(const std::string &scheme, + const std::string &host, int port, Request &req, + Response &res, const std::string &path, + const std::string &location, Error &error); + template void setup_redirect_client(ClientType &client); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error); + Result send_with_content_provider( + const std::string &method, const std::string &path, + const Headers &headers, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, UploadProgress progress); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string &boundary, const UploadFormDataItems &items, + const FormDataProviderItems &provider_items) const; + + std::string adjust_host_string(const std::string &host) const; + + virtual bool + process_socket(const Socket &socket, + std::chrono::time_point start_time, + std::function callback); + virtual bool is_ssl() const; +}; + +class Client { +public: + // Universal interface + explicit Client(const std::string &scheme_host_port); + + explicit Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path); + + // HTTP only interface + explicit Client(const std::string &host, int port); + + explicit Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + Client(Client &&) = default; + Client &operator=(Client &&) = default; + + ~Client(); + + bool is_valid() const; + + // clang-format off + Result Get(const std::string &path, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers); + Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Params ¶ms); + Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers); + Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const Params ¶ms); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Delete(const std::string &path, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Params ¶ms, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const Params ¶ms, DownloadProgress progress = nullptr); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + // clang-format on + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void + set_header_writer(std::function const &writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_max_timeout(time_t msec); + template + void set_max_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_path_encode(bool on); + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); + void enable_server_hostname_verification(bool enabled); + void set_server_certificate_verifier( + std::function verifier); +#endif + + void set_logger(Logger logger); + void set_error_logger(ErrorLogger error_logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif + +private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLServer : public Server { +public: + SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path = nullptr, + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); + + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + + SSLServer( + const std::function &setup_ssl_ctx_callback); + + ~SSLServer() override; + + bool is_valid() const override; + + SSL_CTX *ssl_context() const; + + void update_certs(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + +private: + bool process_and_close_socket(socket_t sock) override; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + int last_ssl_error_ = 0; +#endif +}; + +class SSLClient final : public ClientImpl { +public: + explicit SSLClient(const std::string &host); + + explicit SSLClient(const std::string &host, int port); + + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path, + const std::string &private_key_password = std::string()); + + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key, + const std::string &private_key_password = std::string()); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; + +private: + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully); + + bool + process_socket(const Socket &socket, + std::chrono::time_point start_time, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy( + Socket &sock, + std::chrono::time_point start_time, + Response &res, bool &success, Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + bool load_certs(); + + bool verify_host(X509 *server_cert) const; + bool verify_host_with_subject_alt_name(X509 *server_cert) const; + bool verify_host_with_common_name(X509 *server_cert) const; + bool check_host_name(const char *pattern, size_t pattern_len) const; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + + std::vector host_components_; + + long verify_result_ = 0; + + friend class ClientImpl; +}; +#endif + +/* + * Implementation of template methods. + */ + +namespace detail { + +template +inline void duration_to_sec_and_usec(const T &duration, U callback) { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(static_cast(sec), static_cast(usec)); +} + +template inline constexpr size_t str_len(const char (&)[N]) { + return N - 1; +} + +inline bool is_numeric(const std::string &str) { + return !str.empty() && + std::all_of(str.cbegin(), str.cend(), + [](unsigned char c) { return std::isdigit(c); }); +} + +inline size_t get_header_value_u64(const Headers &headers, + const std::string &key, size_t def, + size_t id, bool &is_invalid_value) { + is_invalid_value = false; + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + if (is_numeric(it->second)) { + return std::strtoull(it->second.data(), nullptr, 10); + } else { + is_invalid_value = true; + } + } + return def; +} + +inline size_t get_header_value_u64(const Headers &headers, + const std::string &key, size_t def, + size_t id) { + auto dummy = false; + return get_header_value_u64(headers, key, def, id, dummy); +} + +} // namespace detail + +inline size_t Request::get_header_value_u64(const std::string &key, size_t def, + size_t id) const { + return detail::get_header_value_u64(headers, key, def, id); +} + +inline size_t Response::get_header_value_u64(const std::string &key, size_t def, + size_t id) const { + return detail::get_header_value_u64(headers, key, def, id); +} + +namespace detail { + +inline bool set_socket_opt_impl(socket_t sock, int level, int optname, + const void *optval, socklen_t optlen) { + return setsockopt(sock, level, optname, +#ifdef _WIN32 + reinterpret_cast(optval), +#else + optval, +#endif + optlen) == 0; +} + +inline bool set_socket_opt(socket_t sock, int level, int optname, int optval) { + return set_socket_opt_impl(sock, level, optname, &optval, sizeof(optval)); +} + +inline bool set_socket_opt_time(socket_t sock, int level, int optname, + time_t sec, time_t usec) { +#ifdef _WIN32 + auto timeout = static_cast(sec * 1000 + usec / 1000); +#else + timeval timeout; + timeout.tv_sec = static_cast(sec); + timeout.tv_usec = static_cast(usec); +#endif + return set_socket_opt_impl(sock, level, optname, &timeout, sizeof(timeout)); +} + +} // namespace detail + +inline void default_socket_options(socket_t sock) { + detail::set_socket_opt(sock, SOL_SOCKET, +#ifdef SO_REUSEPORT + SO_REUSEPORT, +#else + SO_REUSEADDR, +#endif + 1); +} + +inline const char *status_message(int status) { + switch (status) { + case StatusCode::Continue_100: return "Continue"; + case StatusCode::SwitchingProtocol_101: return "Switching Protocol"; + case StatusCode::Processing_102: return "Processing"; + case StatusCode::EarlyHints_103: return "Early Hints"; + case StatusCode::OK_200: return "OK"; + case StatusCode::Created_201: return "Created"; + case StatusCode::Accepted_202: return "Accepted"; + case StatusCode::NonAuthoritativeInformation_203: + return "Non-Authoritative Information"; + case StatusCode::NoContent_204: return "No Content"; + case StatusCode::ResetContent_205: return "Reset Content"; + case StatusCode::PartialContent_206: return "Partial Content"; + case StatusCode::MultiStatus_207: return "Multi-Status"; + case StatusCode::AlreadyReported_208: return "Already Reported"; + case StatusCode::IMUsed_226: return "IM Used"; + case StatusCode::MultipleChoices_300: return "Multiple Choices"; + case StatusCode::MovedPermanently_301: return "Moved Permanently"; + case StatusCode::Found_302: return "Found"; + case StatusCode::SeeOther_303: return "See Other"; + case StatusCode::NotModified_304: return "Not Modified"; + case StatusCode::UseProxy_305: return "Use Proxy"; + case StatusCode::unused_306: return "unused"; + case StatusCode::TemporaryRedirect_307: return "Temporary Redirect"; + case StatusCode::PermanentRedirect_308: return "Permanent Redirect"; + case StatusCode::BadRequest_400: return "Bad Request"; + case StatusCode::Unauthorized_401: return "Unauthorized"; + case StatusCode::PaymentRequired_402: return "Payment Required"; + case StatusCode::Forbidden_403: return "Forbidden"; + case StatusCode::NotFound_404: return "Not Found"; + case StatusCode::MethodNotAllowed_405: return "Method Not Allowed"; + case StatusCode::NotAcceptable_406: return "Not Acceptable"; + case StatusCode::ProxyAuthenticationRequired_407: + return "Proxy Authentication Required"; + case StatusCode::RequestTimeout_408: return "Request Timeout"; + case StatusCode::Conflict_409: return "Conflict"; + case StatusCode::Gone_410: return "Gone"; + case StatusCode::LengthRequired_411: return "Length Required"; + case StatusCode::PreconditionFailed_412: return "Precondition Failed"; + case StatusCode::PayloadTooLarge_413: return "Payload Too Large"; + case StatusCode::UriTooLong_414: return "URI Too Long"; + case StatusCode::UnsupportedMediaType_415: return "Unsupported Media Type"; + case StatusCode::RangeNotSatisfiable_416: return "Range Not Satisfiable"; + case StatusCode::ExpectationFailed_417: return "Expectation Failed"; + case StatusCode::ImATeapot_418: return "I'm a teapot"; + case StatusCode::MisdirectedRequest_421: return "Misdirected Request"; + case StatusCode::UnprocessableContent_422: return "Unprocessable Content"; + case StatusCode::Locked_423: return "Locked"; + case StatusCode::FailedDependency_424: return "Failed Dependency"; + case StatusCode::TooEarly_425: return "Too Early"; + case StatusCode::UpgradeRequired_426: return "Upgrade Required"; + case StatusCode::PreconditionRequired_428: return "Precondition Required"; + case StatusCode::TooManyRequests_429: return "Too Many Requests"; + case StatusCode::RequestHeaderFieldsTooLarge_431: + return "Request Header Fields Too Large"; + case StatusCode::UnavailableForLegalReasons_451: + return "Unavailable For Legal Reasons"; + case StatusCode::NotImplemented_501: return "Not Implemented"; + case StatusCode::BadGateway_502: return "Bad Gateway"; + case StatusCode::ServiceUnavailable_503: return "Service Unavailable"; + case StatusCode::GatewayTimeout_504: return "Gateway Timeout"; + case StatusCode::HttpVersionNotSupported_505: + return "HTTP Version Not Supported"; + case StatusCode::VariantAlsoNegotiates_506: return "Variant Also Negotiates"; + case StatusCode::InsufficientStorage_507: return "Insufficient Storage"; + case StatusCode::LoopDetected_508: return "Loop Detected"; + case StatusCode::NotExtended_510: return "Not Extended"; + case StatusCode::NetworkAuthenticationRequired_511: + return "Network Authentication Required"; + + default: + case StatusCode::InternalServerError_500: return "Internal Server Error"; + } +} + +inline std::string get_bearer_token_auth(const Request &req) { + if (req.has_header("Authorization")) { + constexpr auto bearer_header_prefix_len = detail::str_len("Bearer "); + return req.get_header_value("Authorization") + .substr(bearer_header_prefix_len); + } + return ""; +} + +template +inline Server & +Server::set_read_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_write_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_idle_interval(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; +} + +inline std::string to_string(const Error error) { + switch (error) { + case Error::Success: return "Success (no error)"; + case Error::Unknown: return "Unknown"; + case Error::Connection: return "Could not establish connection"; + case Error::BindIPAddress: return "Failed to bind IP address"; + case Error::Read: return "Failed to read connection"; + case Error::Write: return "Failed to write connection"; + case Error::ExceedRedirectCount: return "Maximum redirect count exceeded"; + case Error::Canceled: return "Connection handling canceled"; + case Error::SSLConnection: return "SSL connection failed"; + case Error::SSLLoadingCerts: return "SSL certificate loading failed"; + case Error::SSLServerVerification: return "SSL server verification failed"; + case Error::SSLServerHostnameVerification: + return "SSL server hostname verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: return "Compression failed"; + case Error::ConnectionTimeout: return "Connection timed out"; + case Error::ProxyConnection: return "Proxy connection failed"; + case Error::ResourceExhaustion: return "Resource exhaustion"; + case Error::TooManyFormDataFiles: return "Too many form data files"; + case Error::ExceedMaxPayloadSize: return "Exceeded maximum payload size"; + case Error::ExceedUriMaxLength: return "Exceeded maximum URI length"; + case Error::ExceedMaxSocketDescriptorCount: + return "Exceeded maximum socket descriptor count"; + case Error::InvalidRequestLine: return "Invalid request line"; + case Error::InvalidHTTPMethod: return "Invalid HTTP method"; + case Error::InvalidHTTPVersion: return "Invalid HTTP version"; + case Error::InvalidHeaders: return "Invalid headers"; + case Error::MultipartParsing: return "Multipart parsing failed"; + case Error::OpenFile: return "Failed to open file"; + case Error::Listen: return "Failed to listen on socket"; + case Error::GetSockName: return "Failed to get socket name"; + case Error::UnsupportedAddressFamily: return "Unsupported address family"; + case Error::HTTPParsing: return "HTTP parsing failed"; + case Error::InvalidRangeHeader: return "Invalid Range header"; + default: break; + } + + return "Invalid"; +} + +inline std::ostream &operator<<(std::ostream &os, const Error &obj) { + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; +} + +inline size_t Result::get_request_header_value_u64(const std::string &key, + size_t def, + size_t id) const { + return detail::get_header_value_u64(request_headers_, key, def, id); +} + +template +inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); +} + +template +inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_max_timeout( + const std::chrono::duration &duration) { + auto msec = + std::chrono::duration_cast(duration).count(); + set_max_timeout(msec); +} + +template +inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { + cli_->set_connection_timeout(duration); +} + +template +inline void +Client::set_read_timeout(const std::chrono::duration &duration) { + cli_->set_read_timeout(duration); +} + +template +inline void +Client::set_write_timeout(const std::chrono::duration &duration) { + cli_->set_write_timeout(duration); +} + +inline void Client::set_max_timeout(time_t msec) { + cli_->set_max_timeout(msec); +} + +template +inline void +Client::set_max_timeout(const std::chrono::duration &duration) { + cli_->set_max_timeout(duration); +} + +/* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + +std::string hosted_at(const std::string &hostname); + +void hosted_at(const std::string &hostname, std::vector &addrs); + +// JavaScript-style URL encoding/decoding functions +std::string encode_uri_component(const std::string &value); +std::string encode_uri(const std::string &value); +std::string decode_uri_component(const std::string &value); +std::string decode_uri(const std::string &value); + +// RFC 3986 compliant URL component encoding/decoding functions +std::string encode_path_component(const std::string &component); +std::string decode_path_component(const std::string &component); +std::string encode_query_component(const std::string &component, + bool space_as_plus = true); +std::string decode_query_component(const std::string &component, + bool plus_as_space = true); + +std::string append_query_params(const std::string &path, const Params ¶ms); + +std::pair make_range_header(const Ranges &ranges); + +std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false); + +namespace detail { + +#if defined(_WIN32) +inline std::wstring u8string_to_wstring(const char *s) { + std::wstring ws; + auto len = static_cast(strlen(s)); + auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0); + if (wlen > 0) { + ws.resize(wlen); + wlen = ::MultiByteToWideChar( + CP_UTF8, 0, s, len, + const_cast(reinterpret_cast(ws.data())), wlen); + if (wlen != static_cast(ws.size())) { ws.clear(); } + } + return ws; +} +#endif + +struct FileStat { + FileStat(const std::string &path); + bool is_file() const; + bool is_dir() const; + +private: +#if defined(_WIN32) + struct _stat st_; +#else + struct stat st_; +#endif + int ret_ = -1; +}; + +std::string trim_copy(const std::string &s); + +void divide( + const char *data, std::size_t size, char d, + std::function + fn); + +void divide( + const std::string &str, char d, + std::function + fn); + +void split(const char *b, const char *e, char d, + std::function fn); + +void split(const char *b, const char *e, char d, size_t m, + std::function fn); + +bool process_client_socket( + socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, + std::function callback); + +socket_t create_client_socket(const std::string &host, const std::string &ip, + int port, int address_family, bool tcp_nodelay, + bool ipv6_v6only, SocketOptions socket_options, + time_t connection_timeout_sec, + time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + const std::string &intf, Error &error); + +const char *get_header_value(const Headers &headers, const std::string &key, + const char *def, size_t id); + +std::string params_to_query_str(const Params ¶ms); + +void parse_query_text(const char *data, std::size_t size, Params ¶ms); + +void parse_query_text(const std::string &s, Params ¶ms); + +bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary); + +bool parse_range_header(const std::string &s, Ranges &ranges); + +bool parse_accept_header(const std::string &s, + std::vector &content_types); + +int close_socket(socket_t sock); + +ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); + +ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); + +enum class EncodingType { None = 0, Gzip, Brotli, Zstd }; + +EncodingType encoding_type(const Request &req, const Response &res); + +class BufferStream final : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool wait_readable() const override; + bool wait_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + time_t duration() const override; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +class compressor { +public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor final : public compressor { +public: + ~nocompressor() override = default; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override; +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor final : public compressor { +public: + gzip_compressor(); + ~gzip_compressor() override; + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor final : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor() override; + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor final : public compressor { +public: + brotli_compressor(); + ~brotli_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor final : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +#ifdef CPPHTTPLIB_ZSTD_SUPPORT +class zstd_compressor : public compressor { +public: + zstd_compressor(); + ~zstd_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + ZSTD_CCtx *ctx_ = nullptr; +}; + +class zstd_decompressor : public decompressor { +public: + zstd_decompressor(); + ~zstd_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + ZSTD_DCtx *ctx_ = nullptr; +}; +#endif + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size); + const char *ptr() const; + size_t size() const; + bool end_with_crlf() const; + bool getline(); + +private: + void append(char c); + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string growable_buffer_; +}; + +class mmap { +public: + mmap(const char *path); + ~mmap(); + + bool open(const char *path); + void close(); + + bool is_open() const; + size_t size() const; + const char *data() const; + +private: +#if defined(_WIN32) + HANDLE hFile_ = NULL; + HANDLE hMapping_ = NULL; +#else + int fd_ = -1; +#endif + size_t size_ = 0; + void *addr_ = nullptr; + bool is_open_empty_file = false; +}; + +// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5 +namespace fields { + +inline bool is_token_char(char c) { + return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' || + c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || + c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~'; +} + +inline bool is_token(const std::string &s) { + if (s.empty()) { return false; } + for (auto c : s) { + if (!is_token_char(c)) { return false; } + } + return true; +} + +inline bool is_field_name(const std::string &s) { return is_token(s); } + +inline bool is_vchar(char c) { return c >= 33 && c <= 126; } + +inline bool is_obs_text(char c) { return 128 <= static_cast(c); } + +inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); } + +inline bool is_field_content(const std::string &s) { + if (s.empty()) { return true; } + + if (s.size() == 1) { + return is_field_vchar(s[0]); + } else if (s.size() == 2) { + return is_field_vchar(s[0]) && is_field_vchar(s[1]); + } else { + size_t i = 0; + + if (!is_field_vchar(s[i])) { return false; } + i++; + + while (i < s.size() - 1) { + auto c = s[i++]; + if (c == ' ' || c == '\t' || is_field_vchar(c)) { + } else { + return false; + } + } + + return is_field_vchar(s[i]); + } +} + +inline bool is_field_value(const std::string &s) { return is_field_content(s); } + +} // namespace fields + +} // namespace detail + +// ---------------------------------------------------------------------------- + +/* + * Implementation that will be part of the .cc file if split into .h + .cc. + */ + +namespace detail { + +inline bool is_hex(char c, int &v) { + if (0x20 <= c && isdigit(c)) { + v = c - '0'; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } + return false; +} + +inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, + int &val) { + if (i >= s.size()) { return false; } + + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { return false; } + auto v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; + } else { + return false; + } + } + return true; +} + +inline std::string from_i_to_hex(size_t n) { + static const auto charset = "0123456789abcdef"; + std::string ret; + do { + ret = charset[n & 15] + ret; + n >>= 4; + } while (n > 0); + return ret; +} + +inline size_t to_utf8(int code, char *buff) { + if (code < 0x0080) { + buff[0] = static_cast(code & 0x7F); + return 1; + } else if (code < 0x0800) { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } else if (code < 0xD800) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } else if (code < 0x10000) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0x110000) { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; +} + +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c +inline std::string base64_encode(const std::string &in) { + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(in.size()); + + auto val = 0; + auto valb = -6; + + for (auto c : in) { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } + + while (out.size() % 4) { + out.push_back('='); + } + + return out; +} + +inline bool is_valid_path(const std::string &path) { + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + + while (i < path.size()) { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') { + if (path[i] == '\0') { + return false; + } else if (path[i] == '\\') { + return false; + } + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) { + ; + } else if (!path.compare(beg, len, "..")) { + if (level == 0) { return false; } + level--; + } else { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + } + + return true; +} + +inline FileStat::FileStat(const std::string &path) { +#if defined(_WIN32) + auto wpath = u8string_to_wstring(path.c_str()); + ret_ = _wstat(wpath.c_str(), &st_); +#else + ret_ = stat(path.c_str(), &st_); +#endif +} +inline bool FileStat::is_file() const { + return ret_ >= 0 && S_ISREG(st_.st_mode); +} +inline bool FileStat::is_dir() const { + return ret_ >= 0 && S_ISDIR(st_.st_mode); +} + +inline std::string encode_path(const std::string &s) { + std::string result; + result.reserve(s.size()); + + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': result += "%20"; break; + case '+': result += "%2B"; break; + case '\r': result += "%0D"; break; + case '\n': result += "%0A"; break; + case '\'': result += "%27"; break; + case ',': result += "%2C"; break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': result += "%3B"; break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline std::string file_extension(const std::string &path) { + std::smatch m; + thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { return m[1].str(); } + return std::string(); +} + +inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } + +inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); +} + +inline std::string trim_copy(const std::string &s) { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); +} + +inline std::string trim_double_quotes_copy(const std::string &s) { + if (s.length() >= 2 && s.front() == '"' && s.back() == '"') { + return s.substr(1, s.size() - 2); + } + return s; +} + +inline void +divide(const char *data, std::size_t size, char d, + std::function + fn) { + const auto it = std::find(data, data + size, d); + const auto found = static_cast(it != data + size); + const auto lhs_data = data; + const auto lhs_size = static_cast(it - data); + const auto rhs_data = it + found; + const auto rhs_size = size - lhs_size - found; + + fn(lhs_data, lhs_size, rhs_data, rhs_size); +} + +inline void +divide(const std::string &str, char d, + std::function + fn) { + divide(str.data(), str.size(), d, std::move(fn)); +} + +inline void split(const char *b, const char *e, char d, + std::function fn) { + return split(b, e, d, (std::numeric_limits::max)(), std::move(fn)); +} + +inline void split(const char *b, const char *e, char d, size_t m, + std::function fn) { + size_t i = 0; + size_t beg = 0; + size_t count = 1; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d && count < m) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + beg = i + 1; + count++; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } +} + +inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} + +inline const char *stream_line_reader::ptr() const { + if (growable_buffer_.empty()) { + return fixed_buffer_; + } else { + return growable_buffer_.data(); + } +} + +inline size_t stream_line_reader::size() const { + if (growable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return growable_buffer_.size(); + } +} + +inline bool stream_line_reader::end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; +} + +inline bool stream_line_reader::getline() { + fixed_buffer_used_size_ = 0; + growable_buffer_.clear(); + +#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + char prev_byte = 0; +#endif + + for (size_t i = 0;; i++) { + if (size() >= CPPHTTPLIB_MAX_LINE_LENGTH) { + // Treat exceptionally long lines as an error to + // prevent infinite loops/memory exhaustion + return false; + } + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { + return false; + } else { + break; + } + } + + append(byte); + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + if (byte == '\n') { break; } +#else + if (prev_byte == '\r' && byte == '\n') { break; } + prev_byte = byte; +#endif + } + + return true; +} + +inline void stream_line_reader::append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (growable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + growable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + growable_buffer_ += c; + } +} + +inline mmap::mmap(const char *path) { open(path); } + +inline mmap::~mmap() { close(); } + +inline bool mmap::open(const char *path) { + close(); + +#if defined(_WIN32) + auto wpath = u8string_to_wstring(path); + if (wpath.empty()) { return false; } + +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 + hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, + OPEN_EXISTING, NULL); +#else + hFile_ = ::CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); +#endif + + if (hFile_ == INVALID_HANDLE_VALUE) { return false; } + + LARGE_INTEGER size{}; + if (!::GetFileSizeEx(hFile_, &size)) { return false; } + // If the following line doesn't compile due to QuadPart, update Windows SDK. + // See: + // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721 + if (static_cast(size.QuadPart) > + (std::numeric_limits::max)()) { + // `size_t` might be 32-bits, on 32-bits Windows. + return false; + } + size_ = static_cast(size.QuadPart); + +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 + hMapping_ = + ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL); +#else + hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, 0, 0, NULL); +#endif + + // Special treatment for an empty file... + if (hMapping_ == NULL && size_ == 0) { + close(); + is_open_empty_file = true; + return true; + } + + if (hMapping_ == NULL) { + close(); + return false; + } + +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 + addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0); +#else + addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0); +#endif + + if (addr_ == nullptr) { + close(); + return false; + } +#else + fd_ = ::open(path, O_RDONLY); + if (fd_ == -1) { return false; } + + struct stat sb; + if (fstat(fd_, &sb) == -1) { + close(); + return false; + } + size_ = static_cast(sb.st_size); + + addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0); + + // Special treatment for an empty file... + if (addr_ == MAP_FAILED && size_ == 0) { + close(); + is_open_empty_file = true; + return false; + } +#endif + + return true; +} + +inline bool mmap::is_open() const { + return is_open_empty_file ? true : addr_ != nullptr; +} + +inline size_t mmap::size() const { return size_; } + +inline const char *mmap::data() const { + return is_open_empty_file ? "" : static_cast(addr_); +} + +inline void mmap::close() { +#if defined(_WIN32) + if (addr_) { + ::UnmapViewOfFile(addr_); + addr_ = nullptr; + } + + if (hMapping_) { + ::CloseHandle(hMapping_); + hMapping_ = NULL; + } + + if (hFile_ != INVALID_HANDLE_VALUE) { + ::CloseHandle(hFile_); + hFile_ = INVALID_HANDLE_VALUE; + } + + is_open_empty_file = false; +#else + if (addr_ != nullptr) { + munmap(addr_, size_); + addr_ = nullptr; + } + + if (fd_ != -1) { + ::close(fd_); + fd_ = -1; + } +#endif + size_ = 0; +} +inline int close_socket(socket_t sock) { +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +template inline ssize_t handle_EINTR(T fn) { + ssize_t res = 0; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { + std::this_thread::sleep_for(std::chrono::microseconds{1}); + continue; + } + break; + } + return res; +} + +inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) { + return handle_EINTR([&]() { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, + int flags) { + return handle_EINTR([&]() { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) { +#ifdef _WIN32 + return ::WSAPoll(fds, nfds, timeout); +#else + return ::poll(fds, nfds, timeout); +#endif +} + +template +inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) { +#ifdef __APPLE__ + if (sock >= FD_SETSIZE) { return -1; } + + fd_set fds, *rfds, *wfds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + rfds = (Read ? &fds : nullptr); + wfds = (Read ? nullptr : &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), rfds, wfds, nullptr, &tv); + }); +#else + struct pollfd pfd; + pfd.fd = sock; + pfd.events = (Read ? POLLIN : POLLOUT); + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll_wrapper(&pfd, 1, timeout); }); +#endif +} + +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { + return select_impl(sock, sec, usec); +} + +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { + return select_impl(sock, sec, usec); +} + +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { +#ifdef __APPLE__ + if (sock >= FD_SETSIZE) { return Error::Connection; } + + fd_set fdsr, fdsw; + FD_ZERO(&fdsr); + FD_ZERO(&fdsw); + FD_SET(sock, &fdsr); + FD_SET(sock, &fdsw); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, nullptr, &tv); + }); + + if (ret == 0) { return Error::ConnectionTimeout; } + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +#else + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = + handle_EINTR([&]() { return poll_wrapper(&pfd_read, 1, timeout); }); + + if (poll_res == 0) { return Error::ConnectionTimeout; } + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +#endif +} + +inline bool is_socket_alive(socket_t sock) { + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) { + return true; + } else if (val < 0 && errno == EBADF) { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; +} + +class SocketStream final : public Stream { +public: + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec = 0, + std::chrono::time_point start_time = + (std::chrono::steady_clock::time_point::min)()); + ~SocketStream() override; + + bool is_readable() const override; + bool wait_readable() const override; + bool wait_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + time_t duration() const override; + +private: + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + time_t max_timeout_msec_; + const std::chrono::time_point start_time_; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + + static const size_t read_buff_size_ = 1024l * 4; +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLSocketStream final : public Stream { +public: + SSLSocketStream( + socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, time_t max_timeout_msec = 0, + std::chrono::time_point start_time = + (std::chrono::steady_clock::time_point::min)()); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool wait_readable() const override; + bool wait_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + time_t duration() const override; + +private: + socket_t sock_; + SSL *ssl_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + time_t max_timeout_msec_; + const std::chrono::time_point start_time_; +}; +#endif + +inline bool keep_alive(const std::atomic &svr_sock, socket_t sock, + time_t keep_alive_timeout_sec) { + using namespace std::chrono; + + const auto interval_usec = + CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND; + + // Avoid expensive `steady_clock::now()` call for the first time + if (select_read(sock, 0, interval_usec) > 0) { return true; } + + const auto start = steady_clock::now() - microseconds{interval_usec}; + const auto timeout = seconds{keep_alive_timeout_sec}; + + while (true) { + if (svr_sock == INVALID_SOCKET) { + break; // Server socket is closed + } + + auto val = select_read(sock, 0, interval_usec); + if (val < 0) { + break; // Ssocket error + } else if (val == 0) { + if (steady_clock::now() - start > timeout) { + break; // Timeout + } + } else { + return true; // Ready for read + } + } + + return false; +} + +template +inline bool +process_server_socket_core(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } + return ret; +} + +template +inline bool +process_server_socket(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +inline bool process_client_socket( + socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, + std::function callback) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec, max_timeout_msec, + start_time); + return callback(strm); +} + +inline int shutdown_socket(socket_t sock) { +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif +} + +inline std::string escape_abstract_namespace_unix_domain(const std::string &s) { + if (s.size() > 1 && s[0] == '\0') { + auto ret = s; + ret[0] = '@'; + return ret; + } + return s; +} + +inline std::string +unescape_abstract_namespace_unix_domain(const std::string &s) { + if (s.size() > 1 && s[0] == '@') { + auto ret = s; + ret[0] = '\0'; + return ret; + } + return s; +} + +inline int getaddrinfo_with_timeout(const char *node, const char *service, + const struct addrinfo *hints, + struct addrinfo **res, time_t timeout_sec) { +#ifdef CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO + if (timeout_sec <= 0) { + // No timeout specified, use standard getaddrinfo + return getaddrinfo(node, service, hints, res); + } + +#ifdef _WIN32 + // Windows-specific implementation using GetAddrInfoEx with overlapped I/O + OVERLAPPED overlapped = {0}; + HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!event) { return EAI_FAIL; } + + overlapped.hEvent = event; + + PADDRINFOEXW result_addrinfo = nullptr; + HANDLE cancel_handle = nullptr; + + ADDRINFOEXW hints_ex = {0}; + if (hints) { + hints_ex.ai_flags = hints->ai_flags; + hints_ex.ai_family = hints->ai_family; + hints_ex.ai_socktype = hints->ai_socktype; + hints_ex.ai_protocol = hints->ai_protocol; + } + + auto wnode = u8string_to_wstring(node); + auto wservice = u8string_to_wstring(service); + + auto ret = ::GetAddrInfoExW(wnode.data(), wservice.data(), NS_DNS, nullptr, + hints ? &hints_ex : nullptr, &result_addrinfo, + nullptr, &overlapped, nullptr, &cancel_handle); + + if (ret == WSA_IO_PENDING) { + auto wait_result = + ::WaitForSingleObject(event, static_cast(timeout_sec * 1000)); + if (wait_result == WAIT_TIMEOUT) { + if (cancel_handle) { ::GetAddrInfoExCancel(&cancel_handle); } + ::CloseHandle(event); + return EAI_AGAIN; + } + + DWORD bytes_returned; + if (!::GetOverlappedResult((HANDLE)INVALID_SOCKET, &overlapped, + &bytes_returned, FALSE)) { + ::CloseHandle(event); + return ::WSAGetLastError(); + } + } + + ::CloseHandle(event); + + if (ret == NO_ERROR || ret == WSA_IO_PENDING) { + *res = reinterpret_cast(result_addrinfo); + return 0; + } + + return ret; +#elif defined(TARGET_OS_OSX) + // macOS implementation using CFHost API for asynchronous DNS resolution + CFStringRef hostname_ref = CFStringCreateWithCString( + kCFAllocatorDefault, node, kCFStringEncodingUTF8); + if (!hostname_ref) { return EAI_MEMORY; } + + CFHostRef host_ref = CFHostCreateWithName(kCFAllocatorDefault, hostname_ref); + CFRelease(hostname_ref); + if (!host_ref) { return EAI_MEMORY; } + + // Set up context for callback + struct CFHostContext { + bool completed = false; + bool success = false; + CFArrayRef addresses = nullptr; + std::mutex mutex; + std::condition_variable cv; + } context; + + CFHostClientContext client_context; + memset(&client_context, 0, sizeof(client_context)); + client_context.info = &context; + + // Set callback + auto callback = [](CFHostRef theHost, CFHostInfoType /*typeInfo*/, + const CFStreamError *error, void *info) { + auto ctx = static_cast(info); + std::lock_guard lock(ctx->mutex); + + if (error && error->error != 0) { + ctx->success = false; + } else { + Boolean hasBeenResolved; + ctx->addresses = CFHostGetAddressing(theHost, &hasBeenResolved); + if (ctx->addresses && hasBeenResolved) { + CFRetain(ctx->addresses); + ctx->success = true; + } else { + ctx->success = false; + } + } + ctx->completed = true; + ctx->cv.notify_one(); + }; + + if (!CFHostSetClient(host_ref, callback, &client_context)) { + CFRelease(host_ref); + return EAI_SYSTEM; + } + + // Schedule on run loop + CFRunLoopRef run_loop = CFRunLoopGetCurrent(); + CFHostScheduleWithRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode); + + // Start resolution + CFStreamError stream_error; + if (!CFHostStartInfoResolution(host_ref, kCFHostAddresses, &stream_error)) { + CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode); + CFRelease(host_ref); + return EAI_FAIL; + } + + // Wait for completion with timeout + auto timeout_time = + std::chrono::steady_clock::now() + std::chrono::seconds(timeout_sec); + bool timed_out = false; + + { + std::unique_lock lock(context.mutex); + + while (!context.completed) { + auto now = std::chrono::steady_clock::now(); + if (now >= timeout_time) { + timed_out = true; + break; + } + + // Run the runloop for a short time + lock.unlock(); + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true); + lock.lock(); + } + } + + // Clean up + CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode); + CFHostSetClient(host_ref, nullptr, nullptr); + + if (timed_out || !context.completed) { + CFHostCancelInfoResolution(host_ref, kCFHostAddresses); + CFRelease(host_ref); + return EAI_AGAIN; + } + + if (!context.success || !context.addresses) { + CFRelease(host_ref); + return EAI_NODATA; + } + + // Convert CFArray to addrinfo + CFIndex count = CFArrayGetCount(context.addresses); + if (count == 0) { + CFRelease(context.addresses); + CFRelease(host_ref); + return EAI_NODATA; + } + + struct addrinfo *result_addrinfo = nullptr; + struct addrinfo **current = &result_addrinfo; + + for (CFIndex i = 0; i < count; i++) { + CFDataRef addr_data = + static_cast(CFArrayGetValueAtIndex(context.addresses, i)); + if (!addr_data) continue; + + const struct sockaddr *sockaddr_ptr = + reinterpret_cast(CFDataGetBytePtr(addr_data)); + socklen_t sockaddr_len = static_cast(CFDataGetLength(addr_data)); + + // Allocate addrinfo structure + *current = static_cast(malloc(sizeof(struct addrinfo))); + if (!*current) { + freeaddrinfo(result_addrinfo); + CFRelease(context.addresses); + CFRelease(host_ref); + return EAI_MEMORY; + } + + memset(*current, 0, sizeof(struct addrinfo)); + + // Set up addrinfo fields + (*current)->ai_family = sockaddr_ptr->sa_family; + (*current)->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM; + (*current)->ai_protocol = hints ? hints->ai_protocol : IPPROTO_TCP; + (*current)->ai_addrlen = sockaddr_len; + + // Copy sockaddr + (*current)->ai_addr = static_cast(malloc(sockaddr_len)); + if (!(*current)->ai_addr) { + freeaddrinfo(result_addrinfo); + CFRelease(context.addresses); + CFRelease(host_ref); + return EAI_MEMORY; + } + memcpy((*current)->ai_addr, sockaddr_ptr, sockaddr_len); + + // Set port if service is specified + if (service && strlen(service) > 0) { + int port = atoi(service); + if (port > 0) { + if (sockaddr_ptr->sa_family == AF_INET) { + reinterpret_cast((*current)->ai_addr) + ->sin_port = htons(static_cast(port)); + } else if (sockaddr_ptr->sa_family == AF_INET6) { + reinterpret_cast((*current)->ai_addr) + ->sin6_port = htons(static_cast(port)); + } + } + } + + current = &((*current)->ai_next); + } + + CFRelease(context.addresses); + CFRelease(host_ref); + + *res = result_addrinfo; + return 0; +#elif defined(_GNU_SOURCE) && defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2)) + // Linux implementation using getaddrinfo_a for asynchronous DNS resolution + struct gaicb request; + struct gaicb *requests[1] = {&request}; + struct sigevent sevp; + struct timespec timeout; + + // Initialize the request structure + memset(&request, 0, sizeof(request)); + request.ar_name = node; + request.ar_service = service; + request.ar_request = hints; + + // Set up timeout + timeout.tv_sec = timeout_sec; + timeout.tv_nsec = 0; + + // Initialize sigevent structure (not used, but required) + memset(&sevp, 0, sizeof(sevp)); + sevp.sigev_notify = SIGEV_NONE; + + // Start asynchronous resolution + int start_result = getaddrinfo_a(GAI_NOWAIT, requests, 1, &sevp); + if (start_result != 0) { return start_result; } + + // Wait for completion with timeout + int wait_result = + gai_suspend((const struct gaicb *const *)requests, 1, &timeout); + + if (wait_result == 0) { + // Completed successfully, get the result + int gai_result = gai_error(&request); + if (gai_result == 0) { + *res = request.ar_result; + return 0; + } else { + // Clean up on error + if (request.ar_result) { freeaddrinfo(request.ar_result); } + return gai_result; + } + } else if (wait_result == EAI_AGAIN) { + // Timeout occurred, cancel the request + gai_cancel(&request); + return EAI_AGAIN; + } else { + // Other error occurred + gai_cancel(&request); + return wait_result; + } +#else + // Fallback implementation using thread-based timeout for other Unix systems + std::mutex result_mutex; + std::condition_variable result_cv; + auto completed = false; + auto result = EAI_SYSTEM; + struct addrinfo *result_addrinfo = nullptr; + + std::thread resolve_thread([&]() { + auto thread_result = getaddrinfo(node, service, hints, &result_addrinfo); + + std::lock_guard lock(result_mutex); + result = thread_result; + completed = true; + result_cv.notify_one(); + }); + + // Wait for completion or timeout + std::unique_lock lock(result_mutex); + auto finished = result_cv.wait_for(lock, std::chrono::seconds(timeout_sec), + [&] { return completed; }); + + if (finished) { + // Operation completed within timeout + resolve_thread.join(); + *res = result_addrinfo; + return result; + } else { + // Timeout occurred + resolve_thread.detach(); // Let the thread finish in background + return EAI_AGAIN; // Return timeout error + } +#endif +#else + (void)(timeout_sec); // Unused parameter for non-blocking getaddrinfo + return getaddrinfo(node, service, hints, res); +#endif +} + +template +socket_t create_socket(const std::string &host, const std::string &ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + bool ipv6_v6only, SocketOptions socket_options, + BindOrConnect bind_or_connect, time_t timeout_sec = 0) { + // Get address info + const char *node = nullptr; + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_IP; + + if (!ip.empty()) { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICHOST; + } else { + if (!host.empty()) { node = host.c_str(); } + hints.ai_family = address_family; hints.ai_flags = socket_flags; - hints.ai_protocol = 0; + } + +#if !defined(_WIN32) || defined(CPPHTTPLIB_HAVE_AFUNIX_H) + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; } + +#ifdef SOCK_CLOEXEC + auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC, + hints.ai_protocol); +#else + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); +#endif + + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + + auto unescaped_host = unescape_abstract_namespace_unix_domain(host); + std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); + +#ifndef SOCK_CLOEXEC +#ifndef _WIN32 + fcntl(sock, F_SETFD, FD_CLOEXEC); +#endif +#endif + + if (socket_options) { socket_options(sock); } + +#ifdef _WIN32 + // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so + // remove the option. + detail::set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0); +#endif + + bool dummy; + if (!bind_or_connect(sock, hints, dummy)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + + auto service = std::to_string(port); + + if (getaddrinfo_with_timeout(node, service.c_str(), &hints, &result, + timeout_sec)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return INVALID_SOCKET; + } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket +#ifdef _WIN32 + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + +#ifdef SOCK_CLOEXEC + auto sock = + socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol); +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + +#endif + if (sock == INVALID_SOCKET) { continue; } + +#if !defined _WIN32 && !defined SOCK_CLOEXEC + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } +#endif + + if (tcp_nodelay) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); } + + if (rp->ai_family == AF_INET6) { + set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ipv6_v6only ? 1 : 0); + } + + if (socket_options) { socket_options(sock); } + + // bind or connect + auto quit = false; + if (bind_or_connect(sock, *rp, quit)) { return sock; } + + close_socket(sock); + + if (quit) { break; } + } + + return INVALID_SOCKET; +} + +inline void set_nonblocking(socket_t sock, bool nonblocking) { +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif +} + +inline bool is_connection_error() { +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif +} + +inline bool bind_ip_address(socket_t sock, const std::string &host) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo_with_timeout(host.c_str(), "0", &hints, &result, 0)) { + return false; + } + + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + ret = true; + break; + } + } + + return ret; +} + +#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__ +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP +inline std::string if2ip(int address_family, const std::string &ifn) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + auto se = detail::scope_exit([&] { freeifaddrs(ifap); }); + + std::string addr_candidate; + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + return std::string(buf, INET_ADDRSTRLEN); + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } else { + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } + } + } + } + return addr_candidate; +} +#endif + +inline socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, bool ipv6_v6only, + SocketOptions socket_options, time_t connection_timeout_sec, + time_t connection_timeout_usec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { + auto sock = create_socket( + host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only, + std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool { + if (!intf.empty()) { +#ifdef USE_IF2IP + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { ip_from_if = intf; } + if (!bind_ip_address(sock2, ip_from_if)) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock2, true); + + auto ret = + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) { + if (is_connection_error()) { + error = Error::Connection; + return false; + } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { + if (error == Error::ConnectionTimeout) { quit = true; } + return false; + } + } + + set_nonblocking(sock2, false); + set_socket_opt_time(sock2, SOL_SOCKET, SO_RCVTIMEO, read_timeout_sec, + read_timeout_usec); + set_socket_opt_time(sock2, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec, + write_timeout_usec); + + error = Error::Success; + return true; + }, + connection_timeout_sec); // Pass DNS timeout + + if (sock != INVALID_SOCKET) { + error = Error::Success; + } else { + if (error == Error::Success) { error = Error::Connection; } + } + + return sock; +} + +inline bool get_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, int &port) { + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return false; + } + + std::array ipstr{}; + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + return false; + } + + ip = ipstr.data(); + return true; +} + +inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) { + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator""_t(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + +inline std::string +find_content_type(const std::string &path, + const std::map &user_data, + const std::string &default_content_type) { + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { return it->second; } + + using udl::operator""_t; + + switch (str2tag(ext)) { + default: return default_content_type; + + case "css"_t: return "text/css"; + case "csv"_t: return "text/csv"; + case "htm"_t: + case "html"_t: return "text/html"; + case "js"_t: + case "mjs"_t: return "text/javascript"; + case "txt"_t: return "text/plain"; + case "vtt"_t: return "text/vtt"; + + case "apng"_t: return "image/apng"; + case "avif"_t: return "image/avif"; + case "bmp"_t: return "image/bmp"; + case "gif"_t: return "image/gif"; + case "png"_t: return "image/png"; + case "svg"_t: return "image/svg+xml"; + case "webp"_t: return "image/webp"; + case "ico"_t: return "image/x-icon"; + case "tif"_t: return "image/tiff"; + case "tiff"_t: return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: return "image/jpeg"; + + case "mp4"_t: return "video/mp4"; + case "mpeg"_t: return "video/mpeg"; + case "webm"_t: return "video/webm"; + + case "mp3"_t: return "audio/mp3"; + case "mpga"_t: return "audio/mpeg"; + case "weba"_t: return "audio/webm"; + case "wav"_t: return "audio/wave"; + + case "otf"_t: return "font/otf"; + case "ttf"_t: return "font/ttf"; + case "woff"_t: return "font/woff"; + case "woff2"_t: return "font/woff2"; + + case "7z"_t: return "application/x-7z-compressed"; + case "atom"_t: return "application/atom+xml"; + case "pdf"_t: return "application/pdf"; + case "json"_t: return "application/json"; + case "rss"_t: return "application/rss+xml"; + case "tar"_t: return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: return "application/xhtml+xml"; + case "xslt"_t: return "application/xslt+xml"; + case "xml"_t: return "application/xml"; + case "gz"_t: return "application/gzip"; + case "zip"_t: return "application/zip"; + case "wasm"_t: return "application/wasm"; + } +} + +inline bool can_compress_content_type(const std::string &content_type) { + using udl::operator""_t; + + auto tag = str2tag(content_type); + + switch (tag) { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/json"_t: + case "application/xml"_t: + case "application/protobuf"_t: + case "application/xhtml+xml"_t: return true; + + case "text/event-stream"_t: return false; + + default: return !content_type.rfind("text/", 0); + } +} + +inline EncodingType encoding_type(const Request &req, const Response &res) { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } + + const auto &s = req.get_header_value("Accept-Encoding"); + (void)(s); + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif + +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + // TODO: 'Accept-Encoding' has zstd, not zstd;q=0 + ret = s.find("zstd") != std::string::npos; + if (ret) { return EncodingType::Zstd; } +#endif + + return EncodingType::None; +} + +inline bool nocompressor::compress(const char *data, size_t data_length, + bool /*last*/, Callback callback) { + if (!data_length) { return true; } + return callback(data, data_length); +} + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +inline gzip_compressor::gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; +} + +inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } + +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + assert(is_valid_); + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; + auto ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); + assert(strm_.avail_in == 0); + } while (data_length > 0); + + return true; +} + +inline gzip_decompressor::gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} + +inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } + +inline bool gzip_decompressor::is_valid() const { return is_valid_; } + +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + assert(is_valid_); + + auto ret = Z_OK; + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + std::array buff{}; + while (strm_.avail_in > 0 && ret == Z_OK) { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = inflate(&strm_, Z_NO_FLUSH); + + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: inflateEnd(&strm_); return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + if (ret != Z_OK && ret != Z_STREAM_END) { return false; } + + } while (data_length > 0); + + return true; +} +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} + +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} + +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; +} + +inline brotli_decompressor::brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } +} + +inline bool brotli_decompressor::is_valid() const { return decoder_s; } + +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + auto next_in = reinterpret_cast(data); + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} +#endif + +#ifdef CPPHTTPLIB_ZSTD_SUPPORT +inline zstd_compressor::zstd_compressor() { + ctx_ = ZSTD_createCCtx(); + ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast); +} + +inline zstd_compressor::~zstd_compressor() { ZSTD_freeCCtx(ctx_); } + +inline bool zstd_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; + + ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue; + ZSTD_inBuffer input = {data, data_length, 0}; + + bool finished; + do { + ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0}; + size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode); + + if (ZSTD_isError(remaining)) { return false; } + + if (!callback(buff.data(), output.pos)) { return false; } + + finished = last ? (remaining == 0) : (input.pos == input.size); + + } while (!finished); + + return true; +} + +inline zstd_decompressor::zstd_decompressor() { ctx_ = ZSTD_createDCtx(); } + +inline zstd_decompressor::~zstd_decompressor() { ZSTD_freeDCtx(ctx_); } + +inline bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; } + +inline bool zstd_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + std::array buff{}; + ZSTD_inBuffer input = {data, data_length, 0}; + + while (input.pos < input.size) { + ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0}; + size_t const remaining = ZSTD_decompressStream(ctx_, &output, &input); + + if (ZSTD_isError(remaining)) { return false; } + + if (!callback(buff.data(), output.pos)) { return false; } + } + + return true; +} +#endif + +inline bool has_header(const Headers &headers, const std::string &key) { + return headers.find(key) != headers.end(); +} + +inline const char *get_header_value(const Headers &headers, + const std::string &key, const char *def, + size_t id) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } + return def; +} + +template +inline bool parse_header(const char *beg, const char *end, T fn) { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + auto name = std::string(beg, p); + if (!detail::fields::is_field_name(name)) { return false; } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p <= end) { + auto key_len = key_end - beg; + if (!key_len) { return false; } + + auto key = std::string(beg, key_end); + auto val = std::string(p, end); + + if (!detail::fields::is_field_value(val)) { return false; } + + if (case_ignore::equal(key, "Location") || + case_ignore::equal(key, "Referer")) { + fn(key, val); + } else { + fn(key, decode_path_component(val)); + } + + return true; + } + + return false; +} + +inline bool read_headers(Stream &strm, Headers &headers) { + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + size_t header_count = 0; + + for (;;) { + if (!line_reader.getline()) { return false; } + + // Check if the line ends with CRLF. + auto line_terminator_len = 2; + if (line_reader.end_with_crlf()) { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { break; } + } else { +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + // Blank line indicates end of headers. + if (line_reader.size() == 1) { break; } + line_terminator_len = 1; +#else + continue; // Skip invalid line. +#endif + } + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Check header count limit + if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + if (!parse_header(line_reader.ptr(), end, + [&](const std::string &key, const std::string &val) { + headers.emplace(key, val); + })) { + return false; + } + + header_count++; + } + + return true; +} + +inline bool read_content_with_length(Stream &strm, size_t len, + DownloadProgress progress, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + size_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); + + if (progress) { + if (!progress(r, len)) { return false; } + } + } + + return true; +} + +inline void skip_content_with_length(Stream &strm, size_t len) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + size_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return; } + r += static_cast(n); + } +} + +enum class ReadContentResult { + Success, // Successfully read the content + PayloadTooLarge, // The content exceeds the specified payload limit + Error // An error occurred while reading the content +}; + +inline ReadContentResult +read_content_without_length(Stream &strm, size_t payload_max_length, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + size_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n == 0) { return ReadContentResult::Success; } + if (n < 0) { return ReadContentResult::Error; } + + // Check if adding this data would exceed the payload limit + if (r > payload_max_length || + payload_max_length - r < static_cast(n)) { + return ReadContentResult::PayloadTooLarge; + } + + if (!out(buf, static_cast(n), r, 0)) { + return ReadContentResult::Error; + } + r += static_cast(n); + } + + return ReadContentResult::Success; +} + +template +inline ReadContentResult read_content_chunked(Stream &strm, T &x, + size_t payload_max_length, + ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + + if (!line_reader.getline()) { return ReadContentResult::Error; } + + unsigned long chunk_len; + size_t total_len = 0; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return ReadContentResult::Error; } + if (chunk_len == ULONG_MAX) { return ReadContentResult::Error; } + + if (chunk_len == 0) { break; } + + // Check if adding this chunk would exceed the payload limit + if (total_len > payload_max_length || + payload_max_length - total_len < chunk_len) { + return ReadContentResult::PayloadTooLarge; + } + + total_len += chunk_len; + + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { + return ReadContentResult::Error; + } + + if (!line_reader.getline()) { return ReadContentResult::Error; } + + if (strcmp(line_reader.ptr(), "\r\n") != 0) { + return ReadContentResult::Error; + } + + if (!line_reader.getline()) { return ReadContentResult::Error; } + } + + assert(chunk_len == 0); + + // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "The chunked + // transfer coding is complete when a chunk with a chunk-size of zero is + // received, possibly followed by a trailer section, and finally terminated by + // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1 + // + // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section + // does't care for the existence of the final CRLF. In other words, it seems + // to be ok whether the final CRLF exists or not in the chunked data. + // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3 + // + // According to the reference code in RFC 9112, cpp-httplib now allows + // chunked transfer coding data without the final CRLF. + if (!line_reader.getline()) { return ReadContentResult::Success; } + + // RFC 7230 Section 4.1.2 - Headers prohibited in trailers + thread_local case_ignore::unordered_set prohibited_trailers = { + // Message framing + "transfer-encoding", "content-length", + + // Routing + "host", + + // Authentication + "authorization", "www-authenticate", "proxy-authenticate", + "proxy-authorization", "cookie", "set-cookie", + + // Request modifiers + "cache-control", "expect", "max-forwards", "pragma", "range", "te", + + // Response control + "age", "expires", "date", "location", "retry-after", "vary", "warning", + + // Payload processing + "content-encoding", "content-type", "content-range", "trailer"}; + + // Parse declared trailer headers once for performance + case_ignore::unordered_set declared_trailers; + if (has_header(x.headers, "Trailer")) { + auto trailer_header = get_header_value(x.headers, "Trailer", "", 0); + auto len = std::strlen(trailer_header); + + split(trailer_header, trailer_header + len, ',', + [&](const char *b, const char *e) { + std::string key(b, e); + if (prohibited_trailers.find(key) == prohibited_trailers.end()) { + declared_trailers.insert(key); + } + }); + } + + size_t trailer_header_count = 0; + while (strcmp(line_reader.ptr(), "\r\n") != 0) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { + return ReadContentResult::Error; + } + + // Check trailer header count limit + if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { + return ReadContentResult::Error; + } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](const std::string &key, const std::string &val) { + if (declared_trailers.find(key) != declared_trailers.end()) { + x.trailers.emplace(key, val); + trailer_header_count++; + } + }); + + if (!line_reader.getline()) { return ReadContentResult::Error; } + } + + return ReadContentResult::Success; +} + +inline bool is_chunked_transfer_encoding(const Headers &headers) { + return case_ignore::equal( + get_header_value(headers, "Transfer-Encoding", "", 0), "chunked"); +} + +template +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (encoding == "gzip" || encoding == "deflate") { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#else + status = StatusCode::UnsupportedMediaType_415; + return false; +#endif + } else if (encoding.find("br") != std::string::npos) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = StatusCode::UnsupportedMediaType_415; + return false; +#endif + } else if (encoding == "zstd") { +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + decompressor = detail::make_unique(); +#else + status = StatusCode::UnsupportedMediaType_415; + return false; +#endif + } + + if (decompressor) { + if (decompressor->is_valid()) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + size_t off, size_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); + }; + return callback(std::move(out)); + } else { + status = StatusCode::InternalServerError_500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char *buf, size_t n, size_t off, + size_t len) { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); +} + +template +bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + DownloadProgress progress, + ContentReceiverWithProgress receiver, bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + auto result = read_content_chunked(strm, x, payload_max_length, out); + if (result == ReadContentResult::Success) { + ret = true; + } else if (result == ReadContentResult::PayloadTooLarge) { + exceed_payload_max_length = true; + ret = false; + } else { + ret = false; + } + } else if (!has_header(x.headers, "Content-Length")) { + auto result = + read_content_without_length(strm, payload_max_length, out); + if (result == ReadContentResult::Success) { + ret = true; + } else if (result == ReadContentResult::PayloadTooLarge) { + exceed_payload_max_length = true; + ret = false; + } else { + ret = false; + } + } else { + auto is_invalid_value = false; + auto len = get_header_value_u64(x.headers, "Content-Length", + (std::numeric_limits::max)(), + 0, is_invalid_value); + + if (is_invalid_value) { + ret = false; + } else if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { + status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413 + : StatusCode::BadRequest_400; + } + return ret; + }); +} + +inline ssize_t write_request_line(Stream &strm, const std::string &method, + const std::string &path) { + std::string s = method; + s += " "; + s += path; + s += " HTTP/1.1\r\n"; + return strm.write(s.data(), s.size()); +} + +inline ssize_t write_response_line(Stream &strm, int status) { + std::string s = "HTTP/1.1 "; + s += std::to_string(status); + s += " "; + s += httplib::status_message(status); + s += "\r\n"; + return strm.write(s.data(), s.size()); +} + +inline ssize_t write_headers(Stream &strm, const Headers &headers) { + ssize_t write_len = 0; + for (const auto &x : headers) { + std::string s; + s = x.first; + s += ": "; + s += x.second; + s += "\r\n"; + + auto len = strm.write(s.data(), s.size()); + if (len < 0) { return len; } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { return len; } + write_len += len; + return write_len; +} + +inline bool write_data(Stream &strm, const char *d, size_t l) { + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; +} + +template +inline bool write_content_with_progress(Stream &strm, + const ContentProvider &content_provider, + size_t offset, size_t length, + T is_shutting_down, + const UploadProgress &upload_progress, + Error &error) { + size_t end_offset = offset + length; + size_t start_offset = offset; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + if (write_data(strm, d, l)) { + offset += l; + + if (upload_progress && length > 0) { + size_t current_written = offset - start_offset; + if (!upload_progress(current_written, length)) { + ok = false; + return false; + } + } + } else { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); }; + + while (offset < end_offset && !is_shutting_down()) { + if (!strm.wait_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + return write_content_with_progress(strm, content_provider, offset, length, + is_shutting_down, nullptr, error); +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); +} + +template +inline bool +write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + offset += l; + if (!write_data(strm, d, l)) { ok = false; } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); }; + + data_sink.done = [&](void) { data_available = false; }; + + while (data_available && !is_shutting_down()) { + if (!strm.wait_writable()) { + return false; + } else if (!content_provider(offset, 0, data_sink)) { + return false; + } else if (!ok) { + return false; + } + } + return true; +} + +template +inline bool +write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; + + std::string payload; + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; } + } + } else { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); }; + + auto done_with_trailer = [&](const Headers *trailer) { + if (!ok) { return; } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + + constexpr const char done_marker[] = "0\r\n"; + if (!write_data(strm, done_marker, str_len(done_marker))) { ok = false; } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + constexpr const char crlf[] = "\r\n"; + if (!write_data(strm, crlf, str_len(crlf))) { ok = false; } + }; + + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); + }; + + while (data_available && !is_shutting_down()) { + if (!strm.wait_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); +} + +template +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == StatusCode::SeeOther_303 && + (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + + if (res.location.empty()) { res.location = location; } + } + return ret; +} + +inline std::string params_to_query_str(const Params ¶ms) { + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { query += "&"; } + query += encode_query_component(it->first); + query += "="; + query += encode_query_component(it->second); + } + return query; +} + +inline void parse_query_text(const char *data, std::size_t size, + Params ¶ms) { + std::set cache; + split(data, data + size, '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(std::move(kv)); + + std::string key; + std::string val; + divide(b, static_cast(e - b), '=', + [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data, + std::size_t rhs_size) { + key.assign(lhs_data, lhs_size); + val.assign(rhs_data, rhs_size); + }); + + if (!key.empty()) { + params.emplace(decode_query_component(key), decode_query_component(val)); + } + }); +} + +inline void parse_query_text(const std::string &s, Params ¶ms) { + parse_query_text(s.data(), s.size(), params); +} + +inline bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary) { + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); + if (pos == std::string::npos) { return false; } + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = trim_double_quotes_copy(content_type.substr(beg, end - beg)); + return !boundary.empty(); +} + +inline void parse_disposition_params(const std::string &s, Params ¶ms) { + std::set cache; + split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(trim_double_quotes_copy((key)), + trim_double_quotes_copy((val))); + } + }); +} + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +inline bool parse_range_header(const std::string &s, Ranges &ranges) { +#else +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { +#endif + auto is_valid = [](const std::string &str) { + return std::all_of(str.cbegin(), str.cend(), + [](unsigned char c) { return std::isdigit(c); }); + }; + + if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) { + const auto pos = static_cast(6); + const auto len = static_cast(s.size() - 6); + auto all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { + if (!all_valid_ranges) { return; } + + const auto it = std::find(b, e, '-'); + if (it == e) { + all_valid_ranges = false; + return; + } + + const auto lhs = std::string(b, it); + const auto rhs = std::string(it + 1, e); + if (!is_valid(lhs) || !is_valid(rhs)) { + all_valid_ranges = false; + return; + } + + const auto first = + static_cast(lhs.empty() ? -1 : std::stoll(lhs)); + const auto last = + static_cast(rhs.empty() ? -1 : std::stoll(rhs)); + if ((first == -1 && last == -1) || + (first != -1 && last != -1 && first > last)) { + all_valid_ranges = false; + return; + } + + ranges.emplace_back(first, last); + }); + return all_valid_ranges && !ranges.empty(); + } + return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +} +#else +} catch (...) { return false; } +#endif + +inline bool parse_accept_header(const std::string &s, + std::vector &content_types) { + content_types.clear(); + + // Empty string is considered valid (no preference) + if (s.empty()) { return true; } + + // Check for invalid patterns: leading/trailing commas or consecutive commas + if (s.front() == ',' || s.back() == ',' || + s.find(",,") != std::string::npos) { + return false; + } + + struct AcceptEntry { + std::string media_type; + double quality; + int order; // Original order in header + }; + + std::vector entries; + int order = 0; + bool has_invalid_entry = false; + + // Split by comma and parse each entry + split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) { + std::string entry(b, e); + entry = trim_copy(entry); + + if (entry.empty()) { + has_invalid_entry = true; + return; + } + + AcceptEntry accept_entry; + accept_entry.quality = 1.0; // Default quality + accept_entry.order = order++; + + // Find q= parameter + auto q_pos = entry.find(";q="); + if (q_pos == std::string::npos) { q_pos = entry.find("; q="); } + + if (q_pos != std::string::npos) { + // Extract media type (before q parameter) + accept_entry.media_type = trim_copy(entry.substr(0, q_pos)); + + // Extract quality value + auto q_start = entry.find('=', q_pos) + 1; + auto q_end = entry.find(';', q_start); + if (q_end == std::string::npos) { q_end = entry.length(); } + + std::string quality_str = + trim_copy(entry.substr(q_start, q_end - q_start)); + if (quality_str.empty()) { + has_invalid_entry = true; + return; + } + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + { + std::istringstream iss(quality_str); + iss >> accept_entry.quality; + + // Check if conversion was successful and entire string was consumed + if (iss.fail() || !iss.eof()) { + has_invalid_entry = true; + return; + } + } +#else + try { + accept_entry.quality = std::stod(quality_str); + } catch (...) { + has_invalid_entry = true; + return; + } +#endif + // Check if quality is in valid range [0.0, 1.0] + if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0) { + has_invalid_entry = true; + return; + } + } else { + // No quality parameter, use entire entry as media type + accept_entry.media_type = entry; + } + + // Remove additional parameters from media type + auto param_pos = accept_entry.media_type.find(';'); + if (param_pos != std::string::npos) { + accept_entry.media_type = + trim_copy(accept_entry.media_type.substr(0, param_pos)); + } + + // Basic validation of media type format + if (accept_entry.media_type.empty()) { + has_invalid_entry = true; + return; + } + + // Check for basic media type format (should contain '/' or be '*') + if (accept_entry.media_type != "*" && + accept_entry.media_type.find('/') == std::string::npos) { + has_invalid_entry = true; + return; + } + + entries.push_back(accept_entry); + }); + + // Return false if any invalid entry was found + if (has_invalid_entry) { return false; } + + // Sort by quality (descending), then by original order (ascending) + std::sort(entries.begin(), entries.end(), + [](const AcceptEntry &a, const AcceptEntry &b) { + if (a.quality != b.quality) { + return a.quality > b.quality; // Higher quality first + } + return a.order < b.order; // Earlier order first for same quality + }); + + // Extract sorted media types + content_types.reserve(entries.size()); + for (const auto &entry : entries) { + content_types.push_back(entry.media_type); + } + + return true; +} + +class FormDataParser { +public: + FormDataParser() = default; + + void set_boundary(std::string &&boundary) { + boundary_ = boundary; + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } + + bool is_valid() const { return is_valid_; } + + bool parse(const char *buf, size_t n, const FormDataHeader &header_callback, + const ContentReceiver &content_callback) { + + buf_append(buf, n); + + while (buf_size() > 0) { + switch (state_) { + case 0: { // Initial boundary + auto pos = buf_find(dash_boundary_crlf_); + if (pos == buf_size()) { return true; } + buf_erase(pos + dash_boundary_crlf_.size()); + state_ = 1; + break; + } + case 1: { // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: { // Headers + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + while (pos < buf_size()) { + // Empty line + if (pos == 0) { + if (!header_callback(file_)) { + is_valid_ = false; + return false; + } + buf_erase(crlf_.size()); + state_ = 3; + break; + } + + const auto header = buf_head(pos); + + if (!parse_header(header.data(), header.data() + header.size(), + [&](const std::string &, const std::string &) {})) { + is_valid_ = false; + return false; + } + + // Parse and emplace space trimmed headers into a map + if (!parse_header( + header.data(), header.data() + header.size(), + [&](const std::string &key, const std::string &val) { + file_.headers.emplace(key, val); + })) { + is_valid_ = false; + return false; + } + + constexpr const char header_content_type[] = "Content-Type:"; + + if (start_with_case_ignore(header, header_content_type)) { + file_.content_type = + trim_copy(header.substr(str_len(header_content_type))); + } else { + thread_local const std::regex re_content_disposition( + R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~", + std::regex_constants::icase); + + std::smatch m; + if (std::regex_match(header, m, re_content_disposition)) { + Params params; + parse_disposition_params(m[1], params); + + auto it = params.find("name"); + if (it != params.end()) { + file_.name = it->second; + } else { + is_valid_ = false; + return false; + } + + it = params.find("filename"); + if (it != params.end()) { file_.filename = it->second; } + + it = params.find("filename*"); + if (it != params.end()) { + // Only allow UTF-8 encoding... + thread_local const std::regex re_rfc5987_encoding( + R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase); + + std::smatch m2; + if (std::regex_match(it->second, m2, re_rfc5987_encoding)) { + file_.filename = decode_path_component(m2[1]); // override... + } else { + is_valid_ = false; + return false; + } + } + } + } + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); + } + if (state_ != 3) { return true; } + break; + } + case 3: { // Body + if (crlf_dash_boundary_.size() > buf_size()) { return true; } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) { + if (!content_callback(buf_data(), pos)) { + is_valid_ = false; + return false; + } + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } else { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) { + if (!content_callback(buf_data(), len)) { + is_valid_ = false; + return false; + } + buf_erase(len); + } + return true; + } + break; + } + case 4: { // Boundary + if (crlf_.size() > buf_size()) { return true; } + if (buf_start_with(crlf_)) { + buf_erase(crlf_.size()); + state_ = 1; + } else { + if (dash_.size() > buf_size()) { return true; } + if (buf_start_with(dash_)) { + buf_erase(dash_.size()); + is_valid_ = true; + buf_erase(buf_size()); // Remove epilogue + } else { + return true; + } + } + break; + } + } + } + + return true; + } + +private: + void clear_file_info() { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + file_.headers.clear(); + } + + bool start_with_case_ignore(const std::string &a, const char *b) const { + const auto b_len = strlen(b); + if (a.size() < b_len) { return false; } + for (size_t i = 0; i < b_len; i++) { + if (case_ignore::to_lower(a[i]) != case_ignore::to_lower(b[i])) { + return false; + } + } + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; + + size_t state_ = 0; + bool is_valid_ = false; + FormData file_; + + // Buffer + bool start_with(const std::string &a, size_t spos, size_t epos, + const std::string &b) const { + if (epos - spos < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + spos] != b[i]) { return false; } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char *buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string &s) const { + return start_with(buf_, buf_spos_, buf_epos_, s); + } + + size_t buf_find(const std::string &s) const { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) { + auto pos = off; + while (true) { + if (pos == buf_epos_) { return buf_size(); } + if (buf_[pos] == c) { break; } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { return buf_size(); } + + if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char *data, size_t n) { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) { + for (size_t i = 0; i < remaining_size; i++) { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); } + + for (size_t i = 0; i < n; i++) { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; +}; + +inline std::string random_string(size_t length) { + constexpr const char data[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + thread_local auto engine([]() { + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + std::random_device seed_gen; + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + return std::mt19937(seed_sequence); + }()); + + std::string result; + for (size_t i = 0; i < length; i++) { + result += data[engine() % (sizeof(data) - 1)]; + } + return result; +} + +inline std::string make_multipart_data_boundary() { + return "--cpp-httplib-multipart-data-" + detail::random_string(16); +} + +inline bool is_multipart_boundary_chars_valid(const std::string &boundary) { + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) { + auto c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + valid = false; + break; + } + } + return valid; +} + +template +inline std::string +serialize_multipart_formdata_item_begin(const T &item, + const std::string &boundary) { + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + + return body; +} + +inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } + +inline std::string +serialize_multipart_formdata_finish(const std::string &boundary) { + return "--" + boundary + "--\r\n"; +} + +inline std::string +serialize_multipart_formdata_get_content_type(const std::string &boundary) { + return "multipart/form-data; boundary=" + boundary; +} + +inline std::string +serialize_multipart_formdata(const UploadFormDataItems &items, + const std::string &boundary, bool finish = true) { + std::string body; + + for (const auto &item : items) { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) { body += serialize_multipart_formdata_finish(boundary); } + + return body; +} + +inline void coalesce_ranges(Ranges &ranges, size_t content_length) { + if (ranges.size() <= 1) return; + + // Sort ranges by start position + std::sort(ranges.begin(), ranges.end(), + [](const Range &a, const Range &b) { return a.first < b.first; }); + + Ranges coalesced; + coalesced.reserve(ranges.size()); + + for (auto &r : ranges) { + auto first_pos = r.first; + auto last_pos = r.second; + + // Handle special cases like in range_error + if (first_pos == -1 && last_pos == -1) { + first_pos = 0; + last_pos = static_cast(content_length); + } + + if (first_pos == -1) { + first_pos = static_cast(content_length) - last_pos; + last_pos = static_cast(content_length) - 1; + } + + if (last_pos == -1 || last_pos >= static_cast(content_length)) { + last_pos = static_cast(content_length) - 1; + } + + // Skip invalid ranges + if (!(0 <= first_pos && first_pos <= last_pos && + last_pos < static_cast(content_length))) { + continue; + } + + // Coalesce with previous range if overlapping or adjacent (but not + // identical) + if (!coalesced.empty()) { + auto &prev = coalesced.back(); + // Check if current range overlaps or is adjacent to previous range + // but don't coalesce identical ranges (allow duplicates) + if (first_pos <= prev.second + 1 && + !(first_pos == prev.first && last_pos == prev.second)) { + // Extend the previous range + prev.second = (std::max)(prev.second, last_pos); + continue; + } + } + + // Add new range + coalesced.emplace_back(first_pos, last_pos); + } + + ranges = std::move(coalesced); +} + +inline bool range_error(Request &req, Response &res) { + if (!req.ranges.empty() && 200 <= res.status && res.status < 300) { + ssize_t content_len = static_cast( + res.content_length_ ? res.content_length_ : res.body.size()); + + std::vector> processed_ranges; + size_t overwrapping_count = 0; + + // NOTE: The following Range check is based on '14.2. Range' in RFC 9110 + // 'HTTP Semantics' to avoid potential denial-of-service attacks. + // https://www.rfc-editor.org/rfc/rfc9110#section-14.2 + + // Too many ranges + if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; } + + for (auto &r : req.ranges) { + auto &first_pos = r.first; + auto &last_pos = r.second; + + if (first_pos == -1 && last_pos == -1) { + first_pos = 0; + last_pos = content_len; + } + + if (first_pos == -1) { + first_pos = content_len - last_pos; + last_pos = content_len - 1; + } + + // NOTE: RFC-9110 '14.1.2. Byte Ranges': + // A client can limit the number of bytes requested without knowing the + // size of the selected representation. If the last-pos value is absent, + // or if the value is greater than or equal to the current length of the + // representation data, the byte range is interpreted as the remainder of + // the representation (i.e., the server replaces the value of last-pos + // with a value that is one less than the current length of the selected + // representation). + // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6 + if (last_pos == -1 || last_pos >= content_len) { + last_pos = content_len - 1; + } + + // Range must be within content length + if (!(0 <= first_pos && first_pos <= last_pos && + last_pos <= content_len - 1)) { + return true; + } + + // Request must not have more than two overlapping ranges + for (const auto &processed_range : processed_ranges) { + if (!(last_pos < processed_range.first || + first_pos > processed_range.second)) { + overwrapping_count++; + if (overwrapping_count > 2) { return true; } + break; // Only count once per range + } + } + + processed_ranges.emplace_back(first_pos, last_pos); + } + + // After validation, coalesce overlapping ranges as per RFC 9110 + coalesce_ranges(req.ranges, static_cast(content_len)); + } + + return false; +} + +inline std::pair +get_range_offset_and_length(Range r, size_t content_length) { + assert(r.first != -1 && r.second != -1); + assert(0 <= r.first && r.first < static_cast(content_length)); + assert(r.first <= r.second && + r.second < static_cast(content_length)); + (void)(content_length); + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); +} + +inline std::string make_content_range_header_field( + const std::pair &offset_and_length, size_t content_length) { + auto st = offset_and_length.first; + auto ed = st + offset_and_length.second - 1; + + std::string field = "bytes "; + field += std::to_string(st); + field += "-"; + field += std::to_string(ed); + field += "/"; + field += std::to_string(content_length); + return field; +} + +template +bool process_multipart_ranges_data(const Request &req, + const std::string &boundary, + const std::string &content_type, + size_t content_length, SToken stoken, + CToken ctoken, Content content) { + for (size_t i = 0; i < req.ranges.size(); i++) { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + auto offset_and_length = + get_range_offset_and_length(req.ranges[i], content_length); + + ctoken("Content-Range: "); + stoken(make_content_range_header_field(offset_and_length, content_length)); + ctoken("\r\n"); + ctoken("\r\n"); + + if (!content(offset_and_length.first, offset_and_length.second)) { + return false; + } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--"); + + return true; +} + +inline void make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + size_t content_length, + std::string &data) { + process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string &token) { data += token; }, + [&](const std::string &token) { data += token; }, + [&](size_t offset, size_t length) { + assert(offset + length <= content_length); + data += res.body.substr(offset, length); + return true; + }); +} + +inline size_t get_multipart_ranges_data_length(const Request &req, + const std::string &boundary, + const std::string &content_type, + size_t content_length) { + size_t data_length = 0; + + process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string &token) { data_length += token.size(); }, + [&](const std::string &token) { data_length += token.size(); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); + + return data_length; +} + +template +inline bool +write_multipart_ranges_data(Stream &strm, const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + size_t content_length, const T &is_shutting_down) { + return process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string &token) { strm.write(token); }, + [&](const std::string &token) { strm.write(token); }, + [&](size_t offset, size_t length) { + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + }); +} + +inline bool expect_content(const Request &req) { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "DELETE") { + return true; + } + if (req.has_header("Content-Length") && + req.get_header_value_u64("Content-Length") > 0) { + return true; + } + if (is_chunked_transfer_encoding(req.headers)) { return true; } + return false; +} + +inline bool has_crlf(const std::string &s) { + auto p = s.c_str(); + while (*p) { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::string message_digest(const std::string &s, const EVP_MD *algo) { + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); + + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; + + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') + << static_cast(hash[i]); + } + + return ss.str(); +} + +inline std::string MD5(const std::string &s) { + return message_digest(s, EVP_md5()); +} + +inline std::string SHA_256(const std::string &s) { + return message_digest(s, EVP_sha256()); +} + +inline std::string SHA_512(const std::string &s) { + return message_digest(s, EVP_sha512()); +} + +inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} + +inline bool is_ssl_peer_could_be_closed(SSL *ssl, socket_t sock) { + detail::set_nonblocking(sock, true); + auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); }); + + char buf[1]; + return !SSL_peek(ssl, buf, 1) && + SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN; +} + +#ifdef _WIN32 +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + if (!hStore) { return false; } + + auto result = false; + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && \ + defined(TARGET_OS_OSX) +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); + return true; +} + +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (auto i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 +class WSInit { +public: + WSInit() { + WSADATA wsaData; + if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true; + } + + ~WSInit() { + if (is_valid_) WSACleanup(); + } + + bool is_valid_ = false; +}; + +static WSInit wsinit_; +#endif + +inline bool parse_www_authenticate(const Response &res, + std::map &auth, + bool is_proxy) { + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) { + thread_local auto re = + std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) { + auto type = s.substr(0, pos); + if (type == "Basic") { + return false; + } else if (type == "Digest") { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) { + const auto &m = *i; + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); + auth[key] = val; + } + return true; + } + } + } + return false; +} + +class ContentProviderAdapter { +public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) + : content_provider_(content_provider) {} + + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } + +private: + ContentProviderWithoutLength content_provider_; +}; + +} // namespace detail + +inline std::string hosted_at(const std::string &hostname) { + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { return std::string(); } + return addrs[0]; +} + +inline void hosted_at(const std::string &hostname, + std::vector &addrs) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (detail::getaddrinfo_with_timeout(hostname.c_str(), nullptr, &hints, + &result, 0)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &addr = + *reinterpret_cast(rp->ai_addr); + std::string ip; + auto dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) { + addrs.push_back(ip); + } + } +} + +inline std::string encode_uri_component(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string encode_uri(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')' || c == ';' || c == '/' || c == '?' || c == ':' || c == '@' || + c == '&' || c == '=' || c == '+' || c == '$' || c == ',' || c == '#') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string decode_uri_component(const std::string &value) { + std::string result; + + for (size_t i = 0; i < value.size(); i++) { + if (value[i] == '%' && i + 2 < value.size()) { + auto val = 0; + if (detail::from_hex_to_i(value, i + 1, 2, val)) { + result += static_cast(val); + i += 2; + } else { + result += value[i]; + } + } else { + result += value[i]; + } + } + + return result; +} + +inline std::string decode_uri(const std::string &value) { + std::string result; + + for (size_t i = 0; i < value.size(); i++) { + if (value[i] == '%' && i + 2 < value.size()) { + auto val = 0; + if (detail::from_hex_to_i(value, i + 1, 2, val)) { + result += static_cast(val); + i += 2; + } else { + result += value[i]; + } + } else { + result += value[i]; + } + } + + return result; +} + +inline std::string encode_path_component(const std::string &component) { + std::string result; + result.reserve(component.size() * 3); + + for (size_t i = 0; i < component.size(); i++) { + auto c = static_cast(component[i]); + + // Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~" + if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') { + result += static_cast(c); + } + // Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / + // "," / ";" / "=" + else if (c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || + c == ')' || c == '*' || c == '+' || c == ',' || c == ';' || + c == '=') { + result += static_cast(c); + } + // Colon is allowed in path segments except first segment + else if (c == ':') { + result += static_cast(c); + } + // @ is allowed in path + else if (c == '@') { + result += static_cast(c); + } else { + result += '%'; + char hex[3]; + snprintf(hex, sizeof(hex), "%02X", c); + result.append(hex, 2); + } + } + return result; +} + +inline std::string decode_path_component(const std::string &component) { + std::string result; + result.reserve(component.size()); + + for (size_t i = 0; i < component.size(); i++) { + if (component[i] == '%' && i + 1 < component.size()) { + if (component[i + 1] == 'u') { + // Unicode %uXXXX encoding + auto val = 0; + if (detail::from_hex_to_i(component, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = detail::to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } else { + result += component[i]; + } + } else { + // Standard %XX encoding + auto val = 0; + if (detail::from_hex_to_i(component, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast(val); + i += 2; // 'XX' + } else { + result += component[i]; + } + } + } else { + result += component[i]; + } + } + return result; +} + +inline std::string encode_query_component(const std::string &component, + bool space_as_plus) { + std::string result; + result.reserve(component.size() * 3); + + for (size_t i = 0; i < component.size(); i++) { + auto c = static_cast(component[i]); + + // Unreserved characters per RFC 3986 + if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') { + result += static_cast(c); + } + // Space handling + else if (c == ' ') { + if (space_as_plus) { + result += '+'; + } else { + result += "%20"; + } + } + // Plus sign handling + else if (c == '+') { + if (space_as_plus) { + result += "%2B"; + } else { + result += static_cast(c); + } + } + // Query-safe sub-delimiters (excluding & and = which are query delimiters) + else if (c == '!' || c == '$' || c == '\'' || c == '(' || c == ')' || + c == '*' || c == ',' || c == ';') { + result += static_cast(c); + } + // Colon and @ are allowed in query + else if (c == ':' || c == '@') { + result += static_cast(c); + } + // Forward slash is allowed in query values + else if (c == '/') { + result += static_cast(c); + } + // Question mark is allowed in query values (after first ?) + else if (c == '?') { + result += static_cast(c); + } else { + result += '%'; + char hex[3]; + snprintf(hex, sizeof(hex), "%02X", c); + result.append(hex, 2); + } + } + return result; +} + +inline std::string decode_query_component(const std::string &component, + bool plus_as_space) { + std::string result; + result.reserve(component.size()); + + for (size_t i = 0; i < component.size(); i++) { + if (component[i] == '%' && i + 2 < component.size()) { + std::string hex = component.substr(i + 1, 2); + char *end; + unsigned long value = std::strtoul(hex.c_str(), &end, 16); + if (end == hex.c_str() + 2) { + result += static_cast(value); + i += 2; + } else { + result += component[i]; + } + } else if (component[i] == '+' && plus_as_space) { + result += ' '; // + becomes space in form-urlencoded + } else { + result += component[i]; + } + } + return result; +} + +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { + std::string path_with_query = path; + thread_local const std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + +// Header utilities +inline std::pair +make_range_header(const Ranges &ranges) { + std::string field = "bytes="; + auto i = 0; + for (const auto &r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, bool is_proxy) { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +inline std::pair +make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +// Request implementation +inline bool Request::has_header(const std::string &key) const { + return detail::has_header(headers, key); +} + +inline std::string Request::get_header_value(const std::string &key, + const char *def, size_t id) const { + return detail::get_header_value(headers, key, def, id); +} + +inline size_t Request::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Request::set_header(const std::string &key, + const std::string &val) { + if (detail::fields::is_field_name(key) && + detail::fields::is_field_value(val)) { + headers.emplace(key, val); + } +} + +inline bool Request::has_trailer(const std::string &key) const { + return trailers.find(key) != trailers.end(); +} + +inline std::string Request::get_trailer_value(const std::string &key, + size_t id) const { + auto rng = trailers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_trailer_value_count(const std::string &key) const { + auto r = trailers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline bool Request::has_param(const std::string &key) const { + return params.find(key) != params.end(); +} + +inline std::string Request::get_param_value(const std::string &key, + size_t id) const { + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_param_value_count(const std::string &key) const { + auto r = params.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline bool Request::is_multipart_form_data() const { + const auto &content_type = get_header_value("Content-Type"); + return !content_type.rfind("multipart/form-data", 0); +} + +// Multipart FormData implementation +inline std::string MultipartFormData::get_field(const std::string &key, + size_t id) const { + auto rng = fields.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.content; } + return std::string(); +} + +inline std::vector +MultipartFormData::get_fields(const std::string &key) const { + std::vector values; + auto rng = fields.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second.content); + } + return values; +} + +inline bool MultipartFormData::has_field(const std::string &key) const { + return fields.find(key) != fields.end(); +} + +inline size_t MultipartFormData::get_field_count(const std::string &key) const { + auto r = fields.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline FormData MultipartFormData::get_file(const std::string &key, + size_t id) const { + auto rng = files.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return FormData(); +} + +inline std::vector +MultipartFormData::get_files(const std::string &key) const { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; +} + +inline bool MultipartFormData::has_file(const std::string &key) const { + return files.find(key) != files.end(); +} + +inline size_t MultipartFormData::get_file_count(const std::string &key) const { + auto r = files.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Response implementation +inline bool Response::has_header(const std::string &key) const { + return headers.find(key) != headers.end(); +} + +inline std::string Response::get_header_value(const std::string &key, + const char *def, + size_t id) const { + return detail::get_header_value(headers, key, def, id); +} + +inline size_t Response::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Response::set_header(const std::string &key, + const std::string &val) { + if (detail::fields::is_field_name(key) && + detail::fields::is_field_value(val)) { + headers.emplace(key, val); + } +} +inline bool Response::has_trailer(const std::string &key) const { + return trailers.find(key) != trailers.end(); +} + +inline std::string Response::get_trailer_value(const std::string &key, + size_t id) const { + auto rng = trailers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Response::get_trailer_value_count(const std::string &key) const { + auto r = trailers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Response::set_redirect(const std::string &url, int stat) { + if (detail::fields::is_field_value(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } else { + this->status = StatusCode::Found_302; + } + } +} + +inline void Response::set_content(const char *s, size_t n, + const std::string &content_type) { + body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content(const std::string &s, + const std::string &content_type) { + set_content(s.data(), s.size(), content_type); +} + +inline void Response::set_content(std::string &&s, + const std::string &content_type) { + body = std::move(s); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content_provider( + size_t in_length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = in_length; + if (in_length > 0) { content_provider_ = std::move(provider); } + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = false; +} + +inline void Response::set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = false; +} + +inline void Response::set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = true; +} + +inline void Response::set_file_content(const std::string &path, + const std::string &content_type) { + file_content_path_ = path; + file_content_content_type_ = content_type; +} + +inline void Response::set_file_content(const std::string &path) { + file_content_path_ = path; +} + +// Result implementation +inline bool Result::has_request_header(const std::string &key) const { + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const std::string &key, + const char *def, + size_t id) const { + return detail::get_header_value(request_headers_, key, def, id); +} + +inline size_t +Result::get_request_header_value_count(const std::string &key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Stream implementation +inline ssize_t Stream::write(const char *ptr) { + return write(ptr, strlen(ptr)); +} + +inline ssize_t Stream::write(const std::string &s) { + return write(s.data(), s.size()); +} + +namespace detail { + +inline void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec, + time_t timeout_sec, time_t timeout_usec, + time_t &actual_timeout_sec, + time_t &actual_timeout_usec) { + auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000); + + auto actual_timeout_msec = + (std::min)(max_timeout_msec - duration_msec, timeout_msec); + + if (actual_timeout_msec < 0) { actual_timeout_msec = 0; } + + actual_timeout_sec = actual_timeout_msec / 1000; + actual_timeout_usec = (actual_timeout_msec % 1000) * 1000; +} + +// Socket stream implementation +inline SocketStream::SocketStream( + socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time) + : sock_(sock), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec), + max_timeout_msec_(max_timeout_msec), start_time_(start_time), + read_buff_(read_buff_size_, 0) {} + +inline SocketStream::~SocketStream() = default; + +inline bool SocketStream::is_readable() const { + return read_buff_off_ < read_buff_content_size_; +} + +inline bool SocketStream::wait_readable() const { + if (max_timeout_msec_ <= 0) { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + } + + time_t read_timeout_sec; + time_t read_timeout_usec; + calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_, + read_timeout_usec_, read_timeout_sec, read_timeout_usec); + + return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0; +} + +inline bool SocketStream::wait_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SocketStream::read(char *ptr, size_t size) { +#ifdef _WIN32 + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } else { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + + if (!wait_readable()) { return -1; } + + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + return n; + } else if (n <= static_cast(size)) { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } else { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } else { + return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); + } +} + +inline ssize_t SocketStream::write(const char *ptr, size_t size) { + if (!wait_writable()) { return -1; } + +#if defined(_WIN32) && !defined(_WIN64) + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); +} + +inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + return detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + return detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const { return sock_; } + +inline time_t SocketStream::duration() const { + return std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time_) + .count(); +} + +// Buffer stream implementation +inline bool BufferStream::is_readable() const { return true; } + +inline bool BufferStream::wait_readable() const { return true; } + +inline bool BufferStream::wait_writable() const { return true; } + +inline ssize_t BufferStream::read(char *ptr, size_t size) { +#if defined(_MSC_VER) && _MSC_VER < 1910 + auto len_read = buffer._Copy_s(ptr, size, size, position); +#else + auto len_read = buffer.copy(ptr, size, position); +#endif + position += static_cast(len_read); + return static_cast(len_read); +} + +inline ssize_t BufferStream::write(const char *ptr, size_t size) { + buffer.append(ptr, size); + return static_cast(size); +} + +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline socket_t BufferStream::socket() const { return 0; } + +inline time_t BufferStream::duration() const { return 0; } + +inline const std::string &BufferStream::get_buffer() const { return buffer; } + +inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) + : MatcherBase(pattern) { + constexpr const char marker[] = "/:"; + + // One past the last ending position of a path param substring + std::size_t last_param_end = 0; + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + // Needed to ensure that parameter names are unique during matcher + // construction + // If exceptions are disabled, only last duplicate path + // parameter will be set + std::unordered_set param_name_set; +#endif + + while (true) { + const auto marker_pos = pattern.find( + marker, last_param_end == 0 ? last_param_end : last_param_end - 1); + if (marker_pos == std::string::npos) { break; } + + static_fragments_.push_back( + pattern.substr(last_param_end, marker_pos - last_param_end + 1)); + + const auto param_name_start = marker_pos + str_len(marker); + + auto sep_pos = pattern.find(separator, param_name_start); + if (sep_pos == std::string::npos) { sep_pos = pattern.length(); } + + auto param_name = + pattern.substr(param_name_start, sep_pos - param_name_start); + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + if (param_name_set.find(param_name) != param_name_set.cend()) { + std::string msg = "Encountered path parameter '" + param_name + + "' multiple times in route pattern '" + pattern + "'."; + throw std::invalid_argument(msg); + } +#endif + + param_names_.push_back(std::move(param_name)); + + last_param_end = sep_pos + 1; + } + + if (last_param_end < pattern.length()) { + static_fragments_.push_back(pattern.substr(last_param_end)); + } +} + +inline bool PathParamsMatcher::match(Request &request) const { + request.matches = std::smatch(); + request.path_params.clear(); + request.path_params.reserve(param_names_.size()); + + // One past the position at which the path matched the pattern last time + std::size_t starting_pos = 0; + for (size_t i = 0; i < static_fragments_.size(); ++i) { + const auto &fragment = static_fragments_[i]; + + if (starting_pos + fragment.length() > request.path.length()) { + return false; + } + + // Avoid unnecessary allocation by using strncmp instead of substr + + // comparison + if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(), + fragment.length()) != 0) { + return false; + } + + starting_pos += fragment.length(); + + // Should only happen when we have a static fragment after a param + // Example: '/users/:id/subscriptions' + // The 'subscriptions' fragment here does not have a corresponding param + if (i >= param_names_.size()) { continue; } + + auto sep_pos = request.path.find(separator, starting_pos); + if (sep_pos == std::string::npos) { sep_pos = request.path.length(); } + + const auto ¶m_name = param_names_[i]; + + request.path_params.emplace( + param_name, request.path.substr(starting_pos, sep_pos - starting_pos)); + + // Mark everything up to '/' as matched + starting_pos = sep_pos + 1; + } + // Returns false if the path is longer than the pattern + return starting_pos >= request.path.length(); +} + +inline bool RegexMatcher::match(Request &request) const { + request.path_params.clear(); + return std::regex_match(request.path, request.matches, regex_); +} + +} // namespace detail + +// HTTP server implementation +inline Server::Server() + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif +} + +inline Server::~Server() = default; + +inline std::unique_ptr +Server::make_matcher(const std::string &pattern) { + if (pattern.find("/:") != std::string::npos) { + return detail::make_unique(pattern); + } else { + return detail::make_unique(pattern); + } +} + +inline Server &Server::Get(const std::string &pattern, Handler handler) { + get_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, Handler handler) { + post_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, + HandlerWithContentReader handler) { + post_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, Handler handler) { + put_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, + HandlerWithContentReader handler) { + put_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, Handler handler) { + patch_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, + HandlerWithContentReader handler) { + patch_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, Handler handler) { + delete_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Options(const std::string &pattern, Handler handler) { + options_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline bool Server::set_base_dir(const std::string &dir, + const std::string &mount_point) { + return set_mount_point(mount_point, dir); +} + +inline bool Server::set_mount_point(const std::string &mount_point, + const std::string &dir, Headers headers) { + detail::FileStat stat(dir); + if (stat.is_dir()) { + std::string mnt = !mount_point.empty() ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') { + base_dirs_.push_back({mnt, dir, std::move(headers)}); + return true; + } + } + return false; +} + +inline bool Server::remove_mount_point(const std::string &mount_point) { + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { + if (it->mount_point == mount_point) { + base_dirs_.erase(it); + return true; + } + } + return false; +} + +inline Server & +Server::set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime) { + file_extension_and_mimetype_map_[ext] = mime; + return *this; +} + +inline Server &Server::set_default_file_mimetype(const std::string &mime) { + default_file_mimetype_ = mime; + return *this; +} + +inline Server &Server::set_file_request_handler(Handler handler) { + file_request_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler_core(HandlerWithResponse handler, + std::true_type) { + error_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler_core(Handler handler, + std::false_type) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; +} + +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_request_handler(HandlerWithResponse handler) { + pre_request_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + return *this; +} + +inline Server &Server::set_error_logger(ErrorLogger error_logger) { + error_logger_ = std::move(error_logger); + return *this; +} + +inline Server &Server::set_pre_compression_logger(Logger logger) { + pre_compression_logger_ = std::move(logger); + return *this; +} + +inline Server & +Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { + expect_100_continue_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_address_family(int family) { + address_family_ = family; + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + return *this; +} + +inline Server &Server::set_ipv6_v6only(bool on) { + ipv6_v6only_ = on; + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + return *this; +} + +inline Server &Server::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + return *this; +} + +inline Server &Server::set_header_writer( + std::function const &writer) { + header_writer_ = writer; + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) { + keep_alive_max_count_ = count; + return *this; +} + +inline Server &Server::set_keep_alive_timeout(time_t sec) { + keep_alive_timeout_sec_ = sec; + return *this; +} + +inline Server &Server::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_idle_interval(time_t sec, time_t usec) { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + return *this; +} + +inline Server &Server::set_payload_max_length(size_t length) { + payload_max_length_ = length; + return *this; +} + +inline bool Server::bind_to_port(const std::string &host, int port, + int socket_flags) { + auto ret = bind_internal(host, port, socket_flags); + if (ret == -1) { is_decommissioned = true; } + return ret >= 0; +} +inline int Server::bind_to_any_port(const std::string &host, int socket_flags) { + auto ret = bind_internal(host, 0, socket_flags); + if (ret == -1) { is_decommissioned = true; } + return ret; +} + +inline bool Server::listen_after_bind() { return listen_internal(); } + +inline bool Server::listen(const std::string &host, int port, + int socket_flags) { + return bind_to_port(host, port, socket_flags) && listen_internal(); +} + +inline bool Server::is_running() const { return is_running_; } + +inline void Server::wait_until_ready() const { + while (!is_running_ && !is_decommissioned) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + +inline void Server::stop() { + if (is_running_) { + assert(svr_sock_ != INVALID_SOCKET); + std::atomic sock(svr_sock_.exchange(INVALID_SOCKET)); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } + is_decommissioned = false; +} + +inline void Server::decommission() { is_decommissioned = true; } + +inline bool Server::parse_request_line(const char *s, Request &req) const { + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; } + len -= 2; + + { + size_t count = 0; + + detail::split(s, s + len, ' ', [&](const char *b, const char *e) { + switch (count) { + case 0: req.method = std::string(b, e); break; + case 1: req.target = std::string(b, e); break; + case 2: req.version = std::string(b, e); break; + default: break; + } + count++; + }); + + if (count != 3) { return false; } + } + + thread_local const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { + output_error_log(Error::InvalidHTTPMethod, &req); + return false; + } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { + output_error_log(Error::InvalidHTTPVersion, &req); + return false; + } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) { + if (req.target[i] == '#') { + req.target.erase(i); + break; + } + } + + detail::divide(req.target, '?', + [&](const char *lhs_data, std::size_t lhs_size, + const char *rhs_data, std::size_t rhs_size) { + req.path = + decode_path_component(std::string(lhs_data, lhs_size)); + detail::parse_query_text(rhs_data, rhs_size, req.params); + }); + } + + return true; +} + +inline bool Server::write_response(Stream &strm, bool close_connection, + Request &req, Response &res) { + // NOTE: `req.ranges` should be empty, otherwise it will be applied + // incorrectly to the error content. + req.ranges.clear(); + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { + assert(res.status != -1); + + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; + } + + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close") { + res.set_header("Connection", "close"); + } else { + std::string s = "timeout="; + s += std::to_string(keep_alive_timeout_sec_); + s += ", max="; + s += std::to_string(keep_alive_max_count_); + res.set_header("Keep-Alive", s); + } + + if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) && + !res.has_header("Content-Type")) { + res.set_header("Content-Type", "text/plain"); + } + + if (res.body.empty() && !res.content_length_ && !res.content_provider_ && + !res.has_header("Content-Length")) { + res.set_header("Content-Length", "0"); + } + + if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { post_routing_handler_(req, res); } + + // Response line and headers + { + detail::BufferStream bstrm; + if (!detail::write_response_line(bstrm, res.status)) { return false; } + if (!header_writer_(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + detail::write_data(strm, data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") { + if (!res.body.empty()) { + if (!detail::write_data(strm, res.body.data(), res.body.size())) { + ret = false; + } + } else if (res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } else { + ret = false; + } + } + } + + // Log + output_log(req, res); + + return ret; +} + +inline bool +Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offset_and_length = detail::get_range_offset_and_length( + req.ranges[0], res.content_length_); + + return detail::write_content(strm, res.content_provider_, + offset_and_length.first, + offset_and_length.second, is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, res.content_length_, + is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Zstd) { +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + FormFields::iterator cur_field; + FormFiles::iterator cur_file; + auto is_text_field = false; + size_t count = 0; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart FormData + [&](const FormData &file) { + if (count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + output_error_log(Error::TooManyFormDataFiles, &req); + return false; + } + + if (file.filename.empty()) { + cur_field = req.form.fields.emplace( + file.name, FormField{file.name, file.content, file.headers}); + is_text_field = true; + } else { + cur_file = req.form.files.emplace(file.name, file); + is_text_field = false; + } + return true; + }, + [&](const char *buf, size_t n) { + if (is_text_field) { + auto &content = cur_field->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + } else { + auto &content = cur_file->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + } + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { + res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414? + output_error_log(Error::ExceedMaxPayloadSize, &req); + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + FormDataHeader multipart_header, ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); +} + +inline bool Server::read_content_core( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + FormDataHeader multipart_header, ContentReceiver multipart_receiver) const { + detail::FormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = StatusCode::BadRequest_400; + output_error_log(Error::MultipartParsing, &req); + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, size_t /*off*/, size_t /*len*/) { + return multipart_form_data_parser.parse(buf, n, multipart_header, + multipart_receiver); + }; + } else { + out = [receiver](const char *buf, size_t n, size_t /*off*/, + size_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = StatusCode::BadRequest_400; + output_error_log(Error::MultipartParsing, &req); + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(const Request &req, Response &res) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + detail::FileStat stat(path); + + if (stat.is_dir()) { + res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301); + return true; + } + + if (stat.is_file()) { + for (const auto &kv : entry.headers) { + res.set_header(kv.first, kv.second); + } + + auto mm = std::make_shared(path.c_str()); + if (!mm->is_open()) { + output_error_log(Error::OpenFile, &req); + return false; + } + + res.set_content_provider( + mm->size(), + detail::find_content_type(path, file_extension_and_mimetype_map_, + default_file_mimetype_), + [mm](size_t offset, size_t length, DataSink &sink) -> bool { + sink.write(mm->data() + offset, length); + return true; + }); + + if (req.method != "HEAD" && file_request_handler_) { + file_request_handler_(req, res); + } + + return true; + } else { + output_error_log(Error::OpenFile, &req); + } + } + } + } + return false; +} + +inline socket_t +Server::create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + ipv6_v6only_, std::move(socket_options), + [&](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + output_error_log(Error::BindIPAddress, nullptr); + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { + output_error_log(Error::Listen, nullptr); + return false; + } + return true; + }); +} + +inline int Server::bind_internal(const std::string &host, int port, + int socket_flags) { + if (is_decommissioned) { return -1; } + + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { + output_error_log(Error::GetSockName, nullptr); + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + output_error_log(Error::UnsupportedAddressFamily, nullptr); + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() { + if (is_decommissioned) { return false; } + + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif - auto service = std::to_string(port); +#if defined _WIN32 + // sockets connected via WASAccept inherit flags NO_HANDLE_INHERIT, + // OVERLAPPED + socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0); +#elif defined SOCK_CLOEXEC + socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC); +#else + socket_t sock = accept(svr_sock_, nullptr, nullptr); +#endif - if (getaddrinfo(host, service.c_str(), &hints, &result)) { - return INVALID_SOCKET; + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::microseconds{1}); + continue; + } else if (errno == EINTR || errno == EAGAIN) { + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + output_error_log(Error::Connection, nullptr); + } else { + ; // The server socket was closed by user. + } + break; + } + + detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO, + read_timeout_sec_, read_timeout_usec_); + detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO, + write_timeout_sec_, write_timeout_usec_); + + if (!task_queue->enqueue( + [this, sock]() { process_and_close_socket(sock); })) { + output_error_log(Error::ResourceExhaustion, nullptr); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } } - for (auto rp = result; rp; rp = rp->ai_next) { - // Create a socket - auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if (sock == INVALID_SOCKET) { - continue; - } + task_queue->shutdown(); + } - // Make 'reuse address' option available - int yes = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes)); + is_decommissioned = !ret; + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } - // bind or connect - if (fn(sock, *rp)) { - freeaddrinfo(result); - return sock; - } + // File handler + if ((req.method == "GET" || req.method == "HEAD") && + handle_file_request(req, res)) { + return true; + } - close_socket(sock); + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + auto result = read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + if (!result) { output_error_log(Error::Read, &req); } + return result; + }, + [&](FormDataHeader header, ContentReceiver receiver) { + auto result = read_content_with_content_receiver( + strm, req, res, nullptr, std::move(header), + std::move(receiver)); + if (!result) { output_error_log(Error::Read, &req); } + return result; + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } } - freeaddrinfo(result); - return INVALID_SOCKET; + // Read content into `req.body` + if (!read_content(strm, req, res)) { + output_error_log(Error::Read, &req); + return false; + } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = StatusCode::BadRequest_400; + return false; } -inline void set_nonblocking(socket_t sock, bool nonblocking) -{ -#ifdef _WIN32 - auto flags = nonblocking ? 1UL : 0UL; - ioctlsocket(sock, FIONBIO, &flags); -#else - auto flags = fcntl(sock, F_GETFL, 0); - fcntl(sock, F_SETFL, nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); -#endif +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) const { + for (const auto &x : handlers) { + const auto &matcher = x.first; + const auto &handler = x.second; + + if (matcher->match(req)) { + req.matched_route = matcher->pattern(); + if (!pre_request_handler_ || + pre_request_handler_(req, res) != HandlerResponse::Handled) { + handler(req, res); + } + return true; + } + } + return false; } -inline bool is_connection_error() -{ -#ifdef _WIN32 - return WSAGetLastError() != WSAEWOULDBLOCK; -#else - return errno != EINPROGRESS; +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) const { + if (req.ranges.size() > 1 && res.status == StatusCode::PartialContent_206) { + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) { + content_type = it->second; + res.headers.erase(it); + } + + boundary = detail::make_multipart_data_boundary(); + + res.set_header("Content-Type", + "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) { + if (res.content_length_ > 0) { + size_t length = 0; + if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) { + length = res.content_length_; + } else if (req.ranges.size() == 1) { + auto offset_and_length = detail::get_range_offset_and_length( + req.ranges[0], res.content_length_); + + length = offset_and_length.second; + + auto content_range = detail::make_content_range_header_field( + offset_and_length, res.content_length_); + res.set_header("Content-Range", content_range); + } else { + length = detail::get_multipart_ranges_data_length( + req, boundary, content_type, res.content_length_); + } + res.set_header("Content-Length", std::to_string(length)); + } else { + if (res.content_provider_) { + if (res.is_chunked_content_provider_) { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) { + res.set_header("Content-Encoding", "gzip"); + } else if (type == detail::EncodingType::Brotli) { + res.set_header("Content-Encoding", "br"); + } else if (type == detail::EncodingType::Zstd) { + res.set_header("Content-Encoding", "zstd"); + } + } + } + } + } else { + if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) { + ; + } else if (req.ranges.size() == 1) { + auto offset_and_length = + detail::get_range_offset_and_length(req.ranges[0], res.body.size()); + auto offset = offset_and_length.first; + auto length = offset_and_length.second; + + auto content_range = detail::make_content_range_header_field( + offset_and_length, res.body.size()); + res.set_header("Content-Range", content_range); + + assert(offset + length <= res.body.size()); + res.body = res.body.substr(offset, length); + } else { + std::string data; + detail::make_multipart_ranges_data(req, res, boundary, content_type, + res.body.size(), data); + res.body.swap(data); + } + + if (type != detail::EncodingType::None) { + output_pre_compression_log(req, res); + + std::unique_ptr compressor; + std::string content_encoding; + + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); + content_encoding = "gzip"; +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + content_encoding = "br"; #endif + } else if (type == detail::EncodingType::Zstd) { +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + compressor = detail::make_unique(); + content_encoding = "zstd"; +#endif + } + + if (compressor) { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); + } + } + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length); + } } -inline std::string get_remote_addr(socket_t sock) { - struct sockaddr_storage addr; - socklen_t len = sizeof(addr); +inline bool Server::dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) const { + for (const auto &x : handlers) { + const auto &matcher = x.first; + const auto &handler = x.second; + + if (matcher->match(req)) { + req.matched_route = matcher->pattern(); + if (!pre_request_handler_ || + pre_request_handler_(req, res) != HandlerResponse::Handled) { + handler(req, res, content_reader); + } + return true; + } + } + return false; +} + +inline bool +Server::process_request(Stream &strm, const std::string &remote_addr, + int remote_port, const std::string &local_addr, + int local_port, bool close_connection, + bool &connection_closed, + const std::function &setup_request) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + // Connection has been closed on client + if (!line_reader.getline()) { return false; } + + Request req; + + Response res; + res.version = "HTTP/1.1"; + res.headers = default_headers_; + +#ifdef __APPLE__ + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = StatusCode::InternalServerError_500; + output_error_log(Error::ExceedMaxSocketDescriptorCount, &req); + return write_response(strm, close_connection, req, res); + } +#endif + + // Request line and headers + if (!parse_request_line(line_reader.ptr(), req)) { + res.status = StatusCode::BadRequest_400; + output_error_log(Error::InvalidRequestLine, &req); + return write_response(strm, close_connection, req, res); + } + + // Request headers + if (!detail::read_headers(strm, req.headers)) { + res.status = StatusCode::BadRequest_400; + output_error_log(Error::InvalidHeaders, &req); + return write_response(strm, close_connection, req, res); + } + + // Check if the request URI doesn't exceed the limit + if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = StatusCode::UriTooLong_414; + output_error_log(Error::ExceedUriMaxLength, &req); + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") { + connection_closed = true; + } + + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") { + connection_closed = true; + } + + req.remote_addr = remote_addr; + req.remote_port = remote_port; + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + + req.local_addr = local_addr; + req.local_port = local_port; + req.set_header("LOCAL_ADDR", req.local_addr); + req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + + if (req.has_header("Accept")) { + const auto &accept_header = req.get_header_value("Accept"); + if (!detail::parse_accept_header(accept_header, req.accept_content_types)) { + res.status = StatusCode::BadRequest_400; + output_error_log(Error::HTTPParsing, &req); + return write_response(strm, close_connection, req, res); + } + } + + if (req.has_header("Range")) { + const auto &range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) { + res.status = StatusCode::RangeNotSatisfiable_416; + output_error_log(Error::InvalidRangeHeader, &req); + return write_response(strm, close_connection, req, res); + } + } - if (!getpeername(sock, (struct sockaddr*)&addr, &len)) { - char ipstr[NI_MAXHOST]; + if (setup_request) { setup_request(req); } - if (!getnameinfo((struct sockaddr*)&addr, len, - ipstr, sizeof(ipstr), nullptr, 0, NI_NUMERICHOST)) { - return ipstr; + if (req.get_header_value("Expect") == "100-continue") { + int status = StatusCode::Continue_100; + if (expect_100_continue_handler_) { + status = expect_100_continue_handler_(req, res); + } + switch (status) { + case StatusCode::Continue_100: + case StatusCode::ExpectationFailed_417: + detail::write_response_line(strm, status); + strm.write("\r\n"); + break; + default: + connection_closed = true; + return write_response(strm, true, req, res); + } + } + + // Setup `is_connection_closed` method + auto sock = strm.socket(); + req.is_connection_closed = [sock]() { + return !detail::is_socket_alive(sock); + }; + + // Routing + auto routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = StatusCode::InternalServerError_500; + std::string val; + auto s = e.what(); + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case '\r': val += "\\r"; break; + case '\n': val += "\\n"; break; + default: val += s[i]; break; } + } + res.set_header("EXCEPTION_WHAT", val); + } + } catch (...) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = StatusCode::InternalServerError_500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + } +#endif + if (routed) { + if (res.status == -1) { + res.status = req.ranges.empty() ? StatusCode::OK_200 + : StatusCode::PartialContent_206; + } + + // Serve file content by using a content provider + if (!res.file_content_path_.empty()) { + const auto &path = res.file_content_path_; + auto mm = std::make_shared(path.c_str()); + if (!mm->is_open()) { + res.body.clear(); + res.content_length_ = 0; + res.content_provider_ = nullptr; + res.status = StatusCode::NotFound_404; + output_error_log(Error::OpenFile, &req); + return write_response(strm, close_connection, req, res); + } + + auto content_type = res.file_content_content_type_; + if (content_type.empty()) { + content_type = detail::find_content_type( + path, file_extension_and_mimetype_map_, default_file_mimetype_); + } + + res.set_content_provider( + mm->size(), content_type, + [mm](size_t offset, size_t length, DataSink &sink) -> bool { + sink.write(mm->data() + offset, length); + return true; + }); } - return std::string(); + if (detail::range_error(req, res)) { + res.body.clear(); + res.content_length_ = 0; + res.content_provider_ = nullptr; + res.status = StatusCode::RangeNotSatisfiable_416; + return write_response(strm, close_connection, req, res); + } + + return write_response_with_content(strm, close_connection, req, res); + } else { + if (res.status == -1) { res.status = StatusCode::NotFound_404; } + + return write_response(strm, close_connection, req, res); + } } -inline bool is_file(const std::string& path) -{ - struct stat st; - return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); +inline bool Server::is_valid() const { return true; } + +inline bool Server::process_and_close_socket(socket_t sock) { + std::string remote_addr; + int remote_port = 0; + detail::get_remote_ip_and_port(sock, remote_addr, remote_port); + + std::string local_addr; + int local_port = 0; + detail::get_local_ip_and_port(sock, local_addr, local_port); + + auto ret = detail::process_server_socket( + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [&](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, remote_addr, remote_port, local_addr, + local_port, close_connection, connection_closed, + nullptr); + }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; } -inline bool is_dir(const std::string& path) -{ - struct stat st; - return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); +inline void Server::output_log(const Request &req, const Response &res) const { + if (logger_) { + std::lock_guard guard(logger_mutex_); + logger_(req, res); + } } -inline bool is_valid_path(const std::string& path) { - size_t level = 0; - size_t i = 0; +inline void Server::output_pre_compression_log(const Request &req, + const Response &res) const { + if (pre_compression_logger_) { + std::lock_guard guard(logger_mutex_); + pre_compression_logger_(req, res); + } +} - // Skip slash - while (i < path.size() && path[i] == '/') { - i++; +inline void Server::output_error_log(const Error &err, + const Request *req) const { + if (error_logger_) { + std::lock_guard guard(logger_mutex_); + error_logger_(err, req); + } +} + +// HTTP client implementation +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port), + host_and_port_(adjust_host_string(host_) + ":" + std::to_string(port)), + client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} + +inline ClientImpl::~ClientImpl() { + // Wait until all the requests in flight are handled. + size_t retry_count = 10; + while (retry_count-- > 0) { + { + std::lock_guard guard(socket_mutex_); + if (socket_requests_in_flight_ == 0) { break; } } + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } - while (i < path.size()) { - // Read component - auto beg = i; - while (i < path.size() && path[i] != '/') { - i++; - } + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); +} - auto len = i - beg; - assert(len > 0); +inline bool ClientImpl::is_valid() const { return true; } + +inline void ClientImpl::copy_settings(const ClientImpl &rhs) { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + max_timeout_msec_ = rhs.max_timeout_msec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + path_encode_ = rhs.path_encode_; + address_family_ = rhs.address_family_; + tcp_nodelay_ = rhs.tcp_nodelay_; + ipv6_v6only_ = rhs.ipv6_v6only_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + ca_cert_store_ = rhs.ca_cert_store_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; + server_hostname_verification_ = rhs.server_hostname_verification_; + server_certificate_verifier_ = rhs.server_certificate_verifier_; +#endif + logger_ = rhs.logger_; + error_logger_ = rhs.error_logger_; +} - if (!path.compare(beg, len, ".")) { - ; - } else if (!path.compare(beg, len, "..")) { - if (level == 0) { - return false; - } - level--; - } else { - level++; - } +inline socket_t ClientImpl::create_client_socket(Error &error) const { + if (!proxy_host_.empty() && proxy_port_ != -1) { + return detail::create_client_socket( + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + ipv6_v6only_, socket_options_, connection_timeout_sec_, + connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, interface_, error); + } + + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) { ip = it->second; } + + return detail::create_client_socket( + host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); +} - // Skip slash - while (i < path.size() && path[i] == '/') { - i++; - } - } +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; +} - return true; +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); } -inline void read_file(const std::string& path, std::string& out) -{ - std::ifstream fs(path, std::ios_base::binary); - fs.seekg(0, std::ios_base::end); - auto size = fs.tellg(); - fs.seekg(0); - out.resize(static_cast(size)); - fs.read(&out[0], size); -} - -inline std::string file_extension(const std::string& path) -{ - std::smatch m; - auto pat = std::regex("\\.([a-zA-Z0-9]+)$"); - if (std::regex_search(path, m, pat)) { - return m[1].str(); - } - return std::string(); -} - -inline const char* find_content_type(const std::string& path) -{ - auto ext = file_extension(path); - if (ext == "txt") { - return "text/plain"; - } else if (ext == "html") { - return "text/html"; - } else if (ext == "css") { - return "text/css"; - } else if (ext == "jpeg" || ext == "jpg") { - return "image/jpg"; - } else if (ext == "png") { - return "image/png"; - } else if (ext == "gif") { - return "image/gif"; - } else if (ext == "svg") { - return "image/svg+xml"; - } else if (ext == "ico") { - return "image/x-icon"; - } else if (ext == "json") { - return "application/json"; - } else if (ext == "pdf") { - return "application/pdf"; - } else if (ext == "js") { - return "application/javascript"; - } else if (ext == "xml") { - return "application/xml"; - } else if (ext == "xhtml") { - return "application/xhtml+xml"; - } - return nullptr; -} - -inline const char* status_message(int status) -{ - switch (status) { - case 200: return "OK"; - case 400: return "Bad Request"; - case 404: return "Not Found"; - case 415: return "Unsupported Media Type"; - default: - case 500: return "Internal Server Error"; - } +inline void ClientImpl::shutdown_socket(Socket &socket) const { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); } -inline const char* get_header_value(const Headers& headers, const char* key, const char* def) -{ - auto it = headers.find(key); - if (it != headers.end()) { - return it->second.c_str(); - } - return def; +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; } -inline int get_header_value_int(const Headers& headers, const char* key, int def) -{ - auto it = headers.find(key); - if (it != headers.end()) { - return std::stoi(it->second); - } - return def; +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) const { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { return false; } + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#else + thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#endif + + std::cmatch m; + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == StatusCode::Continue_100) { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + } + + return true; } -inline bool read_headers(Stream& strm, Headers& headers) -{ - static std::regex re(R"((.+?):\s*(.+?)\s*\r\n)"); +inline bool ClientImpl::send(Request &req, Response &res, Error &error) { + std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + } + return ret; +} - const auto bufsiz = 2048; - char buf[bufsiz]; +inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { + { + std::lock_guard guard(socket_mutex_); - stream_line_reader reader(strm, buf, bufsiz); + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; - for (;;) { - if (!reader.getline()) { - return false; + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::is_socket_alive(socket_.sock); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_alive && is_ssl()) { + if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) { + is_alive = false; } - if (!strcmp(reader.ptr(), "\r\n")) { - break; + } +#endif + + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down non-gracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!is_alive) { + if (!create_and_connect_socket(socket_, error)) { + output_error_log(error, &req); + return false; + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + auto success = false; + if (!scli.connect_with_proxy(socket_, req.start_time_, res, success, + error)) { + if (!success) { output_error_log(error, &req); } + return success; + } } - std::cmatch m; - if (std::regex_match(reader.ptr(), m, re)) { - auto key = std::string(m[1]); - auto val = std::string(m[2]); - headers.emplace(key, val); + + if (!scli.initialize_ssl(socket_, error)) { + output_error_log(error, &req); + return false; } + } +#endif } - return true; + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto ret = false; + auto close_connection = !keep_alive_; + + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + }); + + ret = process_socket(socket_, req.start_time_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); + + if (!ret) { + if (error == Error::Success) { + error = Error::Unknown; + output_error_log(error, &req); + } + } + + return ret; } -inline bool read_content_with_length(Stream& strm, std::string& out, size_t len, Progress progress) -{ - out.assign(len, 0); - size_t r = 0; - while (r < len){ - auto n = strm.read(&out[r], len - r); - if (n <= 0) { - return false; - } +inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); +} - r += n; +inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers), + last_ssl_error_, last_openssl_error_}; +#else + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +#endif +} - if (progress) { - progress(r, len); - } +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + if (req.path.empty()) { + error = Error::Connection; + output_error_log(error, &req); + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { + auto req2 = req; + req2.path = "http://" + host_and_port_ + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; + } else { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { return false; } + + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. + + // This is safe to call because handle_request is only called by send_ + // which locks the request mutex during the process. It would be a bug + // to call it from a different thread since it's a thread-safety issue + // to do these things to the socket if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + if (300 < res.status && res.status < 400 && follow_location_) { + req = req_save; + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if ((res.status == StatusCode::Unauthorized_401 || + res.status == StatusCode::ProxyAuthenticationRequired_407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { res = new_res; } + } } + } +#endif - return true; + return ret; } -inline bool read_content_without_length(Stream& strm, std::string& out) -{ - for (;;) { - char byte; - auto n = strm.read(&byte, 1); - if (n < 0) { - return false; - } else if (n == 0) { - return true; - } - out += byte; +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; + output_error_log(error, &req); + return false; + } + + auto location = res.get_header_value("location"); + if (location.empty()) { return false; } + + thread_local const std::regex re( + R"((?:(https?):)?(?://(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); + + std::smatch m; + if (!std::regex_match(location, m, re)) { return false; } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + if (next_host.empty()) { next_host = m[3].str(); } + auto port_str = m[4].str(); + auto next_path = m[5].str(); + auto next_query = m[6].str(); + + auto next_port = port_; + if (!port_str.empty()) { + next_port = std::stoi(port_str); + } else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { next_scheme = scheme; } + if (next_host.empty()) { next_host = host_; } + if (next_path.empty()) { next_path = "/"; } + + auto path = decode_query_component(next_path, true) + next_query; + + // Same host redirect - use current client + if (next_scheme == scheme && next_host == host_ && next_port == port_) { + return detail::redirect(*this, req, res, path, location, error); + } + + // Cross-host/scheme redirect - create new client with robust setup + return create_redirect_client(next_scheme, next_host, next_port, req, res, + path, location, error); +} + +// New method for robust redirect client creation +inline bool ClientImpl::create_redirect_client( + const std::string &scheme, const std::string &host, int port, Request &req, + Response &res, const std::string &path, const std::string &location, + Error &error) { + // Determine if we need SSL + auto need_ssl = (scheme == "https"); + + // Clean up request headers that are host/client specific + // Remove headers that should not be carried over to new host + auto headers_to_remove = + std::vector{"Host", "Proxy-Authorization", "Authorization"}; + + for (const auto &header_name : headers_to_remove) { + auto it = req.headers.find(header_name); + while (it != req.headers.end()) { + it = req.headers.erase(it); + it = req.headers.find(header_name); } + } - return true; + // Create appropriate client type and handle redirect + if (need_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // Create SSL client for HTTPS redirect + SSLClient redirect_client(host, port); + + // Setup basic client configuration first + setup_redirect_client(redirect_client); + + // SSL-specific configuration for proxy environments + if (!proxy_host_.empty() && proxy_port_ != -1) { + // Critical: Disable SSL verification for proxy environments + redirect_client.enable_server_certificate_verification(false); + redirect_client.enable_server_hostname_verification(false); + } else { + // For direct SSL connections, copy SSL verification settings + redirect_client.enable_server_certificate_verification( + server_certificate_verification_); + redirect_client.enable_server_hostname_verification( + server_hostname_verification_); + } + + // Handle CA certificate store and paths if available + if (ca_cert_store_) { redirect_client.set_ca_cert_store(ca_cert_store_); } + if (!ca_cert_file_path_.empty()) { + redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_); + } + + // Client certificates are set through constructor for SSLClient + // NOTE: SSLClient constructor already takes client_cert_path and + // client_key_path so we need to create it properly if client certs are + // needed + + // Execute the redirect + return detail::redirect(redirect_client, req, res, path, location, error); +#else + // SSL not supported - set appropriate error + error = Error::SSLConnection; + output_error_log(error, &req); + return false; +#endif + } else { + // HTTP redirect + ClientImpl redirect_client(host, port); + + // Setup client with robust configuration + setup_redirect_client(redirect_client); + + // Execute the redirect + return detail::redirect(redirect_client, req, res, path, location, error); + } } -inline bool read_content_chunked(Stream& strm, std::string& out) -{ - const auto bufsiz = 16; - char buf[bufsiz]; +// New method for robust client setup (based on basic_manual_redirect.cpp logic) +template +inline void ClientImpl::setup_redirect_client(ClientType &client) { + // Copy basic settings first + client.set_connection_timeout(connection_timeout_sec_); + client.set_read_timeout(read_timeout_sec_, read_timeout_usec_); + client.set_write_timeout(write_timeout_sec_, write_timeout_usec_); + client.set_keep_alive(keep_alive_); + client.set_follow_location( + true); // Enable redirects to handle multi-step redirects + client.set_path_encode(path_encode_); + client.set_compress(compress_); + client.set_decompress(decompress_); + + // Copy authentication settings BEFORE proxy setup + if (!basic_auth_username_.empty()) { + client.set_basic_auth(basic_auth_username_, basic_auth_password_); + } + if (!bearer_token_auth_token_.empty()) { + client.set_bearer_token_auth(bearer_token_auth_token_); + } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!digest_auth_username_.empty()) { + client.set_digest_auth(digest_auth_username_, digest_auth_password_); + } +#endif - stream_line_reader reader(strm, buf, bufsiz); + // Setup proxy configuration (CRITICAL ORDER - proxy must be set + // before proxy auth) + if (!proxy_host_.empty() && proxy_port_ != -1) { + // First set proxy host and port + client.set_proxy(proxy_host_, proxy_port_); - if (!reader.getline()) { - return false; + // Then set proxy authentication (order matters!) + if (!proxy_basic_auth_username_.empty()) { + client.set_proxy_basic_auth(proxy_basic_auth_username_, + proxy_basic_auth_password_); } + if (!proxy_bearer_token_auth_token_.empty()) { + client.set_proxy_bearer_token_auth(proxy_bearer_token_auth_token_); + } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!proxy_digest_auth_username_.empty()) { + client.set_proxy_digest_auth(proxy_digest_auth_username_, + proxy_digest_auth_password_); + } +#endif + } - auto chunk_len = std::stoi(reader.ptr(), 0, 16); + // Copy network and socket settings + client.set_address_family(address_family_); + client.set_tcp_nodelay(tcp_nodelay_); + client.set_ipv6_v6only(ipv6_v6only_); + if (socket_options_) { client.set_socket_options(socket_options_); } + if (!interface_.empty()) { client.set_interface(interface_); } - while (chunk_len > 0){ - std::string chunk; - if (!read_content_with_length(strm, chunk, chunk_len, nullptr)) { - return false; - } + // Copy logging and headers + if (logger_) { client.set_logger(logger_); } + if (error_logger_) { client.set_error_logger(error_logger_); } - if (!reader.getline()) { - return false; - } + // NOTE: DO NOT copy default_headers_ as they may contain stale Host headers + // Each new client should generate its own headers based on its target host +} - if (strcmp(reader.ptr(), "\r\n")) { - break; - } +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) const { + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) { + // TODO: Brotli support + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } - out += chunk; + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content_with_progress( + strm, req.content_provider_, 0, req.content_length_, is_shutting_down, + req.upload_progress, error); + } +} - if (!reader.getline()) { - return false; +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { + if (!req.has_header("Connection")) { + req.set_header("Connection", "close"); + } + } + + if (!req.has_header("Host")) { + // For Unix socket connections, use "localhost" as Host header (similar to + // curl behavior) + if (address_family_ == AF_UNIX) { + req.set_header("Host", "localhost"); + } else if (is_ssl()) { + if (port_ == 443) { + req.set_header("Host", host_); + } else { + req.set_header("Host", host_and_port_); + } + } else { + if (port_ == 80) { + req.set_header("Host", host_); + } else { + req.set_header("Host", host_and_port_); + } + } + } + + if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); } + + if (!req.content_receiver) { + if (!req.has_header("Accept-Encoding")) { + std::string accept_encoding; +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + accept_encoding = "br"; +#endif +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (!accept_encoding.empty()) { accept_encoding += ", "; } + accept_encoding += "gzip, deflate"; +#endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + if (!accept_encoding.empty()) { accept_encoding += ", "; } + accept_encoding += "zstd"; +#endif + req.set_header("Accept-Encoding", accept_encoding); + } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.set_header("User-Agent", agent); + } +#endif + }; + + if (req.body.empty()) { + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.content_length_); + req.set_header("Content-Length", length); } + } + } else { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.set_header("Content-Length", "0"); + } + } + } else { + if (!req.has_header("Content-Type")) { + req.set_header("Content-Type", "text/plain"); + } - chunk_len = std::stoi(reader.ptr(), 0, 16); + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.body.size()); + req.set_header("Content-Length", length); } + } - if (chunk_len == 0) { - // Reader terminator after chunks - if (!reader.getline() || strcmp(reader.ptr(), "\r\n")) - return false; + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); } + } - return true; -} + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + } -template -bool read_content(Stream& strm, T& x, Progress progress = Progress()) -{ - auto len = get_header_value_int(x.headers, "Content-Length", 0); + if (!bearer_token_auth_token_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + } - if (len) { - return read_content_with_length(strm, x.body, len, progress); + if (!proxy_bearer_token_auth_token_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + } + + // Request line and headers + { + detail::BufferStream bstrm; + + // Extract path and query from req.path + std::string path_part, query_part; + auto query_pos = req.path.find('?'); + if (query_pos != std::string::npos) { + path_part = req.path.substr(0, query_pos); + query_part = req.path.substr(query_pos + 1); } else { - const auto& encoding = get_header_value(x.headers, "Transfer-Encoding", ""); + path_part = req.path; + query_part = ""; + } - if (!strcasecmp(encoding, "chunked")) { - return read_content_chunked(strm, x.body); - } else { - return read_content_without_length(strm, x.body); - } + // Encode path and query + auto path_with_query = + path_encode_ ? detail::encode_path(path_part) : path_part; + + detail::parse_query_text(query_part, req.params); + if (!req.params.empty()) { + path_with_query = append_query_params(path_with_query, req.params); } - return true; -} + // Write request line and headers + detail::write_request_line(bstrm, req.method, path_with_query); + header_writer_(bstrm, req.headers); -template -inline void write_headers(Stream& strm, const T& info) -{ - for (const auto& x: info.headers) { - strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + output_error_log(error, &req); + return false; + } + } + + // Body + if (req.body.empty()) { + return write_content_with_provider(strm, req, error); + } + + if (req.upload_progress) { + auto body_size = req.body.size(); + size_t written = 0; + auto data = req.body.data(); + + while (written < body_size) { + size_t to_write = (std::min)(CPPHTTPLIB_SEND_BUFSIZ, body_size - written); + if (!detail::write_data(strm, data + written, to_write)) { + error = Error::Write; + output_error_log(error, &req); + return false; + } + written += to_write; + + if (!req.upload_progress(written, body_size)) { + error = Error::Canceled; + output_error_log(error, &req); + return false; + } + } + } else { + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + output_error_log(error, &req); + return false; } - strm.write("\r\n"); + } + + return true; } -inline std::string encode_url(const std::string& s) -{ - std::string result; +inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error) { + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } - for (auto i = 0; s[i]; i++) { - switch (s[i]) { - case ' ': result += "+"; break; - case '\'': result += "%27"; break; - case ',': result += "%2C"; break; - case ':': result += "%3A"; break; - case ';': result += "%3B"; break; - default: - if (s[i] < 0) { - result += '%'; - char hex[4]; - size_t len = snprintf(hex, sizeof(hex) - 1, "%02X", (unsigned char)s[i]); - assert(len == 2); - result.append(hex, len); - } else { - result += s[i]; - } - break; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { req.set_header("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support + detail::gzip_compressor compressor; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) -> bool { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor.compress( + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + return ok; + }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + output_error_log(error, &req); + return nullptr; } - } + } + } else { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + output_error_log(error, &req); + return nullptr; + } + } + } else +#endif + { + if (content_provider) { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.set_header("Transfer-Encoding", "chunked"); + } else { + req.body.assign(body, content_length); + } + } - return result; + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; +} + +inline Result ClientImpl::send_with_content_provider( + const std::string &method, const std::string &path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, UploadProgress progress) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + req.upload_progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + return Result{std::move(res), error, std::move(req.headers), last_ssl_error_, + last_openssl_error_}; +#else + return Result{std::move(res), error, std::move(req.headers)}; +#endif } -inline bool is_hex(char c, int& v) -{ - if (0x20 <= c && isdigit(c)) { - v = c - '0'; - return true; - } else if ('A' <= c && c <= 'F') { - v = c - 'A' + 10; - return true; - } else if ('a' <= c && c <= 'f') { - v = c - 'a' + 10; - return true; - } - return false; +inline std::string +ClientImpl::adjust_host_string(const std::string &host) const { + if (host.find(':') != std::string::npos) { return "[" + host + "]"; } + return host; } -inline bool from_hex_to_i(const std::string& s, int i, int cnt, int& val) -{ - val = 0; - for (; cnt; i++, cnt--) { - if (!s[i]) { - return false; - } - int v = 0; - if (is_hex(s[i], v)) { - val = val * 16 + v; - } else { - return false; - } - } - return true; +inline void ClientImpl::output_log(const Request &req, + const Response &res) const { + if (logger_) { + std::lock_guard guard(logger_mutex_); + logger_(req, res); + } } -inline size_t to_utf8(int code, char* buff) -{ - if (code < 0x0080) { - buff[0] = (code & 0x7F); - return 1; - } else if (code < 0x0800) { - buff[0] = (0xC0 | ((code >> 6) & 0x1F)); - buff[1] = (0x80 | (code & 0x3F)); - return 2; - } else if (code < 0xD800) { - buff[0] = (0xE0 | ((code >> 12) & 0xF)); - buff[1] = (0x80 | ((code >> 6) & 0x3F)); - buff[2] = (0x80 | (code & 0x3F)); - return 3; - } else if (code < 0xE000) { // D800 - DFFF is invalid... - return 0; - } else if (code < 0x10000) { - buff[0] = (0xE0 | ((code >> 12) & 0xF)); - buff[1] = (0x80 | ((code >> 6) & 0x3F)); - buff[2] = (0x80 | (code & 0x3F)); - return 3; - } else if (code < 0x110000) { - buff[0] = (0xF0 | ((code >> 18) & 0x7)); - buff[1] = (0x80 | ((code >> 12) & 0x3F)); - buff[2] = (0x80 | ((code >> 6) & 0x3F)); - buff[3] = (0x80 | (code & 0x3F)); - return 4; - } - - // NOTREACHED - return 0; +inline void ClientImpl::output_error_log(const Error &err, + const Request *req) const { + if (error_logger_) { + std::lock_guard guard(logger_mutex_); + error_logger_(err, req); + } } -inline std::string decode_url(const std::string& s) -{ - std::string result; - - for (int i = 0; s[i]; i++) { - if (s[i] == '%') { - if (s[i + 1] && s[i + 1] == 'u') { - int val = 0; - if (from_hex_to_i(s, i + 2, 4, val)) { - // 4 digits Unicode codes - char buff[4]; - size_t len = to_utf8(val, buff); - if (len > 0) { - result.append(buff, len); +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + // Send request + if (!write_request(strm, req, close_connection, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_ssl()) { + auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; + if (!is_proxy_enabled) { + if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) { + error = Error::SSLPeerCouldBeClosed_; + output_error_log(error, &req); + return false; + } + } + } +#endif + + // Receive response and headers + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) { + error = Error::Read; + output_error_log(error, &req); + return false; + } + + // Body + if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" && + req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && + res.status != StatusCode::NotModified_304 && + follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + output_error_log(error, &req); + return false; + } + } + + auto out = + req.content_receiver + ? static_cast( + [&](const char *buf, size_t n, size_t off, size_t len) { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { + error = Error::Canceled; + output_error_log(error, &req); } - i += 5; // 'u0000' - } else { - result += s[i]; - } - } else { - int val = 0; - if (from_hex_to_i(s, i + 1, 2, val)) { - // 2 digits hex codes - result += val; - i += 2; // '00' - } else { - result += s[i]; - } - } - } else if (s[i] == '+') { - result += ' '; - } else { - result += s[i]; + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, size_t /*off*/, + size_t /*len*/) { + assert(res.body.size() + n <= res.body.max_size()); + res.body.append(buf, n); + return true; + }); + + auto progress = [&](size_t current, size_t total) { + if (!req.download_progress || redirect) { return true; } + auto ret = req.download_progress(current, total); + if (!ret) { + error = Error::Canceled; + output_error_log(error, &req); + } + return ret; + }; + + if (res.has_header("Content-Length")) { + if (!req.content_receiver) { + auto len = res.get_header_value_u64("Content-Length"); + if (len > res.body.max_size()) { + error = Error::Read; + output_error_log(error, &req); + return false; } + res.body.reserve(static_cast(len)); + } } - return result; -} + if (res.status != StatusCode::NotModified_304) { + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), + std::move(out), decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } + output_error_log(error, &req); + return false; + } + } + } -inline void parse_query_text(const std::string& s, Params& params) -{ - split(&s[0], &s[s.size()], '&', [&](const char* b, const char* e) { - std::string key; - std::string val; - split(b, e, '=', [&](const char* b, const char* e) { - if (key.empty()) { - key.assign(b, e); - } else { - val.assign(b, e); - } - }); - params.emplace(key, decode_url(val)); - }); + // Log + output_log(req, res); + + return true; } -inline bool parse_multipart_boundary(const std::string& content_type, std::string& boundary) -{ - auto pos = content_type.find("boundary="); - if (pos == std::string::npos) { +inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( + const std::string &boundary, const UploadFormDataItems &items, + const FormDataProviderItems &provider_items) const { + size_t cur_item = 0; + size_t cur_start = 0; + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls + return [&, cur_item, cur_start](size_t offset, + DataSink &sink) mutable -> bool { + if (!offset && !items.empty()) { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } else if (cur_item < provider_items.size()) { + if (!cur_start) { + const auto &begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + auto has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) { return false; + } + + if (!has_data) { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } else { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; } + }; +} - boundary = content_type.substr(pos + 9); - return true; +inline bool ClientImpl::process_socket( + const Socket &socket, + std::chrono::time_point start_time, + std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, max_timeout_msec_, start_time, std::move(callback)); } -inline bool parse_multipart_formdata( - const std::string& boundary, const std::string& body, MultipartFiles& files) -{ - static std::string dash = "--"; - static std::string crlf = "\r\n"; +inline bool ClientImpl::is_ssl() const { return false; } - static std::regex re_content_type( - "Content-Type: (.*?)", std::regex_constants::icase); +inline Result ClientImpl::Get(const std::string &path, + DownloadProgress progress) { + return Get(path, Headers(), std::move(progress)); +} - static std::regex re_content_disposition( - "Content-Disposition: form-data; name=\"(.*?)\"(?:; filename=\"(.*?)\")?", - std::regex_constants::icase); +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + DownloadProgress progress) { + if (params.empty()) { return Get(path, headers); } - auto dash_boundary = dash + boundary; + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(progress)); +} - auto pos = body.find(dash_boundary); - if (pos != 0) { - return false; - } +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + DownloadProgress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.download_progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + return send_(std::move(req)); +} - pos += dash_boundary.size(); +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver, + DownloadProgress progress) { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); +} - auto next_pos = body.find(crlf, pos); - if (next_pos == std::string::npos) { - return false; - } +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, + DownloadProgress progress) { + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} - pos = next_pos + crlf.size(); +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + DownloadProgress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} - while (pos < body.size()) { - next_pos = body.find(crlf, pos); - if (next_pos == std::string::npos) { - return false; - } +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + DownloadProgress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + return send_(std::move(req)); +} - std::string name; - MultipartFile file; +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + DownloadProgress progress) { + return Get(path, params, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} - auto header = body.substr(pos, (next_pos - pos)); +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + DownloadProgress progress) { + if (params.empty()) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} - while (pos != next_pos) { - std::smatch m; - if (std::regex_match(header, m, re_content_type)) { - file.content_type = m[1]; - } else if (std::regex_match(header, m, re_content_disposition)) { - name = m[1]; - file.filename = m[2]; - } +inline Result ClientImpl::Head(const std::string &path) { + return Head(path, Headers()); +} - pos = next_pos + crlf.size(); +inline Result ClientImpl::Head(const std::string &path, + const Headers &headers) { + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + return send_(std::move(req)); +} - next_pos = body.find(crlf, pos); - if (next_pos == std::string::npos) { - return false; - } +inline Result ClientImpl::Post(const std::string &path) { + return Post(path, std::string(), std::string()); +} - header = body.substr(pos, (next_pos - pos)); - } +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers) { + return Post(path, headers, nullptr, 0, std::string()); +} - pos = next_pos + crlf.size(); +inline Result ClientImpl::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return Post(path, Headers(), body, content_length, content_type, progress); +} - next_pos = body.find(crlf + dash_boundary, pos); +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return Post(path, Headers(), body, content_type, progress); +} - if (next_pos == std::string::npos) { - return false; - } +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { + return Post(path, Headers(), params); +} - file.offset = pos; - file.length = next_pos - pos; +inline Result ClientImpl::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type, progress); +} - pos = next_pos + crlf.size() + dash_boundary.size(); +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return Post(path, Headers(), std::move(content_provider), content_type, + progress); +} - next_pos = body.find(crlf, pos); - if (next_pos == std::string::npos) { - return false; - } +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded"); +} - files.emplace(name, file); +inline Result ClientImpl::Post(const std::string &path, + const UploadFormDataItems &items, + UploadProgress progress) { + return Post(path, Headers(), items, progress); +} - pos = next_pos + crlf.size(); - } +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + UploadProgress progress) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type, progress); +} - return true; +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type, progress); } -inline std::string to_lower(const char* beg, const char* end) -{ - std::string out; - auto it = beg; - while (it != end) { - out += ::tolower(*it); - it++; - } - return out; +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type, progress); } -inline void make_range_header_core(std::string&) {} +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); +} -template -inline void make_range_header_core(std::string& field, uint64_t value) -{ - if (!field.empty()) { - field += ", "; - } - field += std::to_string(value) + "-"; +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, progress); } -template -inline void make_range_header_core(std::string& field, uint64_t value1, uint64_t value2, Args... args) -{ - if (!field.empty()) { - field += ", "; - } - field += std::to_string(value1) + "-" + std::to_string(value2); - make_range_header_core(field, args...); +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + progress); } -#ifdef CPPHTTPLIB_ZLIB_SUPPORT -inline bool can_compress(const std::string& content_type) { - return !content_type.find("text/") || - content_type == "image/svg+xml" || - content_type == "application/javascript" || - content_type == "application/json" || - content_type == "application/xml" || - content_type == "application/xhtml+xml"; -} - -inline void compress(std::string& content) -{ - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY); - if (ret != Z_OK) { - return; - } +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, progress); +} - strm.avail_in = content.size(); - strm.next_in = (Bytef *)content.data(); +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + Request req; + req.method = "POST"; + req.path = path; + req.headers = headers; + req.body = body; + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + + return send_(std::move(req)); +} - std::string compressed; +inline Result ClientImpl::Put(const std::string &path) { + return Put(path, std::string(), std::string()); +} - const auto bufsiz = 16384; - char buff[bufsiz]; - do { - strm.avail_out = bufsiz; - strm.next_out = (Bytef *)buff; - deflate(&strm, Z_FINISH); - compressed.append(buff, bufsiz - strm.avail_out); - } while (strm.avail_out == 0); +inline Result ClientImpl::Put(const std::string &path, const Headers &headers) { + return Put(path, headers, nullptr, 0, std::string()); +} - content.swap(compressed); +inline Result ClientImpl::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return Put(path, Headers(), body, content_length, content_type, progress); +} - deflateEnd(&strm); +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return Put(path, Headers(), body, content_type, progress); } -inline void decompress(std::string& content) -{ - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { + return Put(path, Headers(), params); +} - // 15 is the value of wbits, which should be at the maximum possible value to ensure - // that any gzip stream can be decoded. The offset of 16 specifies that the stream - // to decompress will be formatted with a gzip wrapper. - auto ret = inflateInit2(&strm, 16 + 15); - if (ret != Z_OK) { - return; - } +inline Result ClientImpl::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type, progress); +} - strm.avail_in = content.size(); - strm.next_in = (Bytef *)content.data(); +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return Put(path, Headers(), std::move(content_provider), content_type, + progress); +} - std::string decompressed; +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); +} - const auto bufsiz = 16384; - char buff[bufsiz]; - do { - strm.avail_out = bufsiz; - strm.next_out = (Bytef *)buff; - inflate(&strm, Z_NO_FLUSH); - decompressed.append(buff, bufsiz - strm.avail_out); - } while (strm.avail_out == 0); +inline Result ClientImpl::Put(const std::string &path, + const UploadFormDataItems &items, + UploadProgress progress) { + return Put(path, Headers(), items, progress); +} - content.swap(decompressed); +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + UploadProgress progress) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type, progress); +} - inflateEnd(&strm); +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type, progress); } -#endif -#ifdef _WIN32 -class WSInit { -public: - WSInit() { - WSADATA wsaData; - WSAStartup(0x0002, &wsaData); - } +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type, progress); +} - ~WSInit() { - WSACleanup(); - } -}; +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); +} -static WSInit wsinit_; -#endif +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, progress); +} -} // namespace detail +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + progress); +} -// Header utilities -template -inline std::pair make_range_header(uint64_t value, Args... args) -{ - std::string field; - detail::make_range_header_core(field, value, args...); - field.insert(0, "bytes="); - return std::make_pair("Range", field); +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, progress); } -// Request implementation -inline bool Request::has_header(const char* key) const -{ - return headers.find(key) != headers.end(); +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + Request req; + req.method = "PUT"; + req.path = path; + req.headers = headers; + req.body = body; + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + + return send_(std::move(req)); } -inline std::string Request::get_header_value(const char* key) const -{ - return detail::get_header_value(headers, key, ""); +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); } -inline void Request::set_header(const char* key, const char* val) -{ - headers.emplace(key, val); +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + UploadProgress progress) { + return Patch(path, headers, nullptr, 0, std::string(), progress); } -inline bool Request::has_param(const char* key) const -{ - return params.find(key) != params.end(); +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return Patch(path, Headers(), body, content_length, content_type, progress); } -inline std::string Request::get_param_value(const char* key) const -{ - auto it = params.find(key); - if (it != params.end()) { - return it->second; - } - return std::string(); +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return Patch(path, Headers(), body, content_type, progress); } -inline bool Request::has_file(const char* key) const -{ - return files.find(key) != files.end(); +inline Result ClientImpl::Patch(const std::string &path, const Params ¶ms) { + return Patch(path, Headers(), params); } -inline MultipartFile Request::get_file_value(const char* key) const -{ - auto it = files.find(key); - if (it != files.end()) { - return it->second; - } - return MultipartFile(); +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type, progress); } -// Response implementation -inline bool Response::has_header(const char* key) const -{ - return headers.find(key) != headers.end(); +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return Patch(path, Headers(), std::move(content_provider), content_type, + progress); } -inline std::string Response::get_header_value(const char* key) const -{ - return detail::get_header_value(headers, key, ""); +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Patch(path, headers, query, "application/x-www-form-urlencoded"); } -inline void Response::set_header(const char* key, const char* val) -{ - headers.emplace(key, val); +inline Result ClientImpl::Patch(const std::string &path, + const UploadFormDataItems &items, + UploadProgress progress) { + return Patch(path, Headers(), items, progress); } -inline void Response::set_redirect(const char* url) -{ - set_header("Location", url); - status = 302; +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + UploadProgress progress) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Patch(path, headers, body, content_type, progress); } -inline void Response::set_content(const char* s, size_t n, const char* content_type) -{ - body.assign(s, n); - set_header("Content-Type", content_type); +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Patch(path, headers, body, content_type, progress); } -inline void Response::set_content(const std::string& s, const char* content_type) -{ - body = s; - set_header("Content-Type", content_type); +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type, progress); } -// Rstream implementation -template -inline void Stream::write_format(const char* fmt, const Args& ...args) -{ - const auto bufsiz = 2048; - char buf[bufsiz]; +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); +} -#if defined(_MSC_VER) && _MSC_VER < 1900 - auto n = _snprintf_s(buf, bufsiz, bufsiz - 1, fmt, args...); -#else - auto n = snprintf(buf, bufsiz - 1, fmt, args...); -#endif - if (n > 0) { - if (n >= bufsiz - 1) { - std::vector glowable_buf(bufsiz); +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, progress); +} - while (n >= static_cast(glowable_buf.size() - 1)) { - glowable_buf.resize(glowable_buf.size() * 2); -#if defined(_MSC_VER) && _MSC_VER < 1900 - n = _snprintf_s(&glowable_buf[0], glowable_buf.size(), glowable_buf.size() - 1, fmt, args...); -#else - n = snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...); -#endif - } - write(&glowable_buf[0], n); - } else { - write(buf, n); - } - } +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + progress); } -// Socket stream implementation -inline SocketStream::SocketStream(socket_t sock): sock_(sock) -{ +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PATCH", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, progress); } -inline SocketStream::~SocketStream() -{ +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + Request req; + req.method = "PATCH"; + req.path = path; + req.headers = headers; + req.body = body; + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + + return send_(std::move(req)); } -inline int SocketStream::read(char* ptr, size_t size) -{ - return recv(sock_, ptr, size, 0); +inline Result ClientImpl::Delete(const std::string &path, + DownloadProgress progress) { + return Delete(path, Headers(), std::string(), std::string(), progress); } -inline int SocketStream::write(const char* ptr, size_t size) -{ - return send(sock_, ptr, size, 0); +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + DownloadProgress progress) { + return Delete(path, headers, std::string(), std::string(), progress); } -inline int SocketStream::write(const char* ptr) -{ - return write(ptr, strlen(ptr)); +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + DownloadProgress progress) { + return Delete(path, Headers(), body, content_length, content_type, progress); } -inline std::string SocketStream::get_remote_addr() { - return detail::get_remote_addr(sock_); +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type, + DownloadProgress progress) { + return Delete(path, Headers(), body.data(), body.size(), content_type, + progress); } -// HTTP server implementation -inline Server::Server() - : keep_alive_max_count_(5) - , is_running_(false) - , svr_sock_(INVALID_SOCKET) - , running_threads_(0) -{ -#ifndef _WIN32 - signal(SIGPIPE, SIG_IGN); -#endif +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + DownloadProgress progress) { + return Delete(path, headers, body.data(), body.size(), content_type, + progress); } -inline Server::~Server() -{ +inline Result ClientImpl::Delete(const std::string &path, const Params ¶ms, + DownloadProgress progress) { + return Delete(path, Headers(), params, progress); } -inline Server& Server::Get(const char* pattern, Handler handler) -{ - get_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); - return *this; +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const Params ¶ms, + DownloadProgress progress) { + auto query = detail::params_to_query_str(params); + return Delete(path, headers, query, "application/x-www-form-urlencoded", + progress); } -inline Server& Server::Post(const char* pattern, Handler handler) -{ - post_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); - return *this; +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type, + DownloadProgress progress) { + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + req.download_progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + req.body.assign(body, content_length); + + return send_(std::move(req)); } -inline Server& Server::Put(const char* pattern, Handler handler) -{ - put_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); - return *this; +inline Result ClientImpl::Options(const std::string &path) { + return Options(path, Headers()); } -inline Server& Server::Delete(const char* pattern, Handler handler) -{ - delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); - return *this; +inline Result ClientImpl::Options(const std::string &path, + const Headers &headers) { + Request req; + req.method = "OPTIONS"; + req.headers = headers; + req.path = path; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + return send_(std::move(req)); } -inline Server& Server::Options(const char* pattern, Handler handler) -{ - options_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); - return *this; +inline void ClientImpl::stop() { + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + // Otherwise, still holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); } -inline bool Server::set_base_dir(const char* path) -{ - if (detail::is_dir(path)) { - base_dir_ = path; - return true; - } - return false; +inline std::string ClientImpl::host() const { return host_; } + +inline int ClientImpl::port() const { return port_; } + +inline size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); } -inline void Server::set_error_handler(Handler handler) -{ - error_handler_ = handler; +inline socket_t ClientImpl::socket() const { return socket_.sock; } + +inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; } -inline void Server::set_logger(Logger logger) -{ - logger_ = logger; +inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; } -inline void Server::set_keep_alive_max_count(size_t count) -{ - keep_alive_max_count_ = count; +inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; } -inline int Server::bind_to_any_port(const char* host, int socket_flags) -{ - return bind_internal(host, 0, socket_flags); +inline void ClientImpl::set_max_timeout(time_t msec) { + max_timeout_msec_ = msec; } -inline bool Server::listen_after_bind() { - return listen_internal(); +inline void ClientImpl::set_basic_auth(const std::string &username, + const std::string &password) { + basic_auth_username_ = username; + basic_auth_password_ = password; } -inline bool Server::listen(const char* host, int port, int socket_flags) -{ - if (bind_internal(host, port, socket_flags) < 0) - return false; - return listen_internal(); +inline void ClientImpl::set_bearer_token_auth(const std::string &token) { + bearer_token_auth_token_ = token; } -inline bool Server::is_running() const -{ - return is_running_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { + digest_auth_username_ = username; + digest_auth_password_ = password; } +#endif -inline void Server::stop() -{ - if (is_running_) { - assert(svr_sock_ != INVALID_SOCKET); - detail::shutdown_socket(svr_sock_); - detail::close_socket(svr_sock_); - svr_sock_ = INVALID_SOCKET; - } +inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } + +inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } + +inline void ClientImpl::set_path_encode(bool on) { path_encode_ = on; } + +inline void +ClientImpl::set_hostname_addr_map(std::map addr_map) { + addr_map_ = std::move(addr_map); } -inline bool Server::parse_request_line(const char* s, Request& req) -{ - static std::regex re("(GET|HEAD|POST|PUT|DELETE|OPTIONS) (([^?]+)(?:\\?(.+?))?) (HTTP/1\\.[01])\r\n"); +inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); +} - std::cmatch m; - if (std::regex_match(s, m, re)) { - req.version = std::string(m[4]); - req.method = std::string(m[1]); - req.target = std::string(m[2]); - req.path = detail::decode_url(m[3]); +inline void ClientImpl::set_header_writer( + std::function const &writer) { + header_writer_ = writer; +} - // Parse query text - auto len = std::distance(m[4].first, m[4].second); - if (len > 0) { - detail::parse_query_text(m[4], req.params); - } +inline void ClientImpl::set_address_family(int family) { + address_family_ = family; +} - return true; - } +inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } - return false; +inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; } + +inline void ClientImpl::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); } -inline void Server::write_response(Stream& strm, bool last_connection, const Request& req, Response& res) -{ - assert(res.status != -1); +inline void ClientImpl::set_compress(bool on) { compress_ = on; } - if (400 <= res.status && error_handler_) { - error_handler_(req, res); - } +inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } - // Response line - strm.write_format("HTTP/1.1 %d %s\r\n", - res.status, - detail::status_message(res.status)); +inline void ClientImpl::set_interface(const std::string &intf) { + interface_ = intf; +} - // Headers - if (last_connection || - req.version == "HTTP/1.0" || - req.get_header_value("Connection") == "close") { - res.set_header("Connection", "close"); - } +inline void ClientImpl::set_proxy(const std::string &host, int port) { + proxy_host_ = host; + proxy_port_ = port; +} - if (!res.body.empty()) { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT - // TODO: 'Accpet-Encoding' has gzip, not gzip;q=0 - const auto& encodings = req.get_header_value("Accept-Encoding"); - if (encodings.find("gzip") != std::string::npos && - detail::can_compress(res.get_header_value("Content-Type"))) { - detail::compress(res.body); - res.set_header("Content-Encoding", "gzip"); - } -#endif +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; +} - if (!res.has_header("Content-Type")) { - res.set_header("Content-Type", "text/plain"); - } +inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) { + proxy_bearer_token_auth_token_ = token; +} - auto length = std::to_string(res.body.size()); - res.set_header("Content-Length", length.c_str()); - } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} + +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store && ca_cert_store != ca_cert_store_) { + ca_cert_store_ = ca_cert_store; + } +} - detail::write_headers(strm, res); +inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert, + std::size_t size) const { + auto mem = BIO_new_mem_buf(ca_cert, static_cast(size)); + auto se = detail::scope_exit([&] { BIO_free_all(mem); }); + if (!mem) { return nullptr; } - // Body - if (!res.body.empty() && req.method != "HEAD") { - strm.write(res.body.c_str(), res.body.size()); - } + auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr); + if (!inf) { return nullptr; } - // Log - if (logger_) { - logger_(req, res); + auto cts = X509_STORE_new(); + if (cts) { + for (auto i = 0; i < static_cast(sk_X509_INFO_num(inf)); i++) { + auto itmp = sk_X509_INFO_value(inf, i); + if (!itmp) { continue; } + + if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); } + if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); } } + } + + sk_X509_INFO_pop_free(inf, X509_INFO_free); + return cts; } -inline bool Server::handle_file_request(Request& req, Response& res) -{ - if (!base_dir_.empty() && detail::is_valid_path(req.path)) { - std::string path = base_dir_ + req.path; +inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} - if (!path.empty() && path.back() == '/') { - path += "index.html"; - } +inline void ClientImpl::enable_server_hostname_verification(bool enabled) { + server_hostname_verification_ = enabled; +} - if (detail::is_file(path)) { - detail::read_file(path, res.body); - auto type = detail::find_content_type(path); - if (type) { - res.set_header("Content-Type", type); - } - res.status = 200; - return true; - } - } +inline void ClientImpl::set_server_certificate_verifier( + std::function verifier) { + server_certificate_verifier_ = verifier; +} +#endif - return false; +inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); } -inline socket_t Server::create_server_socket(const char* host, int port, int socket_flags) const -{ - return detail::create_socket(host, port, - [](socket_t sock, struct addrinfo& ai) -> bool { - if (::bind(sock, ai.ai_addr, ai.ai_addrlen)) { - return false; - } - if (::listen(sock, 5)) { // Listen through 5 channels - return false; - } - return true; - }, socket_flags); +inline void ClientImpl::set_error_logger(ErrorLogger error_logger) { + error_logger_ = std::move(error_logger); } -inline int Server::bind_internal(const char* host, int port, int socket_flags) -{ - if (!is_valid()) { - return -1; +/* + * SSL Implementation + */ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace detail { + +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { + SSL *ssl = nullptr; + { + std::lock_guard guard(ctx_mutex); + ssl = SSL_new(ctx); + } + + if (ssl) { + set_nonblocking(sock, true); + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); + SSL_set_bio(ssl, bio, bio); + + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); + } + set_nonblocking(sock, false); + return nullptr; } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); + } + + return ssl; +} - svr_sock_ = create_server_socket(host, port, socket_flags); - if (svr_sock_ == INVALID_SOCKET) { - return -1; +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { + (void)(sock); + // SSL_shutdown() returns 0 on first call (indicating close_notify alert + // sent) and 1 on subsequent call (indicating close_notify alert received) + if (SSL_shutdown(ssl) == 0) { + // Expected to return 1, but even if it doesn't, we free ssl + SSL_shutdown(ssl); } + } - if (port == 0) { - struct sockaddr_storage address; - socklen_t len = sizeof(address); - if (getsockname(svr_sock_, reinterpret_cast(&address), &len) == -1) { - return -1; - } - if (address.ss_family == AF_INET) { - return ntohs(reinterpret_cast(&address)->sin_port); - } else if (address.ss_family == AF_INET6) { - return ntohs(reinterpret_cast(&address)->sin6_port); - } else { - return -1; - } - } else { - return port; + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} + +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, time_t timeout_usec, + int *ssl_error) { + auto res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; } + if (ssl_error) { *ssl_error = err; } + return false; + } + return true; } -inline bool Server::listen_internal() -{ - auto ret = true; +template +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} - is_running_ = true; +template +inline bool process_client_socket_ssl( + SSL *ssl, socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec, max_timeout_msec, + start_time); + return callback(strm); +} - for (;;) { - auto val = detail::select_read(svr_sock_, 0, 100000); +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream( + socket_t sock, SSL *ssl, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time) + : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec), + max_timeout_msec_(max_timeout_msec), start_time_(start_time) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} - if (val == 0) { // Timeout - if (svr_sock_ == INVALID_SOCKET) { - // The server socket was closed by 'stop' method. - break; - } - continue; - } +inline SSLSocketStream::~SSLSocketStream() = default; - socket_t sock = accept(svr_sock_, NULL, NULL); +inline bool SSLSocketStream::is_readable() const { + return SSL_pending(ssl_) > 0; +} - if (sock == INVALID_SOCKET) { - if (svr_sock_ != INVALID_SOCKET) { - detail::close_socket(svr_sock_); - ret = false; - } else { - ; // The server socket was closed by user. - } - break; - } +inline bool SSLSocketStream::wait_readable() const { + if (max_timeout_msec_ <= 0) { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + } - // TODO: Use thread pool... - std::thread([=, this]() { - { - std::lock_guard guard(running_threads_mutex_); - running_threads_++; - } + time_t read_timeout_sec; + time_t read_timeout_usec; + calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_, + read_timeout_usec_, read_timeout_sec, read_timeout_usec); - read_and_close_socket(sock); + return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0; +} - { - std::lock_guard guard(running_threads_mutex_); - running_threads_--; - } - }).detach(); - } +inline bool SSLSocketStream::wait_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_) && !is_ssl_peer_could_be_closed(ssl_, sock_); +} - // TODO: Use thread pool... - for (;;) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - std::lock_guard guard(running_threads_mutex_); - if (!running_threads_) { - break; +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (wait_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (wait_readable()) { + std::this_thread::sleep_for(std::chrono::microseconds{10}); + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + break; } + } + assert(ret < 0); } - - is_running_ = false; - return ret; + } else { + return -1; + } } -inline bool Server::routing(Request& req, Response& res) -{ - if (req.method == "GET" && handle_file_request(req, res)) { - return true; - } +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { + if (wait_writable()) { + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); - if (req.method == "GET" || req.method == "HEAD") { - return dispatch_request(req, res, get_handlers_); - } else if (req.method == "POST") { - return dispatch_request(req, res, post_handlers_); - } else if (req.method == "PUT") { - return dispatch_request(req, res, put_handlers_); - } else if (req.method == "DELETE") { - return dispatch_request(req, res, delete_handlers_); - } else if (req.method == "OPTIONS") { - return dispatch_request(req, res, options_handlers_); + auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { +#endif + if (wait_writable()) { + std::this_thread::sleep_for(std::chrono::microseconds{10}); + ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + break; + } + } + assert(ret < 0); } - return false; + return ret; + } + return -1; } -inline bool Server::dispatch_request(Request& req, Response& res, Handlers& handlers) -{ - for (const auto& x: handlers) { - const auto& pattern = x.first; - const auto& handler = x.second; - - if (std::regex_match(req.path, req.matches, pattern)) { - handler(req, res); - return true; - } - } - return false; +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); } -inline bool Server::process_request(Stream& strm, bool last_connection, bool& connection_close) -{ - const auto bufsiz = 2048; - char buf[bufsiz]; +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + detail::get_local_ip_and_port(sock_, ip, port); +} - detail::stream_line_reader reader(strm, buf, bufsiz); +inline socket_t SSLSocketStream::socket() const { return sock_; } - // Connection has been closed on client - if (!reader.getline()) { - return false; - } +inline time_t SSLSocketStream::duration() const { + return std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time_) + .count(); +} - Request req; - Response res; +} // namespace detail - res.version = "HTTP/1.1"; +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path, + const char *private_key_password) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); - // Request line and headers - if (!parse_request_line(reader.ptr(), req) || !detail::read_headers(strm, req.headers)) { - res.status = 400; - write_response(strm, last_connection, req, res); - return true; + SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION); + + if (private_key_password != nullptr && (private_key_password[0] != '\0')) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, + reinterpret_cast(const_cast(private_key_password))); } - auto ret = true; - if (req.get_header_value("Connection") == "close") { - // ret = false; - connection_close = true; + if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != + 1 || + SSL_CTX_check_private_key(ctx_) != 1) { + last_ssl_error_ = static_cast(ERR_get_error()); + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { + SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, + client_ca_cert_dir_path); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); } + } +} - req.set_header("REMOTE_ADDR", strm.get_remote_addr().c_str()); +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(TLS_server_method()); - // Body - if (req.method == "POST" || req.method == "PUT") { - if (!detail::read_content(strm, req)) { - res.status = 400; - write_response(strm, last_connection, req, res); - return ret; - } + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); - const auto& content_type = req.get_header_value("Content-Type"); + SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION); - if (req.get_header_value("Content-Encoding") == "gzip") { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT - detail::decompress(req.body); -#else - res.status = 415; - write_response(strm, last_connection, req, res); - return ret; -#endif - } + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); - if (!content_type.find("application/x-www-form-urlencoded")) { - detail::parse_query_text(req.body, req.params); - } else if(!content_type.find("multipart/form-data")) { - std::string boundary; - if (!detail::parse_multipart_boundary(content_type, boundary) || - !detail::parse_multipart_formdata(boundary, req.body, req.files)) { - res.status = 400; - write_response(strm, last_connection, req, res); - return ret; - } - } + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); } + } +} - if (routing(req, res)) { - if (res.status == -1) { - res.status = 200; - } - } else { - res.status = 404; +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { + ctx_ = SSL_CTX_new(TLS_method()); + if (ctx_) { + if (!setup_ssl_ctx_callback(*ctx_)) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; } - - write_response(strm, last_connection, req, res); - return ret; + } } -inline bool Server::is_valid() const -{ - return true; +inline SSLServer::~SSLServer() { + if (ctx_) { SSL_CTX_free(ctx_); } } -inline bool Server::read_and_close_socket(socket_t sock) -{ - return detail::read_and_close_socket( - sock, - keep_alive_max_count_, - [this](Stream& strm, bool last_connection, bool& connection_close) { - return process_request(strm, last_connection, connection_close); - }); -} +inline bool SSLServer::is_valid() const { return ctx_; } -// HTTP client implementation -inline Client::Client( - const char* host, int port, size_t timeout_sec) - : host_(host) - , port_(port) - , timeout_sec_(timeout_sec) - , host_and_port_(host_ + ":" + std::to_string(port_)) -{ -} +inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; } -inline Client::~Client() -{ -} +inline void SSLServer::update_certs(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { -inline bool Client::is_valid() const -{ - return true; -} + std::lock_guard guard(ctx_mutex_); -inline socket_t Client::create_client_socket() const -{ - return detail::create_socket(host_.c_str(), port_, - [this](socket_t sock, struct addrinfo& ai) -> bool { - detail::set_nonblocking(sock, true); + SSL_CTX_use_certificate(ctx_, cert); + SSL_CTX_use_PrivateKey(ctx_, private_key); - auto ret = connect(sock, ai.ai_addr, ai.ai_addrlen); - if (ret < 0) { - if (detail::is_connection_error() || - !detail::wait_until_socket_is_ready(sock, timeout_sec_, 0)) { - detail::close_socket(sock); - return false; - } - } + if (client_ca_cert_store != nullptr) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + } +} - detail::set_nonblocking(sock, false); - return true; +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_, + &last_ssl_error_); + }, + [](SSL * /*ssl2*/) { return true; }); + + auto ret = false; + if (ssl) { + std::string remote_addr; + int remote_port = 0; + detail::get_remote_ip_and_port(sock, remote_addr, remote_port); + + std::string local_addr; + int local_port = 0; + detail::get_local_ip_and_port(sock, local_addr, local_port); + + ret = detail::process_server_socket_ssl( + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [&](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, remote_addr, remote_port, local_addr, + local_port, close_connection, + connection_closed, + [&](Request &req) { req.ssl = ssl; }); }); -} -inline bool Client::read_response_line(Stream& strm, Response& res) -{ - const auto bufsiz = 2048; - char buf[bufsiz]; + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, sock, shutdown_gracefully); + } - detail::stream_line_reader reader(strm, buf, bufsiz); + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} - if (!reader.getline()) { - return false; +// SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path, + const std::string &private_key_password) + : ClientImpl(host, port, client_cert_path, client_key_path) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(b, e); + }); + + if (!client_cert_path.empty() && !client_key_path.empty()) { + if (!private_key_password.empty()) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, reinterpret_cast( + const_cast(private_key_password.c_str()))); } - const static std::regex re("(HTTP/1\\.[01]) (\\d+?) .+\r\n"); - - std::cmatch m; - if (std::regex_match(reader.ptr(), m, re)) { - res.version = std::string(m[1]); - res.status = std::stoi(std::string(m[2])); + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), + SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { + last_openssl_error_ = ERR_get_error(); + SSL_CTX_free(ctx_); + ctx_ = nullptr; } - - return true; + } } -inline bool Client::send(Request& req, Response& res) -{ - if (req.path.empty()) { - return false; +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key, + const std::string &private_key_password) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(b, e); + }); + + if (client_cert != nullptr && client_key != nullptr) { + if (!private_key_password.empty()) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, reinterpret_cast( + const_cast(private_key_password.c_str()))); } - auto sock = create_client_socket(); - if (sock == INVALID_SOCKET) { - return false; + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + last_openssl_error_ = ERR_get_error(); + SSL_CTX_free(ctx_); + ctx_ = nullptr; } + } +} - return read_and_close_socket(sock, req, res); +inline SSLClient::~SSLClient() { + if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); } -inline void Client::write_request(Stream& strm, Request& req) -{ - auto path = detail::encode_url(req.path); +inline bool SSLClient::is_valid() const { return ctx_; } - // Request line - strm.write_format("%s %s HTTP/1.1\r\n", - req.method.c_str(), - path.c_str()); +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } +} - // Headers - req.set_header("Host", host_and_port_.c_str()); +inline void SSLClient::load_ca_cert_store(const char *ca_cert, + std::size_t size) { + set_ca_cert_store(ClientImpl::create_ca_cert_store(ca_cert, size)); +} - if (!req.has_header("Accept")) { - req.set_header("Accept", "*/*"); - } +inline long SSLClient::get_openssl_verify_result() const { + return verify_result_; +} - if (!req.has_header("User-Agent")) { - req.set_header("User-Agent", "cpp-httplib/0.2"); - } +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } - // TODO: Support KeepAlive connection - // if (!req.has_header("Connection")) { - req.set_header("Connection", "close"); - // } +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); +} - if (!req.body.empty()) { - if (!req.has_header("Content-Type")) { - req.set_header("Content-Type", "text/plain"); +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy( + Socket &socket, + std::chrono::time_point start_time, + Response &res, bool &success, Error &error) { + success = true; + Response proxy_res; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, + start_time, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + if (max_timeout_msec_ > 0) { + req2.start_time_ = std::chrono::steady_clock::now(); + } + return process_request(strm, req2, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + + if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(proxy_res, auth, true)) { + // Close the current socket and create a new one for the authenticated + // request + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + + // Create a new socket for the authenticated CONNECT request + if (!create_and_connect_socket(socket, error)) { + success = false; + output_error_log(error, nullptr); + return false; } - auto length = std::to_string(req.body.size()); - req.set_header("Content-Length", length.c_str()); + proxy_res = Response(); + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, + start_time, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + if (max_timeout_msec_ > 0) { + req3.start_time_ = std::chrono::steady_clock::now(); + } + return process_request(strm, req3, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } } + } + + // If status code is not 200, proxy request is failed. + // Set error to ProxyConnection and return proxy response + // as the response of the request + if (proxy_res.status != StatusCode::OK_200) { + error = Error::ProxyConnection; + output_error_log(error, nullptr); + res = std::move(proxy_res); + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + return false; + } - detail::write_headers(strm, req); + return true; +} - // Body - if (!req.body.empty()) { - if (req.get_header_value("Content-Type") == "application/x-www-form-urlencoded") { - auto str = detail::encode_url(req.body); - strm.write(str.c_str(), str.size()); - } else { - strm.write(req.body.c_str(), req.body.size()); - } +inline bool SSLClient::load_certs() { + auto ret = true; + + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + last_openssl_error_ = ERR_get_error(); + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + last_openssl_error_ = ERR_get_error(); + ret = false; + } + } else { + auto loaded = false; +#ifdef _WIN32 + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && \ + defined(TARGET_OS_OSX) + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } } + }); + + return ret; } -inline bool Client::process_request(Stream& strm, Request& req, Response& res, bool& connection_close) -{ - // Send request - write_request(strm, req); +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + output_error_log(error, nullptr); + return false; + } + SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); + } - // Receive response and headers - if (!read_response_line(strm, res) || !detail::read_headers(strm, res.headers)) { - return false; - } + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_, &last_ssl_error_)) { + error = Error::SSLConnection; + output_error_log(error, nullptr); + return false; + } - if (res.get_header_value("Connection") == "close" || res.version == "HTTP/1.0") { - connection_close = true; - } + if (server_certificate_verification_) { + auto verification_status = SSLVerifierResponse::NoDecisionMade; - // Body - if (req.method != "HEAD") { - if (!detail::read_content(strm, res, req.progress)) { + if (server_certificate_verifier_) { + verification_status = server_certificate_verifier_(ssl2); + } + + if (verification_status == SSLVerifierResponse::CertificateRejected) { + last_openssl_error_ = ERR_get_error(); + error = Error::SSLServerVerification; + output_error_log(error, nullptr); return false; + } + + if (verification_status == SSLVerifierResponse::NoDecisionMade) { + verify_result_ = SSL_get_verify_result(ssl2); + + if (verify_result_ != X509_V_OK) { + last_openssl_error_ = static_cast(verify_result_); + error = Error::SSLServerVerification; + output_error_log(error, nullptr); + return false; + } + + auto server_cert = SSL_get1_peer_certificate(ssl2); + auto se = detail::scope_exit([&] { X509_free(server_cert); }); + + if (server_cert == nullptr) { + last_openssl_error_ = ERR_get_error(); + error = Error::SSLServerVerification; + output_error_log(error, nullptr); + return false; + } + + if (server_hostname_verification_) { + if (!verify_host(server_cert)) { + last_openssl_error_ = X509_V_ERR_HOSTNAME_MISMATCH; + error = Error::SSLServerHostnameVerification; + output_error_log(error, nullptr); + return false; + } + } + } } - if (res.get_header_value("Content-Encoding") == "gzip") { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT - detail::decompress(res.body); + return true; + }, + [&](SSL *ssl2) { +#if defined(OPENSSL_IS_BORINGSSL) + SSL_set_tlsext_host_name(ssl2, host_.c_str()); #else - return false; + // NOTE: Direct call instead of using the OpenSSL macro to suppress + // -Wold-style-cast warning + SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, + static_cast(const_cast(host_.c_str()))); #endif - } - } + return true; + }); + if (ssl) { + socket.ssl = ssl; return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; } -inline bool Client::read_and_close_socket(socket_t sock, Request& req, Response& res) -{ - return detail::read_and_close_socket( - sock, - 0, - [&](Stream& strm, bool /*last_connection*/, bool& connection_close) { - return process_request(strm, req, res, connection_close); - }); +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); } -inline std::shared_ptr Client::Get(const char* path, Progress progress) -{ - return Get(path, Headers(), progress); +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, socket.sock, + shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); } -inline std::shared_ptr Client::Get(const char* path, const Headers& headers, Progress progress) -{ - Request req; - req.method = "GET"; - req.path = path; - req.headers = headers; - req.progress = progress; +inline bool SSLClient::process_socket( + const Socket &socket, + std::chrono::time_point start_time, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time, + std::move(callback)); +} - auto res = std::make_shared(); +inline bool SSLClient::is_ssl() const { return true; } - return send(req, *res) ? res : nullptr; -} +inline bool SSLClient::verify_host(X509 *server_cert) const { + /* Quote from RFC2818 section 3.1 "Server Identity" -inline std::shared_ptr Client::Head(const char* path) -{ - return Head(path, Headers()); -} + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. -inline std::shared_ptr Client::Head(const char* path, const Headers& headers) -{ - Request req; - req.method = "HEAD"; - req.headers = headers; - req.path = path; + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. - auto res = std::make_shared(); + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. - return send(req, *res) ? res : nullptr; + */ + return verify_host_with_subject_alt_name(server_cert) || + verify_host_with_common_name(server_cert); } -inline std::shared_ptr Client::Post( - const char* path, const std::string& body, const char* content_type) -{ - return Post(path, Headers(), body, content_type); -} +inline bool +SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { + auto ret = false; -inline std::shared_ptr Client::Post( - const char* path, const Headers& headers, const std::string& body, const char* content_type) -{ - Request req; - req.method = "POST"; - req.headers = headers; - req.path = path; + auto type = GEN_DNS; - req.headers.emplace("Content-Type", content_type); - req.body = body; + struct in6_addr addr6 = {}; + struct in_addr addr = {}; + size_t addr_len = 0; - auto res = std::make_shared(); +#ifndef __MINGW32__ + if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { + type = GEN_IPADD; + addr_len = sizeof(struct in6_addr); + } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { + type = GEN_IPADD; + addr_len = sizeof(struct in_addr); + } +#endif - return send(req, *res) ? res : nullptr; -} + auto alt_names = static_cast( + X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); -inline std::shared_ptr Client::Post(const char* path, const Params& params) -{ - return Post(path, Headers(), params); -} + if (alt_names) { + auto dsn_matched = false; + auto ip_matched = false; + + auto count = sk_GENERAL_NAME_num(alt_names); + + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { + auto val = sk_GENERAL_NAME_value(alt_names, i); + if (val->type == type) { + auto name = + reinterpret_cast(ASN1_STRING_get0_data(val->d.ia5)); + auto name_len = static_cast(ASN1_STRING_length(val->d.ia5)); + + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; -inline std::shared_ptr Client::Post(const char* path, const Headers& headers, const Params& params) -{ - std::string query; - for (auto it = params.begin(); it != params.end(); ++it) { - if (it != params.begin()) { - query += "&"; + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_matched = true; + } + break; } - query += it->first; - query += "="; - query += it->second; + } } - return Post(path, headers, query, "application/x-www-form-urlencoded"); -} + if (dsn_matched || ip_matched) { ret = true; } + } -inline std::shared_ptr Client::Put( - const char* path, const std::string& body, const char* content_type) -{ - return Put(path, Headers(), body, content_type); + GENERAL_NAMES_free(const_cast( + reinterpret_cast(alt_names))); + return ret; } -inline std::shared_ptr Client::Put( - const char* path, const Headers& headers, const std::string& body, const char* content_type) -{ - Request req; - req.method = "PUT"; - req.headers = headers; - req.path = path; +inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { + const auto subject_name = X509_get_subject_name(server_cert); - req.headers.emplace("Content-Type", content_type); - req.body = body; + if (subject_name != nullptr) { + char name[BUFSIZ]; + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); - auto res = std::make_shared(); + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } + } - return send(req, *res) ? res : nullptr; + return false; } -inline std::shared_ptr Client::Delete(const char* path) -{ - return Delete(path, Headers()); +inline bool SSLClient::check_host_name(const char *pattern, + size_t pattern_len) const { + if (host_.size() == pattern_len && host_ == pattern) { return true; } + + // Wildcard match + // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 + std::vector pattern_components; + detail::split(&pattern[0], &pattern[pattern_len], '.', + [&](const char *b, const char *e) { + pattern_components.emplace_back(b, e); + }); + + if (host_components_.size() != pattern_components.size()) { return false; } + + auto itr = pattern_components.begin(); + for (const auto &h : host_components_) { + auto &p = *itr; + if (p != h && p != "*") { + auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && + !p.compare(0, p.size() - 1, h)); + if (!partial_match) { return false; } + } + ++itr; + } + + return true; } +#endif + +// Universal client implementation +inline Client::Client(const std::string &scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} + +inline Client::Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path) { + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); + + std::smatch m; + if (std::regex_match(scheme_host_port, m, re)) { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); +#endif + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + if (host.empty()) { host = m[3].str(); } + + auto port_str = m[4].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + is_ssl_ = is_ssl; +#endif + } else { + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + } + } else { + // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress) + // if port param below changes. + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } +} // namespace detail + +inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} -inline std::shared_ptr Client::Delete(const char* path, const Headers& headers) -{ - Request req; - req.method = "DELETE"; - req.path = path; - req.headers = headers; +inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} - auto res = std::make_shared(); +inline Client::~Client() = default; - return send(req, *res) ? res : nullptr; +inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); } -inline std::shared_ptr Client::Options(const char* path) -{ - return Options(path, Headers()); +inline Result Client::Get(const std::string &path, DownloadProgress progress) { + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + DownloadProgress progress) { + return cli_->Get(path, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, DownloadProgress progress) { + return cli_->Get(path, params, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Get(path, params, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Get(path, params, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); } -inline std::shared_ptr Client::Options(const char* path, const Headers& headers) -{ - Request req; - req.method = "OPTIONS"; - req.path = path; - req.headers = headers; +inline Result Client::Head(const std::string &path) { return cli_->Head(path); } +inline Result Client::Head(const std::string &path, const Headers &headers) { + return cli_->Head(path, headers); +} - auto res = std::make_shared(); +inline Result Client::Post(const std::string &path) { return cli_->Post(path); } +inline Result Client::Post(const std::string &path, const Headers &headers) { + return cli_->Post(path, headers); +} +inline Result Client::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, body, content_length, content_type, progress); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, headers, body, content_length, content_type, + progress); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, body, content_type, progress); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, headers, body, content_type, progress); +} +inline Result Client::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, content_length, std::move(content_provider), + content_type, progress); +} +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, std::move(content_provider), content_type, progress); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type, progress); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, headers, std::move(content_provider), content_type, + progress); +} +inline Result Client::Post(const std::string &path, const Params ¶ms) { + return cli_->Post(path, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Post(path, headers, params); +} +inline Result Client::Post(const std::string &path, + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Post(path, items, progress); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Post(path, headers, items, progress); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + return cli_->Post(path, headers, items, boundary, progress); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + return cli_->Post(path, headers, items, provider_items, progress); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Post(path, headers, body, content_type, content_receiver, + progress); +} - return send(req, *res) ? res : nullptr; +inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const Headers &headers) { + return cli_->Put(path, headers); +} +inline Result Client::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, body, content_length, content_type, progress); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, headers, body, content_length, content_type, progress); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, body, content_type, progress); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, headers, body, content_type, progress); +} +inline Result Client::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, content_length, std::move(content_provider), + content_type, progress); +} +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, std::move(content_provider), content_type, progress); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type, progress); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, headers, std::move(content_provider), content_type, + progress); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) { + return cli_->Put(path, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Put(path, headers, params); +} +inline Result Client::Put(const std::string &path, + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Put(path, items, progress); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Put(path, headers, items, progress); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + return cli_->Put(path, headers, items, boundary, progress); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + return cli_->Put(path, headers, items, provider_items, progress); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Put(path, headers, body, content_type, content_receiver, + progress); } -/* - * SSL Implementation - */ -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -namespace detail { +inline Result Client::Patch(const std::string &path) { + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const Headers &headers) { + return cli_->Patch(path, headers); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return cli_->Patch(path, body, content_length, content_type, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return cli_->Patch(path, headers, body, content_length, content_type, + progress); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return cli_->Patch(path, body, content_type, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return cli_->Patch(path, headers, body, content_type, progress); +} +inline Result Client::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type, progress); +} +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return cli_->Patch(path, std::move(content_provider), content_type, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return cli_->Patch(path, headers, std::move(content_provider), content_type, + progress); +} +inline Result Client::Patch(const std::string &path, const Params ¶ms) { + return cli_->Patch(path, params); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Patch(path, headers, params); +} +inline Result Client::Patch(const std::string &path, + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Patch(path, items, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Patch(path, headers, items, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + return cli_->Patch(path, headers, items, boundary, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + return cli_->Patch(path, headers, items, provider_items, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Patch(path, headers, body, content_type, content_receiver, + progress); +} -template -inline bool read_and_close_socket_ssl( - socket_t sock, size_t keep_alive_max_count, - // TODO: OpenSSL 1.0.2 occasionally crashes... - // The upcoming 1.1.0 is going to be thread safe. - SSL_CTX* ctx, std::mutex& ctx_mutex, - U SSL_connect_or_accept, V setup, - T callback) -{ - SSL* ssl = nullptr; - { - std::lock_guard guard(ctx_mutex); +inline Result Client::Delete(const std::string &path, + DownloadProgress progress) { + return cli_->Delete(path, progress); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + DownloadProgress progress) { + return cli_->Delete(path, headers, progress); +} +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + DownloadProgress progress) { + return cli_->Delete(path, body, content_length, content_type, progress); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + DownloadProgress progress) { + return cli_->Delete(path, headers, body, content_length, content_type, + progress); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type, + DownloadProgress progress) { + return cli_->Delete(path, body, content_type, progress); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + DownloadProgress progress) { + return cli_->Delete(path, headers, body, content_type, progress); +} +inline Result Client::Delete(const std::string &path, const Params ¶ms, + DownloadProgress progress) { + return cli_->Delete(path, params, progress); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const Params ¶ms, DownloadProgress progress) { + return cli_->Delete(path, headers, params, progress); +} - ssl = SSL_new(ctx); - if (!ssl) { - return false; - } - } +inline Result Client::Options(const std::string &path) { + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) { + return cli_->Options(path, headers); +} - auto bio = BIO_new_socket(sock, BIO_NOCLOSE); - SSL_set_bio(ssl, bio, bio); +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); +} - setup(ssl); +inline Result Client::send(const Request &req) { return cli_->send(req); } - SSL_connect_or_accept(ssl); +inline void Client::stop() { cli_->stop(); } - bool ret = false; +inline std::string Client::host() const { return cli_->host(); } - if (keep_alive_max_count > 0) { - auto count = keep_alive_max_count; - while (count > 0 && - detail::select_read(sock, - CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, - CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0) { - SSLSocketStream strm(sock, ssl); - auto last_connection = count == 1; - auto connection_close = false; +inline int Client::port() const { return cli_->port(); } - ret = callback(strm, last_connection, connection_close); - if (!ret || connection_close) { - break; - } +inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } - count--; - } - } else { - SSLSocketStream strm(sock, ssl); - auto dummy_connection_close = false; - ret = callback(strm, true, dummy_connection_close); - } +inline socket_t Client::socket() const { return cli_->socket(); } - SSL_shutdown(ssl); +inline void +Client::set_hostname_addr_map(std::map addr_map) { + cli_->set_hostname_addr_map(std::move(addr_map)); +} - { - std::lock_guard guard(ctx_mutex); - SSL_free(ssl); - } +inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); +} - close_socket(sock); +inline void Client::set_header_writer( + std::function const &writer) { + cli_->set_header_writer(writer); +} - return ret; +inline void Client::set_address_family(int family) { + cli_->set_address_family(family); } -class SSLInit { -public: - SSLInit() { - SSL_load_error_strings(); - SSL_library_init(); - } -}; +inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } -static SSLInit sslinit_; +inline void Client::set_socket_options(SocketOptions socket_options) { + cli_->set_socket_options(std::move(socket_options)); +} -} // namespace detail +inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); +} -// SSL socket stream implementation -inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL* ssl) - : sock_(sock), ssl_(ssl) -{ +inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); } -inline SSLSocketStream::~SSLSocketStream() -{ +inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); } -inline int SSLSocketStream::read(char* ptr, size_t size) -{ - return SSL_read(ssl_, ptr, size); +inline void Client::set_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_basic_auth(username, password); +} +inline void Client::set_bearer_token_auth(const std::string &token) { + cli_->set_bearer_token_auth(token); } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_digest_auth(username, password); +} +#endif -inline int SSLSocketStream::write(const char* ptr, size_t size) -{ - return SSL_write(ssl_, ptr, size); +inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } +inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); } -inline int SSLSocketStream::write(const char* ptr) -{ - return write(ptr, strlen(ptr)); +inline void Client::set_path_encode(bool on) { cli_->set_path_encode(on); } + +[[deprecated("Use set_path_encode instead")]] +inline void Client::set_url_encode(bool on) { + cli_->set_path_encode(on); } -inline std::string SSLSocketStream::get_remote_addr() { - return detail::get_remote_addr(sock_); +inline void Client::set_compress(bool on) { cli_->set_compress(on); } + +inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + +inline void Client::set_interface(const std::string &intf) { + cli_->set_interface(intf); } -// SSL HTTP server implementation -inline SSLServer::SSLServer(const char* cert_path, const char* private_key_path) -{ - ctx_ = SSL_CTX_new(SSLv23_server_method()); +inline void Client::set_proxy(const std::string &host, int port) { + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_basic_auth(username, password); +} +inline void Client::set_proxy_bearer_token_auth(const std::string &token) { + cli_->set_proxy_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif - if (ctx_) { - SSL_CTX_set_options(ctx_, - SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | - SSL_OP_NO_COMPRESSION | - SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} - // auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); - // SSL_CTX_set_tmp_ecdh(ctx_, ecdh); - // EC_KEY_free(ecdh); +inline void Client::enable_server_hostname_verification(bool enabled) { + cli_->enable_server_hostname_verification(enabled); +} - if (SSL_CTX_use_certificate_file(ctx_, cert_path, SSL_FILETYPE_PEM) != 1 || - SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx_); - ctx_ = nullptr; - } - } +inline void Client::set_server_certificate_verifier( + std::function verifier) { + cli_->set_server_certificate_verifier(verifier); } +#endif -inline SSLServer::~SSLServer() -{ - if (ctx_) { - SSL_CTX_free(ctx_); - } +inline void Client::set_logger(Logger logger) { + cli_->set_logger(std::move(logger)); } -inline bool SSLServer::is_valid() const -{ - return ctx_; +inline void Client::set_error_logger(ErrorLogger error_logger) { + cli_->set_error_logger(std::move(error_logger)); } -inline bool SSLServer::read_and_close_socket(socket_t sock) -{ - return detail::read_and_close_socket_ssl( - sock, - keep_alive_max_count_, - ctx_, ctx_mutex_, - SSL_accept, - [](SSL* /*ssl*/) {}, - [this](Stream& strm, bool last_connection, bool& connection_close) { - return process_request(strm, last_connection, connection_close); - }); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); } -// SSL HTTP client implementation -inline SSLClient::SSLClient(const char* host, int port, size_t timeout_sec) - : Client(host, port, timeout_sec) -{ - ctx_ = SSL_CTX_new(SSLv23_client_method()); +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } else { + cli_->set_ca_cert_store(ca_cert_store); + } } -inline SSLClient::~SSLClient() -{ - if (ctx_) { - SSL_CTX_free(ctx_); - } +inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) { + set_ca_cert_store(cli_->create_ca_cert_store(ca_cert, size)); } -inline bool SSLClient::is_valid() const -{ - return ctx_; +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? } -inline bool SSLClient::read_and_close_socket(socket_t sock, Request& req, Response& res) -{ - return is_valid() && detail::read_and_close_socket_ssl( - sock, 0, - ctx_, ctx_mutex_, - SSL_connect, - [&](SSL* ssl) { - SSL_set_tlsext_host_name(ssl, host_.c_str()); - }, - [&](Stream& strm, bool /*last_connection*/, bool& connection_close) { - return process_request(strm, req, res, connection_close); - }); +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; } #endif -} // namespace httplib +// ---------------------------------------------------------------------------- -#endif +} // namespace httplib -// vim: et ts=4 sw=4 cin cino={1s ff=unix +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/third_party/edo/BUILD.gn b/third_party/edo/BUILD.gn index 1bd98e17ae..9744cbc989 100644 --- a/third_party/edo/BUILD.gn +++ b/third_party/edo/BUILD.gn @@ -1,6 +1,16 @@ -# Copyright 2018 The Chromium Authors -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. +# Copyright 2025 The Crashpad Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import("../../build/crashpad_buildconfig.gni") @@ -12,6 +22,10 @@ if (crashpad_is_in_chromium) { } else { config("config") { include_dirs = [ "../../third_party/edo/edo" ] + cflags = [ "-Wno-newline-eof" ] + if (xcode_version_int >= 1630) { + cflags += [ "-Wno-cast-function-type-mismatch" ] + } } source_set("edo") { diff --git a/third_party/gyp/README.crashpad b/third_party/gyp/README.crashpad deleted file mode 100644 index ea5a342bf2..0000000000 --- a/third_party/gyp/README.crashpad +++ /dev/null @@ -1,14 +0,0 @@ -Name: GYP (Generate Your Projects) -Short Name: gyp -URL: https://gyp.gsrc.io/ -Revision: See DEPS -License: BSD 3-clause -License File: gyp/LICENSE -Security Critical: no -Shipped: no - -Description: -GYP is used to generate build files. - -Local Modifications: -None diff --git a/third_party/linux/README.crashpad b/third_party/linux/README.md similarity index 100% rename from third_party/linux/README.crashpad rename to third_party/linux/README.md diff --git a/third_party/mini_chromium/BUILD.gn b/third_party/mini_chromium/BUILD.gn index 9d4ff01c05..61a60ffac5 100644 --- a/third_party/mini_chromium/BUILD.gn +++ b/third_party/mini_chromium/BUILD.gn @@ -49,17 +49,3 @@ group("build") { public_deps = [ "//third_party/mini_chromium/mini_chromium/build" ] } } - -group("chromeos_buildflags") { - if (crashpad_is_in_chromium) { - public_deps = [ "//build:chromeos_buildflags" ] - } else if (crashpad_is_standalone) { - public_deps = [ "mini_chromium/build:chromeos_buildflags" ] - } else if (crashpad_is_in_fuchsia) { - public_deps = [ mini_chromium_import_root + "/build:chromeos_buildflags" ] - } else if (crashpad_is_external) { - public_deps = [ "../../../../mini_chromium/mini_chromium/build:chromeos_buildflags" ] - } else if (crashpad_is_in_dart) { - public_deps = [ "//third_party/mini_chromium/mini_chromium/build:chromeos_buildflags" ] - } -} diff --git a/third_party/mini_chromium/CMakeLists.txt b/third_party/mini_chromium/CMakeLists.txt index e70342d8f1..177f130076 100644 --- a/third_party/mini_chromium/CMakeLists.txt +++ b/third_party/mini_chromium/CMakeLists.txt @@ -64,7 +64,6 @@ mc_append_sources( strings/strcat_internal.h strings/string_number_conversions.cc strings/string_number_conversions.h - strings/string_piece.h strings/string_util.h strings/stringprintf.cc strings/stringprintf.h diff --git a/third_party/mini_chromium/mini_chromium b/third_party/mini_chromium/mini_chromium index fd75723d16..5d060033db 160000 --- a/third_party/mini_chromium/mini_chromium +++ b/third_party/mini_chromium/mini_chromium @@ -1 +1 @@ -Subproject commit fd75723d16e99fb1e6d1487c4f278a5ce618fdf0 +Subproject commit 5d060033dbe1595f612a2f506b7988c8d513d32e diff --git a/third_party/ninja/ninja b/third_party/ninja/ninja index 402ab2a190..ca0874608c 100755 --- a/third_party/ninja/ninja +++ b/third_party/ninja/ninja @@ -1,8 +1,18 @@ #!/bin/sh -# Copyright 2022 Google Inc. All rights reserved -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. +# Copyright 2022 The Crashpad Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. set -eu diff --git a/third_party/xnu/README.crashpad b/third_party/xnu/README.crashpad index dbcf740874..5859195009 100644 --- a/third_party/xnu/README.crashpad +++ b/third_party/xnu/README.crashpad @@ -1,8 +1,8 @@ Name: XNU Short Name: xnu -URL: https://opensource.apple.com/source/xnu/ -URL: https://opensource.apple.com/tarballs/xnu/ +URL: https://github.com/apple-oss-distributions/xnu/ Version: 6153.11.26 (from macOS 10.15.0) +Revision: a5e7219620fa69943647714baaf632196f37249e License: APSL 2.0 License File: APPLE_LICENSE Security Critical: no diff --git a/third_party/zlib/BUILD.gn b/third_party/zlib/BUILD.gn index 92069a5757..9c243979c1 100644 --- a/third_party/zlib/BUILD.gn +++ b/third_party/zlib/BUILD.gn @@ -103,12 +103,6 @@ if (zlib_source == "external") { "/wd4324", # structure was padded due to alignment specifier "/wd4702", # unreachable code ] - if (current_cpu == "arm64" && !crashpad_is_clang) { - # Select code path for clang in zlib to avoid using MSVC x86/x64 - # intrinsics for Windows ARM64. - # TODO: https://crashpad.chromium.org/bug/267 - defines += [ "__clang__" ] - } } else { defines += [ "HAVE_HIDDEN", diff --git a/tools/base94_encoder.cc b/tools/base94_encoder.cc index c99210f2bf..31cabe79dd 100644 --- a/tools/base94_encoder.cc +++ b/tools/base94_encoder.cc @@ -14,6 +14,7 @@ #include #include +#include #include "base/files/file_path.h" #include "build/build_config.h" diff --git a/tools/run_with_crashpad.md b/tools/run_with_crashpad.md index 69302eef8b..84d748c55e 100644 --- a/tools/run_with_crashpad.md +++ b/tools/run_with_crashpad.md @@ -33,9 +33,9 @@ along with any arguments specified (_ARG…_) with the new exception port in effect. On macOS, the exception port is configured to receive exceptions of type -`EXC_CRASH`, `EXC_RESOURCE`, and `EXC_GUARD`. The exception behavior is -configured as `EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES`. The thread -state flavor is set to `MACHINE_THREAD_STATE`. +`EXC_CRASH`, `EXC_RESOURCE`, and `EXC_GUARD` (until macOS 13). The exception +behavior is configured as `EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES`. The +thread state flavor is set to `MACHINE_THREAD_STATE`. Programs that use the Crashpad client library directly will not normally use this tool. This tool exists to allow programs that are unaware of Crashpad to be diff --git a/tools/tool_support.cc b/tools/tool_support.cc index cd83366adb..7092682573 100644 --- a/tools/tool_support.cc +++ b/tools/tool_support.cc @@ -16,10 +16,10 @@ #include +#include #include #include "base/containers/heap_array.h" -#include "base/strings/string_piece.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "package.h" @@ -92,7 +92,7 @@ int ToolSupport::Wmain(int argc, wchar_t* argv[], int (*entry)(int, char* [])) { // static base::FilePath::StringType ToolSupport::CommandLineArgumentToFilePathStringType( - const base::StringPiece& path) { + std::string_view path) { #if BUILDFLAG(IS_POSIX) return std::string(path.data(), path.size()); #elif BUILDFLAG(IS_WIN) diff --git a/tools/tool_support.h b/tools/tool_support.h index fbe28e77f7..d117deb501 100644 --- a/tools/tool_support.h +++ b/tools/tool_support.h @@ -16,9 +16,9 @@ #define CRASHPAD_TOOLS_TOOL_SUPPORT_H_ #include +#include #include "base/files/file_path.h" -#include "base/strings/string_piece.h" #include "build/build_config.h" namespace crashpad { @@ -77,7 +77,7 @@ class ToolSupport { //! \sa Wmain() //! \sa FilePathToCommandLineArgument() static base::FilePath::StringType CommandLineArgumentToFilePathStringType( - const base::StringPiece& arg); + std::string_view arg); //! \brief Converts a base::FilePath to a command line argument. //! diff --git a/util/BUILD.gn b/util/BUILD.gn index d4f4b2d015..a34176d1e1 100644 --- a/util/BUILD.gn +++ b/util/BUILD.gn @@ -19,7 +19,7 @@ if (crashpad_is_in_chromium) { import("//build/config/sanitizers/sanitizers.gni") } -if (crashpad_is_apple) { +if (crashpad_is_apple && !crashpad_is_tvos) { if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { import("//build/config/sysroot.gni") } else { @@ -299,8 +299,6 @@ crashpad_static_library("util") { "posix/drop_privileges.cc", "posix/drop_privileges.h", "posix/process_info.h", - "posix/spawn_subprocess.cc", - "posix/spawn_subprocess.h", # These map signals to and from strings. While Fuchsia defines some of # the common SIGx defines, signals are never raised on Fuchsia, so @@ -308,6 +306,13 @@ crashpad_static_library("util") { "posix/symbolic_constants_posix.cc", "posix/symbolic_constants_posix.h", ] + + if (crashpad_is_android || crashpad_is_linux || crashpad_is_mac) { + sources += [ + "posix/spawn_subprocess.cc", + "posix/spawn_subprocess.h", + ] + } } } @@ -317,29 +322,37 @@ crashpad_static_library("util") { "mac/sysctl.h", "mac/xattr.cc", "mac/xattr.h", - "mach/composite_mach_message_server.cc", - "mach/composite_mach_message_server.h", - "mach/exc_client_variants.cc", - "mach/exc_client_variants.h", - "mach/exc_server_variants.cc", - "mach/exc_server_variants.h", - "mach/exception_behaviors.cc", - "mach/exception_behaviors.h", - "mach/exception_ports.cc", - "mach/exception_ports.h", "mach/mach_extensions.cc", "mach/mach_extensions.h", - "mach/mach_message.cc", - "mach/mach_message.h", - "mach/mach_message_server.cc", - "mach/mach_message_server.h", - "mach/symbolic_constants_mach.cc", - "mach/symbolic_constants_mach.h", "misc/capture_context_mac.S", "misc/clock_mac.cc", "misc/paths_mac.cc", "synchronization/semaphore_mac.cc", ] + + # Exclude files related to Mach exceptions when building for tvOS. Mach + # messaging APIs are not available to third-party applications on tvOS so + # crashes are handled via POSIX signals. + if (!crashpad_is_tvos) { + sources += [ + "mach/composite_mach_message_server.cc", + "mach/composite_mach_message_server.h", + "mach/exc_client_variants.cc", + "mach/exc_client_variants.h", + "mach/exc_server_variants.cc", + "mach/exc_server_variants.h", + "mach/exception_behaviors.cc", + "mach/exception_behaviors.h", + "mach/exception_ports.cc", + "mach/exception_ports.h", + "mach/mach_message.cc", + "mach/mach_message.h", + "mach/mach_message_server.cc", + "mach/mach_message_server.h", + "mach/symbolic_constants_mach.cc", + "mach/symbolic_constants_mach.h", + ] + } } if (crashpad_is_mac && !crashpad_is_in_fuchsia) { @@ -528,13 +541,12 @@ crashpad_static_library("util") { "win/safe_terminate_process.asm", ] } else { - # Most Crashpad builds use Microsoft's armasm64.exe macro assembler for - # .asm source files. When building in Chromium, clang-cl is used as the - # assembler instead. Since the two assemblers recognize different - # assembly dialects, the same .asm file can't be used for each. As a - # workaround, use a prebuilt .obj file when the Microsoft-dialect - # assembler isn't available. - if (crashpad_is_in_chromium) { + # When building with clang, clang-cl is used as the assembler. Since + # clang-cl recognizes a different assembly dialect than Microsoft’s + # armasm64 macro assembler, the same .asm file can’t be used for each. As + # a workaround, use a prebuilt .obj file when the Microsoft-dialect + # assembler isn’t available. + if (crashpad_is_clang) { sources += [ "misc/capture_context_win_arm64.obj" ] } else { sources += [ "misc/capture_context_win_arm64.asm" ] @@ -573,19 +585,17 @@ crashpad_static_library("util") { "../third_party/zlib", ] - deps += [ - "$mini_chromium_source_parent:base", - "$mini_chromium_source_parent:chromeos_buildflags", - ] + deps = [ "$mini_chromium_source_parent:base" ] configs = [ "../build:flock_always_supported_defines" ] if (crashpad_is_apple) { include_dirs += [ "$root_gen_dir" ] - deps += [ - ":mig_output", - "../build:apple_enable_arc", - ] + deps += [ "../build:apple_enable_arc" ] + + if (!crashpad_is_tvos) { + deps += [ ":mig_output" ] + } } if (crashpad_is_mac && !crashpad_is_in_fuchsia) { @@ -730,7 +740,10 @@ if (!crashpad_is_android && !crashpad_is_ios) { source_set("no_cfi_icall") { sources = [ "misc/no_cfi_icall.h" ] public_configs = [ "..:crashpad_config" ] - public_deps = [ "$mini_chromium_source_parent:build" ] + public_deps = [ + "$mini_chromium_source_parent:base", + "$mini_chromium_source_parent:build", + ] } source_set("util_test") { @@ -809,14 +822,19 @@ source_set("util_test") { if (crashpad_is_apple) { sources += [ "mac/xattr_test.cc", - "mach/composite_mach_message_server_test.cc", - "mach/exc_server_variants_test.cc", - "mach/exception_behaviors_test.cc", - "mach/mach_extensions_test.cc", - "mach/mach_message_test.cc", - "mach/symbolic_constants_mach_test.cc", "misc/capture_context_test_util_mac.cc", ] + + if (!crashpad_is_tvos) { + sources += [ + "mach/composite_mach_message_server_test.cc", + "mach/exc_server_variants_test.cc", + "mach/exception_behaviors_test.cc", + "mach/mach_extensions_test.cc", + "mach/mach_message_test.cc", + "mach/symbolic_constants_mach_test.cc", + ] + } } if (crashpad_is_mac) { @@ -911,8 +929,8 @@ source_set("util_test") { "../client", "../compat", "../test", + "../third_party/googletest", "../third_party/googletest:googlemock", - "../third_party/googletest:googletest", "../third_party/zlib", ] diff --git a/util/file/file_io.cc b/util/file/file_io.cc index 3e8a6169c6..8f1d4ca576 100644 --- a/util/file/file_io.cc +++ b/util/file/file_io.cc @@ -14,6 +14,8 @@ #include "util/file/file_io.h" +#include + #include "base/check_op.h" #include "base/logging.h" #include "base/numerics/safe_conversions.h" @@ -22,29 +24,17 @@ namespace crashpad { namespace { -class FileIOReadExactly final : public internal::ReadExactlyInternal { - public: - explicit FileIOReadExactly(FileHandle file) - : ReadExactlyInternal(), file_(file) {} - - FileIOReadExactly(const FileIOReadExactly&) = delete; - FileIOReadExactly& operator=(const FileIOReadExactly&) = delete; - - ~FileIOReadExactly() {} - - private: - // ReadExactlyInternal: - FileOperationResult Read(void* buffer, size_t size, bool can_log) override { - FileOperationResult rv = ReadFile(file_, buffer, size); - if (rv < 0) { - PLOG_IF(ERROR, can_log) << internal::kNativeReadFunctionName; - return -1; - } - return rv; +FileOperationResult FileIORead(FileHandle file, + bool can_log, + void* buffer, + size_t size) { + FileOperationResult rv = ReadFile(file, buffer, size); + if (rv < 0) { + PLOG_IF(ERROR, can_log) << internal::kNativeReadFunctionName; + return -1; } - - FileHandle file_; -}; + return rv; +} class FileIOWriteAll final : public internal::WriteAllInternal { public: @@ -64,19 +54,21 @@ class FileIOWriteAll final : public internal::WriteAllInternal { FileHandle file_; }; -} // namespace - -namespace internal { - -bool ReadExactlyInternal::ReadExactly(void* buffer, size_t size, bool can_log) { +FileOperationResult ReadUntil( + std::function read_function, + void* buffer, + size_t size) { + // Ensure bytes read fit within int32_t::max to make sure that they also fit + // into FileOperationResult on all platforms. + DCHECK_LE(size, size_t{std::numeric_limits::max()}); uintptr_t buffer_int = reinterpret_cast(buffer); size_t total_bytes = 0; size_t remaining = size; while (remaining > 0) { - FileOperationResult bytes_read = - Read(reinterpret_cast(buffer_int), remaining, can_log); + const FileOperationResult bytes_read = + read_function(reinterpret_cast(buffer_int), remaining); if (bytes_read < 0) { - return false; + return bytes_read; } DCHECK_LE(static_cast(bytes_read), remaining); @@ -89,10 +81,31 @@ bool ReadExactlyInternal::ReadExactly(void* buffer, size_t size, bool can_log) { remaining -= bytes_read; total_bytes += bytes_read; } + return total_bytes; +} + +} // namespace + +namespace internal { + +bool ReadExactly( + std::function read_function, + bool can_log, + void* buffer, + size_t size) { + const FileOperationResult result = ReadUntil( + [read_function, can_log](void* buf, size_t sz) { + return read_function(can_log, buf, sz); + }, + buffer, + size); + if (result < 0) { + return false; + } - if (total_bytes != size) { + if (static_cast(result) != size) { LOG_IF(ERROR, can_log) << "ReadExactly: expected " << size << ", observed " - << total_bytes; + << result; return false; } @@ -121,13 +134,47 @@ bool WriteAllInternal::WriteAll(const void* buffer, size_t size) { } // namespace internal bool ReadFileExactly(FileHandle file, void* buffer, size_t size) { - FileIOReadExactly read_exactly(file); - return read_exactly.ReadExactly(buffer, size, false); + FileOperationResult (*pFileIORead)(FileHandle, bool, void*, size_t) = &FileIORead; + return internal::ReadExactly( + [pFileIORead, file](bool cl, void* buf, size_t sz) { + return pFileIORead(file, cl, buf, sz); + }, + false, + buffer, + size); +} + +FileOperationResult ReadFileUntil(FileHandle file, void* buffer, size_t size) { + FileOperationResult (*pFileIORead)(FileHandle, bool, void*, size_t) = &FileIORead; + return ReadUntil( + [pFileIORead, file](void* buf, size_t sz) { + return pFileIORead(file, false, buf, sz); + }, + buffer, + size); } bool LoggingReadFileExactly(FileHandle file, void* buffer, size_t size) { - FileIOReadExactly read_exactly(file); - return read_exactly.ReadExactly(buffer, size, true); + FileOperationResult (*pFileIORead)(FileHandle, bool, void*, size_t) = &FileIORead; + return internal::ReadExactly( + [pFileIORead, file](bool cl, void* buf, size_t sz) { + return pFileIORead(file, cl, buf, sz); + }, + true, + buffer, + size); +} + +FileOperationResult LoggingReadFileUntil(FileHandle file, + void* buffer, + size_t size) { + FileOperationResult (*pFileIORead)(FileHandle, bool, void*, size_t) = &FileIORead; + return ReadUntil( + [pFileIORead, file](void* buf, size_t sz) { + return pFileIORead(file, true, buf, sz); + }, + buffer, + size); } bool WriteFile(FileHandle file, const void* buffer, size_t size) { diff --git a/util/file/file_io.h b/util/file/file_io.h index 607cd1eb52..c0dc9e16ef 100644 --- a/util/file/file_io.h +++ b/util/file/file_io.h @@ -17,6 +17,7 @@ #include +#include #include #include "build/build_config.h" @@ -162,30 +163,11 @@ constexpr char kNativeWriteFunctionName[] = "WriteFile"; //! intended to be used more generally. Use ReadFileExactly(), //! LoggingReadFileExactly(), CheckedReadFileExactly(), or //! FileReaderInterface::ReadExactly() instead. -class ReadExactlyInternal { - public: - ReadExactlyInternal(const ReadExactlyInternal&) = delete; - ReadExactlyInternal& operator=(const ReadExactlyInternal&) = delete; - - //! \brief Calls Read(), retrying following a short read, ensuring that - //! exactly \a size bytes are read. - //! - //! \return `true` on success. `false` if the underlying Read() fails or if - //! fewer than \a size bytes were read. When returning `false`, if \a - //! can_log is `true`, logs a message. - bool ReadExactly(void* buffer, size_t size, bool can_log); - - protected: - ReadExactlyInternal() {} - ~ReadExactlyInternal() {} - - private: - //! \brief Wraps a read operation, such as ReadFile(). - //! - //! \return The number of bytes read and placed into \a buffer, or `-1` on - //! error. When returning `-1`, if \a can_log is `true`, logs a message. - virtual FileOperationResult Read(void* buffer, size_t size, bool can_log) = 0; -}; +bool ReadExactly( + std::function read_function, + bool can_log, + void* buffer, + size_t size); //! \brief The internal implementation of WriteFile() and its wrappers. //! @@ -252,7 +234,9 @@ FileOperationResult NativeWriteFile(FileHandle file, //! //! \sa WriteFile //! \sa ReadFileExactly +//! \sa ReadFileUntil //! \sa LoggingReadFileExactly +//! \sa LoggingReadFileUntil //! \sa CheckedReadFileExactly //! \sa CheckedReadFileAtEOF FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size); @@ -273,33 +257,62 @@ FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size); bool WriteFile(FileHandle file, const void* buffer, size_t size); //! \brief Wraps ReadFile(), retrying following a short read, ensuring that -//! exactly \a size bytes are read. +//! exactly \a size bytes are read. Does not log on failure. //! -//! \return `true` on success. If the underlying ReadFile() fails, or if fewer -//! than \a size bytes were read, this function logs a message and -//! returns `false`. +//! \return `true` on success. Returns `false` if the underlying ReadFile() +//! fails or if fewer than \a size bytes were read. //! //! \sa LoggingWriteFile //! \sa ReadFile +//! \sa ReadFileUntil //! \sa LoggingReadFileExactly +//! \sa LoggingReadFileUntil //! \sa CheckedReadFileExactly //! \sa CheckedReadFileAtEOF bool ReadFileExactly(FileHandle file, void* buffer, size_t size); +//! \brief Wraps ReadFile(), retrying following a short read. Does not log on +//! failure. +//! +//! \returns The number of bytes read or `-1` if the underlying ReadFile() +//! fails. +//! +//! \sa ReadFile +//! \sa ReadFileExactly +//! \sa LoggingReadFileExactly +//! \sa LoggingReadFileUntil +FileOperationResult ReadFileUntil(FileHandle file, void* buffer, size_t size); + //! \brief Wraps ReadFile(), retrying following a short read, ensuring that -//! exactly \a size bytes are read. +//! exactly \a size bytes are read. Logs an error on failure. //! -//! \return `true` on success. If the underlying ReadFile() fails, or if fewer -//! than \a size bytes were read, this function logs a message and -//! returns `false`. +//! \return `true` on success. Returns `false` if the underlying ReadFile() +//! fails or if fewer than \a size bytes were read. //! //! \sa LoggingWriteFile //! \sa ReadFile //! \sa ReadFileExactly +//! \sa ReadFileUntil +//! \sa LoggingReadFileUntil //! \sa CheckedReadFileExactly //! \sa CheckedReadFileAtEOF bool LoggingReadFileExactly(FileHandle file, void* buffer, size_t size); +//! \brief Wraps ReadFile(), retrying following a short read. Logs an error on +//! failure. +//! +//! \returns The number of bytes read or `-1` if the underlying ReadFile() +//! fails. +//! +//! \sa ReadFileExactly +//! \sa ReadFileUntil +//! \sa LoggingReadFileExactly +//! \sa CheckedReadFileExactly +//! \sa CheckedReadFileAtEOF +FileOperationResult LoggingReadFileUntil(FileHandle file, + void* buffer, + size_t size); + //! \brief Wraps WriteFile(), ensuring that exactly \a size bytes are written. //! //! \return `true` on success. If the underlying WriteFile() fails, or if fewer diff --git a/util/file/file_io_posix.cc b/util/file/file_io_posix.cc index 37e923ac50..fe7ed06036 100644 --- a/util/file/file_io_posix.cc +++ b/util/file/file_io_posix.cc @@ -276,8 +276,7 @@ FileHandle StdioFileHandle(StdioStream stdio_stream) { return STDERR_FILENO; } - NOTREACHED_IN_MIGRATION(); - return kInvalidFileHandle; + NOTREACHED(); } } // namespace crashpad diff --git a/util/file/file_io_test.cc b/util/file/file_io_test.cc index 253f6a03a8..a0c79f9a4a 100644 --- a/util/file/file_io_test.cc +++ b/util/file/file_io_test.cc @@ -16,6 +16,7 @@ #include +#include #include #include #include @@ -39,202 +40,204 @@ using testing::_; using testing::InSequence; using testing::Return; -class MockReadExactly : public internal::ReadExactlyInternal { +class MockReadExactly { public: - MockReadExactly() : ReadExactlyInternal() {} + MockReadExactly() = default; MockReadExactly(const MockReadExactly&) = delete; MockReadExactly& operator=(const MockReadExactly&) = delete; - ~MockReadExactly() {} + ~MockReadExactly() = default; // Since it’s more convenient for the test to use uintptr_t than void*, // ReadExactlyInt() and ReadInt() adapt the types. - bool ReadExactlyInt(uintptr_t data, size_t size, bool can_log) { - return ReadExactly(reinterpret_cast(data), size, can_log); + bool ReadExactlyInt(bool can_log, uintptr_t data, size_t size) { + return internal::ReadExactly(std::bind_front(&MockReadExactly::Read, this), + can_log, + reinterpret_cast(data), + size); } - MOCK_METHOD(FileOperationResult, ReadInt, (uintptr_t, size_t, bool)); + MOCK_METHOD(FileOperationResult, ReadInt, (bool, uintptr_t, size_t)); - // ReadExactlyInternal: - FileOperationResult Read(void* data, size_t size, bool can_log) { - return ReadInt(reinterpret_cast(data), size, can_log); + FileOperationResult Read(bool can_log, void* data, size_t size) { + return ReadInt(can_log, reinterpret_cast(data), size); } }; TEST(FileIO, ReadExactly_Zero) { MockReadExactly read_exactly; InSequence in_sequence; - EXPECT_CALL(read_exactly, ReadInt(_, _, false)).Times(0); - EXPECT_TRUE(read_exactly.ReadExactlyInt(100, 0, false)); + EXPECT_CALL(read_exactly, ReadInt(false, _, _)).Times(0); + EXPECT_TRUE(read_exactly.ReadExactlyInt(false, 100, 0)); } TEST(FileIO, ReadExactly_SingleSmallSuccess) { MockReadExactly read_exactly; InSequence in_sequence; - EXPECT_CALL(read_exactly, ReadInt(1000, 1, false)).WillOnce(Return(1)); - EXPECT_TRUE(read_exactly.ReadExactlyInt(1000, 1, false)); + EXPECT_CALL(read_exactly, ReadInt(false, 1000, 1)).WillOnce(Return(1)); + EXPECT_TRUE(read_exactly.ReadExactlyInt(false, 1000, 1)); } TEST(FileIO, ReadExactly_SingleSmallSuccessCanLog) { MockReadExactly read_exactly; InSequence in_sequence; - EXPECT_CALL(read_exactly, ReadInt(1000, 1, true)).WillOnce(Return(1)); - EXPECT_TRUE(read_exactly.ReadExactlyInt(1000, 1, true)); + EXPECT_CALL(read_exactly, ReadInt(true, 1000, 1)).WillOnce(Return(1)); + EXPECT_TRUE(read_exactly.ReadExactlyInt(true, 1000, 1)); } TEST(FileIO, ReadExactly_SingleSmallFailure) { MockReadExactly read_exactly; InSequence in_sequence; - EXPECT_CALL(read_exactly, ReadInt(1000, 1, false)).WillOnce(Return(-1)); - EXPECT_FALSE(read_exactly.ReadExactlyInt(1000, 1, false)); + EXPECT_CALL(read_exactly, ReadInt(false, 1000, 1)).WillOnce(Return(-1)); + EXPECT_FALSE(read_exactly.ReadExactlyInt(false, 1000, 1)); } TEST(FileIO, ReadExactly_SingleSmallFailureCanLog) { MockReadExactly read_exactly; InSequence in_sequence; - EXPECT_CALL(read_exactly, ReadInt(1000, 1, true)).WillOnce(Return(-1)); - EXPECT_FALSE(read_exactly.ReadExactlyInt(1000, 1, true)); + EXPECT_CALL(read_exactly, ReadInt(true, 1000, 1)).WillOnce(Return(-1)); + EXPECT_FALSE(read_exactly.ReadExactlyInt(true, 1000, 1)); } TEST(FileIO, ReadExactly_DoubleSmallSuccess) { MockReadExactly read_exactly; InSequence in_sequence; - EXPECT_CALL(read_exactly, ReadInt(0x1000, 2, false)).WillOnce(Return(1)); - EXPECT_CALL(read_exactly, ReadInt(0x1001, 1, false)).WillOnce(Return(1)); - EXPECT_TRUE(read_exactly.ReadExactlyInt(0x1000, 2, false)); + EXPECT_CALL(read_exactly, ReadInt(false, 0x1000, 2)).WillOnce(Return(1)); + EXPECT_CALL(read_exactly, ReadInt(false, 0x1001, 1)).WillOnce(Return(1)); + EXPECT_TRUE(read_exactly.ReadExactlyInt(false, 0x1000, 2)); } TEST(FileIO, ReadExactly_DoubleSmallShort) { MockReadExactly read_exactly; InSequence in_sequence; - EXPECT_CALL(read_exactly, ReadInt(0x20000, 2, false)).WillOnce(Return(1)); - EXPECT_CALL(read_exactly, ReadInt(0x20001, 1, false)).WillOnce(Return(0)); - EXPECT_FALSE(read_exactly.ReadExactlyInt(0x20000, 2, false)); + EXPECT_CALL(read_exactly, ReadInt(false, 0x20000, 2)).WillOnce(Return(1)); + EXPECT_CALL(read_exactly, ReadInt(false, 0x20001, 1)).WillOnce(Return(0)); + EXPECT_FALSE(read_exactly.ReadExactlyInt(false, 0x20000, 2)); } TEST(FileIO, ReadExactly_DoubleSmallShortCanLog) { MockReadExactly read_exactly; InSequence in_sequence; - EXPECT_CALL(read_exactly, ReadInt(0x20000, 2, true)).WillOnce(Return(1)); - EXPECT_CALL(read_exactly, ReadInt(0x20001, 1, true)).WillOnce(Return(0)); - EXPECT_FALSE(read_exactly.ReadExactlyInt(0x20000, 2, true)); + EXPECT_CALL(read_exactly, ReadInt(true, 0x20000, 2)).WillOnce(Return(1)); + EXPECT_CALL(read_exactly, ReadInt(true, 0x20001, 1)).WillOnce(Return(0)); + EXPECT_FALSE(read_exactly.ReadExactlyInt(true, 0x20000, 2)); } TEST(FileIO, ReadExactly_Medium) { MockReadExactly read_exactly; InSequence in_sequence; - EXPECT_CALL(read_exactly, ReadInt(0x80000000, 0x20000000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x80000000, 0x20000000)) .WillOnce(Return(0x10000000)); - EXPECT_CALL(read_exactly, ReadInt(0x90000000, 0x10000000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x90000000, 0x10000000)) .WillOnce(Return(0x8000000)); - EXPECT_CALL(read_exactly, ReadInt(0x98000000, 0x8000000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x98000000, 0x8000000)) .WillOnce(Return(0x4000000)); - EXPECT_CALL(read_exactly, ReadInt(0x9c000000, 0x4000000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9c000000, 0x4000000)) .WillOnce(Return(0x2000000)); - EXPECT_CALL(read_exactly, ReadInt(0x9e000000, 0x2000000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9e000000, 0x2000000)) .WillOnce(Return(0x1000000)); - EXPECT_CALL(read_exactly, ReadInt(0x9f000000, 0x1000000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9f000000, 0x1000000)) .WillOnce(Return(0x800000)); - EXPECT_CALL(read_exactly, ReadInt(0x9f800000, 0x800000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9f800000, 0x800000)) .WillOnce(Return(0x400000)); - EXPECT_CALL(read_exactly, ReadInt(0x9fc00000, 0x400000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9fc00000, 0x400000)) .WillOnce(Return(0x200000)); - EXPECT_CALL(read_exactly, ReadInt(0x9fe00000, 0x200000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9fe00000, 0x200000)) .WillOnce(Return(0x100000)); - EXPECT_CALL(read_exactly, ReadInt(0x9ff00000, 0x100000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9ff00000, 0x100000)) .WillOnce(Return(0x80000)); - EXPECT_CALL(read_exactly, ReadInt(0x9ff80000, 0x80000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9ff80000, 0x80000)) .WillOnce(Return(0x40000)); - EXPECT_CALL(read_exactly, ReadInt(0x9ffc0000, 0x40000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9ffc0000, 0x40000)) .WillOnce(Return(0x20000)); - EXPECT_CALL(read_exactly, ReadInt(0x9ffe0000, 0x20000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9ffe0000, 0x20000)) .WillOnce(Return(0x10000)); - EXPECT_CALL(read_exactly, ReadInt(0x9fff0000, 0x10000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9fff0000, 0x10000)) .WillOnce(Return(0x8000)); - EXPECT_CALL(read_exactly, ReadInt(0x9fff8000, 0x8000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9fff8000, 0x8000)) .WillOnce(Return(0x4000)); - EXPECT_CALL(read_exactly, ReadInt(0x9fffc000, 0x4000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9fffc000, 0x4000)) .WillOnce(Return(0x2000)); - EXPECT_CALL(read_exactly, ReadInt(0x9fffe000, 0x2000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9fffe000, 0x2000)) .WillOnce(Return(0x1000)); - EXPECT_CALL(read_exactly, ReadInt(0x9ffff000, 0x1000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9ffff000, 0x1000)) .WillOnce(Return(0x800)); - EXPECT_CALL(read_exactly, ReadInt(0x9ffff800, 0x800, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9ffff800, 0x800)) .WillOnce(Return(0x400)); - EXPECT_CALL(read_exactly, ReadInt(0x9ffffc00, 0x400, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9ffffc00, 0x400)) .WillOnce(Return(0x200)); - EXPECT_CALL(read_exactly, ReadInt(0x9ffffe00, 0x200, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9ffffe00, 0x200)) .WillOnce(Return(0x100)); - EXPECT_CALL(read_exactly, ReadInt(0x9fffff00, 0x100, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9fffff00, 0x100)) .WillOnce(Return(0x80)); - EXPECT_CALL(read_exactly, ReadInt(0x9fffff80, 0x80, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9fffff80, 0x80)) .WillOnce(Return(0x40)); - EXPECT_CALL(read_exactly, ReadInt(0x9fffffc0, 0x40, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9fffffc0, 0x40)) .WillOnce(Return(0x20)); - EXPECT_CALL(read_exactly, ReadInt(0x9fffffe0, 0x20, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9fffffe0, 0x20)) .WillOnce(Return(0x10)); - EXPECT_CALL(read_exactly, ReadInt(0x9ffffff0, 0x10, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9ffffff0, 0x10)) .WillOnce(Return(0x8)); - EXPECT_CALL(read_exactly, ReadInt(0x9ffffff8, 0x8, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9ffffff8, 0x8)) .WillOnce(Return(0x4)); - EXPECT_CALL(read_exactly, ReadInt(0x9ffffffc, 0x4, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9ffffffc, 0x4)) .WillOnce(Return(0x2)); - EXPECT_CALL(read_exactly, ReadInt(0x9ffffffe, 0x2, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9ffffffe, 0x2)) .WillOnce(Return(0x1)); - EXPECT_CALL(read_exactly, ReadInt(0x9fffffff, 0x1, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x9fffffff, 0x1)) .WillOnce(Return(0x1)); - EXPECT_TRUE(read_exactly.ReadExactlyInt(0x80000000, 0x20000000, false)); + EXPECT_TRUE(read_exactly.ReadExactlyInt(false, 0x80000000, 0x20000000)); } TEST(FileIO, ReadExactly_LargeSuccess) { MockReadExactly read_exactly; InSequence in_sequence; - constexpr size_t max = std::numeric_limits::max(); - constexpr size_t increment = std::numeric_limits::max(); - EXPECT_CALL(read_exactly, ReadInt(0, max, false)).WillOnce(Return(increment)); - EXPECT_CALL(read_exactly, ReadInt(increment, max - increment, false)) + constexpr size_t max = std::numeric_limits::max(); + constexpr size_t increment = max / 2; + EXPECT_CALL(read_exactly, ReadInt(false, 0, max)).WillOnce(Return(increment)); + EXPECT_CALL(read_exactly, ReadInt(false, increment, max - increment)) .WillOnce(Return(increment)); - EXPECT_CALL(read_exactly, ReadInt(2 * increment, 1, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 2 * increment, 1)) .WillOnce(Return(1)); - EXPECT_TRUE(read_exactly.ReadExactlyInt(0, max, false)); + EXPECT_TRUE(read_exactly.ReadExactlyInt(false, 0, max)); } TEST(FileIO, ReadExactly_LargeShort) { MockReadExactly read_exactly; InSequence in_sequence; - EXPECT_CALL(read_exactly, ReadInt(0, 0xffffffff, false)) - .WillOnce(Return(0x7fffffff)); - EXPECT_CALL(read_exactly, ReadInt(0x7fffffff, 0x80000000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0, 0x7fffffff)) + .WillOnce(Return(0x3fffffff)); + EXPECT_CALL(read_exactly, ReadInt(false, 0x3fffffff, 0x40000000)) .WillOnce(Return(0x10000000)); - EXPECT_CALL(read_exactly, ReadInt(0x8fffffff, 0x70000000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0x4fffffff, 0x30000000)) .WillOnce(Return(0)); - EXPECT_FALSE(read_exactly.ReadExactlyInt(0, 0xffffffff, false)); + EXPECT_FALSE(read_exactly.ReadExactlyInt(false, 0, 0x7fffffff)); } TEST(FileIO, ReadExactly_LargeFailure) { MockReadExactly read_exactly; InSequence in_sequence; - EXPECT_CALL(read_exactly, ReadInt(0, 0xffffffff, false)) - .WillOnce(Return(0x7fffffff)); - EXPECT_CALL(read_exactly, ReadInt(0x7fffffff, 0x80000000, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 0, 0x7fffffff)) + .WillOnce(Return(0x3fffffff)); + EXPECT_CALL(read_exactly, ReadInt(false, 0x3fffffff, 0x40000000)) .WillOnce(Return(-1)); - EXPECT_FALSE(read_exactly.ReadExactlyInt(0, 0xffffffff, false)); + EXPECT_FALSE(read_exactly.ReadExactlyInt(false, 0, 0x7fffffff)); } TEST(FileIO, ReadExactly_TripleMax) { MockReadExactly read_exactly; InSequence in_sequence; - constexpr size_t max = std::numeric_limits::max(); - constexpr size_t increment = - std::numeric_limits::type>::max(); - EXPECT_CALL(read_exactly, ReadInt(0, max, false)).WillOnce(Return(increment)); - EXPECT_CALL(read_exactly, ReadInt(increment, max - increment, false)) + // ReadExactly supports at most int32_t bytes. + constexpr size_t max = std::numeric_limits::max(); + constexpr size_t increment = max / 2; + EXPECT_CALL(read_exactly, ReadInt(false, 0, max)).WillOnce(Return(increment)); + EXPECT_CALL(read_exactly, ReadInt(false, increment, max - increment)) .WillOnce(Return(increment)); - EXPECT_CALL(read_exactly, ReadInt(2 * increment, 1, false)) + EXPECT_CALL(read_exactly, ReadInt(false, 2 * increment, 1)) .WillOnce(Return(1)); - EXPECT_TRUE(read_exactly.ReadExactlyInt(0, max, false)); + EXPECT_TRUE(read_exactly.ReadExactlyInt(false, 0, max)); } class MockWriteAll : public internal::WriteAllInternal { diff --git a/util/file/file_io_win.cc b/util/file/file_io_win.cc index 876f578355..002831fb98 100644 --- a/util/file/file_io_win.cc +++ b/util/file/file_io_win.cc @@ -230,8 +230,7 @@ FileOffset LoggingSeekFile(FileHandle file, FileOffset offset, int whence) { method = FILE_END; break; default: - NOTREACHED_IN_MIGRATION(); - break; + NOTREACHED(); } LARGE_INTEGER distance_to_move; @@ -283,8 +282,7 @@ FileHandle StdioFileHandle(StdioStream stdio_stream) { standard_handle = STD_ERROR_HANDLE; break; default: - NOTREACHED_IN_MIGRATION(); - return INVALID_HANDLE_VALUE; + NOTREACHED(); } HANDLE handle = GetStdHandle(standard_handle); diff --git a/util/file/file_reader.cc b/util/file/file_reader.cc index f8fcf818ff..6c30e524e0 100644 --- a/util/file/file_reader.cc +++ b/util/file/file_reader.cc @@ -21,33 +21,15 @@ namespace crashpad { -namespace { - -class FileReaderReadExactly final : public internal::ReadExactlyInternal { - public: - explicit FileReaderReadExactly(FileReaderInterface* file_reader) - : ReadExactlyInternal(), file_reader_(file_reader) {} - - FileReaderReadExactly(const FileReaderReadExactly&) = delete; - FileReaderReadExactly& operator=(const FileReaderReadExactly&) = delete; - - ~FileReaderReadExactly() {} - - private: - // ReadExactlyInternal: - FileOperationResult Read(void* buffer, size_t size, bool can_log) override { - DCHECK(can_log); - return file_reader_->Read(buffer, size); - } - - FileReaderInterface* file_reader_; // weak -}; - -} // namespace - bool FileReaderInterface::ReadExactly(void* data, size_t size) { - FileReaderReadExactly read_exactly(this); - return read_exactly.ReadExactly(data, size, true); + return internal::ReadExactly( + [this](bool can_log, void* buffer, size_t size) { + DCHECK(can_log); + return Read(buffer, size); + }, + true, + data, + size); } WeakFileHandleFileReader::WeakFileHandleFileReader(FileHandle file_handle) diff --git a/util/file/file_reader_test.cc b/util/file/file_reader_test.cc index c3e9486e26..e68959f041 100644 --- a/util/file/file_reader_test.cc +++ b/util/file/file_reader_test.cc @@ -156,8 +156,8 @@ TEST(FileReader, ReadExactly_Medium) { TEST(FileReader, ReadExactly_LargeSuccess) { MockFileReader file_reader; InSequence in_sequence; - constexpr size_t max = std::numeric_limits::max(); - constexpr size_t increment = std::numeric_limits::max(); + constexpr size_t max = std::numeric_limits::max(); + constexpr size_t increment = max / 2; EXPECT_CALL(file_reader, ReadInt(0, max)).WillOnce(Return(increment)); EXPECT_CALL(file_reader, ReadInt(increment, max - increment)) .WillOnce(Return(increment)); @@ -169,30 +169,30 @@ TEST(FileReader, ReadExactly_LargeSuccess) { TEST(FileReader, ReadExactly_LargeShort) { MockFileReader file_reader; InSequence in_sequence; - EXPECT_CALL(file_reader, ReadInt(0, 0xffffffff)).WillOnce(Return(0x7fffffff)); - EXPECT_CALL(file_reader, ReadInt(0x7fffffff, 0x80000000)) + EXPECT_CALL(file_reader, ReadInt(0, 0x7fffffff)).WillOnce(Return(0x3fffffff)); + EXPECT_CALL(file_reader, ReadInt(0x3fffffff, 0x40000000)) .WillOnce(Return(0x10000000)); - EXPECT_CALL(file_reader, ReadInt(0x8fffffff, 0x70000000)).WillOnce(Return(0)); + EXPECT_CALL(file_reader, ReadInt(0x4fffffff, 0x30000000)).WillOnce(Return(0)); EXPECT_CALL(file_reader, Seek(_, _)).Times(0); - EXPECT_FALSE(file_reader.ReadExactlyInt(0, 0xffffffff)); + EXPECT_FALSE(file_reader.ReadExactlyInt(0, 0x7fffffff)); } TEST(FileReader, ReadExactly_LargeFailure) { MockFileReader file_reader; InSequence in_sequence; - EXPECT_CALL(file_reader, ReadInt(0, 0xffffffff)).WillOnce(Return(0x7fffffff)); - EXPECT_CALL(file_reader, ReadInt(0x7fffffff, 0x80000000)) + EXPECT_CALL(file_reader, ReadInt(0, 0x7fffffff)).WillOnce(Return(0x3fffffff)); + EXPECT_CALL(file_reader, ReadInt(0x3fffffff, 0x40000000)) .WillOnce(Return(-1)); EXPECT_CALL(file_reader, Seek(_, _)).Times(0); - EXPECT_FALSE(file_reader.ReadExactlyInt(0, 0xffffffff)); + EXPECT_FALSE(file_reader.ReadExactlyInt(0, 0x7fffffff)); } TEST(FileReader, ReadExactly_TripleMax) { MockFileReader file_reader; InSequence in_sequence; - constexpr size_t max = std::numeric_limits::max(); - constexpr size_t increment = - std::numeric_limits::type>::max(); + // ReadExactly supports at most int32_t bytes. + constexpr size_t max = std::numeric_limits::max(); + constexpr size_t increment = max / 2; EXPECT_CALL(file_reader, ReadInt(0, max)).WillOnce(Return(increment)); EXPECT_CALL(file_reader, ReadInt(increment, max - increment)) .WillOnce(Return(increment)); diff --git a/util/file/output_stream_file_writer.cc b/util/file/output_stream_file_writer.cc index 8d9cb976ed..150a9e7a16 100644 --- a/util/file/output_stream_file_writer.cc +++ b/util/file/output_stream_file_writer.cc @@ -56,8 +56,7 @@ bool OutputStreamFileWriter::WriteIoVec(std::vector* iovecs) { } FileOffset OutputStreamFileWriter::Seek(FileOffset offset, int whence) { - NOTREACHED_IN_MIGRATION(); - return -1; + NOTREACHED(); } bool OutputStreamFileWriter::Flush() { diff --git a/util/ios/ios_intermediate_dump_data.h b/util/ios/ios_intermediate_dump_data.h index 94fe847111..024c22e617 100644 --- a/util/ios/ios_intermediate_dump_data.h +++ b/util/ios/ios_intermediate_dump_data.h @@ -35,8 +35,7 @@ class IOSIntermediateDumpData : public IOSIntermediateDumpObject { //! \brief Constructs a new data object which owns a std::vector. //! - //! \param[in] data An array of uint8_t. - //! \param[in] length The length of \a data. + //! \param[in] data A vector of uint8_t. IOSIntermediateDumpData(std::vector data) : data_(std::move(data)) {} // IOSIntermediateDumpObject: diff --git a/util/ios/ios_intermediate_dump_format.h b/util/ios/ios_intermediate_dump_format.h index 2f0a898078..32752f6116 100644 --- a/util/ios/ios_intermediate_dump_format.h +++ b/util/ios/ios_intermediate_dump_format.h @@ -56,6 +56,12 @@ namespace internal { TD(kAnnotationsCrashInfoMessage1, 3016) \ TD(kAnnotationsCrashInfoMessage2, 3017) \ TD(kAnnotationsDyldErrorString, 3018) \ + TD(kModuleExtraMemoryRegions, 3019) \ + TD(kModuleExtraMemoryRegionAddress, 3020) \ + TD(kModuleExtraMemoryRegionData, 3021) \ + TD(kModuleIntermediateDumpExtraMemoryRegions, 3022) \ + TD(kModuleIntermediateDumpExtraMemoryRegionAddress, 3023) \ + TD(kModuleIntermediateDumpExtraMemoryRegionData, 3024) \ TD(kProcessInfo, 4000) \ TD(kParentPID, 4001) \ TD(kPID, 4002) \ diff --git a/util/ios/ios_system_data_collector.mm b/util/ios/ios_system_data_collector.mm index 104f3cd512..b2e3eb480c 100644 --- a/util/ios/ios_system_data_collector.mm +++ b/util/ios/ios_system_data_collector.mm @@ -155,11 +155,13 @@ void AddObserver(CFStringRef notification_name, T* observer) { (__bridge CFStringRef)NSSystemTimeZoneDidChangeNotification, this); SystemTimeZoneDidChangeNotification(); +#if !BUILDFLAG(IS_IOS_TVOS) // Orientation. AddObserver( (__bridge CFStringRef)UIDeviceOrientationDidChangeNotification, this); OrientationDidChangeNotification(); +#endif #if !defined(CRASHPAD_IS_IOS_APP_EXTENSION) // Foreground/Background. Extensions shouldn't use UIApplication*. @@ -214,13 +216,15 @@ void AddObserver(CFStringRef notification_name, T* observer) { } void IOSSystemDataCollector::OrientationDidChangeNotification() { +#if !BUILDFLAG(IS_IOS_TVOS) orientation_ = base::saturated_cast([[UIDevice currentDevice] orientation]); +#endif } void IOSSystemDataCollector::ApplicationDidChangeActiveNotification() { #if defined(CRASHPAD_IS_IOS_APP_EXTENSION) - NOTREACHED_NORETURN(); + NOTREACHED(); #else dispatch_assert_queue_debug(dispatch_get_main_queue()); bool old_active = active_; diff --git a/util/linux/scoped_ptrace_attach.h b/util/linux/scoped_ptrace_attach.h index edd2c75aaf..7fea6571c0 100644 --- a/util/linux/scoped_ptrace_attach.h +++ b/util/linux/scoped_ptrace_attach.h @@ -17,7 +17,6 @@ #include - namespace crashpad { //! \brief Attaches to the process with process ID \a pid and blocks until the @@ -35,7 +34,7 @@ bool PtraceAttach(pid_t pid, bool can_log = true); //! \param pid The process ID of the process to detach. //! \param can_log Whether this function may log messages on failure. //! \return `true` on success. `false` on failure with a message logged if \a -//! ca_log is `true `true` +//! can_log is `true`. bool PtraceDetach(pid_t pid, bool can_log = true); //! \brief Maintains a `ptrace()` attachment to a process. diff --git a/util/mac/mac_util.cc b/util/mac/mac_util.cc index 37b595b607..6b2666c881 100644 --- a/util/mac/mac_util.cc +++ b/util/mac/mac_util.cc @@ -14,21 +14,18 @@ #include "util/mac/mac_util.h" -#include #include #include -#include #include -#include + +#include #include "base/apple/foundation_util.h" #include "base/apple/scoped_cftyperef.h" #include "base/check_op.h" #include "base/logging.h" #include "base/mac/scoped_ioobject.h" -#include "base/notreached.h" #include "base/strings/string_number_conversions.h" -#include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" #include "build/build_config.h" @@ -60,45 +57,6 @@ extern const CFStringRef _kCFSystemVersionBuildVersionKey WEAK_IMPORT; namespace { -#if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_13_4 -// Returns the running system’s Darwin major version. Don’t call this, it’s an -// implementation detail and its result is meant to be cached by -// MacOSVersionNumber(). -// -// This is very similar to Chromium’s base/mac/mac_util.mm -// DarwinMajorVersionInternal(). -int DarwinMajorVersion() { - // base::OperatingSystemVersionNumbers calls Gestalt(), which is a - // higher-level function than is needed. It might perform unnecessary - // operations. On 10.6, it was observed to be able to spawn threads (see - // https://crbug.com/53200). It might also read files or perform other - // blocking operations. Actually, nobody really knows for sure just what - // Gestalt() might do, or what it might be taught to do in the future. - // - // uname(), on the other hand, is implemented as a simple series of sysctl() - // system calls to obtain the relevant data from the kernel. The data is - // compiled right into the kernel, so no threads or blocking or other funny - // business is necessary. - - utsname uname_info; - int rv = uname(&uname_info); - PCHECK(rv == 0) << "uname"; - - DCHECK_EQ(strcmp(uname_info.sysname, "Darwin"), 0) - << "unexpected sysname " << uname_info.sysname; - - char* dot = strchr(uname_info.release, '.'); - CHECK(dot); - - int darwin_major_version = 0; - CHECK(base::StringToInt( - base::StringPiece(uname_info.release, dot - uname_info.release), - &darwin_major_version)); - - return darwin_major_version; -} -#endif // DT < 10.13.4 - // Helpers for the weak-imported private CoreFoundation internals. CFDictionaryRef TryCFCopySystemVersionDictionary() { @@ -131,7 +89,7 @@ bool StringToVersionNumbers(const std::string& version, LOG(ERROR) << "version has unexpected format"; return false; } - if (!base::StringToInt(base::StringPiece(&version[0], first_dot), major)) { + if (!base::StringToInt(std::string_view(&version[0], first_dot), major)) { LOG(ERROR) << "version has unexpected format"; return false; } @@ -144,9 +102,9 @@ bool StringToVersionNumbers(const std::string& version, second_dot = version.length(); } - if (!base::StringToInt(base::StringPiece(&version[first_dot + 1], - second_dot - first_dot - 1), - minor)) { + if (!base::StringToInt( + std::string_view(&version[first_dot + 1], second_dot - first_dot - 1), + minor)) { LOG(ERROR) << "version has unexpected format"; return false; } @@ -154,8 +112,8 @@ bool StringToVersionNumbers(const std::string& version, if (second_dot == version.length()) { *bugfix = 0; } else if (!base::StringToInt( - base::StringPiece(&version[second_dot + 1], - version.length() - second_dot - 1), + std::string_view(&version[second_dot + 1], + version.length() - second_dot - 1), bugfix)) { LOG(ERROR) << "version has unexpected format"; return false; @@ -186,45 +144,24 @@ int MacOSVersionNumber() { // version from the kernel without having to open any files or spin up any // threads, but it’s only available in macOS 10.13.4 and later. std::string macos_version_number_string = ReadStringSysctlByName( - "kern.osproductversion", - __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_13_4); - if (!macos_version_number_string.empty()) { - int major; - int minor; - int bugfix; - if (StringToVersionNumbers( - macos_version_number_string, &major, &minor, &bugfix)) { - DCHECK_GE(major, 10); - DCHECK_LE(major, 99); - DCHECK_GE(minor, 0); - DCHECK_LE(minor, 99); - DCHECK_GE(bugfix, 0); - DCHECK_LE(bugfix, 99); - return major * 1'00'00 + minor * 1'00 + bugfix; - } - } - -#if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_13_4 - // On macOS 10.13.4 and later, the sysctlbyname above should have been - // successful. - NOTREACHED_IN_MIGRATION(); - return -1; -#else // DT >= 10.13.4 - // The Darwin major version is always 4 greater than the macOS minor version - // for Darwin versions beginning with 6, corresponding to Mac OS X 10.2, - // through Darwin 19, corresponding to macOS 10.15. - int darwin_major_version = DarwinMajorVersion(); - DCHECK_GE(darwin_major_version, 6); - DCHECK_LE(darwin_major_version, 19); - - int macos_version_number = 10'00'00 + (darwin_major_version - 4) * 1'00; - - // On macOS 10.13.4 and later, the sysctlbyname above should have been - // successful. - DCHECK_LT(macos_version_number, 10'13'04); - - return macos_version_number; -#endif // DT >= 10.13.4 + "kern.osproductversion", true); + DCHECK(!macos_version_number_string.empty()); + + int major; + int minor; + int bugfix; + bool success = StringToVersionNumbers( + macos_version_number_string, &major, &minor, &bugfix); + DCHECK(success); + + DCHECK_GE(major, 10); + DCHECK_LE(major, 99); + DCHECK_GE(minor, 0); + DCHECK_LE(minor, 99); + DCHECK_GE(bugfix, 0); + DCHECK_LE(bugfix, 99); + + return major * 1'00'00 + minor * 1'00 + bugfix; }(); return macos_version_number; @@ -313,22 +250,24 @@ bool MacOSVersionComponents(int* major, void MacModelAndBoard(std::string* model, std::string* board_id) { base::mac::ScopedIOObject platform_expert( - IOServiceGetMatchingService(kIOMasterPortDefault, + IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice"))); if (platform_expert) { model->assign(IORegistryEntryDataPropertyAsString(platform_expert.get(), CFSTR("model"))); #if defined(ARCH_CPU_X86_FAMILY) - CFStringRef kBoardProperty = CFSTR("board-id"); + board_id->assign(IORegistryEntryDataPropertyAsString(platform_expert.get(), + CFSTR("board-id"))); #elif defined(ARCH_CPU_ARM64) - // TODO(https://crashpad.chromium.org/bug/352): When production arm64 - // hardware is available, determine whether board-id works and switch to it - // if feasible, otherwise, determine whether target-type remains a viable - // alternative. - CFStringRef kBoardProperty = CFSTR("target-type"); + board_id->assign(IORegistryEntryDataPropertyAsString( + platform_expert.get(), CFSTR("target-sub-type"))); + if (board_id->empty()) { + board_id->assign(IORegistryEntryDataPropertyAsString( + platform_expert.get(), CFSTR("target-type"))); + } +#else +#error Port. #endif - board_id->assign(IORegistryEntryDataPropertyAsString(platform_expert.get(), - kBoardProperty)); } else { model->clear(); board_id->clear(); diff --git a/util/mac/mac_util.h b/util/mac/mac_util.h index 7a32b5f9e9..16edcb2bbf 100644 --- a/util/mac/mac_util.h +++ b/util/mac/mac_util.h @@ -21,21 +21,17 @@ namespace crashpad { //! \brief Returns the version of the running operating system. //! -//! \return The version of the operating system, such as `10'15'06` for macOS -//! 10.15.6. +//! \return The version of the operating system, such as `15'04'01` for macOS +//! 15.4.1. //! -//! The format of the return value matches what is used by the -//! `__MAC_OS_X_VERSION_MIN_REQUIRED`, `__MAC_OS_X_VERSION_MAX_ALLOWED`, and -//! per-version `__MAC_*` macros, for versions since OS X 10.10. +//! This function returns the major, minor, and bugfix components combined into +//! a single number. The format of the return value matches what is used by the +//! `__MAC_OS_X_VERSION_MIN_REQUIRED`, +//! `__MAC_OS_X_VERSION_MAX_ALLOWED`, and per-version `__MAC_*` macros, for +//! versions since OS X 10.10. //! -//! On macOS 10.13.4 and later, this function will return the major, minor, and -//! bugfix components combined into a single number. On older OS versions, only -//! the major and minor components will be returned, and the bugfix component -//! will always be reported as 0. By contrast, MacOSVersionComponents() always -//! returns the bugfix component. -//! -//! \note This is similar to the base::mac::IsOS*() family of functions, but -//! is provided for situations where the caller needs to obtain version +//! \note This is similar to the base::mac::IsOS*() family of functions, but is +//! provided for situations where the caller needs to obtain version //! information beyond what is provided by Chromium’s base, or for when the //! caller needs the actual minor version value. int MacOSVersionNumber(); @@ -44,18 +40,17 @@ int MacOSVersionNumber(); //! //! All parameters are required. No parameter may be `nullptr`. //! -//! \param[out] major The major version of the operating system, such as `10` -//! for macOS 10.12.1. -//! \param[out] minor The major version of the operating system, such as `12` -//! for macOS 10.12.1. +//! \param[out] major The major version of the operating system, such as `15` +//! for macOS 15.4.1. +//! \param[out] minor The major version of the operating system, such as `4` +//! for macOS 15.4.1. //! \param[out] bugfix The bugfix version of the operating system, such as `1` -//! for macOS 10.12.1. -//! \param[out] build The operating system’s build string, such as `"16B2657"` -//! for macOS 10.12.1. -//! \param[out] server `true` for a macOS Server installation, `false` otherwise -//! (for a desktop/laptop, client, or workstation system). +//! for macOS 15.4.1. +//! \param[out] build The operating system’s build string, such as `"24E263"` +//! for macOS 15.4.1. //! \param[out] version_string A string representing the full operating system -//! version, such as `"macOS 10.12.1 (16B2657)"`. +//! version, such as `"macOS 15.4.1 (24E263)"`. If \a bugfix is 0, it will +//! not be included in \a version_string: `"macOS 15.5 (24F74)"`. //! //! \return `true` on success, `false` on failure, with an error message logged. //! A failure is considered to have occurred if any element could not be @@ -69,10 +64,13 @@ bool MacOSVersionComponents(int* major, //! \brief Returns the model name and board ID of the running system. //! -//! \param[out] model The system’s model name. A mid-2012 15" MacBook Pro would -//! report “MacBookPro10,1”. -//! \param[out] board_id The system’s board ID. A mid-2012 15" MacBook Pro would -//! report “Mac-C3EC7CD22292981F”. +//! \param[out] model The system’s model name. A mid-2012 15\" MacBook Pro would +//! report “MacBookPro10,1”, and a 2021 16\" M1 Max MacBook Pro would report +//! “MacBookPro18,2”. +//! \param[out] board_id The system’s board ID or target type. An x86_64 system +//! reports a board ID: a mid-2012 15\" MacBook Pro would report +//! “Mac-C3EC7CD22292981F”. An arm64 system reports a target subtype or +//! target type: a 2021 16\" M1 Max MacBook Pro would report “J316cAP”. //! //! If a value cannot be determined, its string is cleared. void MacModelAndBoard(std::string* model, std::string* board_id); diff --git a/util/mac/mac_util_test.mm b/util/mac/mac_util_test.mm index e1cc3ad73b..acf8a17631 100644 --- a/util/mac/mac_util_test.mm +++ b/util/mac/mac_util_test.mm @@ -40,8 +40,7 @@ void SwVers(NSString* argument, std::string* output) { @try { [task launch]; - } - @catch (NSException* exception) { + } @catch (NSException* exception) { FAIL() << [[exception name] UTF8String] << ": " << [[exception reason] UTF8String]; } @@ -54,8 +53,11 @@ void SwVers(NSString* argument, std::string* output) { output->assign(reinterpret_cast([data bytes]), [data length]); - EXPECT_EQ(output->at(output->size() - 1), '\n'); - output->resize(output->size() - 1); + EXPECT_FALSE(output->empty()); + if (!output->empty()) { + EXPECT_EQ(output->back(), '\n'); + output->pop_back(); + } } } @@ -75,11 +77,17 @@ void SwVers(NSString* argument, std::string* output) { EXPECT_GE(bugfix, 0); EXPECT_LE(bugfix, 99); + if (major == 10) { + EXPECT_LE(minor, 15); + } else { + EXPECT_TRUE(major <= 15 || major >= 26); + } + std::string version; if (bugfix) { version = base::StringPrintf("%d.%d.%d", major, minor, bugfix); } else { - // 10.x.0 releases report their version string as simply 10.x. + // x.y.0 releases report their version string as simply x.y. version = base::StringPrintf("%d.%d", major, minor); } @@ -97,9 +105,12 @@ void SwVers(NSString* argument, std::string* output) { std::string expected_product_name; ASSERT_NO_FATAL_FAILURE(SwVers(@"-productName", &expected_product_name)); - // Look for a space after the product name in the complete version string. - expected_product_name += ' '; - EXPECT_EQ(version_string.find(expected_product_name), 0u); + std::string expected_version_string_start = + expected_product_name + ' ' + expected_product_version + ' '; + std::string expected_version_string_end = " (" + expected_build_version + ')'; + + EXPECT_TRUE(version_string.starts_with(expected_version_string_start)); + EXPECT_TRUE(version_string.ends_with(expected_version_string_end)); } TEST(MacUtil, MacOSVersionNumber) { @@ -118,9 +129,7 @@ void SwVers(NSString* argument, std::string* output) { ASSERT_TRUE( MacOSVersionComponents(&major, &minor, &bugfix, &build, &version_string)); - EXPECT_EQ(macos_version_number, - major * 1'00'00 + minor * 1'00 + - (macos_version_number >= 10'13'04 ? bugfix : 0)); + EXPECT_EQ(macos_version_number, major * 1'00'00 + minor * 1'00 + bugfix); } TEST(MacUtil, MacModelAndBoard) { diff --git a/util/mac/service_management_test.mm b/util/mac/service_management_test.mm index 26c96049cc..a48d201e64 100644 --- a/util/mac/service_management_test.mm +++ b/util/mac/service_management_test.mm @@ -56,7 +56,7 @@ void ExpectProcessIsRunning(pid_t pid, std::string& last_arg) { break; } if (inner_tries > 0) { - SleepNanoseconds(1E6); // 1 millisecond + SleepNanoseconds(1E7); // 10 milliseconds } } while (inner_tries--); ASSERT_TRUE(success); @@ -120,8 +120,11 @@ void ExpectProcessIsNotRunning(pid_t pid, std::string& last_arg) { NSDictionary* job_dictionary_ns = @{ @LAUNCH_JOBKEY_LABEL : @"org.chromium.crashpad.test.service_management", @LAUNCH_JOBKEY_RUNATLOAD : @YES, - @LAUNCH_JOBKEY_PROGRAMARGUMENTS : - @[ @"/bin/sh", @"-c", shell_script_ns, ], + @LAUNCH_JOBKEY_PROGRAMARGUMENTS : @[ + @"/bin/sh", + @"-c", + shell_script_ns, + ], }; CFDictionaryRef job_dictionary_cf = base::apple::NSToCFPtrCast(job_dictionary_ns); diff --git a/util/mac/xattr.cc b/util/mac/xattr.cc index 6c12e91364..a5c57eb1a3 100644 --- a/util/mac/xattr.cc +++ b/util/mac/xattr.cc @@ -19,6 +19,8 @@ #include #include +#include + #include "base/check_op.h" #include "base/logging.h" #include "base/numerics/safe_conversions.h" @@ -28,7 +30,7 @@ namespace crashpad { XattrStatus ReadXattr(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, std::string* value) { // First get the size of the attribute value. ssize_t buffer_size = getxattr(file.value().c_str(), name.data(), nullptr, @@ -57,7 +59,7 @@ XattrStatus ReadXattr(const base::FilePath& file, } bool WriteXattr(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, const std::string& value) { int rv = setxattr(file.value().c_str(), name.data(), value.c_str(), value.length(), 0, 0); @@ -67,7 +69,7 @@ bool WriteXattr(const base::FilePath& file, } XattrStatus ReadXattrBool(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, bool* value) { std::string tmp; XattrStatus status; @@ -87,13 +89,13 @@ XattrStatus ReadXattrBool(const base::FilePath& file, } bool WriteXattrBool(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, bool value) { return WriteXattr(file, name, (value ? "1" : "0")); } XattrStatus ReadXattrInt(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, int* value) { std::string tmp; XattrStatus status; @@ -108,14 +110,14 @@ XattrStatus ReadXattrInt(const base::FilePath& file, } bool WriteXattrInt(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, int value) { std::string tmp = base::StringPrintf("%d", value); return WriteXattr(file, name, tmp); } XattrStatus ReadXattrTimeT(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, time_t* value) { // time_t on macOS is defined as a long, but it will be read into an int64_t // here, since there is no string conversion method for long. @@ -142,14 +144,13 @@ XattrStatus ReadXattrTimeT(const base::FilePath& file, } bool WriteXattrTimeT(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, time_t value) { std::string tmp = base::StringPrintf("%ld", value); return WriteXattr(file, name, tmp); } -XattrStatus RemoveXattr(const base::FilePath& file, - const base::StringPiece& name) { +XattrStatus RemoveXattr(const base::FilePath& file, std::string_view name) { int rv = removexattr(file.value().c_str(), name.data(), 0); if (rv != 0) { if (errno == ENOATTR) diff --git a/util/mac/xattr.h b/util/mac/xattr.h index 897d3c826e..6b8516e52a 100644 --- a/util/mac/xattr.h +++ b/util/mac/xattr.h @@ -18,9 +18,9 @@ #include #include +#include #include "base/files/file_path.h" -#include "base/strings/string_piece.h" namespace crashpad { @@ -44,7 +44,7 @@ enum class XattrStatus { //! //! \return XattrStatus XattrStatus ReadXattr(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, std::string* value); //! \brief Writes an extended attribute on a file. @@ -56,7 +56,7 @@ XattrStatus ReadXattr(const base::FilePath& file, //! \return `true` if the write was successful. `false` on error, with a message //! logged. bool WriteXattr(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, const std::string& value); //! \copydoc ReadXattr @@ -64,32 +64,32 @@ bool WriteXattr(const base::FilePath& file, //! Only the values `"0"` and `"1"`, for `false` and `true` respectively, are //! valid conversions. XattrStatus ReadXattrBool(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, bool* value); //! \copydoc WriteXattr bool WriteXattrBool(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, bool value); //! \copydoc ReadXattr XattrStatus ReadXattrInt(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, int* value); //! \copydoc WriteXattr bool WriteXattrInt(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, int value); //! \copydoc ReadXattr XattrStatus ReadXattrTimeT(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, time_t* value); //! \copydoc WriteXattr bool WriteXattrTimeT(const base::FilePath& file, - const base::StringPiece& name, + std::string_view name, time_t value); //! \brief Removes an extended attribute from a file. @@ -98,8 +98,7 @@ bool WriteXattrTimeT(const base::FilePath& file, //! \param[in] name The name of the extended attribute to remove. //! //! \return XattrStatus -XattrStatus RemoveXattr(const base::FilePath& file, - const base::StringPiece& name); +XattrStatus RemoveXattr(const base::FilePath& file, std::string_view name); } // namespace crashpad diff --git a/util/mach/child_port_handshake.cc b/util/mach/child_port_handshake.cc index 3402d2daaf..5ddfb8152e 100644 --- a/util/mach/child_port_handshake.cc +++ b/util/mach/child_port_handshake.cc @@ -256,8 +256,7 @@ mach_port_t ChildPortHandshakeServer::RunServer( break; default: - NOTREACHED_IN_MIGRATION(); - break; + NOTREACHED(); } } diff --git a/util/mach/exc_client_variants.cc b/util/mach/exc_client_variants.cc index a2c63139d7..7c0baa88a2 100644 --- a/util/mach/exc_client_variants.cc +++ b/util/mach/exc_client_variants.cc @@ -123,8 +123,7 @@ kern_return_t UniversalExceptionRaise(exception_behavior_t behavior, new_state_count); default: - NOTREACHED_IN_MIGRATION(); - return KERN_INVALID_ARGUMENT; + NOTREACHED(); } } diff --git a/util/mach/exception_ports.cc b/util/mach/exception_ports.cc index 0f80887a1a..055fa116ea 100644 --- a/util/mach/exception_ports.cc +++ b/util/mach/exception_ports.cc @@ -80,12 +80,7 @@ ExceptionPorts::ExceptionPorts(TargetType target_type, mach_port_t target_port) break; default: - NOTREACHED_IN_MIGRATION(); - get_exception_ports_ = nullptr; - set_exception_ports_ = nullptr; - target_name_ = nullptr; - target_port_ = MACH_PORT_NULL; - break; + NOTREACHED(); } } diff --git a/util/mach/exception_ports_test.cc b/util/mach/exception_ports_test.cc index a6d065bc7e..15375d326a 100644 --- a/util/mach/exception_ports_test.cc +++ b/util/mach/exception_ports_test.cc @@ -188,8 +188,7 @@ class TestExceptionPorts : public MachMultiprocess, } else if (who_crashes_ == kOtherThreadCrashes) { expect_behavior = EXCEPTION_STATE_IDENTITY; } else { - NOTREACHED_IN_MIGRATION(); - expect_behavior = 0; + NOTREACHED(); } EXPECT_EQ(behavior, expect_behavior); @@ -286,7 +285,7 @@ class TestExceptionPorts : public MachMultiprocess, } default: { - NOTREACHED_IN_MIGRATION(); + NOTREACHED(); } } } @@ -369,7 +368,7 @@ class TestExceptionPorts : public MachMultiprocess, break; } default: { - NOTREACHED_IN_MIGRATION(); + NOTREACHED(); } } } @@ -520,7 +519,7 @@ class TestExceptionPorts : public MachMultiprocess, } default: { - NOTREACHED_IN_MIGRATION(); + NOTREACHED(); } } } diff --git a/util/mach/exception_types.cc b/util/mach/exception_types.cc index e73b7c8be1..f9e1923f7a 100644 --- a/util/mach/exception_types.cc +++ b/util/mach/exception_types.cc @@ -135,14 +135,15 @@ exception_type_t ExcCrashRecoverOriginalException( bool ExcCrashCouldContainException(exception_type_t exception) { // EXC_CRASH should never be wrapped in another EXC_CRASH. // - // EXC_RESOURCE and EXC_GUARD are software exceptions that are never wrapped - // in EXC_CRASH. The only time EXC_CRASH is generated is for processes exiting - // due to an unhandled core-generating signal or being killed by SIGKILL for - // code-signing reasons. Neither of these apply to EXC_RESOURCE or EXC_GUARD. - // See 10.10 xnu-2782.1.97/bsd/kern/kern_exit.c proc_prepareexit(). Receiving - // these exception types wrapped in EXC_CRASH would lose information because - // their code[0] uses all 64 bits (see ExceptionSnapshotMac::Initialize()) and - // the code[0] recovered from EXC_CRASH only contains 20 significant bits. + // EXC_RESOURCE is a software exceptions that is never wrapped in EXC_CRASH. + // The same applies to EXC_GUARD below macOS 13. The only time EXC_CRASH is + // generated is for processes exiting due to an unhandled core-generating + // signal or being killed by SIGKILL for code-signing reasons. Neither of + // these apply to EXC_RESOURCE or EXC_GUARD. See 10.10 + // xnu-2782.1.97/bsd/kern/kern_exit.c proc_prepareexit(). Receiving these + // exception types wrapped in EXC_CRASH would lose information because their + // code[0] uses all 64 bits (see ExceptionSnapshotMac::Initialize()) and the + // code[0] recovered from EXC_CRASH only contains 20 significant bits. // // EXC_CORPSE_NOTIFY may be generated from EXC_CRASH, but the opposite should // never occur. @@ -150,11 +151,11 @@ bool ExcCrashCouldContainException(exception_type_t exception) { // kMachExceptionSimulated is a non-fatal Crashpad-specific pseudo-exception // that never exists as an exception within the kernel and should thus never // be wrapped in EXC_CRASH. - return exception != EXC_CRASH && - exception != EXC_RESOURCE && - exception != EXC_GUARD && - exception != EXC_CORPSE_NOTIFY && - exception != kMachExceptionSimulated; + if (MacOSVersionNumber() < 13'00'00 && exception == EXC_GUARD) { + return false; + } + return exception != EXC_CRASH && exception != EXC_RESOURCE && + exception != EXC_CORPSE_NOTIFY && exception != kMachExceptionSimulated; } int32_t ExceptionCodeForMetrics(exception_type_t exception, diff --git a/util/mach/exception_types_test.cc b/util/mach/exception_types_test.cc index 8b620798a0..a66a2be121 100644 --- a/util/mach/exception_types_test.cc +++ b/util/mach/exception_types_test.cc @@ -124,7 +124,6 @@ TEST(ExceptionTypes, ExcCrashCouldContainException) { EXPECT_TRUE(ExcCrashCouldContainException(EXC_RPC_ALERT)); EXPECT_FALSE(ExcCrashCouldContainException(EXC_CRASH)); EXPECT_FALSE(ExcCrashCouldContainException(EXC_RESOURCE)); - EXPECT_FALSE(ExcCrashCouldContainException(EXC_GUARD)); EXPECT_FALSE(ExcCrashCouldContainException(EXC_CORPSE_NOTIFY)); EXPECT_FALSE(ExcCrashCouldContainException(kMachExceptionSimulated)); } diff --git a/util/mach/mig_fix.py b/util/mach/mig_fix.py index 79d9a6ed51..2151ad2215 100755 --- a/util/mach/mig_fix.py +++ b/util/mach/mig_fix.py @@ -69,7 +69,7 @@ def _fix_user_implementation(implementation, fixed_implementation, header, file = open(implementation, 'r+' if fixed_implementation is None else 'r') contents = file.read() - pattern = re.compile('^(\t} __Reply);$', re.MULTILINE) + pattern = re.compile('^(\t} __Reply);$', flags=re.MULTILINE) contents = pattern.sub(r'\1 __attribute__((unused));', contents) if fixed_header is not None: @@ -112,7 +112,7 @@ def _fix_server_implementation(implementation, fixed_implementation, header, # Find interesting declarations. declaration_pattern = re.compile( - '^mig_internal (kern_return_t __MIG_check__.*)$', re.MULTILINE) + '^mig_internal (kern_return_t __MIG_check__.*)$', flags=re.MULTILINE) declarations = declaration_pattern.findall(contents) # Remove “__attribute__((__unused__))” from the declarations, and call them diff --git a/util/mach/notify_server_test.cc b/util/mach/notify_server_test.cc index 2c9e7e6ad9..e068e0e450 100644 --- a/util/mach/notify_server_test.cc +++ b/util/mach/notify_server_test.cc @@ -33,7 +33,6 @@ namespace { using testing::AllOf; using testing::DoAll; using testing::Eq; -using testing::Invoke; using testing::Pointee; using testing::ResultOf; using testing::Return; @@ -528,7 +527,7 @@ TEST_F(NotifyServerTest, MachNotifyDeadName) { ResultOf(DeadNameRightRefCount, 2)), ResultOf(AuditPIDFromMachMessageTrailer, 0))) .WillOnce( - DoAll(WithArg<1>(Invoke(MachPortDeallocate)), Return(MIG_NO_REPLY))) + DoAll(WithArg<1>(MachPortDeallocate), Return(MIG_NO_REPLY))) .RetiresOnSaturation(); receive_right.reset(); diff --git a/util/mach/symbolic_constants_mach.cc b/util/mach/symbolic_constants_mach.cc index 7a5919016d..bea8a8c6b1 100644 --- a/util/mach/symbolic_constants_mach.cc +++ b/util/mach/symbolic_constants_mach.cc @@ -18,6 +18,7 @@ #include #include +#include #include "base/strings/stringprintf.h" #include "util/mach/exception_behaviors.h" @@ -138,7 +139,7 @@ constexpr struct { }; // Returns the short name for a flavor name, given its full flavor name. -std::string ThreadStateFlavorFullToShort(const base::StringPiece& flavor) { +std::string ThreadStateFlavorFullToShort(std::string_view flavor) { // For generic flavors like THREAD_STATE_NONE and THREAD_STATE_FLAVOR_LIST_*. static constexpr char kThreadState[] = "THREAD_STATE_"; size_t prefix_len = strlen(kThreadState); @@ -211,14 +212,14 @@ std::string ExceptionToString(exception_type_t exception, return base::StringPrintf("%s%s", kExcPrefix, exception_name); } -bool StringToException(const base::StringPiece& string, +bool StringToException(std::string_view string, StringToSymbolicConstantOptions options, exception_type_t* exception) { if ((options & kAllowFullName) || (options & kAllowShortName)) { bool can_match_full = (options & kAllowFullName) && string.substr(0, strlen(kExcPrefix)).compare(kExcPrefix) == 0; - base::StringPiece short_string = + std::string_view short_string = can_match_full ? string.substr(strlen(kExcPrefix)) : string; for (exception_type_t index = 0; index < implicit_cast(std::size(kExceptionNames)); @@ -290,7 +291,7 @@ std::string ExceptionMaskToString(exception_mask_t exception_mask, return mask_string; } -bool StringToExceptionMask(const base::StringPiece& string, +bool StringToExceptionMask(std::string_view string, StringToSymbolicConstantOptions options, exception_mask_t* exception_mask) { if (options & kAllowOr) { @@ -301,15 +302,15 @@ bool StringToExceptionMask(const base::StringPiece& string, ++pos; const size_t start = pos; pos = string.find('|', pos); - base::StringPiece substring = (pos == base::StringPiece::npos) - ? string.substr(start) - : string.substr(start, pos - start); + std::string_view substring = (pos == std::string_view::npos) + ? string.substr(start) + : string.substr(start, pos - start); exception_mask_t temp_mask; if (!StringToExceptionMask(substring, options, &temp_mask)) { return false; } build_mask |= temp_mask; - } while (pos != base::StringPiece::npos); + } while (pos != std::string_view::npos); *exception_mask = build_mask; return true; @@ -319,7 +320,7 @@ bool StringToExceptionMask(const base::StringPiece& string, bool can_match_full = (options & kAllowFullName) && string.substr(0, strlen(kExcMaskPrefix)).compare(kExcMaskPrefix) == 0; - base::StringPiece short_string = + std::string_view short_string = can_match_full ? string.substr(strlen(kExcMaskPrefix)) : string; for (exception_type_t index = 0; index < implicit_cast(std::size(kExceptionNames)); @@ -388,15 +389,15 @@ std::string ExceptionBehaviorToString(exception_behavior_t behavior, return behavior_string; } -bool StringToExceptionBehavior(const base::StringPiece& string, +bool StringToExceptionBehavior(std::string_view string, StringToSymbolicConstantOptions options, exception_behavior_t* behavior) { - base::StringPiece sp = string; + std::string_view sp = string; exception_behavior_t build_behavior = 0; size_t pos = sp.find('|', 0); - if (pos != base::StringPiece::npos) { - base::StringPiece left = sp.substr(0, pos); - base::StringPiece right = sp.substr(pos + 1, sp.length() - pos - 1); + if (pos != std::string_view::npos) { + std::string_view left = sp.substr(0, pos); + std::string_view right = sp.substr(pos + 1, sp.length() - pos - 1); if (options & kAllowFullName) { if (left.compare(kMachExceptionCodesFull) == 0) { build_behavior |= MACH_EXCEPTION_CODES; @@ -425,7 +426,7 @@ bool StringToExceptionBehavior(const base::StringPiece& string, bool can_match_full = (options & kAllowFullName) && sp.substr(0, strlen(kBehaviorPrefix)).compare(kBehaviorPrefix) == 0; - base::StringPiece short_string = + std::string_view short_string = can_match_full ? sp.substr(strlen(kBehaviorPrefix)) : sp; for (exception_behavior_t index = 0; index < implicit_cast(std::size(kBehaviorNames)); @@ -492,7 +493,7 @@ std::string ThreadStateFlavorToString(thread_state_flavor_t flavor, return std::string(flavor_name); } -bool StringToThreadStateFlavor(const base::StringPiece& string, +bool StringToThreadStateFlavor(std::string_view string, StringToSymbolicConstantOptions options, thread_state_flavor_t* flavor) { if ((options & kAllowFullName) || (options & kAllowShortName)) { diff --git a/util/mach/symbolic_constants_mach.h b/util/mach/symbolic_constants_mach.h index 0ced0e5ea8..18b9ec581a 100644 --- a/util/mach/symbolic_constants_mach.h +++ b/util/mach/symbolic_constants_mach.h @@ -18,8 +18,8 @@ #include #include +#include -#include "base/strings/string_piece.h" #include "util/misc/symbolic_constants_common.h" namespace crashpad { @@ -42,7 +42,7 @@ std::string ExceptionToString(exception_type_t exception, //! //! \return `true` on success, `false` if \a string could not be converted as //! requested. -bool StringToException(const base::StringPiece& string, +bool StringToException(std::string_view string, StringToSymbolicConstantOptions options, exception_type_t* exception); @@ -64,7 +64,7 @@ std::string ExceptionMaskToString(exception_mask_t exception_mask, //! //! \return `true` on success, `false` if \a string could not be converted as //! requested. -bool StringToExceptionMask(const base::StringPiece& string, +bool StringToExceptionMask(std::string_view string, StringToSymbolicConstantOptions options, exception_mask_t* exception_mask); @@ -89,7 +89,7 @@ std::string ExceptionBehaviorToString(exception_behavior_t behavior, //! //! \return `true` on success, `false` if \a string could not be converted as //! requested. -bool StringToExceptionBehavior(const base::StringPiece& string, +bool StringToExceptionBehavior(std::string_view string, StringToSymbolicConstantOptions options, exception_behavior_t* behavior); @@ -111,7 +111,7 @@ std::string ThreadStateFlavorToString(thread_state_flavor_t flavor, //! //! \return `true` on success, `false` if \a string could not be converted as //! requested. -bool StringToThreadStateFlavor(const base::StringPiece& string, +bool StringToThreadStateFlavor(std::string_view string, StringToSymbolicConstantOptions options, thread_state_flavor_t* flavor); diff --git a/util/mach/symbolic_constants_mach_test.cc b/util/mach/symbolic_constants_mach_test.cc index 90468f25b6..84d7b5df8e 100644 --- a/util/mach/symbolic_constants_mach_test.cc +++ b/util/mach/symbolic_constants_mach_test.cc @@ -19,8 +19,8 @@ #include #include +#include -#include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "util/mach/mach_extensions.h" @@ -98,7 +98,7 @@ void TestSomethingToString(typename Traits::ValueType value, } template -void TestStringToSomething(const base::StringPiece& string, +void TestStringToSomething(std::string_view string, StringToSymbolicConstantOptions options, bool expect_result, typename Traits::ValueType expect_value) { @@ -144,7 +144,7 @@ struct ConvertExceptionTraits { SymbolicConstantToStringOptions options) { return ExceptionToString(value, options); } - static bool StringToSomething(const base::StringPiece& string, + static bool StringToSomething(std::string_view string, StringToSymbolicConstantOptions options, ValueType* value) { return StringToException(string, options, value); @@ -180,7 +180,7 @@ TEST(SymbolicConstantsMach, ExceptionToString) { } } -void TestStringToException(const base::StringPiece& string, +void TestStringToException(std::string_view string, StringToSymbolicConstantOptions options, bool expect_result, exception_type_t expect_value) { @@ -254,8 +254,8 @@ TEST(SymbolicConstantsMach, StringToException) { for (size_t index = 0; index < std::size(kNULTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); - base::StringPiece string(kNULTestData[index].string, - kNULTestData[index].length); + std::string_view string(kNULTestData[index].string, + kNULTestData[index].length); TestStringToException(string, options, false, 0); } } @@ -263,14 +263,14 @@ TEST(SymbolicConstantsMach, StringToException) { // Ensure that a NUL is not required at the end of the string. { SCOPED_TRACE("trailing_NUL_full"); - TestStringToException(base::StringPiece("EXC_BREAKPOINTED", 14), + TestStringToException(std::string_view("EXC_BREAKPOINTED", 14), kAllowFullName, true, EXC_BREAKPOINT); } { SCOPED_TRACE("trailing_NUL_short"); - TestStringToException(base::StringPiece("BREAKPOINTED", 10), + TestStringToException(std::string_view("BREAKPOINTED", 10), kAllowShortName, true, EXC_BREAKPOINT); @@ -318,7 +318,7 @@ struct ConvertExceptionMaskTraits { SymbolicConstantToStringOptions options) { return ExceptionMaskToString(value, options); } - static bool StringToSomething(const base::StringPiece& string, + static bool StringToSomething(std::string_view string, StringToSymbolicConstantOptions options, ValueType* value) { return StringToExceptionMask(string, options, value); @@ -361,7 +361,7 @@ TEST(SymbolicConstantsMach, ExceptionMaskToString) { "CRASH|GUARD"); } -void TestStringToExceptionMask(const base::StringPiece& string, +void TestStringToExceptionMask(std::string_view string, StringToSymbolicConstantOptions options, bool expect_result, exception_mask_t expect_value) { @@ -400,9 +400,9 @@ TEST(SymbolicConstantsMach, StringToExceptionMask) { kExceptionMaskTestData[index].exception_mask; { SCOPED_TRACE("full_name"); - base::StringPiece full_name(kExceptionMaskTestData[index].full_name); - bool has_number = full_name.find("0x", 0) != base::StringPiece::npos; - bool has_or = full_name.find('|', 0) != base::StringPiece::npos; + std::string_view full_name(kExceptionMaskTestData[index].full_name); + bool has_number = full_name.find("0x", 0) != std::string_view::npos; + bool has_or = full_name.find('|', 0) != std::string_view::npos; bool allowed_characteristics = (has_number ? (options & kAllowNumber) : true) && (has_or ? (options & kAllowOr) : true); @@ -415,9 +415,9 @@ TEST(SymbolicConstantsMach, StringToExceptionMask) { } { SCOPED_TRACE("short_name"); - base::StringPiece short_name(kExceptionMaskTestData[index].short_name); - bool has_number = short_name.find("0x", 0) != base::StringPiece::npos; - bool has_or = short_name.find('|', 0) != base::StringPiece::npos; + std::string_view short_name(kExceptionMaskTestData[index].short_name); + bool has_number = short_name.find("0x", 0) != std::string_view::npos; + bool has_or = short_name.find('|', 0) != std::string_view::npos; bool allowed_characteristics = (has_number ? (options & kAllowNumber) : true) && (has_or ? (options & kAllowOr) : true); @@ -473,8 +473,8 @@ TEST(SymbolicConstantsMach, StringToExceptionMask) { for (size_t index = 0; index < std::size(kNULTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); - base::StringPiece string(kNULTestData[index].string, - kNULTestData[index].length); + std::string_view string(kNULTestData[index].string, + kNULTestData[index].length); TestStringToExceptionMask(string, options, false, 0); } } @@ -517,14 +517,14 @@ TEST(SymbolicConstantsMach, StringToExceptionMask) { // Ensure that a NUL is not required at the end of the string. { SCOPED_TRACE("trailing_NUL_full"); - TestStringToExceptionMask(base::StringPiece("EXC_MASK_BREAKPOINTED", 19), + TestStringToExceptionMask(std::string_view("EXC_MASK_BREAKPOINTED", 19), kAllowFullName, true, EXC_MASK_BREAKPOINT); } { SCOPED_TRACE("trailing_NUL_short"); - TestStringToExceptionMask(base::StringPiece("BREAKPOINTED", 10), + TestStringToExceptionMask(std::string_view("BREAKPOINTED", 10), kAllowShortName, true, EXC_MASK_BREAKPOINT); @@ -560,7 +560,7 @@ struct ConvertExceptionBehaviorTraits { SymbolicConstantToStringOptions options) { return ExceptionBehaviorToString(value, options); } - static bool StringToSomething(const base::StringPiece& string, + static bool StringToSomething(std::string_view string, StringToSymbolicConstantOptions options, ValueType* value) { return StringToExceptionBehavior(string, options, value); @@ -598,7 +598,7 @@ TEST(SymbolicConstantsMach, ExceptionBehaviorToString) { } } -void TestStringToExceptionBehavior(const base::StringPiece& string, +void TestStringToExceptionBehavior(std::string_view string, StringToSymbolicConstantOptions options, bool expect_result, exception_behavior_t expect_value) { @@ -685,8 +685,8 @@ TEST(SymbolicConstantsMach, StringToExceptionBehavior) { for (size_t index = 0; index < std::size(kNULTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); - base::StringPiece string(kNULTestData[index].string, - kNULTestData[index].length); + std::string_view string(kNULTestData[index].string, + kNULTestData[index].length); TestStringToExceptionBehavior(string, options, false, 0); } } @@ -731,21 +731,21 @@ TEST(SymbolicConstantsMach, StringToExceptionBehavior) { // Ensure that a NUL is not required at the end of the string. { SCOPED_TRACE("trailing_NUL_full"); - TestStringToExceptionBehavior(base::StringPiece("EXCEPTION_DEFAULTS", 17), + TestStringToExceptionBehavior(std::string_view("EXCEPTION_DEFAULTS", 17), kAllowFullName, true, EXCEPTION_DEFAULT); } { SCOPED_TRACE("trailing_NUL_short"); - TestStringToExceptionBehavior(base::StringPiece("DEFAULTS", 7), + TestStringToExceptionBehavior(std::string_view("DEFAULTS", 7), kAllowShortName, true, EXCEPTION_DEFAULT); } { SCOPED_TRACE("trailing_NUL_full_mach"); - base::StringPiece string("EXCEPTION_DEFAULT|MACH_EXCEPTION_CODESS", 38); + std::string_view string("EXCEPTION_DEFAULT|MACH_EXCEPTION_CODESS", 38); TestStringToExceptionBehavior(string, kAllowFullName | kAllowOr, true, @@ -753,7 +753,7 @@ TEST(SymbolicConstantsMach, StringToExceptionBehavior) { } { SCOPED_TRACE("trailing_NUL_short_mach"); - TestStringToExceptionBehavior(base::StringPiece("DEFAULT|MACH_", 12), + TestStringToExceptionBehavior(std::string_view("DEFAULT|MACH_", 12), kAllowShortName | kAllowOr, true, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES); @@ -820,7 +820,7 @@ struct ConvertThreadStateFlavorTraits { SymbolicConstantToStringOptions options) { return ThreadStateFlavorToString(value, options); } - static bool StringToSomething(const base::StringPiece& string, + static bool StringToSomething(std::string_view string, StringToSymbolicConstantOptions options, ValueType* value) { return StringToThreadStateFlavor(string, options, value); @@ -870,7 +870,7 @@ TEST(SymbolicConstantsMach, ThreadStateFlavorToString) { } } -void TestStringToThreadStateFlavor(const base::StringPiece& string, +void TestStringToThreadStateFlavor(std::string_view string, StringToSymbolicConstantOptions options, bool expect_result, thread_state_flavor_t expect_value) { @@ -1021,8 +1021,8 @@ TEST(SymbolicConstantsMach, StringToThreadStateFlavor) { for (size_t index = 0; index < std::size(kNULTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); - base::StringPiece string(kNULTestData[index].string, - kNULTestData[index].length); + std::string_view string(kNULTestData[index].string, + kNULTestData[index].length); TestStringToThreadStateFlavor(string, options, false, 0); } } @@ -1030,27 +1030,25 @@ TEST(SymbolicConstantsMach, StringToThreadStateFlavor) { // Ensure that a NUL is not required at the end of the string. { SCOPED_TRACE("trailing_NUL_full"); - TestStringToThreadStateFlavor(base::StringPiece("THREAD_STATE_NONER", 17), + TestStringToThreadStateFlavor(std::string_view("THREAD_STATE_NONER", 17), kAllowFullName, true, THREAD_STATE_NONE); } { SCOPED_TRACE("trailing_NUL_short"); - TestStringToThreadStateFlavor(base::StringPiece("NONER", 4), - kAllowShortName, - true, - THREAD_STATE_NONE); + TestStringToThreadStateFlavor( + std::string_view("NONER", 4), kAllowShortName, true, THREAD_STATE_NONE); } { SCOPED_TRACE("trailing_NUL_full_new"); - base::StringPiece string("THREAD_STATE_FLAVOR_LIST_NEWS", 28); + std::string_view string("THREAD_STATE_FLAVOR_LIST_NEWS", 28); TestStringToThreadStateFlavor( string, kAllowFullName, true, THREAD_STATE_FLAVOR_LIST_NEW); } { SCOPED_TRACE("trailing_NUL_short_new"); - TestStringToThreadStateFlavor(base::StringPiece("FLAVOR_LIST_NEWS", 15), + TestStringToThreadStateFlavor(std::string_view("FLAVOR_LIST_NEWS", 15), kAllowShortName, true, THREAD_STATE_FLAVOR_LIST_NEW); diff --git a/util/misc/from_pointer_cast.h b/util/misc/from_pointer_cast.h index becf08f0ca..64ed96f738 100644 --- a/util/misc/from_pointer_cast.h +++ b/util/misc/from_pointer_cast.h @@ -15,6 +15,7 @@ #ifndef CRASHPAD_UTIL_MISC_FROM_POINTER_CAST_H_ #define CRASHPAD_UTIL_MISC_FROM_POINTER_CAST_H_ +#include #include #include diff --git a/util/misc/initialization_state.h b/util/misc/initialization_state.h index be78d8c54f..b6d313e756 100644 --- a/util/misc/initialization_state.h +++ b/util/misc/initialization_state.h @@ -56,6 +56,7 @@ class InitializationState { }; InitializationState() : state_(kStateUninitialized) {} + explicit InitializationState(State state) : state_(state) {} InitializationState(const InitializationState&) = delete; InitializationState& operator=(const InitializationState&) = delete; diff --git a/util/misc/initialization_state_test.cc b/util/misc/initialization_state_test.cc index f36811730c..d6b96cedb7 100644 --- a/util/misc/initialization_state_test.cc +++ b/util/misc/initialization_state_test.cc @@ -18,6 +18,7 @@ #include +#include "base/compiler_specific.h" #include "base/memory/free_deleter.h" #include "gtest/gtest.h" @@ -59,6 +60,10 @@ TEST(InitializationState, InitializationState) { // buffer that’s still valid and its destructor was called directly, this // approximates use-after-free without risking that the memory formerly used // for the InitializationState object has been repurposed. + + // (Though this is still UB and MSan does not like this) + MSAN_UNPOISON(initialization_state, sizeof(*initialization_state)); + EXPECT_FALSE(initialization_state->is_uninitialized()); EXPECT_FALSE(initialization_state->is_valid()); } diff --git a/util/misc/lexing.cc b/util/misc/lexing.cc index 36d239c951..f08cb259a7 100644 --- a/util/misc/lexing.cc +++ b/util/misc/lexing.cc @@ -19,18 +19,18 @@ #include #include +#include #include "base/strings/string_number_conversions.h" -#include "base/strings/string_piece.h" #include "base/strings/string_util.h" namespace crashpad { namespace { -#define MAKE_ADAPTER(type, function) \ - bool ConvertStringToNumber(const base::StringPiece& input, type* value) { \ - return function(input, value); \ +#define MAKE_ADAPTER(type, function) \ + bool ConvertStringToNumber(std::string_view input, type* value) { \ + return function(input, value); \ } MAKE_ADAPTER(int, base::StringToInt) MAKE_ADAPTER(unsigned int, base::StringToUint) @@ -58,8 +58,7 @@ bool AdvancePastNumber(const char** input, T* value) { while (base::IsAsciiDigit((*input)[length])) { ++length; } - bool success = - ConvertStringToNumber(base::StringPiece(*input, length), value); + bool success = ConvertStringToNumber(std::string_view(*input, length), value); if (success) { *input += length; return true; diff --git a/util/misc/no_cfi_icall.h b/util/misc/no_cfi_icall.h index bb3e594068..7410026dc6 100644 --- a/util/misc/no_cfi_icall.h +++ b/util/misc/no_cfi_icall.h @@ -18,6 +18,7 @@ #include #include +#include "base/compiler_specific.h" #include "build/build_config.h" #if BUILDFLAG(IS_WIN) @@ -28,24 +29,6 @@ namespace crashpad { namespace { -// Sanitizers annotations. -#if defined(__has_attribute) -#if __has_attribute(no_sanitize) -#define NO_SANITIZE(what) __attribute__((no_sanitize(what))) -#endif -#endif -#if !defined(NO_SANITIZE) -#define NO_SANITIZE(what) -#endif - -// DISABLE_CFI_ICALL -- Disable Control Flow Integrity indirect call checks. -#if BUILDFLAG(IS_WIN) -// Windows also needs __declspec(guard(nocf)). -#define DISABLE_CFI_ICALL NO_SANITIZE("cfi-icall") __declspec(guard(nocf)) -#else -#define DISABLE_CFI_ICALL NO_SANITIZE("cfi-icall") -#endif - template struct FunctorTraits; diff --git a/util/misc/uuid.cc b/util/misc/uuid.cc index ecb8a388ab..d446cba4d8 100644 --- a/util/misc/uuid.cc +++ b/util/misc/uuid.cc @@ -61,9 +61,9 @@ void UUID::InitializeFromBytes(const uint8_t* bytes_ptr) { // TODO(crbug.com/40284755): This span construction is unsound. The caller // should provide a span instead of an unbounded pointer. base::span bytes(bytes_ptr, sizeof(UUID)); - data_1 = base::numerics::U32FromBigEndian(bytes.subspan<0u, 4u>()); - data_2 = base::numerics::U16FromBigEndian(bytes.subspan<4u, 2u>()); - data_3 = base::numerics::U16FromBigEndian(bytes.subspan<6u, 2u>()); + data_1 = base::U32FromBigEndian(bytes.subspan<0u, 4u>()); + data_2 = base::U16FromBigEndian(bytes.subspan<4u, 2u>()); + data_3 = base::U16FromBigEndian(bytes.subspan<6u, 2u>()); #if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L std::ranges::copy(bytes.subspan<8u, 2u>(), data_4); std::ranges::copy(bytes.subspan<10u, 6u>(), data_5); @@ -73,7 +73,7 @@ void UUID::InitializeFromBytes(const uint8_t* bytes_ptr) { #endif } -bool UUID::InitializeFromString(const base::StringPiece& string) { +bool UUID::InitializeFromString(std::string_view string) { if (string.length() != 36) return false; @@ -103,7 +103,7 @@ bool UUID::InitializeFromString(const base::StringPiece& string) { } #if BUILDFLAG(IS_WIN) -bool UUID::InitializeFromString(const std::wstring_view& string) { +bool UUID::InitializeFromString(std::wstring_view string) { return InitializeFromString(base::WideToUTF8(string)); } #endif diff --git a/util/misc/uuid.h b/util/misc/uuid.h index 3e401ab00f..a441b86263 100644 --- a/util/misc/uuid.h +++ b/util/misc/uuid.h @@ -20,7 +20,6 @@ #include #include -#include "base/strings/string_piece.h" #include "build/build_config.h" #if BUILDFLAG(IS_WIN) @@ -63,9 +62,9 @@ struct UUID { //! \return `true` if the string was formatted correctly and the object has //! been initialized with the data. `false` if the string could not be //! parsed, with the object state untouched. - bool InitializeFromString(const base::StringPiece& string); + bool InitializeFromString(std::string_view string); #if BUILDFLAG(IS_WIN) || DOXYGEN - bool InitializeFromString(const std::wstring_view& string); + bool InitializeFromString(std::wstring_view string); #endif // BUILDFLAG(IS_WIN) //! \brief Initializes the %UUID using a standard system facility to generate diff --git a/util/misc/uuid_test.cc b/util/misc/uuid_test.cc index 0d7b7302fe..5dfe24fc3f 100644 --- a/util/misc/uuid_test.cc +++ b/util/misc/uuid_test.cc @@ -19,6 +19,7 @@ #include #include +#include #include "base/format_macros.h" #include "base/scoped_generic.h" @@ -233,7 +234,7 @@ TEST(UUID, FromString) { EXPECT_EQ(uuid.ToString(), "5762c15d-50b5-4171-a2e9-7429c9ec6cab"); #if BUILDFLAG(IS_WIN) - // Test accepting a StringPiece16 via L"" literals on Windows. + // Test accepting a std::u16string_view via L"" literals on Windows. EXPECT_TRUE( uuid.InitializeFromString(L"F32E5BDC-2681-4C73-A4E6-444FFD44B444")); EXPECT_EQ(uuid.ToString(), "f32e5bdc-2681-4c73-a4e6-444ffd44b444"); diff --git a/util/net/http_transport_test_server.cc b/util/net/http_transport_test_server.cc index 2d6e69b838..e4df221b3f 100644 --- a/util/net/http_transport_test_server.cc +++ b/util/net/http_transport_test_server.cc @@ -92,6 +92,18 @@ int HttpTransportTestServerMain(int argc, char* argv[]) { } to_stdout += "\r\n"; to_stdout += req.body; + if (req.is_multipart_form_data()) { + std::string boundary; + httplib::detail::parse_multipart_boundary( + req.get_header_value("Content-Type"), boundary); + for (const auto& part : req.form.fields) { + to_stdout += "--" + boundary + "\r\n"; + to_stdout += "Content-Disposition: form-data; name=\"" + + part.first + "\"\r\n\r\n"; + to_stdout += part.second.content + "\r\n"; + } + to_stdout += "--" + boundary + "--\r\n"; + } server->stop(); }); diff --git a/util/posix/scoped_mmap.cc b/util/posix/scoped_mmap.cc index b9b4248096..ee76c8569c 100644 --- a/util/posix/scoped_mmap.cc +++ b/util/posix/scoped_mmap.cc @@ -24,19 +24,14 @@ #include "base/numerics/safe_conversions.h" #include "base/numerics/safe_math.h" #include "build/build_config.h" -#include "build/chromeos_buildflags.h" -// TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is -// complete. -#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) +#if BUILDFLAG(IS_LINUX) #include "third_party/lss/lss.h" #endif namespace { -// TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is -// complete. -#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) +#if BUILDFLAG(IS_LINUX) void* CallMmap(void* addr, size_t len, int prot, diff --git a/util/posix/signals.cc b/util/posix/signals.cc index e24bb60d7a..58f957c40d 100644 --- a/util/posix/signals.cc +++ b/util/posix/signals.cc @@ -278,14 +278,8 @@ bool Signals::WillSignalReraiseAutonomously(const siginfo_t* siginfo) { } // static -void Signals::RestoreHandlerAndReraiseSignalOnReturn( - const siginfo_t* siginfo, - const struct sigaction* old_action) { - // Failures in this function should _exit(kFailureExitCode). This is a quick - // and quiet failure. This function runs in signal handler context, and it’s - // difficult to safely be loud from a signal handler. - constexpr int kFailureExitCode = 191; - +bool Signals::RestoreOrResetHandler(int sig, + const struct sigaction* old_action) { struct sigaction default_action; sigemptyset(&default_action.sa_mask); default_action.sa_flags = 0; @@ -297,9 +291,25 @@ void Signals::RestoreHandlerAndReraiseSignalOnReturn( // Try to restore restore_action. If that fails and restore_action was // old_action, the problem may have been that old_action was bogus, so try to // set the default action. - const int sig = siginfo->si_signo; if (sigaction(sig, restore_action, nullptr) != 0 && old_action && sigaction(sig, &default_action, nullptr) != 0) { + return false; + } + return true; +} + +// static +void Signals::RestoreHandlerAndReraiseSignalOnReturn( + const siginfo_t* siginfo, + const struct sigaction* old_action) { + // Failures in this function should _exit(kFailureExitCode). This is a quick + // and quiet failure. This function runs in signal handler context, and it’s + // difficult to safely be loud from a signal handler. + constexpr int kFailureExitCode = 191; + + const int sig = siginfo->si_signo; + + if (!RestoreOrResetHandler(sig, old_action)) { _exit(kFailureExitCode); } diff --git a/util/posix/signals.h b/util/posix/signals.h index e12ad72b5b..934b31284b 100644 --- a/util/posix/signals.h +++ b/util/posix/signals.h @@ -196,6 +196,24 @@ class Signals { //! \note This function is safe to call from a signal handler. static bool WillSignalReraiseAutonomously(const siginfo_t* siginfo); + //! \brief Restores a previous signal action or reinstalls the default signal + //! handler for a given signal. + //! + //! Attempts to reinstate the action given by \a old_action and, in case of + //! failure or if \a old_actiono is `nullptr`, resets the handler for \a sig + //! to the default action. + //! + //! \param[in] sig The signal to manage. + //! \param[in] old_action The previous action for the signal, which will be + //! re-established as the signal’s action. May be `nullptr`, which directs + //! the default action for the signal to be used. + //! + //! \return `true` on success, `false` if `sigaction()` fails. + //! + //! \note This function is safe to call from a signal handler. + static bool RestoreOrResetHandler(int sig, + const struct sigaction* old_action); + //! \brief Restores a previous signal action and arranges to re-raise a signal //! on return from a signal handler. //! diff --git a/util/posix/symbolic_constants_posix.cc b/util/posix/symbolic_constants_posix.cc index 95cb5dcefd..58c9057fbd 100644 --- a/util/posix/symbolic_constants_posix.cc +++ b/util/posix/symbolic_constants_posix.cc @@ -19,6 +19,7 @@ #include #include +#include #include "base/strings/stringprintf.h" #include "build/build_config.h" @@ -170,14 +171,14 @@ std::string SignalToString(int signal, return base::StringPrintf("%s%s", kSigPrefix, signal_name); } -bool StringToSignal(const base::StringPiece& string, +bool StringToSignal(std::string_view string, StringToSymbolicConstantOptions options, int* signal) { if ((options & kAllowFullName) || (options & kAllowShortName)) { bool can_match_full = (options & kAllowFullName) && string.substr(0, strlen(kSigPrefix)).compare(kSigPrefix) == 0; - base::StringPiece short_string = + std::string_view short_string = can_match_full ? string.substr(strlen(kSigPrefix)) : string; for (int index = 0; index < implicit_cast(std::size(kSignalNames)); ++index) { diff --git a/util/posix/symbolic_constants_posix.h b/util/posix/symbolic_constants_posix.h index 9b096cb755..0a38d7d6f9 100644 --- a/util/posix/symbolic_constants_posix.h +++ b/util/posix/symbolic_constants_posix.h @@ -16,8 +16,8 @@ #define CRASHPAD_UTIL_POSIX_SYMBOLIC_CONSTANTS_POSIX_H_ #include +#include -#include "base/strings/string_piece.h" #include "util/misc/symbolic_constants_common.h" namespace crashpad { @@ -39,7 +39,7 @@ std::string SignalToString(int signal, SymbolicConstantToStringOptions options); //! //! \return `true` on success, `false` if \a string could not be converted as //! requested. -bool StringToSignal(const base::StringPiece& string, +bool StringToSignal(std::string_view string, StringToSymbolicConstantOptions options, int* signal); diff --git a/util/posix/symbolic_constants_posix_test.cc b/util/posix/symbolic_constants_posix_test.cc index 6c7a5ba742..e2efd2ec80 100644 --- a/util/posix/symbolic_constants_posix_test.cc +++ b/util/posix/symbolic_constants_posix_test.cc @@ -18,8 +18,8 @@ #include #include +#include -#include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" @@ -126,7 +126,7 @@ TEST(SymbolicConstantsPOSIX, SignalToString) { } } -void TestStringToSignal(const base::StringPiece& string, +void TestStringToSignal(std::string_view string, StringToSymbolicConstantOptions options, bool expect_result, int expect_value) { @@ -222,8 +222,8 @@ TEST(SymbolicConstantsPOSIX, StringToSignal) { for (size_t index = 0; index < std::size(kNULTestData); ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); - base::StringPiece string(kNULTestData[index].string, - kNULTestData[index].length); + std::string_view string(kNULTestData[index].string, + kNULTestData[index].length); TestStringToSignal(string, options, false, 0); } } @@ -232,12 +232,12 @@ TEST(SymbolicConstantsPOSIX, StringToSignal) { { SCOPED_TRACE("trailing_NUL_full"); TestStringToSignal( - base::StringPiece("SIGBUST", 6), kAllowFullName, true, SIGBUS); + std::string_view("SIGBUST", 6), kAllowFullName, true, SIGBUS); } { SCOPED_TRACE("trailing_NUL_short"); TestStringToSignal( - base::StringPiece("BUST", 3), kAllowShortName, true, SIGBUS); + std::string_view("BUST", 3), kAllowShortName, true, SIGBUS); } } diff --git a/util/stdlib/strlcpy.h b/util/stdlib/strlcpy.h index d6a8a43ab7..9f60b45916 100644 --- a/util/stdlib/strlcpy.h +++ b/util/stdlib/strlcpy.h @@ -16,6 +16,7 @@ #define CRASHPAD_UTIL_STDLIB_STRLCPY_H_ #include +#include namespace crashpad { diff --git a/util/stream/log_output_stream.cc b/util/stream/log_output_stream.cc index af4e07ad54..f633d86806 100644 --- a/util/stream/log_output_stream.cc +++ b/util/stream/log_output_stream.cc @@ -39,7 +39,7 @@ bool LogOutputStream::Write(const uint8_t* data, size_t size) { DCHECK(!flushed_); static constexpr char kBeginMessage[] = "-----BEGIN CRASHPAD MINIDUMP-----"; - if (output_count_ == 0 && WriteToLog(kBeginMessage) < 0) { + if (output_count_ == 0 && !WriteToLog(kBeginMessage)) { return false; } @@ -69,11 +69,7 @@ bool LogOutputStream::WriteBuffer() { return false; } - int result = WriteToLog(buffer_.c_str()); - if (result < 0) { - if (result == -EAGAIN) { - WriteToLog(kAbortMessage); - } + if (!WriteToLog(buffer_.c_str())) { flush_needed_ = false; return false; } @@ -93,7 +89,7 @@ bool LogOutputStream::Flush() { flushed_ = true; static constexpr char kEndMessage[] = "-----END CRASHPAD MINIDUMP-----"; - if (!WriteBuffer() || WriteToLog(kEndMessage) < 0) { + if (!WriteBuffer() || !WriteToLog(kEndMessage)) { result = false; } } diff --git a/util/stream/log_output_stream.h b/util/stream/log_output_stream.h index 500ffa32c4..c12b69ba5c 100644 --- a/util/stream/log_output_stream.h +++ b/util/stream/log_output_stream.h @@ -38,8 +38,8 @@ class LogOutputStream : public OutputStreamInterface { //! //! \param buf the buffer to write to the log. More bytes than are in |buf| //! may be written, e.g. to convey metadata. - //! \return the number of bytes written, or a negative error code. - virtual int Log(const char* buf) = 0; + //! \return `true` if the |buf| was successfully written. + virtual bool Log(const char* buf) = 0; //! \brief Returns the maximum number of bytes to allow writing to this log. virtual size_t OutputCap() = 0; diff --git a/util/stream/log_output_stream_test.cc b/util/stream/log_output_stream_test.cc index 74213d96df..2a835e80c6 100644 --- a/util/stream/log_output_stream_test.cc +++ b/util/stream/log_output_stream_test.cc @@ -36,11 +36,11 @@ class LogOutputStreamTestDelegate final : public LogOutputStream::Delegate { : logging_destination_(logging_destination) {} ~LogOutputStreamTestDelegate() override = default; - int Log(const char* buf) override { + bool Log(const char* buf) override { size_t len = strnlen(buf, kLineBufferSize + 1); EXPECT_LE(len, kLineBufferSize); logging_destination_->append(buf, len); - return static_cast(len); + return true; } size_t OutputCap() override { return kOutputCap; } diff --git a/util/synchronization/scoped_spin_guard.h b/util/synchronization/scoped_spin_guard.h index 0814192224..6e445b85d3 100644 --- a/util/synchronization/scoped_spin_guard.h +++ b/util/synchronization/scoped_spin_guard.h @@ -92,7 +92,7 @@ class ScopedSpinGuard final { SleepNanoseconds(kSpinGuardSleepTimeNanos); } - NOTREACHED_IN_MIGRATION(); + NOTREACHED(); } ~ScopedSpinGuard() { diff --git a/util/synchronization/semaphore_posix.cc b/util/synchronization/semaphore_posix.cc index 1aa4a64942..ea20d3ca74 100644 --- a/util/synchronization/semaphore_posix.cc +++ b/util/synchronization/semaphore_posix.cc @@ -15,10 +15,10 @@ #include "util/synchronization/semaphore.h" #include -#include #include #include +#include #include "base/check_op.h" #include "base/logging.h" @@ -43,7 +43,7 @@ void Semaphore::Wait() { bool Semaphore::TimedWait(double seconds) { DCHECK_GE(seconds, 0.0); - if (isinf(seconds)) { + if (std::isinf(seconds)) { Wait(); return true; } @@ -81,7 +81,7 @@ void Semaphore::Wait() { bool Semaphore::TimedWait(double seconds) { DCHECK_GE(seconds, 0.0); - if (isinf(seconds)) { + if (std::isinf(seconds)) { Wait(); return true; } diff --git a/util/thread/thread.h b/util/thread/thread.h index 8c82e86964..2736abc4df 100644 --- a/util/thread/thread.h +++ b/util/thread/thread.h @@ -49,7 +49,7 @@ class Thread { #if BUILDFLAG(IS_APPLE) //! \brief Returns the thread id of the Thread pthread_t. - uint64_t GetThreadIdForTesting(); + static uint64_t GetThreadIdForTesting(); #endif // BUILDFLAG(IS_APPLE) private: diff --git a/util/thread/thread_posix.cc b/util/thread/thread_posix.cc index c5000c7b94..b510b34bdc 100644 --- a/util/thread/thread_posix.cc +++ b/util/thread/thread_posix.cc @@ -37,6 +37,7 @@ void Thread::Join() { } #if BUILDFLAG(IS_APPLE) +// static uint64_t Thread::GetThreadIdForTesting() { uint64_t thread_self; errno = pthread_threadid_np(pthread_self(), &thread_self); diff --git a/util/win/registration_protocol_win_test.cc b/util/win/registration_protocol_win_test.cc index 9ce265ac1a..4538d23c96 100644 --- a/util/win/registration_protocol_win_test.cc +++ b/util/win/registration_protocol_win_test.cc @@ -98,8 +98,7 @@ void CheckAce(PACL acl, mask = label_ace->Mask; } break; default: - NOTREACHED_IN_MIGRATION(); - break; + NOTREACHED(); } ASSERT_EQ(check_mask, mask); diff --git a/util/win/scoped_registry_key.h b/util/win/scoped_registry_key.h index 933e947bfc..0ed9992159 100644 --- a/util/win/scoped_registry_key.h +++ b/util/win/scoped_registry_key.h @@ -31,4 +31,4 @@ using ScopedRegistryKey = } // namespace crashpad -#endif // CRASHPAD_UTIL_WIN_SCOPED_REGISTRY_KEY_H_ \ No newline at end of file +#endif // CRASHPAD_UTIL_WIN_SCOPED_REGISTRY_KEY_H_