Skip to content

Commit 2494c46

Browse files
etrclaude
andcommitted
ip_representation::operator< + http_request_impl::populate_all_cert_fields
Two unrelated functions, both at CCN 18; refactor bundled because the shape of each extraction is small. ip_representation::operator< (CCN 18 -> 7): * accumulate_octet_score: shared "(16-i)*piece[i]" accumulator used by the main 0..15 sweep (skipping 10/11) and again for the 10..11 tail. Pulls the two CHECK_BIT clauses out of three different sites. * is_v4_mapped_prefix_octet_pair: collapses the nested "((a == 0x00 || a == 0xFF) && (b == 0x00 || b == 0xFF))" check into a named predicate. The composite if at the top of operator< was contributing 8 boolean ops alone. http_request_impl::populate_all_cert_fields (CCN 18 -> 5): * extract_x509_string: parameterised two-pass GnuTLS string getter (function pointer takes gnutls_x509_crt_get_dn or gnutls_x509_crt_get_issuer_dn -- same signature, identical wrapping). * extract_x509_common_name: get_dn_by_oid wrapper (separate because of the extra OID/index/flags parameters). * extract_x509_fingerprint_sha256: fingerprint hex-encode. * verify_peer_certificate: wraps gnutls_certificate_verify_peers2 and returns the verified bool. populate_all_cert_fields now reads as a flat sequence: assign to each pmr::string member via the per-field helper, then the two int64_t validity times. The cross-allocator .assign(ptr, len) idiom is preserved. Both refactors are HAVE_GNUTLS-gated in the case of cert handling; local build skips it but the helpers still compile-test against the operator< side of the change, and CI's GNUTLS-on lane exercises the cert path end to end. scripts/check-complexity.sh CCN_MAX ratcheted 19 -> 15 (new worst offender is webserver::register_impl_ at CCN 14). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 368bf85 commit 2494c46

3 files changed

Lines changed: 101 additions & 81 deletions

File tree

scripts/check-complexity.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
set -euo pipefail
2828

2929
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
30-
CCN_MAX="${CCN_MAX:-19}"
30+
CCN_MAX="${CCN_MAX:-15}"
3131

3232
# Prefer the standalone `lizard` entrypoint if it's on PATH; fall back to
3333
# `python3 -m lizard` which is what `pip install --user lizard` produces

src/http_request.cpp

Lines changed: 73 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -484,90 +484,97 @@ bool http_request_impl::has_client_certificate() const {
484484
return (cert_list != nullptr && list_size > 0);
485485
}
486486

487-
void http_request_impl::populate_all_cert_fields() const {
488-
if (client_cert_fields_cached) {
489-
return;
487+
namespace {
488+
489+
// Two-pass GnuTLS string extractor: first call discovers the length,
490+
// second call fills the buffer. The DN APIs (get_dn / get_issuer_dn)
491+
// share this exact shape, so parameterising on the function pointer
492+
// dedupes the wrapping and keeps the surrounding ctor under the CCN
493+
// bar.
494+
using x509_string_getter = int (*)(gnutls_x509_crt_t, char*, size_t*);
495+
496+
std::string extract_x509_string(gnutls_x509_crt_t cert,
497+
x509_string_getter getter) {
498+
size_t size = 0;
499+
getter(cert, nullptr, &size);
500+
std::string buf(size, '\0');
501+
if (getter(cert, buf.data(), &size) != GNUTLS_E_SUCCESS) return {};
502+
if (!buf.empty() && buf.back() == '\0') buf.pop_back();
503+
return buf;
504+
}
505+
506+
std::string extract_x509_common_name(gnutls_x509_crt_t cert) {
507+
size_t cn_size = 0;
508+
gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0,
509+
nullptr, &cn_size);
510+
if (cn_size == 0) return {};
511+
std::string cn(cn_size, '\0');
512+
if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0,
513+
cn.data(), &cn_size) != GNUTLS_E_SUCCESS) {
514+
return {};
515+
}
516+
if (!cn.empty() && cn.back() == '\0') cn.pop_back();
517+
return cn;
518+
}
519+
520+
std::string extract_x509_fingerprint_sha256(gnutls_x509_crt_t cert) {
521+
unsigned char fingerprint[32];
522+
size_t fingerprint_size = sizeof(fingerprint);
523+
if (gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA256, fingerprint,
524+
&fingerprint_size) != GNUTLS_E_SUCCESS) {
525+
return {};
526+
}
527+
std::string hex;
528+
hex.reserve(fingerprint_size * 2);
529+
for (size_t i = 0; i < fingerprint_size; ++i) {
530+
char buf[3];
531+
snprintf(buf, sizeof(buf), "%02x", fingerprint[i]);
532+
hex += buf;
490533
}
534+
return hex;
535+
}
536+
537+
bool verify_peer_certificate(gnutls_session_t session) {
538+
unsigned int status = 0;
539+
if (gnutls_certificate_verify_peers2(session, &status) != GNUTLS_E_SUCCESS) {
540+
return false;
541+
}
542+
return status == 0;
543+
}
544+
545+
} // namespace
491546

547+
void http_request_impl::populate_all_cert_fields() const {
548+
if (client_cert_fields_cached) return;
492549
client_cert_fields_cached = true;
493550

494551
gnutls_session_t session = nullptr;
495-
if (has_tls_session()) {
496-
session = get_tls_session();
497-
}
552+
if (has_tls_session()) session = get_tls_session();
498553

499554
scoped_x509_cert cert;
500-
if (session != nullptr) {
501-
cert.init_from_session(session);
502-
}
555+
if (session != nullptr) cert.init_from_session(session);
503556

504557
if (!cert.is_valid()) {
505558
// Default values (empty strings and -1) are already set by the
506559
// impl member initializers; client_cert_verified defaults to false.
507560
return;
508561
}
509562

510-
// Client certificate verification
511-
{
512-
unsigned int status = 0;
513-
if (gnutls_certificate_verify_peers2(session, &status) == GNUTLS_E_SUCCESS) {
514-
client_cert_verified = (status == 0);
515-
}
516-
}
563+
client_cert_verified = verify_peer_certificate(session);
517564

518-
// Subject DN
519-
{
520-
size_t dn_size = 0;
521-
gnutls_x509_crt_get_dn(cert.get(), nullptr, &dn_size);
522-
std::string dn(dn_size, '\0');
523-
if (gnutls_x509_crt_get_dn(cert.get(), &dn[0], &dn_size) == GNUTLS_E_SUCCESS) {
524-
if (!dn.empty() && dn.back() == '\0') dn.pop_back();
525-
// pmr::string has no cross-allocator copy-assign, so we
526-
// assign through the .assign(ptr, len) overload.
527-
client_cert_dn.assign(dn.data(), dn.size());
528-
}
529-
}
565+
// pmr::string has no cross-allocator copy-assign, so we route every
566+
// assignment through .assign(ptr, len).
567+
auto subject_dn = extract_x509_string(cert.get(), gnutls_x509_crt_get_dn);
568+
client_cert_dn.assign(subject_dn.data(), subject_dn.size());
530569

531-
// Issuer DN
532-
{
533-
size_t dn_size = 0;
534-
gnutls_x509_crt_get_issuer_dn(cert.get(), nullptr, &dn_size);
535-
std::string dn(dn_size, '\0');
536-
if (gnutls_x509_crt_get_issuer_dn(cert.get(), &dn[0], &dn_size) == GNUTLS_E_SUCCESS) {
537-
if (!dn.empty() && dn.back() == '\0') dn.pop_back();
538-
client_cert_issuer_dn.assign(dn.data(), dn.size());
539-
}
540-
}
570+
auto issuer_dn = extract_x509_string(cert.get(), gnutls_x509_crt_get_issuer_dn);
571+
client_cert_issuer_dn.assign(issuer_dn.data(), issuer_dn.size());
541572

542-
// Common Name
543-
{
544-
size_t cn_size = 0;
545-
gnutls_x509_crt_get_dn_by_oid(cert.get(), GNUTLS_OID_X520_COMMON_NAME, 0, 0, nullptr, &cn_size);
546-
if (cn_size > 0) {
547-
std::string cn(cn_size, '\0');
548-
if (gnutls_x509_crt_get_dn_by_oid(cert.get(), GNUTLS_OID_X520_COMMON_NAME, 0, 0, &cn[0], &cn_size) == GNUTLS_E_SUCCESS) {
549-
if (!cn.empty() && cn.back() == '\0') cn.pop_back();
550-
client_cert_cn.assign(cn.data(), cn.size());
551-
}
552-
}
553-
}
573+
auto cn = extract_x509_common_name(cert.get());
574+
client_cert_cn.assign(cn.data(), cn.size());
554575

555-
// SHA-256 fingerprint
556-
{
557-
unsigned char fingerprint[32];
558-
size_t fingerprint_size = sizeof(fingerprint);
559-
if (gnutls_x509_crt_get_fingerprint(cert.get(), GNUTLS_DIG_SHA256, fingerprint, &fingerprint_size) == GNUTLS_E_SUCCESS) {
560-
std::string hex_fingerprint;
561-
hex_fingerprint.reserve(fingerprint_size * 2);
562-
for (size_t i = 0; i < fingerprint_size; ++i) {
563-
char hex[3];
564-
snprintf(hex, sizeof(hex), "%02x", fingerprint[i]);
565-
hex_fingerprint += hex;
566-
}
567-
client_cert_fingerprint_sha256.assign(hex_fingerprint.data(),
568-
hex_fingerprint.size());
569-
}
570-
}
576+
auto fp = extract_x509_fingerprint_sha256(cert.get());
577+
client_cert_fingerprint_sha256.assign(fp.data(), fp.size());
571578

572579
// Validity times. The GnuTLS API returns std::time_t; we cast to
573580
// int64_t so the public accessors can return a fixed-width type

src/http_utils.cpp

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -540,30 +540,43 @@ ip_representation::ip_representation(const std::string& ip) {
540540
}
541541
}
542542

543+
namespace {
544+
545+
// Add (16 - i) * piece[i] for both ip representations when both have
546+
// the i-th octet masked in. Pulled out of operator< so the surrounding
547+
// function stays below the CCN bar.
548+
void accumulate_octet_score(const ip_representation& a,
549+
const ip_representation& b, int i,
550+
int64_t& a_score, int64_t& b_score) {
551+
if (!(CHECK_BIT(a.mask, i) && CHECK_BIT(b.mask, i))) return;
552+
a_score += (16 - i) * a.pieces[i];
553+
b_score += (16 - i) * b.pieces[i];
554+
}
555+
556+
// True when both v4-mapped-prefix octets on a and b are 0x00 or 0xFF.
557+
// Mirrors the "::ffff:" / "::"-prefix invariant from parse_nested_ipv4.
558+
bool is_v4_mapped_prefix_octet_pair(uint16_t a, uint16_t b) {
559+
return (a == 0x00 || a == 0xFF) && (b == 0x00 || b == 0xFF);
560+
}
561+
562+
} // namespace
563+
543564
bool ip_representation::operator <(const ip_representation& b) const {
544565
int64_t this_score = 0;
545566
int64_t b_score = 0;
546567
for (int i = 0; i < 16; i++) {
547568
if (i == 10 || i == 11) continue;
548-
549-
if (CHECK_BIT(mask, i) && CHECK_BIT(b.mask, i)) {
550-
this_score += (16 - i) * pieces[i];
551-
b_score += (16 - i) * b.pieces[i];
552-
}
569+
accumulate_octet_score(*this, b, i, this_score, b_score);
553570
}
554571

555-
if (this_score == b_score &&
556-
((pieces[10] == 0x00 || pieces[10] == 0xFF) && (b.pieces[10] == 0x00 || b.pieces[10] == 0xFF)) &&
557-
((pieces[11] == 0x00 || pieces[11] == 0xFF) && (b.pieces[11] == 0x00 || b.pieces[11] == 0xFF))) {
572+
if (this_score == b_score
573+
&& is_v4_mapped_prefix_octet_pair(pieces[10], b.pieces[10])
574+
&& is_v4_mapped_prefix_octet_pair(pieces[11], b.pieces[11])) {
558575
return false;
559576
}
560577

561-
for (int i = 10; i < 12; i++) {
562-
if (CHECK_BIT(mask, i) && CHECK_BIT(b.mask, i)) {
563-
this_score += (16 - i) * pieces[i];
564-
b_score += (16 - i) * b.pieces[i];
565-
}
566-
}
578+
accumulate_octet_score(*this, b, 10, this_score, b_score);
579+
accumulate_octet_score(*this, b, 11, this_score, b_score);
567580

568581
return this_score < b_score;
569582
}

0 commit comments

Comments
 (0)