Socket Error #0 in TIdIOHandler.ReadFromSource

Giganews Newsgroups
Subject: Socket Error #0 in TIdIOHandler.ReadFromSource
Posted by:  Alex Ivlev (nospam@nospam.net)
Date: Wed, 22 Aug 2007

Bug: Indy improperly raises an exception EIdSocketError(0).

How to reproduce: If you have a GMail account, test its SMTP connection
without sending anything (note: this is not an SMTP bug, just using SMTP to
demonstrate). A simplified version of the code:

with TIdSMTP.Create(nil) do
try
  IOHandler:=TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  ManagedIOHandler:=True;
  with TIdSSLIOHandlerSocketOpenSSL(IOHandler).SSLOptions do
  begin
    Method:=sslvSSLv23;
    Mode:=sslmClient;
  end;
  UseTLS:=utUseImplicitTLS;
  Host:='smtp.gmail.com';
  Port:=465;
  AuthType:=atDefault;
  UserName:='yourna…@gmail.com';
  Password:='yourpassword';
  Connect;
  if Connected then Disconnect;
finally
  Free;
end;

The connection works fine. Then this happens when Disconnect is called:

TIdSMTP.DisconnectNotifyPeer sends QUIT:
SendCmd('QUIT', 221);

TIdTCPConnection.SendCmd sends it and wants a response, calling
TIdTCPConnection.GetResponse, which in turn calls
TIdTCPConnection.GetInternalResponse --> TIdIOHandler.ReadLnWait -->
TIdIOHandler.ReadLn --> TIdIOHandler.ReadFromSource.

GMail doesn't respond after QUIT by whatever reason (but doesn't disconnect
yet either). So the response is empty, and we arrive at the following code
in TIdIOHandler.ReadFromSource:

        if LByteCount < 0 then
        begin
          LLastError := GStack.CheckForSocketError(Result, [Id_WSAESHUTDOWN,
Id_WSAECONNABORTED]);
          FClosedGracefully := True;
          Close;
          // Do not raise unless all data has been read by the user
          if InputBufferIsEmpty then begin
            GStack.RaiseSocketError(LLastError);
          end;
          LByteCount := 0;
        end;

CheckForSocketError is called with Result as its argument for some reason.
Result is the number of bytes read from source (which is 0).
CheckForSocketError simply returns it back and LLastError is 0. But the
input buffer is empty, so a Socker Error exception gets raised with code 0.

I see at least two things wrong here:
1) using Result in CheckForSocketError call;
2) raising an exception when LLastError = 0.

Replies