From b12f7da0d340ddc6f74a8c0a9189506c753aa320 Mon Sep 17 00:00:00 2001 From: toto Date: Thu, 5 Mar 2026 16:17:07 +0100 Subject: [PATCH] feat: add client_headers field - ordered list of client header names Add a new JSON field 'client_headers' containing all HTTP header names received from the client (r->headers_in), in original order and with original case preserved. Useful for browser/bot fingerprinting since header order is client-specific. Example: "client_headers":["Host","User-Agent","Accept"] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- architecture.yml | 9 ++++++++- mod_reqin_log.spec | 7 ++++++- src/mod_reqin_log.c | 22 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/architecture.yml b/architecture.yml index daa2690..9f6ab45 100644 --- a/architecture.yml +++ b/architecture.yml @@ -148,6 +148,13 @@ module: If 0, it indicates a newly established TCP connection. If > 0, it confirms an active Keep-Alive session. example: 2 + - name: client_headers + type: array of strings + description: > + Ordered list of all HTTP header names as received from the client + (r->headers_in), preserving original order and case. + Useful for browser/bot fingerprinting (header order is client-specific). + example: ["Host", "User-Agent", "Accept", "Accept-Language", "Accept-Encoding"] - name: content_length type: integer description: > @@ -166,7 +173,7 @@ module: header_X-Request-Id: "abcd-1234" header_User-Agent: "curl/7.70.0" example_full: | - {"time":"2026-02-26T11:59:30Z","timestamp":1708948770000000000,"scheme":"https","src_ip":"192.0.2.10","src_port":45678,"dst_ip":"198.51.100.5","dst_port":443,"method":"GET","path":"/api/users","query":"id=1","host":"example.com","http_version":"HTTP/1.1","keepalives":0,"content_length":0,"header_X-Request-Id":"abcd-1234","header_User-Agent":"curl/7.70.0"} + {"time":"2026-02-26T11:59:30Z","timestamp":1708948770000000000,"scheme":"https","src_ip":"192.0.2.10","src_port":45678,"dst_ip":"198.51.100.5","dst_port":443,"method":"GET","path":"/api/users","query":"id=1","host":"example.com","http_version":"HTTP/1.1","keepalives":0,"client_headers":["Host","User-Agent","Accept","Accept-Language","Accept-Encoding","X-Request-Id"],"content_length":0,"header_X-Request-Id":"abcd-1234","header_User-Agent":"curl/7.70.0"} configuration: scope: global diff --git a/mod_reqin_log.spec b/mod_reqin_log.spec index 76f30f3..b7d8093 100644 --- a/mod_reqin_log.spec +++ b/mod_reqin_log.spec @@ -1,4 +1,4 @@ -%global spec_version 1.0.18 +%global spec_version 1.0.19 Name: mod_reqin_log Version: %{spec_version} @@ -37,6 +37,11 @@ install -m 644 %{_pkgroot}/%{_sysconfdir}/httpd/conf.d/mod_reqin_log.conf %{buil %doc %{_docdir}/%{name} %changelog +* Thu Mar 05 2026 Developer - 1.0.19 +- FEATURE: Add client_headers JSON field - ordered list of all header names + as received from the client, preserving original order and case +- DOC: Update architecture.yml with client_headers field and example_full + * Thu Mar 05 2026 Developer - 1.0.18 - FIX: JsonSockLogMaxHeaders now counts configured headers (by position in list) regardless of their presence in the request, matching the documented behavior diff --git a/src/mod_reqin_log.c b/src/mod_reqin_log.c index 95b251c..6c91785 100644 --- a/src/mod_reqin_log.c +++ b/src/mod_reqin_log.c @@ -809,6 +809,28 @@ static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child dynbuf_append(&buf, ka_buf, -1); } + /* client_headers - ordered list of all header names as received from the client, + * preserving original order and case */ + { + const apr_array_header_t *arr = apr_table_elts(r->headers_in); + const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts; + int first = 1; + + dynbuf_append(&buf, ",\"client_headers\":[", 19); + for (int i = 0; i < arr->nelts; i++) { + if (elts[i].key != NULL) { + if (!first) { + dynbuf_append(&buf, ",", 1); + } + dynbuf_append(&buf, "\"", 1); + append_json_string(&buf, elts[i].key); + dynbuf_append(&buf, "\"", 1); + first = 0; + } + } + dynbuf_append(&buf, "]", 1); + } + /* Check buffer size before adding headers to prevent memory exhaustion */ if (buf.len >= MAX_JSON_SIZE) { if (SHOULD_LOG(srv_conf, REQIN_LOG_LEVEL_DEBUG)) {