Skip to content

Commit c435774

Browse files
etrclaude
andcommitted
CI: reduce parse_nested_ipv4 and parse_cookie_header below CCN 10
The complexity gate (scripts/check-complexity.sh, lizard, CCN_MAX=10) ran green for the first time once cpplint stopped failing earlier in the lint lane, exposing two pre-existing over-threshold functions: - ip_representation.cpp parse_nested_ipv4 (CCN 11) - cookie.cpp parse_cookie_header (CCN 12) Extract cohesive blocks into commented anonymous-namespace helpers (validate_ipv4_mapped_prefix, parse_cookie_token), preserving exact validation/exception behavior. New CCN: 8 and 6. Verified with lizard, library build, cpplint, and the ip_access_control / cookie_render / cookie_header_sentinel / http_request_cookies_parsed / http_response_cookie_wire test binaries (0 failures). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NpysYDDJac63yz2mZKKiDf
1 parent 9b50b12 commit c435774

2 files changed

Lines changed: 69 additions & 45 deletions

File tree

src/cookie.cpp

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,44 @@ std::string_view same_site_attribute_text(same_site_mode m) noexcept {
179179
return {};
180180
}
181181

182+
// Parse a single `; `-delimited cookie entry into a (name, value) view
183+
// pair. Returns nullopt for entries that carry no usable cookie: empty
184+
// after trimming, missing `=`, a leading `=` (empty name), or an empty
185+
// name after trimming. Strips one pair of outer DQUOTEs from the value
186+
// and trims ASCII whitespace around the name; byte-transparent
187+
// otherwise (the wire may deliver bytes the setters would reject).
188+
// Lifted out of parse_cookie_header to keep that function below the
189+
// CCN bar.
190+
std::optional<std::pair<std::string_view, std::string_view>>
191+
parse_cookie_token(std::string_view tok) {
192+
tok = trim_ws(tok);
193+
if (tok.empty()) {
194+
return std::nullopt;
195+
}
196+
const std::size_t eq = tok.find('=');
197+
if (eq == std::string_view::npos || eq == 0) {
198+
return std::nullopt;
199+
}
200+
std::string_view name_sv = tok.substr(0, eq);
201+
std::string_view value_sv = tok.substr(eq + 1);
202+
203+
// Strip a single pair of outer DQUOTEs from the value (RFC 6265
204+
// §5.2 step 2 implementation detail and common browser practice).
205+
if (value_sv.size() >= 2
206+
&& value_sv.front() == '"'
207+
&& value_sv.back() == '"') {
208+
value_sv = value_sv.substr(1, value_sv.size() - 2);
209+
}
210+
211+
// Trim trailing whitespace from the name (rare, but defends
212+
// against `a =b`).
213+
name_sv = trim_ws(name_sv);
214+
if (name_sv.empty()) {
215+
return std::nullopt;
216+
}
217+
return std::make_pair(name_sv, value_sv);
218+
}
219+
182220
} // namespace
183221

184222
// ----------------------------------------------------------------------
@@ -385,39 +423,16 @@ std::vector<cookie> cookie::parse_cookie_header(std::string_view header_value) {
385423
const std::size_t tok_end =
386424
(end == std::string_view::npos) ? header_value.size() : end;
387425

388-
std::string_view tok = header_value.substr(pos, tok_end - pos);
389-
tok = trim_ws(tok);
390-
391-
if (!tok.empty()) {
392-
const std::size_t eq = tok.find('=');
393-
if (eq != std::string_view::npos && eq > 0) {
394-
std::string_view name_sv = tok.substr(0, eq);
395-
std::string_view value_sv = tok.substr(eq + 1);
396-
397-
// Strip a single pair of outer DQUOTEs from the value
398-
// (RFC 6265 §5.2 step 2 implementation detail and
399-
// common browser practice).
400-
if (value_sv.size() >= 2
401-
&& value_sv.front() == '"'
402-
&& value_sv.back() == '"') {
403-
value_sv = value_sv.substr(1, value_sv.size() - 2);
404-
}
405-
406-
// Trim trailing whitespace from the name (rare, but
407-
// defends against `a =b`).
408-
name_sv = trim_ws(name_sv);
409-
410-
if (!name_sv.empty()) {
411-
cookie c;
412-
// Bypass validation: parsed input may technically
413-
// contain bytes the validator would reject (e.g. a
414-
// SP inside a quoted value). We accept what the
415-
// wire delivers; the renderer is the strict side.
416-
c.name_ = std::string(name_sv);
417-
c.value_ = std::string(value_sv);
418-
out.push_back(std::move(c));
419-
}
420-
}
426+
auto parsed = parse_cookie_token(header_value.substr(pos, tok_end - pos));
427+
if (parsed.has_value()) {
428+
cookie c;
429+
// Bypass validation: parsed input may technically contain
430+
// bytes the validator would reject (e.g. a SP inside a
431+
// quoted value). We accept what the wire delivers; the
432+
// renderer is the strict side.
433+
c.name_ = std::string(parsed->first);
434+
c.value_ = std::string(parsed->second);
435+
out.push_back(std::move(c));
421436
}
422437

423438
if (end == std::string_view::npos) {

src/detail/ip_representation.cpp

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,26 @@ bool ipv4_mapped_prefix_invalid(uint16_t a, uint16_t b) {
128128
return (a != 0 && a != 255) || (b != 0 && b != 255);
129129
}
130130

131+
// The 12-byte prefix that precedes a nested IPv4 dotted-quad must be
132+
// all-zero for bytes 0-9, with bytes 10-11 each 0x00 (pure ::-mapped)
133+
// or 0xFF (v4-mapped). Throws invalid_argument on any other prefix.
134+
// Lifted out of parse_nested_ipv4 so the surrounding function stays
135+
// below the CCN bar.
136+
void validate_ipv4_mapped_prefix(const uint16_t* pieces) {
137+
for (unsigned int k = 0; k < 10; k++) {
138+
if (pieces[k] != 0) {
139+
throw std::invalid_argument(
140+
"IP is badly formatted. Nested IPV4 can be preceded only by 0 "
141+
"(and, optionally, two 255 octects)");
142+
}
143+
}
144+
if (ipv4_mapped_prefix_invalid(pieces[10], pieces[11])) {
145+
throw std::invalid_argument(
146+
"IP is badly formatted. Nested IPV4 can be preceded only by 0 "
147+
"(and, optionally, two 255 octects)");
148+
}
149+
}
150+
131151
} // namespace
132152

133153
void ip_representation::parse_ipv4(const std::string& ip) {
@@ -189,18 +209,7 @@ void ip_representation::parse_nested_ipv4(const std::vector<std::string>& parts,
189209
throw std::invalid_argument("IP is badly formatted. Nested IPV4 can have max 4 parts.");
190210
}
191211
// Bytes 0-9 must be zero; bytes 10-11 must be 0x00 or 0xFF.
192-
for (unsigned int k = 0; k < 10; k++) {
193-
if (pieces[k] != 0) {
194-
throw std::invalid_argument(
195-
"IP is badly formatted. Nested IPV4 can be preceded only by 0 "
196-
"(and, optionally, two 255 octects)");
197-
}
198-
}
199-
if (ipv4_mapped_prefix_invalid(pieces[10], pieces[11])) {
200-
throw std::invalid_argument(
201-
"IP is badly formatted. Nested IPV4 can be preceded only by 0 "
202-
"(and, optionally, two 255 octects)");
203-
}
212+
validate_ipv4_mapped_prefix(pieces);
204213
for (unsigned int ii = 0; ii < subparts.size(); ii++) {
205214
if (subparts[ii] == "*") {
206215
clear_bit(mask, static_cast<unsigned int>(y + ii));

0 commit comments

Comments
 (0)