java-topology/defects/squid-0002/test/SquidConnHeaderTest.java
russell@unturf.com 1edc2f5a93 squid: 2 defects (squid-0002 CWE-407, squid-0003 CWE-312); MOADs 0002/0003/0005 CLEAN
MOAD-0001 squid-0002: HttpHeader::removeConnectionHeaderEntries() O(H*C) per response hop.
strListIsMember() scans all C Connection tokens for each of H header entries. Fix: pre-build
unordered_set from Connection tokens once, probe O(1) per entry. 4.84x measured speedup at
H=200 headers / C=50 Connection tokens. Called per hop in removeHopByHopEntries().

MOAD-0004 squid-0003: CWE-312 credentials logged verbatim in debug output.
FtpGateway.cc loginParser() logs user:password at debug 9; basic/Config.cc decodeCleartext()
logs decoded cleartext at debug 9 AND logs full Authorization header at DBG_IMPORTANT (level 1,
always on); basic/UserRequest.cc startHelperLookup() logs user:password at debug 9.
Fix: replace credential values with redacted markers / length-only diagnostic info.

MOAD-0002: SquidConfig 571-line god object in 209 files, 1408 call sites — structural,
documented in defects/squid/scan/MOAD-RESULTS.md.
MOAD-0003: CLEAN (event-loop single-threaded, no thread_local for request context).
MOAD-0005: CLEAN (event-loop single-threaded, no concurrent cache race).
2026-03-31 19:55:05 -04:00

134 lines
5.1 KiB
Java

import java.util.*;
/**
* Unit test for squid-0002: HttpHeader::removeConnectionHeaderEntries()
* O(H * C) linear scan per response hop.
*
* Context: Called once per HTTP response in removeHopByHopEntries(), which runs
* for every response forwarded by the proxy (client_side_reply.cc, Http1Server.cc).
*
* Defect: For each of H header entries, strListIsMember() scans all C tokens
* in the Connection header string. Total: O(H * C) string comparisons per hop.
*
* Fix: Pre-build std::unordered_set from Connection tokens once (O(C)), then
* probe O(1) per header entry. Total: O(C + H).
*
* At H=200 headers, C=50 Connection tokens: 10000 comparisons vs ~250 ops.
*/
public class SquidConnHeaderTest {
// Defect: O(H * C) scan - strListIsMember called inside getEntry loop
static int removeDefect(List<String> headers, List<String> connTokens) {
int removed = 0;
Iterator<String> it = headers.iterator();
while (it.hasNext()) {
String h = it.next();
// strListIsMember: iterate all C tokens
for (String token : connTokens) {
if (h.equalsIgnoreCase(token)) {
it.remove();
removed++;
break;
}
}
}
return removed;
}
// Fix: O(C + H) - pre-build HashSet from Connection tokens
static int removeFix(List<String> headers, List<String> connTokens) {
// Build set once: O(C)
Set<String> tokenSet = new HashSet<>();
for (String t : connTokens)
tokenSet.add(t.toLowerCase(Locale.ROOT));
int removed = 0;
Iterator<String> it = headers.iterator();
while (it.hasNext()) {
if (tokenSet.contains(it.next().toLowerCase(Locale.ROOT))) {
it.remove();
removed++;
}
}
return removed;
}
static List<String> makeHeaders(int H, int matchCount) {
List<String> h = new ArrayList<>();
for (int i = 0; i < H - matchCount; i++)
h.add("X-Custom-Header-" + i);
for (int i = 0; i < matchCount; i++)
h.add("conn-token-" + i);
Collections.shuffle(h, new Random(42));
return h;
}
static List<String> makeTokens(int C) {
List<String> t = new ArrayList<>();
for (int i = 0; i < C; i++)
t.add("conn-token-" + i);
return t;
}
public static void main(String[] args) {
System.out.println("=== squid-0002: HttpHeader::removeConnectionHeaderEntries O(H*C) ===");
System.out.println();
// --- Correctness ---
List<String> headers = Arrays.asList(
"Content-Type", "Keep-Alive", "Transfer-Encoding",
"Upgrade", "X-Custom", "Authorization", "Accept");
List<String> connTokens = Arrays.asList("keep-alive", "upgrade", "transfer-encoding");
List<String> dh = new ArrayList<>(headers);
int dRemoved = removeDefect(dh, connTokens);
List<String> fh = new ArrayList<>(headers);
int fRemoved = removeFix(fh, connTokens);
assert dRemoved == 3 : "defect: expected 3 removed, got " + dRemoved;
assert fRemoved == 3 : "fix: expected 3 removed, got " + fRemoved;
assert dh.equals(fh) : "result mismatch: " + dh + " vs " + fh;
System.out.println("Correctness: PASS (both removed " + dRemoved + " hop-by-hop headers)");
System.out.println();
// --- Performance: adversarial case (H=200, C=50) ---
int H = 200, C = 50, match = 20;
int iters = 100_000;
List<String> baseHeaders = makeHeaders(H, match);
List<String> tokens = makeTokens(C);
// Warmup
for (int i = 0; i < 5000; i++) {
removeDefect(new ArrayList<>(baseHeaders), tokens);
removeFix(new ArrayList<>(baseHeaders), tokens);
}
long t0 = System.nanoTime();
int totalD = 0;
for (int i = 0; i < iters; i++)
totalD += removeDefect(new ArrayList<>(baseHeaders), tokens);
long defectNs = System.nanoTime() - t0;
long t1 = System.nanoTime();
int totalF = 0;
for (int i = 0; i < iters; i++)
totalF += removeFix(new ArrayList<>(baseHeaders), tokens);
long fixNs = System.nanoTime() - t1;
assert totalD == totalF : "removed count mismatch: " + totalD + " vs " + totalF;
double ratio = (double) defectNs / fixNs;
System.out.printf("H=%d C=%d match=%d iters=%d%n", H, C, match, iters);
System.out.printf(" defect: %,d ns total (%,d ns/iter)%n", defectNs, defectNs / iters);
System.out.printf(" fix: %,d ns total (%,d ns/iter)%n", fixNs, fixNs / iters);
System.out.printf(" speedup: %.2fx%n%n", ratio);
// At H=200, C=50 the defect does 200*50=10000 comparisons per call;
// the fix does 50+200=250. Even with JVM overhead, expect >= 2x.
assert ratio >= 2.0 :
"Expected >= 2x speedup at H=" + H + " C=" + C + ", got " + ratio + "x";
System.out.println("Performance: PASS");
System.out.println("=== squid-0002 PASS ===");
}
}