{"record":{"id":"dc613917d366d1e6","repo":"affaan-m/ECC","slug":"missing-http-request-line","errorCode":null,"errorMessage":"Missing HTTP request line","messagePattern":"Missing HTTP request line","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"ecc2/src/main.rs","lineNumber":4215,"sourceCode":"        if read == 0 {\n            anyhow::bail!(\"Unexpected EOF while reading HTTP request\");\n        }\n        buffer.extend_from_slice(&temp[..read]);\n        if let Some(index) = buffer.windows(4).position(|window| window == b\"\\r\\n\\r\\n\") {\n            break index + 4;\n        }\n        if buffer.len() > 64 * 1024 {\n            anyhow::bail!(\"HTTP request headers too large\");\n        }\n    };\n\n    let header_text = String::from_utf8(buffer[..header_end].to_vec())\n        .context(\"HTTP request headers were not valid UTF-8\")?;\n    let mut lines = header_text.split(\"\\r\\n\");\n    let request_line = lines\n        .next()\n        .filter(|line| !line.trim().is_empty())\n        .ok_or_else(|| anyhow::anyhow!(\"Missing HTTP request line\"))?;\n    let mut request_parts = request_line.split_whitespace();\n    let method = request_parts\n        .next()\n        .ok_or_else(|| anyhow::anyhow!(\"Missing HTTP method\"))?\n        .to_string();\n    let path = request_parts\n        .next()\n        .ok_or_else(|| anyhow::anyhow!(\"Missing HTTP path\"))?\n        .to_string();\n\n    let mut headers = BTreeMap::new();\n    for line in lines {\n        if line.is_empty() {\n            break;\n        }\n        if let Some((key, value)) = line.split_once(':') {\n            headers.insert(key.trim().to_ascii_lowercase(), value.trim().to_string());\n        }","sourceCodeStart":4197,"sourceCodeEnd":4233,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/main.rs#L4197-L4233","documentation":"Thrown by read_http_request after it reads the full header block (terminated by \\r\\n\\r\\n) but finds the first line is missing or blank. The parser splits headers on \\r\\n and requires the very first line to be a non-empty HTTP request line (e.g. 'GET /path HTTP/1.1'). If lines.next() is None or trims to empty, there is no request line to parse method/path from, so the request is rejected as malformed before any routing happens.","triggerScenarios":"A TCP client connects to the embedded HTTP listener and sends only \\r\\n\\r\\n (empty request), or sends a header block whose first line is whitespace-only (e.g. '\\r\\nGET / HTTP/1.1'). Also produced by port scanners, TCP health probes that open the socket and immediately close, or TLS handshakes sent to a plaintext HTTP port (the TLS bytes do not split into a valid request line).","commonSituations":"Running the ecc2 HTTP listener on a port also exposed to load balancer health checks that send no data; a curl with no URL/method; telnet/netcat sessions that connect and disconnect; HTTPS client hitting an HTTP-only endpoint.","solutions":["If this is a load balancer/health-check probe, point the probe at a path with a real GET request (e.g. 'GET /healthz HTTP/1.1') instead of an empty connect.","Confirm the client is speaking plain HTTP/1.1 to this port and HTTPS/TLS is not being sent here (move TLS clients to the TLS port or terminate TLS upstream).","Wrap the accept loop so read_http_request failures log the peer address and close the connection with a 400 rather than propagating the error.","Reproduce with a netcat session to see the exact bytes the client sends: printf '' | nc host port."],"exampleFix":"// before: bare parse with propagated error\nlet (method, path, headers, body) = read_http_request(&mut stream)?;\n\n// after: treat malformed request as a 400 and keep serving\nmatch read_http_request(&mut stream) {\n    Ok((method, path, headers, body)) => handle_request(method, path, headers, body),\n    Err(e) => {\n        log::warn!(\"malformed HTTP request from peer: {e:#}\");\n        let _ = write_http_response(&mut stream, 400, \"text/plain\", \"{\\\"error\\\":\\\"bad request\\\"}\");\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Before accepting, peek at the first bytes to confirm a request line is present.\nfn looks_like_http_request(buf: &[u8]) -> bool {\n    let text = std::str::from_utf8(buf).unwrap_or(\"\");\n    text.lines()\n        .next()\n        .map(|line| line.trim().split_whitespace().count() >= 3)\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"// Isolate malformed-request errors at the accept loop and respond 400.\nmatch read_http_request(&mut stream) {\n    Ok(req) => handle(req),\n    Err(e) => {\n        log::warn!(\"bad request from {peer}: {e:#}\");\n        let _ = write_http_response(&mut stream, 400, \"text/plain\", \"bad request\");\n    }\n}","preventionTips":["Front the HTTP listener with a reverse proxy that normalizes requests.","Log the peer address on every accept so malformed clients are identifiable.","Never expose the plaintext HTTP port directly to the internet without a proxy."],"tags":["http","network","input-validation","tcp-server"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}