Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TIdIMAP4.InternalRetrieveText() does not retreive text correctly #368

Open
rlebeau opened this issue Jul 27, 2021 · 1 comment
Open

TIdIMAP4.InternalRetrieveText() does not retreive text correctly #368

rlebeau opened this issue Jul 27, 2021 · 1 comment
Assignees
Labels
Element: IMAP4 Issues related to TIdIMAP4 and TIdIMAP4Server Status: Review Needed Issue needs further review to decide next status Type: Bug Issue is a bug in existing code

Comments

@rlebeau
Copy link
Member

rlebeau commented Jul 27, 2021

When InternalRetrieveText() is called with AUseFirstPartInsteadOfText=true, it calls (UID)RetrieveStructure(), which may return text parts that have TIdImapMessagePart.ImapPartNumber set to an empty string, which InternalRetrieveText() is not accounting for.

For example:

Sent 26/7/2021 10:09:45 ??: C55 UID FETCH 16805 (BODYSTRUCTURE)<EOL>
Recv 26/7/2021 10:09:45 ??: * 51 FETCH (UID 16805 BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "utf-8") NIL NIL "8BIT" 760 16 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "utf-8") NIL NIL "QUOTED-PRINTABLE" 3962 80 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "----=_NextPart_000_0008_01D583AF.54009F20") NIL NIL))<EOL>
Recv 26/7/2021 10:09:45 ??: C55 OK Success<EOL>
Sent 26/7/2021 10:09:45 ??: C56 UID FETCH 16805 (BODY.PEEK[2])<EOL>
Recv 26/7/2021 10:09:46 ??: * 51 FETCH (UID 16805 BODY[2] 
Recv 26/7/2021 10:09:46 ??: {3962}<EOL>
Recv 26/7/2021 10:09:46 ??: ...
Recv 26/7/2021 10:09:46 ??: )<EOL>
Recv 26/7/2021 10:09:46 ??: C56 OK Success<EOL>

In this case, the local TIdImapMessageParts is being populated like this:

[0]: BodyType = alternative, Encoding = 1, CharSet =, ContentTransferEncoding =, Size = 0, ImapPartNumber =
[1]: BodyType = TEXT, Encoding = 1, CharSet = utf-8, ContentTransferEncoding = 8BIT, Size = 760, ImapPartNumber = 1
[2]: BodyType = TEXT, Encoding = 1, CharSet = utf-8, ContentTransferEncoding = QUOTED-PRINTABLE, Size = 3962, ImapPartNumber = 2

InternalRetrieveText() decides to request the text/plain text part, so it requests BODY.PEEK[2] where 2 is index 1, but that is actually requesting the text/html text part instead. And since the text/plain text part has a transfer-encoding of 8BIT, InternalRetrieveText() does not attempt to QuotedPrintable-decode the text that it retreived.

InternalRetrieveText() should be using the actual ImapPartNumber, not index 1.

@rlebeau rlebeau added Type: Bug Issue is a bug in existing code Element: IMAP4 Issues related to TIdIMAP4 and TIdIMAP4Server labels Jul 27, 2021
@rlebeau rlebeau self-assigned this Jul 27, 2021
rlebeau added a commit that referenced this issue Jul 27, 2021
…tual IMAP PartNumber instead of Index 1 when AUseFirstPartInsteadOfText is True.
@aminalinezhad
Copy link

aminalinezhad commented Dec 7, 2021

below snippet code in InternalRetrieveText() is the bug. because it assume that MIME parts are flat whereas they have tree structure.

      repeat
        LThePart := LParts.Items[LTextPart];
        if (LThePart.FSize <> 0) then begin
          Break;
        end;
        Inc(LTextPart);
      until LTextPart >= LParts.Count - 1;

To retrieve text part only I propose this:

function TForm1.TryGetBodyStrOnly(const AUIDStr: String; var ABodyStr: String): Boolean;
var
  i: Integer;
  ATextPartNumStr: String;
  AStream: TStream;
  AnIdImapMsgParts: TIdImapMessageParts;
  AnIdImapMsgPart: TIdIMapMessagePart;
  ACntEnc, ACntCharSet: String;
begin
  Result := False;
  ABodyStr := '';
  ATextPartNumStr := '';
  ACntEnc  = '';

  AnIdIMapMsgParts := TIdImapMessageParts.Create(nil);
  try
    if not id_IMAP.UIDRetrieveStructure(AUIDStr, AnIdIMapMsgParts) then
    begin
      DoLog('UIDRetrieveStructure failed', True, True, False, True);
      Exit;
    end;

    for i := 0 to AnIdIMapMsgParts.Count - 1 do
    begin
      AnIdImapMsgPart := AnIdImapMsgParts.Items[i];
      if (AnIdImapMsgPart.BodyType.ToUpper = 'TEXT') and (AnIdImapMsgPart.BodySubType.ToUpper = 'PLAIN') then
      begin
        ATextPartNumStr := AnIdImapMsgPart.ImapPartNumber;
        ACntEnc         := AnIdImapMsgPart.ContentTransferEncoding;
        ACntCharSet     := AnIdImapMsgPart.CharSet;
        Break;
      end;
    end;
  finally
    AnIdImapMsgParts.Free;
  end;

  if ATextPartNumStr = '' then
  begin
    DoLog('Text part not exists', False, True, True, True);
    Result := True;
    Exit;
  end;

  AStream := TStringStream.Create;
  try
    if not id_IMAP.UIDRetrievePart(AUIDStr, ATextPartNumStr, AStream, ACntEnc) then
    begin
      (* UIDRetrievePart sometimes return data in first line of response like this [1 FETCH (FLAGS (\Seen $NotJunk) UID 123 BODY[1] "SomeData")].
         In this case UIDRetrieve returned False whereas we excepted response like [1 FETCH (FLAGS (\Seen $NotJunk) UID 123 BODY[1] {8}] *)
      DoLog('UIDRetrievePart failed', True, True, False, True);
      Exit;
    end;

    Result := True;

    ABodyStr := (AStream as TStringStream).DataString;
    if (ACntCharSet.ToUpper = 'UTF-8') then
      ABodyStr := UTF8ToString(RawByteString(ABodyStr))
    else
      DoLog(Format('Unsupported charset:"%s" with encoding:"%s"', [ACntCharSet, ACntEnc]), False, True, True, True);
  finally
    AStream.Free;
  end;
end;

@rlebeau rlebeau added the Status: Review Needed Issue needs further review to decide next status label Apr 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Element: IMAP4 Issues related to TIdIMAP4 and TIdIMAP4Server Status: Review Needed Issue needs further review to decide next status Type: Bug Issue is a bug in existing code
Projects
None yet
Development

No branches or pull requests

2 participants