diff --git a/Documentation/Examples/SmtpExamples.cs b/Documentation/Examples/SmtpExamples.cs index 2f9d275fea..af99028989 100644 --- a/Documentation/Examples/SmtpExamples.cs +++ b/Documentation/Examples/SmtpExamples.cs @@ -62,7 +62,7 @@ public static void SaveToPickupDirectory (MimeMessage message, string pickupDire // which means that lines beginning with "." need to be escaped // by adding an extra "." to the beginning of the line. // - // Use an SmtpDataFilter "byte-stuff" the message as it is written + // Use an SmtpDataFilter to "byte-stuff" the message as it is written // to the file stream. This is the same process that an SmtpClient // would use when sending the message in a `DATA` command. using (var filtered = new FilteredStream (stream)) { @@ -89,6 +89,26 @@ public static void SaveToPickupDirectory (MimeMessage message, string pickupDire } #endregion + #region LoadFromPickupDirectory + public static MimeMessage LoadFromPickupDirectory (string fileName) + { + using (var stream = File.OpenRead (fileName)) { + // IIS pickup directories store messages that have been "byte-stuffed" + // which means that lines beginning with "." have been escaped by + // adding an extra "." to the beginning of the line. + // + // Use an SmtpDataFilter to decode the message as it is loaded from + // the file stream. This is the reverse process that an SmtpClient + // would use when sending the message in a `DATA` command. + using (var filtered = new FilteredStream (stream)) { + filtered.Add (new SmtpDataFilter (decode: true)); + + return MimeMessage.Load (filtered); + } + } + } + #endregion + #region ProtocolLogger public static void SendMessage (MimeMessage message) { diff --git a/MailKit/Net/Smtp/SmtpDataFilter.cs b/MailKit/Net/Smtp/SmtpDataFilter.cs index 8a6759d152..898c4cbbcf 100644 --- a/MailKit/Net/Smtp/SmtpDataFilter.cs +++ b/MailKit/Net/Smtp/SmtpDataFilter.cs @@ -31,15 +31,17 @@ namespace MailKit.Net.Smtp { /// An SMTP filter designed to format a message stream for the DATA command. /// /// - /// A special stream filter that escapes lines beginning with a '.' as needed when - /// sending a message via the SMTP protocol or when saving a message to an IIS - /// message pickup directory. + /// A special stream filter that can encode or decode lines beginning with a '.' as + /// needed when sending/receiving a message via the SMTP protocol or when saving a + /// message to an IIS message pickup directory. /// /// /// + /// /// public class SmtpDataFilter : MimeFilterBase { + readonly bool decode; bool bol; /// @@ -48,29 +50,18 @@ public class SmtpDataFilter : MimeFilterBase /// /// Creates a new . /// + /// true if the filter should decode the content; otherwise, false. /// /// + /// /// - public SmtpDataFilter () + public SmtpDataFilter (bool decode = false) { + this.decode = decode; bol = true; } - /// - /// Filter the specified input. - /// - /// - /// Filters the specified input buffer starting at the given index, - /// spanning across the specified number of bytes. - /// - /// The filtered output. - /// The input buffer. - /// The starting index of the input buffer. - /// The length of the input buffer, starting at . - /// The output index. - /// The output length. - /// If set to true, all internally buffered data should be flushed to the output buffer. - protected override byte[] Filter (byte[] input, int startIndex, int length, out int outputIndex, out int outputLength, bool flush) + byte[] Encode (byte[] input, int startIndex, int length, out int outputIndex, out int outputLength, bool flush) { int inputEnd = startIndex + length; bool escape = bol; @@ -125,6 +116,51 @@ protected override byte[] Filter (byte[] input, int startIndex, int length, out return OutputBuffer; } + byte[] Decode (byte[] input, int startIndex, int length, out int outputIndex, out int outputLength, bool flush) + { + int inputEnd = startIndex + length; + int index = startIndex; + + EnsureOutputSize (length, false); + outputLength = 0; + outputIndex = 0; + + while (index < inputEnd) { + byte c = input[index++]; + + if (bol && c == (byte) '.') { + bol = false; + } else { + OutputBuffer[outputLength++] = c; + bol = c == (byte) '\n'; + } + } + + return OutputBuffer; + } + + /// + /// Filter the specified input. + /// + /// + /// Filters the specified input buffer starting at the given index, + /// spanning across the specified number of bytes. + /// + /// The filtered output. + /// The input buffer. + /// The starting index of the input buffer. + /// The length of the input buffer, starting at . + /// The output index. + /// The output length. + /// If set to true, all internally buffered data should be flushed to the output buffer. + protected override byte[] Filter (byte[] input, int startIndex, int length, out int outputIndex, out int outputLength, bool flush) + { + if (decode) + return Decode (input, startIndex, length, out outputIndex, out outputLength, flush); + + return Encode (input, startIndex, length, out outputIndex, out outputLength, flush); + } + /// /// Reset the filter. /// diff --git a/UnitTests/Net/Smtp/SmtpDataFilterTests.cs b/UnitTests/Net/Smtp/SmtpDataFilterTests.cs index 69f625c383..bc5c11be83 100644 --- a/UnitTests/Net/Smtp/SmtpDataFilterTests.cs +++ b/UnitTests/Net/Smtp/SmtpDataFilterTests.cs @@ -41,6 +41,38 @@ public class SmtpDataFilterTests const string ComplexDataInput = "This is a bit more complicated\r\n... This line starts with a '.' and\r\ntherefore needs to be byte-stuffed\r\n. And so does this line!\r\n"; const string ComplexDataOutput = "This is a bit more complicated\r\n.... This line starts with a '.' and\r\ntherefore needs to be byte-stuffed\r\n.. And so does this line!\r\n"; + [Test] + public void TestSmtpDataFilterDecode () + { + var inputs = new string[] { SimpleDataInput, ComplexDataOutput }; + var outputs = new string[] { SimpleDataInput, ComplexDataInput }; + var filter = new SmtpDataFilter (decode: true); + + for (int i = 0; i < inputs.Length; i++) { + using (var memory = new MemoryStream ()) { + byte[] buffer; + int n; + + using (var filtered = new FilteredStream (memory)) { + filtered.Add (filter); + + buffer = Encoding.ASCII.GetBytes (inputs[i]); + filtered.Write (buffer, 0, buffer.Length); + filtered.Flush (); + } + + buffer = memory.GetBuffer (); + n = (int) memory.Length; + + var text = Encoding.ASCII.GetString (buffer, 0, n); + + Assert.AreEqual (outputs[i], text); + + filter.Reset (); + } + } + } + [Test] public void TestSmtpDataFilter () {