From 2746968ae9a064e15cd1768f557ab208fb032b39 Mon Sep 17 00:00:00 2001 From: smiley22 Date: Fri, 12 Oct 2012 11:14:25 +0200 Subject: [PATCH] Bugfix for quoted-printable and "Q" encoding Improved QDecode/QPDecode methods to address several issues, see #15 --- MessageBuilder.cs | 28 +++++++++--------- Properties/AssemblyInfo.cs | 4 +-- SearchCondition.cs | 35 ++++++++++++++-------- Util.cs | 60 +++++++++++++++++++++++++------------- 4 files changed, 80 insertions(+), 47 deletions(-) diff --git a/MessageBuilder.cs b/MessageBuilder.cs index b28d98e..dc21f2a 100644 --- a/MessageBuilder.cs +++ b/MessageBuilder.cs @@ -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. diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 3305a3b..0ba3696 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -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")] diff --git a/SearchCondition.cs b/SearchCondition.cs index 0a3ac2e..dfc08fb 100644 --- a/SearchCondition.cs +++ b/SearchCondition.cs @@ -324,7 +324,7 @@ public class SearchCondition { /// SearchCondition instance with /// A new SearchCondition instance which can be further chained /// with other search conditions. - public SearchCondition And(params SearchCondition[] other) { + public SearchCondition And(SearchCondition other) { return Join(string.Empty, this, other); } @@ -337,7 +337,7 @@ public class SearchCondition { /// message for it to be included in the search result set /// A new SearchCondition instance which can be further chained /// with other search conditions. - public SearchCondition Not(params SearchCondition[] other) { + public SearchCondition Not(SearchCondition other) { return Join("NOT", this, other); } @@ -350,10 +350,14 @@ public class SearchCondition { /// SearchCondition instance with /// A new SearchCondition instance which can be further chained /// with other search conditions. - public SearchCondition Or(params SearchCondition[] other) { + public SearchCondition Or(SearchCondition other) { return Join("OR", this, other); } + /// + /// The search keys which can be used with the IMAP SEARCH command, as + /// are defined in section 6.4.4 of RFC 3501. + /// private enum Fields { BCC, Before, Body, Cc, From, Header, Keyword, Larger, On, SentBefore, SentOn, SentSince, Since, Smaller, Subject, @@ -367,16 +371,23 @@ private enum Fields { private string Operator { get; set; } private bool Quote = true; + /// + /// Joins two SearchCondition objects into a new one using the specified + /// logical operator. + /// + /// The logical operator to use for joining the + /// search conditions. Possible values are "OR", "NOT" and the empty + /// string "" which denotes a logical AND. + /// The first SearchCondition object + /// The second SearchCondition object + /// A new SearchCondition object representing the two + /// search conditions joined by the specified logical operator. 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 { left } - }; - left.Conditions.AddRange(right); - return left; + SearchCondition right) { + return new SearchCondition { + Operator = condition.ToUpper(), + Conditions = new List { left, right } + }; } /// diff --git a/Util.cs b/Util.cs index b90be1c..b2fec09 100644 --- a/Util.cs +++ b/Util.cs @@ -127,40 +127,60 @@ internal static void Raise(this EventHandler @event, object sender, T args /// The Q-encoded string to decode /// The encoding to use for encoding the /// returned string + /// Thrown if the string is + /// not a valid Q-encoded string. /// A Q-decoded string 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)); } /// - /// Takes a quoted-printable-encoded string and decodes it using + /// Takes a quoted-printable encoded string and decodes it using /// the specified encoding. /// /// The quoted-printable-encoded string to /// decode /// The encoding to use for encoding the /// returned string - /// A quoted-printable-decoded string + /// Thrown if the string is + /// not a valid quoted-printable encoded string. + /// A quoted-printable decoded string 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)); } ///