Add IMAP connection timeout handling

Introduce idleConn wrapper to prevent wedged connections
Add test for silent server timeout behavior
Implement proper TLS handshake and greeting handling
Set explicit dial and read timeouts
This commit is contained in:
2026-07-04 19:37:36 +07:00
parent 8656f46674
commit dca1363ec9
3 changed files with 136 additions and 9 deletions
+60
View File
@@ -0,0 +1,60 @@
package imapx
import (
"io"
"net"
"testing"
"time"
"context"
)
// A server that sends the IMAP greeting, accepts one command, then goes silent
// forever must NOT wedge the client: the idle read-timeout has to abort the
// blocked read so the command returns an error instead of hanging. This is the
// exact production failure (account stuck in "running" with zero progress).
func TestConnectIdleReadTimeoutUnwedgesSilentServer(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
defer ln.Close()
go func() {
c, err := ln.Accept()
if err != nil {
return
}
defer c.Close()
_, _ = io.WriteString(c, "* OK IMAP4rev2 ready\r\n")
// Read the LOGIN command bytes, then never answer.
buf := make([]byte, 512)
_, _ = c.Read(buf)
// Block until the client gives up and closes the connection.
_, _ = c.Read(buf)
}()
old := idleReadTimeout
idleReadTimeout = 150 * time.Millisecond
defer func() { idleReadTimeout = old }()
port := ln.Addr().(*net.TCPAddr).Port
ep := Endpoint{Host: "127.0.0.1", Port: port, TLSMode: "plain"}
c, err := Connect(context.Background(), ep)
if err != nil {
t.Fatalf("Connect: %v", err)
}
done := make(chan error, 1)
go func() { done <- c.Login("user", "pass").Wait() }()
select {
case err := <-done:
if err == nil {
t.Fatal("expected an error from Login against a silent server, got nil")
}
case <-time.After(3 * time.Second):
t.Fatal("Login did not return: idle read-timeout was not enforced (connection wedged)")
}
}