Skip to content

Commit 5c84039

Browse files
etrclaude
andcommitted
Merge chore/unworked-minor-issues: sweep all unworked minor review findings
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NpysYDDJac63yz2mZKKiDf
2 parents d6ccb44 + 3540386 commit 5c84039

179 files changed

Lines changed: 4830 additions & 1915 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/codeql-analysis.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,14 @@ jobs:
6363
- run: git checkout HEAD^2
6464
if: ${{ github.event_name == 'pull_request' }}
6565

66-
- name: Install libmicrohttpd dependency
66+
- name: Fetch libmicrohttpd from cache
67+
id: cache-libmicrohttpd
68+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
69+
with:
70+
path: libmicrohttpd-1.0.3
71+
key: ${{ runner.os }}-libmicrohttpd-1.0.3-codeql
72+
73+
- name: Build libmicrohttpd dependency (if not cached)
6774
# Supply-chain: libmicrohttpd-1.0.3.tar.gz is sha256-pinned. The digest
6875
# must match the value used in verify-build.yml; both workflows track the
6976
# same release. To rotate the libmicrohttpd version, update both the URL
@@ -79,7 +86,12 @@ jobs:
7986
cd libmicrohttpd-1.0.3 ;
8087
./configure --disable-examples ;
8188
make ;
82-
sudo make install ;
89+
timeout-minutes: 30
90+
if: steps.cache-libmicrohttpd.outputs.cache-hit != 'true'
91+
92+
- name: Install libmicrohttpd dependency
93+
run: cd libmicrohttpd-1.0.3 ; sudo make install ;
94+
timeout-minutes: 30
8395

8496
# Initializes the CodeQL tools for scanning.
8597
- name: Initialize CodeQL
@@ -98,6 +110,8 @@ jobs:
98110
./bootstrap ;
99111
./configure --enable-same-directory-build ;
100112
make ;
113+
timeout-minutes: 30
101114

102115
- name: Perform CodeQL Analysis
103116
uses: github/codeql-action/analyze@411c4c9a36b3fca4d674f06b6396b2c6d23522c6 # v3.36.3
117+
timeout-minutes: 30

.github/workflows/verify-build.yml

Lines changed: 89 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ jobs:
234234
compiler-family: gcc
235235
c-compiler: gcc-14
236236
cc-compiler: g++-14
237-
debug: nodebug # configure step passes --enable-debug unconditionally for valgrind-* lanes
237+
debug: nodebug
238238
coverage: nocoverage
239239
shell: bash
240240
- test-group: extra
@@ -244,7 +244,7 @@ jobs:
244244
compiler-family: gcc
245245
c-compiler: gcc-14
246246
cc-compiler: g++-14
247-
debug: nodebug # configure step passes --enable-debug unconditionally for valgrind-* lanes
247+
debug: nodebug
248248
coverage: nocoverage
249249
shell: bash
250250
# Test build without digest auth support (issue #232)
@@ -674,13 +674,27 @@ jobs:
674674
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
675675
with:
676676
path: ${{ github.workspace }}/libcxx-msan
677-
key: ${{ matrix.os }}-${{ matrix.c-compiler }}-libcxx-msan-origins-v1
677+
key: ${{ matrix.os }}-${{ matrix.c-compiler }}-libcxx-msan-llvmorg-18.1.8-origins-v1
678678
if: ${{ matrix.build-type == 'msan' }}
679679

680680
- name: Build instrumented libc++ (msan lane, if not cached)
681+
# Supply-chain: llvmorg-18.1.8 is a mutable git tag, not an immutable
682+
# commit SHA, so a post-clone SHA assertion guards against the tag
683+
# being force-moved upstream (CWE-829). LLVM_SHA was obtained via
684+
# `git ls-remote https://github.com/llvm/llvm-project refs/tags/llvmorg-18.1.8`
685+
# at pin time; to rotate the tag, update both the --branch value below
686+
# and LLVM_SHA in the same PR. Mirrors the IWYU SHA-pin pattern above.
681687
run: |
688+
set -euo pipefail
682689
sudo apt-get install -y ninja-build cmake ;
690+
LLVM_SHA=3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff ;
683691
git clone --depth 1 --branch llvmorg-18.1.8 https://github.com/llvm/llvm-project.git llvm-project ;
692+
HEAD_SHA="$(git -C llvm-project rev-parse --verify HEAD)" ;
693+
if [ "${HEAD_SHA}" != "${LLVM_SHA}" ]; then
694+
echo "LLVM SHA mismatch: expected ${LLVM_SHA}, got ${HEAD_SHA}" >&2 ;
695+
exit 1 ;
696+
fi
697+
echo "llvm-project pinned at SHA: ${HEAD_SHA}" ;
684698
cmake -G Ninja -S llvm-project/runtimes -B build-libcxx-msan \
685699
-DCMAKE_C_COMPILER=clang-18 \
686700
-DCMAKE_CXX_COMPILER=clang++-18 \
@@ -754,6 +768,11 @@ jobs:
754768
- name: Fetch libmicrohttpd from cache (msan lane)
755769
id: cache-libmicrohttpd-msan
756770
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
771+
# NOTE: the '-v1' suffix below must be bumped manually whenever the
772+
# CFLAGS/LDFLAGS in the "Build instrumented libmicrohttpd" step change
773+
# -- this key does not otherwise encode them, so a flag change alone
774+
# would silently keep reusing a stale (differently-instrumented)
775+
# cached artifact.
757776
with:
758777
path: libmicrohttpd-1.0.3
759778
key: ${{ matrix.os }}-${{ matrix.c-compiler }}-libmicrohttpd-1.0.3-msan-v1
@@ -772,7 +791,7 @@ jobs:
772791
export CFLAGS='-fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer -g -O1' ;
773792
export LDFLAGS='-fsanitize=memory' ;
774793
./configure --disable-examples --disable-https ;
775-
make ;
794+
make -j$(nproc) ;
776795
if: ${{ matrix.build-type == 'msan' && steps.cache-libmicrohttpd-msan.outputs.cache-hit != 'true' }}
777796

778797
- name: Install libmicrohttpd
@@ -911,14 +930,24 @@ jobs:
911930
- name: Run valgrind-lane structural gate
912931
# TASK-088: asserts the Valgrind CI lane is wired correctly (valid YAML,
913932
# valgrind matrix entry present, no --disable-valgrind-{helgrind,drd,
914-
# sgcheck} flags remain, `make check-valgrind` still invoked, and the
915-
# results-print step surfaces helgrind + drd logs). Valgrind is
933+
# sgcheck} flags remain, `make "check-valgrind-${TOOL}"` (per-tool
934+
# AX_VALGRIND_CHECK target) invoked, and the results-print step
935+
# surfaces helgrind + drd logs). Valgrind is
916936
# Linux-only, so the actual race-detector run + finding-triage happen on
917937
# the PR's Linux lane; this gate only guards the wiring. Mirrors the
918938
# check-msan-lane.sh gate idiom.
919939
run: ./scripts/check-valgrind-lane.sh
920940
if: ${{ matrix.build-type == 'lint' && matrix.os-type == 'ubuntu' }}
921941

942+
- name: Run DR-008 stress-lane structural gate
943+
# TASK-092: asserts the two DR-008 concurrency/deadlock stress-gate
944+
# steps (route_table_concurrency under TSan, stop()-from-handler
945+
# deadlock contract) stay wired correctly -- present, correctly `if:`
946+
# gated, and timeout-boxed. See test/PERFORMANCE.md ("CI wiring —
947+
# DR-008 stress gates"). Mirrors the check-msan-lane.sh gate idiom.
948+
run: ./scripts/check-dr008-lanes.sh
949+
if: ${{ matrix.build-type == 'lint' && matrix.os-type == 'ubuntu' }}
950+
922951
- name: Run parallel-install-lane structural gate
923952
# TASK-089: asserts the parallel-install CI gate is wired correctly
924953
# (valid YAML, a matrix entry opts in via parallel-install: check, a step
@@ -1158,8 +1187,27 @@ jobs:
11581187
export MSAN_OPTIONS="${MSAN_OPTIONS}:suppressions=$(pwd)/../test/msan.supp" ;
11591188
fi
11601189
# Derive the unit/ subset from the test Makefile so newly-added
1161-
# unit tests are covered automatically.
1162-
MSAN_TESTS="$(grep -oE '^[A-Za-z0-9_]+_SOURCES[[:space:]]*=[[:space:]]*unit/' ../test/Makefile.am | sed -E 's/_SOURCES.*//' | tr '\n' ' ')" ;
1190+
# unit tests are covered automatically, then further exclude any
1191+
# unit/-sourced target whose test file transitively links curl
1192+
# (e.g. via curl_easy_perform() round-trips) -- curl cannot
1193+
# practically be msan-instrumented, so its uninstrumented output
1194+
# would surface as false use-of-uninitialized-value reports.
1195+
# NOTE: this only matches a target's _SOURCES entry when it is
1196+
# written on a single line (`<target>_SOURCES = unit/<file>.cpp`);
1197+
# a future multi-line _SOURCES assignment, or a unit/ target whose
1198+
# first source is not itself under unit/, would be silently
1199+
# dropped without failing CI (the empty-set guard below only
1200+
# catches total loss, not partial narrowing).
1201+
MSAN_TESTS="" ;
1202+
while IFS= read -r src_line; do
1203+
target="$(printf '%s' "$src_line" | sed -E 's/_SOURCES.*//')" ;
1204+
source_rel="$(printf '%s' "$src_line" | sed -E 's/^[A-Za-z0-9_]+_SOURCES[[:space:]]*=[[:space:]]*//')" ;
1205+
if grep -q 'curl/curl.h' "../test/${source_rel}" 2>/dev/null; then
1206+
echo "msan: excluding ${target} (${source_rel} includes curl/curl.h)" ;
1207+
continue ;
1208+
fi
1209+
MSAN_TESTS="${MSAN_TESTS}${target} " ;
1210+
done <<< "$(grep -E '^[A-Za-z0-9_]+_SOURCES[[:space:]]*=[[:space:]]*unit/' ../test/Makefile.am)" ;
11631211
if [ -z "$MSAN_TESTS" ]; then echo "ERROR: msan: derived empty test set — grep pattern found no unit/ targets in test/Makefile.am" >&2; exit 1; fi ;
11641212
make check TESTS="$MSAN_TESTS" ;
11651213
else
@@ -1178,7 +1226,7 @@ jobs:
11781226
# Reuses the same TSAN_OPTIONS suppression file as "Run tests" to
11791227
# mask the benign libstdc++ std::ctype narrow-cache race; any
11801228
# libhttpserver-internal race stays fatal.
1181-
timeout-minutes: 2
1229+
timeout-minutes: 5
11821230
run: |
11831231
cd build ;
11841232
export TSAN_OPTIONS="suppressions=$(pwd)/../test/tsan.supp" ;
@@ -1196,8 +1244,36 @@ jobs:
11961244
# is the safety net; a clean zero-exit child would be a regression.
11971245
timeout-minutes: 3
11981246
run: |
1247+
set -o pipefail ;
11991248
cd build ;
1200-
make -C test check-stop-from-handler ;
1249+
make -C test check-stop-from-handler 2>&1 | tee test/stop_from_handler.log ;
1250+
if: ${{ matrix.os-type == 'ubuntu' && matrix.test-group == 'basic' && matrix.c-compiler == 'gcc' && matrix.debug == 'nodebug' && matrix.linking == 'dynamic' && matrix.build-type == 'classic' }}
1251+
1252+
- name: Verify stop()-from-handler contract fired (TASK-092)
1253+
# TASK-092: guards against Sub-test B
1254+
# (stop_from_handler_deadlocks_as_documented) silently reporting
1255+
# [SKIP] instead of exercising the DR-008 deadlock contract -- mirrors
1256+
# the "Verify [SKIP] markers fire on tls-no-cli lane (TASK-076)"
1257+
# grep-the-log pattern.
1258+
run: |
1259+
set -euo pipefail
1260+
cd build
1261+
LOG=test/stop_from_handler.log
1262+
if [ ! -f "$LOG" ]; then
1263+
echo "ERROR: $LOG missing — check-stop-from-handler did not run" >&2
1264+
exit 1
1265+
fi
1266+
if grep -q '^\[SKIP\] stop_from_handler_deadlocks_as_documented' "$LOG"; then
1267+
echo "ERROR: stop_from_handler_deadlocks_as_documented reported [SKIP] instead of exercising the deadlock contract" >&2
1268+
cat "$LOG"
1269+
exit 1
1270+
fi
1271+
if ! grep -q '^\[OK\] stop_from_handler' "$LOG"; then
1272+
echo "ERROR: expected an '[OK] stop_from_handler' marker in $LOG; none found" >&2
1273+
cat "$LOG"
1274+
exit 1
1275+
fi
1276+
echo "PASS — stop_from_handler_deadlocks_as_documented exercised the DR-008 deadlock contract"
12011277
if: ${{ matrix.os-type == 'ubuntu' && matrix.test-group == 'basic' && matrix.c-compiler == 'gcc' && matrix.debug == 'nodebug' && matrix.linking == 'dynamic' && matrix.build-type == 'classic' }}
12021278

12031279
- name: Fetch master ref for parallel-install
@@ -1249,7 +1325,7 @@ jobs:
12491325
echo "ERROR: $LOG missing — ws_start_stop did not run" >&2
12501326
exit 1
12511327
fi
1252-
SKIP_COUNT=$(grep -c '^\[SKIP\].*gnutls-cli' "$LOG" || true)
1328+
SKIP_COUNT=$(grep -c '^\[SKIP\].*psk_connection' "$LOG" || true)
12531329
echo "Found $SKIP_COUNT [SKIP] markers attributable to gnutls-cli"
12541330
if [ "$SKIP_COUNT" -lt 5 ]; then
12551331
echo "ERROR: expected >=5 [SKIP] markers in $LOG; got $SKIP_COUNT" >&2
@@ -1329,6 +1405,8 @@ jobs:
13291405
if [ -f "test/$log" ]; then
13301406
echo "===== test/$log =====" ;
13311407
cat "test/$log" ;
1408+
else
1409+
echo "test/$log not produced by this lane" ;
13321410
fi
13331411
done
13341412
if: ${{ always() && startsWith(matrix.build-type, 'valgrind-') }}

Makefile.am

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ EXTRA_DIST = libhttpserver.pc.in $(DX_CONFIG) scripts/extract-release-notes.sh s
4343
scripts/check-doxygen.sh scripts/check-hooks-doc-spotcheck.sh \
4444
scripts/check-soversion.sh scripts/check-parallel-install.sh \
4545
scripts/check-parallel-install-lane.sh \
46+
scripts/check-msan-lane.sh scripts/check-valgrind-lane.sh \
4647
scripts/check-codeql-workflow.sh scripts/check-workflow-pinning.sh \
4748
scripts/test_check_parallel_install.sh \
4849
scripts/test_check_parallel_install_lane.sh \
@@ -54,6 +55,7 @@ EXTRA_DIST = libhttpserver.pc.in $(DX_CONFIG) scripts/extract-release-notes.sh s
5455
scripts/check-complexity.sh scripts/check-duplication.sh \
5556
scripts/check-file-size.sh \
5657
scripts/check-warning-suppressions.sh \
58+
scripts/test_check_warning_suppressions.sh \
5759
scripts/check-server-ready-helper.sh \
5860
scripts/test_check_server_ready_helper.sh \
5961
scripts/check-no-tautological-asserts.sh \
@@ -556,11 +558,35 @@ lint-fence-balance:
556558
# `make check-parallel-install` invocation, and no SKIP authorization so
557559
# environment-quirk SKIPs stay fatal). Parses the workflow file, so it is
558560
# invoked directly by CI's `lint` lane rather than every check-local run —
559-
# mirrors the msan/valgrind lane gates.
561+
# mirrors the msan/valgrind lane gates below.
562+
# Unlike the newer lint-codeql-workflow/lint-workflow-pinning targets, this
563+
# target does not run test_check_parallel_install_lane.sh first -- that is
564+
# the original (TASK-089) idiom; the self-test-then-gate pattern in the
565+
# newer targets is an intentional TASK-090 enhancement, not a regression to
566+
# fix here.
560567
lint-parallel-install-lane:
561568
@echo "=== lint-parallel-install-lane: verify parallel-install CI wiring ==="
562569
@$(top_srcdir)/scripts/check-parallel-install-lane.sh
563570

571+
# TASK-087: structural gate asserting the msan CI lane in verify-build.yml
572+
# stays wired (matrix entry present, stale commented-out block gone,
573+
# MSAN_OPTIONS + scoped TESTS= wiring on the run step). Parses the workflow
574+
# file, so — like lint-parallel-install-lane — it is invoked directly by
575+
# CI's `lint` lane rather than every check-local run.
576+
lint-msan-lane:
577+
@echo "=== lint-msan-lane: verify msan CI wiring ==="
578+
@$(top_srcdir)/scripts/check-msan-lane.sh
579+
580+
# TASK-088: structural gate asserting the valgrind CI lane in
581+
# verify-build.yml stays wired (helgrind/drd matrix entries present, the
582+
# --disable-valgrind-* configure flags gone, per-tool check-valgrind-*
583+
# targets invoked, helgrind/drd logs surfaced). Parses the workflow file, so
584+
# — like lint-parallel-install-lane — it is invoked directly by CI's `lint`
585+
# lane rather than every check-local run.
586+
lint-valgrind-lane:
587+
@echo "=== lint-valgrind-lane: verify valgrind CI wiring ==="
588+
@$(top_srcdir)/scripts/check-valgrind-lane.sh
589+
564590
# TASK-090: structural gate asserting codeql-analysis.yml stays hardened
565591
# (every action SHA-pinned, Autobuild deleted, explicit ./configure + make
566592
# build, init/analyze on one v3 SHA). The gate parses the workflow with
@@ -582,7 +608,7 @@ lint-workflow-pinning:
582608
@$(top_srcdir)/scripts/test_check_workflow_pinning.sh
583609
@$(top_srcdir)/scripts/check-workflow-pinning.sh
584610

585-
.PHONY: check-headers check-install-layout check-hygiene check-local check-examples check-readme check-release-notes check-doxygen check-hooks-doc-spotcheck check-soversion check-parallel-install lint-complexity lint-duplication lint-file-size lint-warning-suppressions lint-server-ready-helper lint-server-ready-helper-selftest lint-no-tautological-asserts lint-deprecated-cookie-overload lint-littletest-skip-exit-code lint-parallel-install-skip-contract lint-fence-balance lint-parallel-install-lane lint-codeql-workflow lint-workflow-pinning
611+
.PHONY: check-headers check-install-layout check-hygiene check-local check-examples check-readme check-release-notes check-doxygen check-hooks-doc-spotcheck check-soversion check-parallel-install lint-complexity lint-duplication lint-file-size lint-warning-suppressions lint-server-ready-helper lint-server-ready-helper-selftest lint-no-tautological-asserts lint-deprecated-cookie-overload lint-littletest-skip-exit-code lint-parallel-install-skip-contract lint-fence-balance lint-parallel-install-lane lint-msan-lane lint-valgrind-lane lint-codeql-workflow lint-workflow-pinning
586612

587613
# TASK-039: top-level convenience rule that descends into test/ to
588614
# build and run the bench binaries (see test/Makefile.am and

RELEASE_NOTES.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,16 @@ v1.x is end-of-life on the day v2.0 ships.
8686
- **`create_webserver::validator(...)` dispatch semantics — confirmed
8787
never wired (TASK-078).** The `validator` builder method and the
8888
`validator_ptr` callback typedef on `create_webserver` are retained
89-
as inert v1 surface — the callback has not been invoked from the
90-
dispatch path since commit `9163a4f` (Jan 2013, "Eliminated unescaper
91-
and validator delegates"). v2 ships the surface unchanged for source
92-
compatibility, but a configured validator callback is dead code. The
89+
unchanged — the callback has not been invoked from the dispatch path
90+
since commit `9163a4f` (Jan 2013, "Eliminated unescaper and validator
91+
delegates"). v2 ships the surface for source compatibility, but a
92+
configured validator callback is dead code. The
9393
v2 replacement is `webserver::add_hook(hook_phase::request_received,
9494
...)` returning `hook_action::respond_with(http_response)` to reject
95-
a request, or `hook_phase::accept_decision` for a connection-scoped
96-
veto (see `specs/architecture/04-components/hooks.md`). The legacy
95+
a request, or `webserver::block_ip`/`unblock_ip` for a connection-scoped
96+
veto; `hook_phase::accept_decision` is observation-only (Short-circuit:
97+
no, per `specs/architecture/04-components/hooks.md`) and cannot reject
98+
a connection. The legacy
9799
`validator_builder` integ test in `test/integ/basic.cpp` exercised
98100
no validation behaviour (it only asserted the server booted with a
99101
validator set) and has been removed; the equivalent compile-time

docs/debug-env-vars.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,19 @@ Multiple `webserver` instances constructed in the same process share a
5858
process-wide print-once flag, so the warning is emitted exactly once
5959
per process.
6060

61+
**stdout/stderr are independent streams.** The request-body dump goes to
62+
`stdout`; the `SECURITY WARNING` above goes to `stderr`. Process
63+
supervisors, container log aggregators, and the systemd journal may
64+
capture these two streams separately (different files, different log
65+
sinks, or different retention policies) rather than interleaving them
66+
into one ordered log. If only `stdout` is retained/shipped, the leaked
67+
credential/PII lines can end up with no accompanying warning context;
68+
if only `stderr` is retained, the warning fires with no visible dump to
69+
correlate it against. Operators enabling this variable must ensure both
70+
streams are captured together (e.g. `2>&1` redirection, or a supervisor
71+
configured to merge/correlate them) so the warning and the dump it
72+
describes stay associated.
73+
6174
### How to disable
6275

6376
```sh

examples/centralized_authentication.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ namespace {
6464
// mirrors OpenSSL's CRYPTO_memcmp, which also requires equal lengths).
6565
bool constant_time_equal(std::string_view a, std::string_view b) {
6666
if (a.size() != b.size()) return false;
67-
unsigned char diff = 0;
67+
volatile unsigned char diff = 0;
6868
for (std::size_t i = 0; i < a.size(); ++i) {
6969
diff |= static_cast<unsigned char>(a[i] ^ b[i]);
7070
}

examples/clf_access_log.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ std::string sanitize_clf(std::string_view sv) {
7575
std::string out;
7676
out.reserve(sv.size());
7777
for (unsigned char c : sv) {
78-
out += (c < 0x20 || c == 0x7f) ? '-' : static_cast<char>(c);
78+
// '"' is also replaced: an unescaped double-quote inside a CLF
79+
// quoted field ("METHOD PATH VERSION") would prematurely close
80+
// the quoted-string and corrupt the log line's structure.
81+
out += (c < 0x20 || c == 0x7f || c == '"') ? '-' : static_cast<char>(c);
7982
}
8083
return out;
8184
}
@@ -102,8 +105,8 @@ void emit_clf_line(const hs::response_sent_ctx& ctx) {
102105
// read from ctx.request->get_version() (TASK-018). It is sanitized
103106
// like method and path for defence-in-depth even though it comes from
104107
// MHD's request-line parse.
105-
const std::string method = sanitize_clf(ctx.request->get_method());
106-
const std::string path = sanitize_clf(ctx.request->get_path());
108+
const std::string method = sanitize_clf(ctx.request->get_method());
109+
const std::string path = sanitize_clf(ctx.request->get_path());
107110
const std::string version = sanitize_clf(ctx.request->get_version());
108111
std::printf("- - - [%s] \"%s %s %s\" %d %zu %lld\n",
109112
ts,

0 commit comments

Comments
 (0)