keydb: classify server-dropped connection as KeydbConnect, not KeydbParse
In http_get, when the server closes the TCP connection before the HTTP header block completes (n == 0 on the byte-by-byte header read), or sends a header block exceeding 64 KiB, the code returned KeydbParse (E8004). Both are connection/protocol-level faults from the server, not parse failures of keydb content — the keydb bytes were never received. Return KeydbConnect (E8000) instead, which already covers TCP-level exchange failures. A CLI user hitting a transient drop or a redirect target that immediately closes now sees the correct 'server hung up' diagnostic rather than 'the downloaded file was malformed'. Add a regression test that stands up a loopback listener which accepts then drops the connection before headers, asserting KeydbConnect.
This commit is contained in:
+42
-3
@@ -246,15 +246,19 @@ fn http_get(url: &str) -> Result<Vec<u8>> {
|
||||
.read(&mut byte)
|
||||
.map_err(|_| Error::KeydbConnect { host: host.clone() })?;
|
||||
if n == 0 {
|
||||
// Connection closed before headers completed.
|
||||
return Err(Error::KeydbParse);
|
||||
// Server closed the connection before the header block
|
||||
// completed: a connection/protocol-level fault, not malformed
|
||||
// keydb content.
|
||||
return Err(Error::KeydbConnect { host: host.clone() });
|
||||
}
|
||||
header_buf.push(byte[0]);
|
||||
if header_buf.ends_with(b"\r\n\r\n") {
|
||||
break;
|
||||
}
|
||||
if header_buf.len() > MAX_HEADER_BYTES {
|
||||
return Err(Error::KeydbParse);
|
||||
// Oversized header block from the server: a protocol-level
|
||||
// fault, not a keydb content parse failure.
|
||||
return Err(Error::KeydbConnect { host: host.clone() });
|
||||
}
|
||||
}
|
||||
// header_buf includes the trailing \r\n\r\n.
|
||||
@@ -801,4 +805,39 @@ mod tests {
|
||||
e => panic!("expected KeydbConnect for unreachable host, got: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Regression: when the server accepts the connection but closes it before
|
||||
/// sending complete HTTP headers (the `n == 0` byte-read path), that is a
|
||||
/// connection/protocol-level fault. It must surface as KeydbConnect (E8000),
|
||||
/// NOT KeydbParse (E8004) — the keydb content was never received, let alone
|
||||
/// malformed.
|
||||
#[test]
|
||||
fn http_get_server_drops_before_headers_returns_keydb_connect() {
|
||||
use std::io::Read as _;
|
||||
use std::net::TcpListener;
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
let server = std::thread::spawn(move || {
|
||||
if let Ok((mut sock, _)) = listener.accept() {
|
||||
// Drain the request so the client's write_all completes, then
|
||||
// drop the socket without writing any response. The client's
|
||||
// header read then returns n == 0.
|
||||
let mut buf = [0u8; 512];
|
||||
let _ = sock.read(&mut buf);
|
||||
drop(sock);
|
||||
}
|
||||
});
|
||||
|
||||
let url = format!("http://127.0.0.1:{}/keydb.zip", addr.port());
|
||||
let result = http_get(&url);
|
||||
server.join().unwrap();
|
||||
|
||||
assert!(result.is_err(), "dropped connection must fail");
|
||||
match result.unwrap_err() {
|
||||
Error::KeydbConnect { .. } => {}
|
||||
e => panic!("expected KeydbConnect for dropped connection, got: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user