Managed IIS SMTP Sink Wrapper: Message.CopyContentToStream

Posted by

It seems there's a bug in the Managed Sink Wrappers for SMTP and Transport Events, which I used to write SMTP Command and Transport sinks for IIS in C#. The bug is in the Message.CopyContentToStream method. The problem only occurs when a message's MIME content is a multiple of 1024 bytes long. It manifests itself as an EndOfStreamException being thrown when you call Message.CopyContentToStream.

Here's some replacement code that I've written which fixes the problem (note, I didn't modify the actual source to the managed wrappers; instead I wrote a separate method for doing the same thing. I didn't want to change the source in case it ever gets updated by Microsoft).


void CopyContentToStream(Message msg, Stream stream)
{
  const int BYTES_TO_READ = 1024;
 
  byte[] buffer;
  uint offset = 0;
  do
  {
    try
    {
      buffer = msg.ReadContent(offset, BYTES_TO_READ);
    }
    catch(EndOfStreamException)
    {
      // We get this exception when we try to read
      // from the stream, but we're already at the end.
      // This just means we can exit our loop now.
      break;
    }
 
    if (buffer.Length > 0)
    {
      stream.Write(buffer, 0, buffer.Length);
    }
    offset += ((uint) buffer.Length);
  } while (buffer.Length == BYTES_TO_READ);
 
  stream.Position = 0;
}

The code above is almost the same as the code in the wrapped CopyContentToStream, except I've added an exception handler to catch EndOfStreamException. The problem is that if you pass a dwOffset to ReadContent that's past the end of the stream, it throws the EndOfStreamException. But notice the condition of the while loop: it's while(buffer.Length == BYTES_TO_READ) - this means that as soon as ReadContent returns a value that's < 1024, we exit the loop and all is well.

However, if the content is a multiple of 1024 bytes long, the last chunk returns 1024, meaning the loop condition is still true, but the offset is then placed after the end of the stream, and the next time it calls ReadContent, it gets the exceptions.

So, in my code, I kept the loop condition the same, because it saves us getting an exception when the content is not a multiple of 1024 bytes (which is most of the time, of course). But on those rare(-ish) occasions when the content is a multiple of 1024, the exception will be thrown, and we just break out of the loop.

blog comments powered by Disqus