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>
This commit is contained in:
toto
2026-03-05 16:17:07 +01:00
parent f018f0e1f6
commit b12f7da0d3
3 changed files with 36 additions and 2 deletions

View File

@ -148,6 +148,13 @@ module:
If 0, it indicates a newly established TCP connection. If 0, it indicates a newly established TCP connection.
If > 0, it confirms an active Keep-Alive session. If > 0, it confirms an active Keep-Alive session.
example: 2 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 - name: content_length
type: integer type: integer
description: > description: >
@ -166,7 +173,7 @@ module:
header_X-Request-Id: "abcd-1234" header_X-Request-Id: "abcd-1234"
header_User-Agent: "curl/7.70.0" header_User-Agent: "curl/7.70.0"
example_full: | 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: configuration:
scope: global scope: global

View File

@ -1,4 +1,4 @@
%global spec_version 1.0.18 %global spec_version 1.0.19
Name: mod_reqin_log Name: mod_reqin_log
Version: %{spec_version} Version: %{spec_version}
@ -37,6 +37,11 @@ install -m 644 %{_pkgroot}/%{_sysconfdir}/httpd/conf.d/mod_reqin_log.conf %{buil
%doc %{_docdir}/%{name} %doc %{_docdir}/%{name}
%changelog %changelog
* Thu Mar 05 2026 Developer <dev@example.com> - 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 <dev@example.com> - 1.0.18 * Thu Mar 05 2026 Developer <dev@example.com> - 1.0.18
- FIX: JsonSockLogMaxHeaders now counts configured headers (by position in list) - FIX: JsonSockLogMaxHeaders now counts configured headers (by position in list)
regardless of their presence in the request, matching the documented behavior regardless of their presence in the request, matching the documented behavior

View File

@ -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); 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 */ /* Check buffer size before adding headers to prevent memory exhaustion */
if (buf.len >= MAX_JSON_SIZE) { if (buf.len >= MAX_JSON_SIZE) {
if (SHOULD_LOG(srv_conf, REQIN_LOG_LEVEL_DEBUG)) { if (SHOULD_LOG(srv_conf, REQIN_LOG_LEVEL_DEBUG)) {