Fix for FetchBodyStructure method

Feb 12, 2009 at 12:19 PM

Below is a fix for the FetchBodyStructure method in the file ImapCommand.cs.

The problem occurred when the mail server gave multi-line responses to the FETCH command.
......

public ImapMailboxMessage FetchBodyStructure(ImapMailboxMessage message)
{
if (!(Connection.ConnectionState == ConnectionState.Open))
NoOpenConnection();
Connection.Write(string.Format("FETCH {0} BODYSTRUCTURE\r\n", message.ID));
// +++ START of fix, part A +++
//string response = Connection.Read();  -- Removed this line
string response = "";
string responsetemp = "";
do
{
response += responsetemp;
responsetemp = Connection.Read();
} while (!Regex.IsMatch(responsetemp, patFetchComplete));
// +++ END of fix, part A  +++
if (response.StartsWith("*"))
{
response = response.Substring(response.IndexOf(" (", response.IndexOf("BODYSTRUCTURE")));
message.Errors = response;
message.BodyParts = BodyPartSplit(response.Trim().Substring(0, response.Trim().Length - 1));
//response = Connection.Read();  +++ Fix, part B. Removed this line +++
for (int i = 0; i < message.BodyParts.Count && i < 2; i++)
{
if (message.BodyParts[i].ContentType.MediaType.ToLower() == "text/html")
{
message.HasHTML = true;
message.HTML = i;
}
else if (message.BodyParts[i].ContentType.MediaType.ToLower() == "text/plain")
{
message.HasHTML = true;
message.Text = i;
}
}
return message;
}
else
throw new ImapCommandInvalidMessageNumber("No UID found for message number" + message.ID);
}

------------------------
I found this while writing a small program to fetch mails from a Microsoft Exchange Server.
------------------------

Mar 6, 2009 at 10:16 AM

Below I have incorporated another fix for the FetchBodyStructure method in the file ImapCommand.cs.

There was another problem with the Microsoft Exchange Server which is now taken care of. The problem was that sometimes the quotation marks was missing in the server response. In stead the server informed about the length of the substring that needed quotation marks. The fix below inserts the quotation marks correctly, and then the code works again (hopefully).

I have added a new function (AddQuotes) which is used in the method FetchBodyStructure.

------------------------------------

/// <summary>
/// Retrieves the bodystructure of a message.
/// </summary>
/// <param name="message">An ImapMailboxMessage object.</param>
/// <returns>Returns an ImapMailboxMessage object.</returns>
public ImapMailboxMessage FetchBodyStructure(ImapMailboxMessage message)
{
if (!(Connection.ConnectionState == ConnectionState.Open))
NoOpenConnection();
Connection.Write(string.Format("FETCH {0} BODYSTRUCTURE\r\n", message.ID));
//string response = Connection.Read();
string response = "";
string responsetemp = "";
do
{
response += responsetemp;
responsetemp = Connection.Read();
} while (!Regex.IsMatch(responsetemp, patFetchComplete));
if (response.StartsWith("*"))
{
response = response.Substring(response.IndexOf(" (", response.IndexOf("BODYSTRUCTURE")));
message.Errors = response;
response = response.Trim().Substring(0, response.Trim().Length - 1);
response = AddQuotes(response);
message.BodyParts = BodyPartSplit(response);
//response = Connection.Read();
for (int i = 0; i < message.BodyParts.Count && i < 2; i++)
{
if (message.BodyParts[i].ContentType.MediaType.ToLower() == "text/html")
{
message.HasHTML = true;
message.HTML = i;
}
else if (message.BodyParts[i].ContentType.MediaType.ToLower() == "text/plain")
{
message.HasHTML = true;
message.Text = i;
}
}
return message;
}
else
throw new ImapCommandInvalidMessageNumber("No UID found for message number " + message.ID);
}

/// <summary>
/// Replaces {5}abcde with "abcde". The number 5 denotes the length of the string to be enclosed in quotes.
/// </summary>
/// <param name="s">A string.</param>
/// <returns>A string.</returns>
private string AddQuotes(string s)
{
string result = "";

for (int i = 0; i < s.Length; i++)
{
char c = s[i];
if (c == '{')
{
int t = i + 1;
int length = 0;
while (t < s.Length && s[t] >= '0' && s[t] <= '9')
{
length = 10 * length + Convert.ToInt32(s[t].ToString());
t++;
}
if (t < s.Length && s[t] == '}')
{
result += "\"" + s.Substring(t + 1, length) + "\"";
i = t + length;
}
else
result += c;
}
else
result += c;
}
return result;
}

Jun 2, 2009 at 12:59 PM

Would you like to provide definition of 'patFetchComplete' variable?

thx,

ap2

Jun 3, 2009 at 8:20 AM
Edited Jun 3, 2009 at 8:22 AM

Sorry, I forgot that. Here it is:

        #region private variables
        const string patFetchComplete = @"^kw\d+\WOK\W([Ff][Ee][Tt][Cc][Hh]\W|)[Cc][Oo][Mm][Pp][Ll][Ee][Tt][Ee]";
        const string patFetchNotOk = @"^kw\d+\WNO";
        const string patExpungeComplete = @"^kw\d+\WOK\W([Ee][Xx][Pp][Uu][Nn][Gg][Ee]\W|)[Cc][Oo][Mm][Pp][Ll][Ee][Tt][Ee]";
        ImapConnect _connection;
        #endregion
 

Regards,

Morten