Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
TIdMessage.LoadFromStream: Missing fields
#10
(03-19-2023, 06:16 AM)BartKindt Wrote: // BART
// Result := IOHandler.ReadLnRFC(LMsgEnd);
result := IOHandler.ReadLn; // Bart: I called ReadLn direct for testing, but not the problem.

TIdIOHandler.ReadLnRFC() simply calls TIdIOHandler.ReadLn(), and then sets LMsgEnd=True if the line is '.', otherwise it sets LMsgEnd=False and strips off the 1st '.' if the line begins with '..'.  This is the decoding process for handling dot-transparency in emails.

(03-19-2023, 06:16 AM)BartKindt Wrote: And now: Where is the FInputBuffer data loaded? I may be blind but I do not see it.
The Stream is loaded into the IoHandler; But how is the FInputbuffer loaded??

Whenever a TIdIOHandler reading method, such as ReadLn(), needs bytes that are not already buffered in the InputBuffer, TIdIOHandler.ReadFromSource() is called to read whatever bytes are available from its data source and put them into the InputBuffer, repeating as needed until the read operation can finish its work.

TIdIOHandler.ReadFromSource() calls TIdIOHandler.ReadDataFromSource(), which TIdIOHandlerStreamMsg overrides to read from its FReceiveStream:

Code:
function TIdIOHandlerStreamMsg.ReadDataFromSource(var VBuffer: TIdBytes): Integer;
var
  LTerminator: String;
begin
  if not FTerminatorWasRead then
  begin
    Result := inherited ReadDataFromSource(VBuffer);

Code:
function TIdIOHandlerStream.ReadDataFromSource(var VBuffer: TIdBytes): Integer;
begin
  // We dont want to read the whole stream in at a time. If its a big
  // file will consume way too much memory by loading it all at once.
  // So lets read it in chunks.
  if Assigned(FReceiveStream) then begin
    Result := IndyMin(32 * 1024, Length(VBuffer));
    if Result > 0 then begin
      Result := TIdStreamHelper.ReadBytes(FReceiveStream, VBuffer, Result);
    end;
  end else begin
    Result := 0;
  end;
end;

Code:
class function TIdStreamHelperVCL.ReadBytes(const AStream: TStream; var VBytes: TIdBytes;
  const ACount, AOffset: Integer): Integer;
var
  LActual: Integer;
begin
  Assert(AStream<>nil);
  Result := 0;

  if VBytes = nil then begin
    SetLength(VBytes, 0);
  end;
  //check that offset<length(buffer)? offset+count?
  //is there a need for this to be called with an offset into a nil buffer?

  LActual := ACount;
  if LActual < 0 then begin
    LActual := AStream.Size - AStream.Position;
  end;

  //this prevents eg reading 0 bytes at Offset=10 from allocating memory
  if LActual = 0 then begin
    Exit;
  end;

  if Length(VBytes) < (AOffset+LActual) then begin
    SetLength(VBytes, AOffset+LActual);
  end;

  Assert(VBytes<>nil);
  Result := AStream.Read(VBytes[AOffset], LActual);
end;

So, in this case, TIdMessage.LoadFromStream() creates a TIdMessageClient with a TIdIOHandlerStreamMsg as its IOHandler, which reads data from the input TStream. Thus, when TIdMessageClient.ReceiveHeader() calls IOHandler.ReadLn(), it will keep reading raw bytes from the TStream until a LF line break is read and buffered in the InputBuffer, then it will extract the whole line from the InputBuffer.

(03-19-2023, 06:16 AM)BartKindt Wrote: The FInputBuffer is only 5 bytes big.

If TIdIOHandlerStreamMsg.ReadDataFromSource() fails to read from the TStream, ie end-of-stream is reached, it will return a final email terminator (<CRLF>.<CRLF>), just in case, which will force TIdIOHandler.ReadLnRFC() to return LMsgEnd=True if it hasn't already:

Code:
function TIdIOHandlerStreamMsg.ReadDataFromSource(var VBuffer: TIdBytes): Integer;
var
  LTerminator: String;
begin
  if not FTerminatorWasRead then
  begin
    Result := inherited ReadDataFromSource(VBuffer);
    if Result > 0 then begin
      FLastByteRecv := VBuffer[Result-1];
      Exit;
    end;
    // determine whether the stream ended with a line
    // break, adding an extra CR and/or LF if needed...
    if (FLastByteRecv = Ord(LF)) then begin
      // don't add an extra line break
      LTerminator := '.' + EOL;
    end else if (FLastByteRecv = Ord(CR)) then begin
      // add extra LF
      LTerminator := LF + '.' + EOL;
    end else begin
      // add extra CRLF
      LTerminator := EOL + '.' + EOL;
    end;
    FTerminatorWasRead := True;
    // in theory, CopyTIdString() will write the string
    // into the byte array using 1-byte characters even
    // under DotNet where strings are usually Unicode
    // instead of ASCII...
    CopyTIdString(LTerminator, VBuffer, 0);
    Result := Length(LTerminator);
  end else begin
    Result := 0;
  end;
end;

(03-19-2023, 06:16 AM)BartKindt Wrote: [12:18:34/555] D3: IdMessageClient.ProcessMessage...
[12:18:34/571] D3: IdIOHandler.ReadLn: FInputBuffer.Size=5
[12:18:34/571] D3: IdIOHandler.ReadLn: length(LResult)=2
[12:18:34/586] D3: IdIOHandler.ReadLn: Line okay
[12:18:34/586] D3: IdIOHandler.ReadLn: Result=

This implies that the very 1st call to IOHandler.ReadLn() in TIdMessageClient.ReceiveHeader() is failing to read any bytes at all from the TStream, causing TIdIOHanderStreamMsg to return its generated terminator (the 5 bytes you are logging), which ReadLn() will then extract from the InputBuffer up to the 1st CRLF (the 2 bytes you are logging) and then strip off the CRLF from the Result (the blank string you are logging).  TIdMessageClient.ReceiveHeader() will then interpret that as end-of-headers, causing TIdMessageClient.ReceiveHeaders() to exit so TIdMessageClient.ProcessMessage() will then call TIdMessageClient.ReceiveBody() next.

However, it is concerning that the input TStream would fail to read any bytes at all, when it clearly has bytes available, according to your logging of its Size.  Any such failure would be happening outside of Indy's control.

Which goes right back to my original question to you 2 weeks ago - when you save a TIdMessage to a TStream and then load it back, are you remembering to reset the TStream.Position back to 0 in between?  If you are loading from a file instead, then that is a non-issue, since file streams always start at Position 0.

You will have to debug the TStream itself to find out what is going on with it.

(03-19-2023, 06:16 AM)BartKindt Wrote: [12:18:34/624] D3: IdIOHandler.ReadLn: FInputBuffer.Size=3
[12:18:34/624] D3: IdIOHandler.ReadLn: length(LResult)=3
[12:18:34/640] D3: IdIOHandler.ReadLn: Line okay
[12:18:34/640] D3: IdIOHandler.ReadLn: Result=.

This is from TIdMessage.ReadBody() calling IOHandler.ReadLn(), extracting the 2nd half of TIdIOHandlerStreamMsg's generated terminator from the InputBuffer, which TIdMessageClient.ReceiveBody() will then interpret as end-of-message.

Reply


Messages In This Thread
RE: TIdMessage.LoadFromStream: Missing fields - by rlebeau - 03-20-2023, 08:22 PM

Forum Jump:


Users browsing this thread: 1 Guest(s)