TIdMessage.LoadFromStream: Missing fields - Printable Version +- Atozed Forums (https://www.atozed.com/forums) +-- Forum: Indy (https://www.atozed.com/forums/forum-8.html) +--- Forum: Indy General Discussion (https://www.atozed.com/forums/forum-9.html) +--- Thread: TIdMessage.LoadFromStream: Missing fields (/thread-3062.html) Pages:
1
2
|
TIdMessage.LoadFromStream: Missing fields - BartKindt - 03-06-2023 I use the IMAP system to dowload an email. MSG: TIdMessage; IMAP: TIdIMAP4; I then call IMAP.UIDRetrieve(IntToStr(IMAP.MailBox.SearchResult[i]), MSG); I can now read all fields like From, Subject etc. fine. Then the entire email is loaded into a Stream using IMAP.UIDRetrieveNoDecodeToStream(IntToStr(IMAP.MailBox.SearchResult[i]), AStream); This Stream is then saved to file, as an .eml file. I can load this file with an email program fine, all data complete. The same Stream is internally kept. But when I afterwards use an IdMessage.LoadFromStream, all fields like From, Subject, etc. are all empty. The LoadFromStream does not seem to initialize the fields. It seems only the IMAP does this, during the call to IMAP.UIDRetrieve. How do I get my data back in the TIdMessage after a LoadFromStream? RE: TIdMessage.LoadFromStream: Missing fields - rlebeau - 03-07-2023 (03-06-2023, 10:29 PM)BartKindt Wrote: I then call IMAP.UIDRetrieve(IntToStr(IMAP.MailBox.SearchResult[i]), MSG); Internally, TIdIMAP4.UIDRetrieve() downloads the raw bytes of the email into a TMemoryStream, and then calls TIdMessage.LoadFromStream() with the AUsesDotTransparency parameter set to False (its default is True instead). (03-06-2023, 10:29 PM)BartKindt Wrote: Then the entire email is loaded into a Stream using Internally, TIdIMAP4.UIDRetrieveNoDecodeToStream() creates a temporary TIdMessage and then downloads the email using the same logic as TIdIMAP4.UIDRetrieve(), except that it sets both TIdMessage.NoDecode and TIdMessage.NoEncode to True. Then, it calls TIdMessage.SaveToStream() with the AUsesDotTransparency parameter set to True. You really need AUsesDotTransparency to be set to False instead in this situation. There is a TODO comment about this in the source code for TIdIMAP4.UIDRetrieveNoDecodeToStream() (and all of the other TIdIMAP4.(UID)RetrieveNoDecodeTo...() methods): Code: {RLebeau 12/09/2012: NOT currently using the same workaround here that (03-06-2023, 10:29 PM)BartKindt Wrote: This Stream is then saved to file, as an .eml file. Most other programs are not expecting an .EML file to have its data escaped with dot-transparency. That is a detail of the POP3 and SMTP protocols only, IMAP doesn't use dot-transparency at all. But TIdMessage does by default, so watch out for that! (03-06-2023, 10:29 PM)BartKindt Wrote: The same Stream is internally kept. Are you resetting the stream's Position back to 0 before you load the stream? TIdMessage.LoadFromStream() will not do that for you. Also, make sure that you always call TIdMessage.LoadFromStream() using the same AUsesDotTransparency parameter value that was used when TIdMessage.SaveToStream() was called. (03-06-2023, 10:29 PM)BartKindt Wrote: It seems only the IMAP does this, during the call to IMAP.UIDRetrieve. TIdIMAP4.UIDRetrieve() and TIdIMAP4.UIDRetrieveNoDecodeToStream() are using TIdMessage in different ways. RE: TIdMessage.LoadFromStream: Missing fields - BartKindt - 03-07-2023 I have no access to the AUsesDotTransparency. I also don't know what it means. But: - I load the TIdIMAP4.UIDRetrieveNoDecodeToStream, then save the resulting TIdMessage.SaveToFile(); - I create a *new* TIdMessage, and reload from File. There is no valid data in any of the fields. What do I have to do in the TIdMessage itself, to actually populate all the fields? Even if I use 'IdMessage.LoadFromFile(xxx,true) // Headers only then there are NO headers. The Count is 0. All other fields are empty. There must be some command inside the IdMessage itself to populate all fields, after the loading of the Stream or File??? RE: TIdMessage.LoadFromStream: Missing fields - rlebeau - 03-07-2023 (03-07-2023, 03:16 AM)BartKindt Wrote: I have no access to the AUsesDotTransparency. Add the IdMessageHelper unit to your uses clause. Right now, a class helper is being used to overload the TIdMessage.LoadFrom...() and TIdMessage.SaveTo...() methods with an additional AUsesDotTransparency parameter: Code: type (03-07-2023, 03:16 AM)BartKindt Wrote: I also don't know what it means. The way that the POP3 and SMTP protocols are structured (but not the IMAP protocol), email data needs to be escaped when transmitted over the socket connection. Specifically, that escaping involves adding an extra period ('.') character in front of any line of text that already begins with a period, because the email data is terminated by sending a period ('.') character on a line by itself. The TIdMessage.SaveTo...() methods save only escaped emails, and the TIdMessage.LoadFrom...() methods load only escaped emails, regardless of whether the emails are being used with POP3/SMTP or not (see https://github.com/IndySockets/Indy/issues/135). The TIdMessageHelper class helper was added to workaround that issue without completely redesigning TIdMessage's internals. When AUsesDotTransparency is True, the escaping is saved/parsed as before. When AUsesDotTransparency is False, the escaping is not saved/parsed. (03-07-2023, 03:16 AM)BartKindt Wrote: I load the TIdIMAP4.UIDRetrieveNoDecodeToStream, then save the resulting TIdMessage.SaveToFile(); Since TIdIMAP4.UIDRetrieveNoDecodeToStream() uses TIdMessage.SaveToStream(), the stream data will be dot-escaped. If you are simply calling TIdMessage.LoadFromStream() and then TIdMessage.SaveToFile(), the stream will be loaded with dot-escaping parsed, and then the file will be saved with dot-escaping applied. (03-07-2023, 03:16 AM)BartKindt Wrote: I create a *new* TIdMessage, and reload from File. If you are simply calling TIdMessage.LoadFromFile(), then it will expect a dot-escaped file (which you have), and so the TIdMessage would end up holding the original un-escaped data. (03-07-2023, 03:16 AM)BartKindt Wrote: There is no valid data in any of the fields. It should be. I would need to see the raw stream/file data. (03-07-2023, 03:16 AM)BartKindt Wrote: What do I have to do in the TIdMessage itself, to actually populate all the fields? Given everything you have described, you should NOT have to do anything extra. However, I would suggest that you call TIdMessage.SaveToFile() and TIdMessage.LoadFromFile() with the AUseDotTransparency parameter set to False for both, since .EML files don't need to be escaped. (03-07-2023, 03:16 AM)BartKindt Wrote: Even if I use 'IdMessage.LoadFromFile(xxx,true) // Headers only The ONLY way that should happen is if either:
RE: TIdMessage.LoadFromStream: Missing fields - BartKindt - 03-16-2023 - I load a IdMessage via the IMAP system, and all data is complete (headers, body, etc.). - I save the IdMessage both to File and to Stream - I create a new IdMessage, and do a LoadFrom File: The message shows empty. - I Create a new IdMessage, and do a LoadFromSteam, and it is also empty. The raw file (as saved to disk) is clean, and I can open it fine in the Thunderbird email client. The only dot in the file is at the end. Somehow the IdMessageClient.ProcessMessage is not processing anything. In the latest compiler, I cannot edit the Indy files at all; When I copy the various units into my own directory, and try to compile I get errors about some other units 'not compiled with the save version'. What else can I do? I attach the raw file as a TXT file. RE: TIdMessage.LoadFromStream: Missing fields - rlebeau - 03-17-2023 (03-16-2023, 06:54 PM)BartKindt Wrote: - I load a IdMessage via the IMAP system, and all data is complete (headers, body, etc.). The TIdIMAP4.UIDRetrieveNoDecode...() methods don't actually parse an email's body, they just store the raw body text as-is. The non-NoDecode TIdIMAP4.UIDRetrieve...() methods actually parse an email body. (03-16-2023, 06:54 PM)BartKindt Wrote: Somehow the IdMessageClient.ProcessMessage is not processing anything. If that were true, the decoding TIdIMAP4.UIDRetrieve...() methods would be suffering from the same issue. But based on your description, they are not. So something else is going on. (03-16-2023, 06:54 PM)BartKindt Wrote: In the latest compiler, I cannot edit the Indy files at all; Why are you trying to edit them? What are you editing, exactly? (03-16-2023, 06:54 PM)BartKindt Wrote: When I copy the various units into my own directory, and try to compile I get errors about some other units 'not compiled with the save version'. Then your project must be using other things that are linked to the version of Indy that shipped with the IDE. (03-16-2023, 06:54 PM)BartKindt Wrote: I attach the raw file as a TXT file. The file looks fine to me. TIdMessage.LoadFromFile() should not be having any issues loading it. So, how exactly are you validating that it does have issue? Even if TIdMessage.LoadFromFile() can't parse the email body, it should be parsing the email headers at least, but you claim that all of the TIdMessage properties are blank, and I find that very hard to believe. For instance, that would imply that the very 1st call to IOHandler.ReadLnRFC() inside of TIdMessageClient.ReceiveHeader() is either returning a blank line or setting LMsgEnd=True, either way breaking the reading loop. The TXT file you have shown clearly does not begin with a blank line or a message terminator. RE: TIdMessage.LoadFromStream: Missing fields - BartKindt - 03-17-2023 I will have to find a way to copy the indy files accros, to that I can edit them and make changes to see where it goes wrong. At the moment I have no idea where it goes wrong in the ProcessMessage, but it does look like it has something to do with the code here: Code: repeat As said, even loading directly from the Stream I just saved, the new copy shows no headers and no body after loading it. When copying Indy files accross, I get these errors: [dcc32 Fatal Error] IdSMTPBase.pas(603): F2051 Unit IdSMTP was compiled with a different version of IdSMTPBase.TIdSMTPBase This keeps on going, until I probably have all Indy files copied over... Is there any way to circumvent this? RE: TIdMessage.LoadFromStream: Missing fields - BartKindt - 03-18-2023 I tried to re-compile the Indy10 included in the latest RAD Studio 11.3 I got an error: [Fatal Error] Cannot compile package 'IndySystem280' which is currently required by RAD Studio 11. I have now downloaded the entire Indy10 from Github. I load the file: Indy280.groupproj Loads normally. But I cannot compile it at all. I immediately get the compiler error: [Fatal Error] Cannot compile package 'IndySystem280' which is currently required by RAD Studio 11. I don't understand what is going on. I never had problems before compiling a downloaded version of Indy10. RE: TIdMessage.LoadFromStream: Missing fields - BartKindt - 03-19-2023 Okay, after whole dramas where I lost all Embarcadero Indy10 files, and had to re-install the entire IDE, I now managed to have a copy of Indy10 which I can actually edit. Remy, The problem seems to be that a record called TIdIOHandler.FInputBuffer is empty, after the Stream is loaded. Please see the following pieces of code ( view from top to bottom) Code: procedure TIdMessageClient.ProcessMessage(AMsg: TIdMessage; AStream: TStream; AHeaderOnly: Boolean = False); Code: function TIdIOHandler.ReadLn(ATerminator: string; ATimeout: Integer = IdTimeoutDefault; The FInputBuffer is only 5 bytes big. The loaded Stream is the original email file. My Debug Log shows this: [12:18:34/555] D3: IdMessageClient.ProcessMessage (Stream): Stream.Size=107825 [12:18:34/555] D3: IdIOHandlerStreamMsg.Create: AReceiveStream.Size=107825 [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= [12:18:34/602] D3: IdMessageClient.ReceiveHeader: 1 [12:18:34/602] D3: IdMessageClient.ProcessMessage: ReceiveHeader="" [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=. [12:18:34/655] D3: IdMessageClient.ProcessMessage: Done. AMsg.Headers.Count=0 RE: TIdMessage.LoadFromStream: Missing fields - rlebeau - 03-20-2023 (03-19-2023, 06:16 AM)BartKindt Wrote: // BART 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. 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; Code: function TIdIOHandlerStream.ReadDataFromSource(var VBuffer: TIdBytes): Integer; Code: class function TIdStreamHelperVCL.ReadBytes(const AStream: TStream; var VBytes: TIdBytes; 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; (03-19-2023, 06:16 AM)BartKindt Wrote: [12:18:34/555] D3: IdMessageClient.ProcessMessage... 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 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. |