Skip to content

Commit

Permalink
Bugfix for quoted-printable and "Q" encoding
Browse files Browse the repository at this point in the history
Improved QDecode/QPDecode methods to address several issues, see smiley22#15
  • Loading branch information
smiley22 committed Oct 12, 2012
1 parent 2cbbed3 commit 2746968
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 47 deletions.
28 changes: 15 additions & 13 deletions MessageBuilder.cs
Expand Up @@ -257,22 +257,24 @@ internal static class MessageBuilder {
Util.GetEncoding(part.Parameters["Charset"]) : Encoding.ASCII;
// decode content if it was encoded
byte[] bytes;
switch (part.Encoding) {
case ContentTransferEncoding.QuotedPrintable:
bytes = encoding.GetBytes(Util.QPDecode(content, encoding));
break;
case ContentTransferEncoding.Base64:
try {
try {
switch (part.Encoding) {
case ContentTransferEncoding.QuotedPrintable:
bytes = encoding.GetBytes(Util.QPDecode(content, encoding));
break;
case ContentTransferEncoding.Base64:
bytes = Util.Base64Decode(content);
} catch {
// If it's not a valid Base64 string just leave the data as is
break;
default:
bytes = Encoding.ASCII.GetBytes(content);
}
break;
default:
bytes = Encoding.ASCII.GetBytes(content);
break;
break;
}
} catch {
// If it's not a valid Base64 or quoted-printable encoded string
// just leave the data as is
bytes = Encoding.ASCII.GetBytes(content);
}

// If the MailMessage's Body fields haven't been initialized yet, put it there.
// Some weird (i.e. spam) mails like to omit content-types so don't check for
// that here and just assume it's text.
Expand Down
4 changes: 2 additions & 2 deletions Properties/AssemblyInfo.cs
Expand Up @@ -32,5 +32,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.5.*")]
[assembly: AssemblyFileVersion("2.5.0.0")]
[assembly: AssemblyVersion("2.6.*")]
[assembly: AssemblyFileVersion("2.6.0.0")]
35 changes: 23 additions & 12 deletions SearchCondition.cs
Expand Up @@ -324,7 +324,7 @@ public class SearchCondition {
/// SearchCondition instance with</param>
/// <returns>A new SearchCondition instance which can be further chained
/// with other search conditions.</returns>
public SearchCondition And(params SearchCondition[] other) {
public SearchCondition And(SearchCondition other) {
return Join(string.Empty, this, other);
}

Expand All @@ -337,7 +337,7 @@ public class SearchCondition {
/// message for it to be included in the search result set</param>
/// <returns>A new SearchCondition instance which can be further chained
/// with other search conditions.</returns>
public SearchCondition Not(params SearchCondition[] other) {
public SearchCondition Not(SearchCondition other) {
return Join("NOT", this, other);
}

Expand All @@ -350,10 +350,14 @@ public class SearchCondition {
/// SearchCondition instance with</param>
/// <returns>A new SearchCondition instance which can be further chained
/// with other search conditions.</returns>
public SearchCondition Or(params SearchCondition[] other) {
public SearchCondition Or(SearchCondition other) {
return Join("OR", this, other);
}

/// <summary>
/// The search keys which can be used with the IMAP SEARCH command, as
/// are defined in section 6.4.4 of RFC 3501.
/// </summary>
private enum Fields {
BCC, Before, Body, Cc, From, Header, Keyword,
Larger, On, SentBefore, SentOn, SentSince, Since, Smaller, Subject,
Expand All @@ -367,16 +371,23 @@ private enum Fields {
private string Operator { get; set; }
private bool Quote = true;

/// <summary>
/// Joins two SearchCondition objects into a new one using the specified
/// logical operator.
/// </summary>
/// <param name="condition">The logical operator to use for joining the
/// search conditions. Possible values are "OR", "NOT" and the empty
/// string "" which denotes a logical AND.</param>
/// <param name="left">The first SearchCondition object</param>
/// <param name="right">The second SearchCondition object</param>
/// <returns>A new SearchCondition object representing the two
/// search conditions joined by the specified logical operator.</returns>
private static SearchCondition Join(string condition, SearchCondition left,
params SearchCondition[] right) {
condition = condition.ToUpper();
if (left.Operator != condition)
left = new SearchCondition {
Operator = condition,
Conditions = new List<SearchCondition> { left }
};
left.Conditions.AddRange(right);
return left;
SearchCondition right) {
return new SearchCondition {
Operator = condition.ToUpper(),
Conditions = new List<SearchCondition> { left, right }
};
}

/// <summary>
Expand Down
60 changes: 40 additions & 20 deletions Util.cs
Expand Up @@ -127,40 +127,60 @@ internal static void Raise<T>(this EventHandler<T> @event, object sender, T args
/// <param name="value">The Q-encoded string to decode</param>
/// <param name="encoding">The encoding to use for encoding the
/// returned string</param>
/// <exception cref="FormatException">Thrown if the string is
/// not a valid Q-encoded string.</exception>
/// <returns>A Q-decoded string</returns>
internal static string QDecode(string value, Encoding encoding) {
MatchCollection matches = Regex.Matches(value, @"=[0-9A-Z]{2}",
RegexOptions.Multiline);
foreach (Match match in matches) {
char hexChar = (char)Convert.ToInt32(
match.Groups[0].Value.Substring(1), 16);
value = value.Replace(match.Groups[0].Value, hexChar.ToString());
try {
using (MemoryStream m = new MemoryStream()) {
for (int i = 0; i < value.Length; i++) {
if (value[i] == '=') {
string hex = value.Substring(i + 1, 2);
m.WriteByte(Convert.ToByte(hex, 16));
i = i + 2;
} else if (value[i] == '_') {
m.WriteByte(Convert.ToByte(' '));
} else {
m.WriteByte(Convert.ToByte(value[i]));
}
}
return encoding.GetString(m.ToArray());
}
} catch {
throw new FormatException("value is not a valid Q-encoded " +
"string");
}
value = value.Replace("=\r\n", "").Replace("_", " ");
return encoding.GetString(
Encoding.Default.GetBytes(value));
}

/// <summary>
/// Takes a quoted-printable-encoded string and decodes it using
/// Takes a quoted-printable encoded string and decodes it using
/// the specified encoding.
/// </summary>
/// <param name="value">The quoted-printable-encoded string to
/// decode</param>
/// <param name="encoding">The encoding to use for encoding the
/// returned string</param>
/// <returns>A quoted-printable-decoded string</returns>
/// <exception cref="FormatException">Thrown if the string is
/// not a valid quoted-printable encoded string.</exception>
/// <returns>A quoted-printable decoded string</returns>
internal static string QPDecode(string value, Encoding encoding) {
MatchCollection matches = Regex.Matches(value, @"=[0-9A-Z]{2}",
RegexOptions.Multiline);
foreach (Match match in matches) {
char hexChar = (char)Convert.ToInt32(
match.Groups[0].Value.Substring(1), 16);
value = value.Replace(match.Groups[0].Value, hexChar.ToString());
try {
using (MemoryStream m = new MemoryStream()) {
for (int i = 0; i < value.Length; i++) {
if (value[i] == '=') {
string hex = value.Substring(i + 1, 2);
m.WriteByte(Convert.ToByte(hex, 16));
i = i + 2;
} else {
m.WriteByte(Convert.ToByte(value[i]));
}
}
return encoding.GetString(m.ToArray());
}
} catch {
throw new FormatException("value is not a valid quoted-printable " +
"encoded string");
}
value = value.Replace("=\r\n", "");
return encoding.GetString(
Encoding.Default.GetBytes(value));
}

/// <summary>
Expand Down

0 comments on commit 2746968

Please sign in to comment.