package unit; import java.util.*; /** * curl-0004 — CWE-407: curl_mime search_header O(P×H) per multipart request build * * Models lib/mime.c search_header() called 3× per MIME part in mime_add_headers(): * Slow: for each part, walk user headers singly-linked list 3× → O(P × H) * Fast: pre-indexed header name set → O(P) total * * Trigger: curl_mime_formadd with many parts each having custom userheaders. */ public class CurlMimeBuildHeaderSearchTest { // Simulates struct curl_slist (singly-linked list of strings) static class CurlSlist { final String data; CurlSlist next; CurlSlist(String data) { this.data = data; } } // Simulates curl_mimepart static class CurlMimePart { CurlSlist userheaders; // linked list of user-supplied headers Set headerNameCache; // fast: pre-built from userheaders } // Append to slist static CurlSlist slistAppend(CurlSlist head, String data) { if (head == null) return new CurlSlist(data); CurlSlist cur = head; while (cur.next != null) cur = cur.next; cur.next = new CurlSlist(data); return head; } // Build a mime part with H user headers static CurlMimePart buildPart(int numHeaders, String[] fixedHeaders) { CurlMimePart part = new CurlMimePart(); for (int i = 0; i < numHeaders; i++) { String hdr = (i < fixedHeaders.length) ? (fixedHeaders[i] + ": value" + i) : ("X-Custom-" + i + ": value" + i); part.userheaders = slistAppend(part.userheaders, hdr); } return part; } // Simulate search_header: O(H) scan - returns true if header name found static boolean searchHeaderSlow(CurlSlist hdrlist, String headerName, long[] opsOut) { String target = headerName.toLowerCase() + ":"; for (CurlSlist h = hdrlist; h != null; h = h.next) { opsOut[0]++; if (h.data.toLowerCase().startsWith(target)) return true; } return false; } // Simulate fast header check: O(1) set lookup static boolean searchHeaderFast(Set cache, String headerName, long[] opsOut) { opsOut[0]++; return cache.contains(headerName.toLowerCase()); } // Pre-build header name cache for a part static Set buildHeaderCache(CurlSlist userheaders) { Set cache = new HashSet<>(); for (CurlSlist h = userheaders; h != null; h = h.next) { int colon = h.data.indexOf(':'); if (colon > 0) cache.add(h.data.substring(0, colon).toLowerCase().trim()); } return cache; } // Simulate mime_add_headers calling search_header 3× per part static long mimeAddHeadersSlow(List parts) { long[] ops = { 0 }; String[] checks = { "Content-Type", "Content-Disposition", "Content-Transfer-Encoding" }; for (CurlMimePart part : parts) { for (String check : checks) searchHeaderSlow(part.userheaders, check, ops); } return ops[0]; } static long mimeAddHeadersFast(List parts) { long[] ops = { 0 }; String[] checks = { "Content-Type", "Content-Disposition", "Content-Transfer-Encoding" }; for (CurlMimePart part : parts) { // Build cache once per part (amortized O(H) for the part, O(1) per lookup) if (part.headerNameCache == null) part.headerNameCache = buildHeaderCache(part.userheaders); for (String check : checks) searchHeaderFast(part.headerNameCache, check, ops); } return ops[0]; } public static void main(String[] args) { System.out.println("curl-0004 CWE-407: curl_mime search_header O(P*H) vs O(P)"); System.out.println("==========================================================="); String[] knownHeaders = { "Content-Type", "Content-Disposition", "Content-Transfer-Encoding", "Content-ID", "Content-Description" }; int[][] params = { {10, 10}, {50, 20}, {100, 30} }; for (int[] p : params) { int P = p[0], H = p[1]; List parts = new ArrayList<>(); for (int i = 0; i < P; i++) parts.add(buildPart(H, knownHeaders)); long slowOps = mimeAddHeadersSlow(parts); long fastOps = mimeAddHeadersFast(parts); double ratio = (double) slowOps / fastOps; System.out.printf(" P=%3d parts, H=%2d headers/part: slow=%,6d ops fast=%,5d ops speedup=%.1fx%n", P, H, slowOps, fastOps, ratio); // Fast should be ~H/3 times cheaper (3 lookups but we only scan up to H headers) assert slowOps > fastOps : "Expected slow > fast but slow=" + slowOps + " fast=" + fastOps; } System.out.println("\nPASS"); } }