Skip to content

Commit 4099004

Browse files
committed
Modified the ExperimentalMimeParser to record raw boundary markers
MimeParser and previous iterations of the ExperimentalMimeParser did not record the exact boundary marker used in the MIME stream to separate each child of a multipart. Normally, this isn't a problem because the boundary marker used by properly written email software is just "--{boundary}\r\n", BUT according to the MIME specifications, these lines are allowed to contain trailing whitespace characters that should be ignored by MIME parsers (which is what MimeParser and ExperimentalMimeParser have been doing). That said, when writing a Multipart back out to a stream, if we do not record the *exact* boundary markers used between each child part, then we cannot write out an *exact* copy of what the parser encountered in the original MIME stream. As far as I have been able to tell, this is an extremely uncommon scenario but in the interest of persuing perfection, ExperimentalMimeParser and Multipart have now been modified to handle this. MimeParser still suffers from this flaw, but I'll be looking into solving that next.
1 parent d4d8588 commit 4099004

8 files changed

+592
-94
lines changed

MimeKit/AsyncMimeParser.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,11 @@ async Task ConstructMultipartAsync (Multipart multipart, MimeEntityEndEventArgs
482482
//OnMultipartEndBoundaryBegin (multipart, GetEndOffset (inputIndex));
483483

484484
// consume the end boundary and read the epilogue (if there is one)
485-
multipart.WriteEndBoundary = true;
486485
await SkipLineAsync (false, cancellationToken).ConfigureAwait (false);
486+
487+
// FIXME: we should save the raw end boundary marker in case it contains trailing whitespace
488+
multipart.RawEndBoundary = null;
489+
487490
PopBoundary ();
488491

489492
//OnMultipartEndBoundaryEnd (multipart, GetOffset (inputIndex));
@@ -498,8 +501,6 @@ async Task ConstructMultipartAsync (Multipart multipart, MimeEntityEndEventArgs
498501
endOffset = GetEndOffset (inputIndex);
499502
args.Lines = GetLineCount (beginLineNumber, beginOffset, endOffset);
500503

501-
multipart.WriteEndBoundary = false;
502-
503504
// We either found the end of the stream or we found a parent's boundary
504505
PopBoundary ();
505506
}

MimeKit/ExperimentalMimeParser.cs

+69-32
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ public class ExperimentalMimeParser : MimeReader, IMimeParser
5757
byte[] preHeaderBuffer;
5858
int preHeaderLength;
5959

60+
byte[] rawBoundary;
61+
6062
// MimePart content and Multipart preamble/epilogue state
6163
Stream content;
6264

@@ -251,17 +253,26 @@ public void SetStream (Stream stream, bool persistent)
251253
SetStream (stream, MimeFormat.Default, persistent);
252254
}
253255

254-
void PopEntity ()
256+
void PushEntity (MimeEntity entity)
255257
{
256-
if (stack.Count > 1) {
257-
var entity = (MimeEntity) stack.Pop ();
258+
if (stack.Count > 0) {
258259
var parent = stack.Peek ();
259260

260-
if (parent is MimeMessage message)
261+
if (parent is Multipart multipart) {
262+
multipart.AddInternal (entity, rawBoundary);
263+
rawBoundary = null;
264+
} else if (parent is MimeMessage message) {
261265
message.Body = entity;
262-
else
263-
((Multipart) parent).InternalAdd (entity);
266+
}
264267
}
268+
269+
stack.Push (entity);
270+
}
271+
272+
void PopEntity ()
273+
{
274+
if (stack.Count > 1)
275+
stack.Pop ();
265276
}
266277

267278
#region Mbox Events
@@ -390,6 +401,12 @@ protected override void OnMimeMessageBegin (long beginOffset, int beginLineNumbe
390401
Buffer.BlockCopy (preHeaderBuffer, 0, message.MboxMarker, 0, preHeaderLength);
391402
}
392403

404+
if (stack.Count > 0) {
405+
var rfc822 = (MessagePart) stack.Peek ();
406+
407+
rfc822.Message = message;
408+
}
409+
393410
stack.Push (message);
394411
}
395412

@@ -408,12 +425,8 @@ protected override void OnMimeMessageBegin (long beginOffset, int beginLineNumbe
408425
/// <param name="cancellationToken">The cancellation token.</param>
409426
protected override void OnMimeMessageEnd (long beginOffset, int beginLineNumber, long headersEndOffset, long endOffset, int lines, CancellationToken cancellationToken)
410427
{
411-
if (stack.Count > 1) {
412-
var message = (MimeMessage) stack.Pop ();
413-
var rfc822 = (MessagePart) stack.Peek ();
414-
415-
rfc822.Message = message;
416-
}
428+
if (stack.Count > 1)
429+
stack.Pop ();
417430
}
418431

419432
#endregion MimeMessage Events
@@ -436,7 +449,7 @@ protected override void OnMimePartBegin (ContentType contentType, long beginOffs
436449
var toplevel = stack.Count > 0 && stack.Peek () is MimeMessage;
437450
var part = Options.CreateEntity (contentType, headers, toplevel, depth);
438451

439-
stack.Push (part);
452+
PushEntity (part);
440453
}
441454

442455
/// <summary>
@@ -545,7 +558,7 @@ protected override void OnMessagePartBegin (ContentType contentType, long beginO
545558
var rfc822 = Options.CreateEntity (contentType, headers, toplevel, depth);
546559

547560
parsingMessageHeaders = true;
548-
stack.Push (rfc822);
561+
PushEntity (rfc822);
549562
depth++;
550563
}
551564

@@ -589,27 +602,10 @@ protected override void OnMultipartBegin (ContentType contentType, long beginOff
589602
var toplevel = stack.Count > 0 && stack.Peek () is MimeMessage;
590603
var multipart = Options.CreateEntity (contentType, headers, toplevel, depth);
591604

592-
stack.Push (multipart);
605+
PushEntity (multipart);
593606
depth++;
594607
}
595608

596-
/// <summary>
597-
/// Called when a multipart end boundary is encountered in the stream.
598-
/// </summary>
599-
/// <remarks>
600-
/// Called when a multipart end boundary is encountered in the stream.
601-
/// </remarks>
602-
/// <param name="beginOffset">The offset into the stream where the boundary marker began.</param>
603-
/// <param name="lineNumber">The line number where the boundary marker was found in the stream.</param>
604-
/// <param name="endOffset">The offset into the stream where the boundary marker ended.</param>
605-
/// <param name="cancellationToken">The cancellation token.</param>
606-
protected override void OnMultipartEndBoundaryEnd (long beginOffset, int lineNumber, long endOffset, CancellationToken cancellationToken)
607-
{
608-
var multipart = (Multipart) stack.Peek ();
609-
610-
multipart.WriteEndBoundary = true;
611-
}
612-
613609
/// <summary>
614610
/// Called when the beginning of the preamble of a multipart is encountered in the stream.
615611
/// </summary>
@@ -663,6 +659,47 @@ protected override void OnMultipartPreambleEnd (long beginOffset, int beginLineN
663659
content = null;
664660
}
665661

662+
/// <summary>
663+
/// Called when a multipart boundary is encountered in the stream.
664+
/// </summary>
665+
/// <remarks>
666+
/// Called when a multipart boundary is encountered in the stream.
667+
/// </remarks>
668+
/// <param name="buffer">The buffer containing the boundary marker.</param>
669+
/// <param name="startIndex">The index denoting the starting position of the boundary marker within the buffer.</param>
670+
/// <param name="count">The length of the boundary marker within the buffer, in bytes.</param>
671+
/// <param name="beginOffset">The offset into the stream where the boundary marker began.</param>
672+
/// <param name="lineNumber">The line number where the boundary marker exists within the stream.</param>
673+
/// <param name="cancellationToken">The cancellation token.</param>
674+
protected override void OnMultipartBoundaryRead (byte[] buffer, int startIndex, int count, long beginOffset, int lineNumber, CancellationToken cancellationToken)
675+
{
676+
rawBoundary = new byte[count];
677+
678+
Buffer.BlockCopy (buffer, startIndex, rawBoundary, 0, count);
679+
}
680+
681+
/// <summary>
682+
/// Called when a multipart end boundary is encountered in the stream.
683+
/// </summary>
684+
/// <remarks>
685+
/// Called when a multipart end boundary is encountered in the stream.
686+
/// </remarks>
687+
/// <param name="buffer">The buffer containing the boundary marker.</param>
688+
/// <param name="startIndex">The index denoting the starting position of the boundary marker within the buffer.</param>
689+
/// <param name="count">The length of the boundary marker within the buffer, in bytes.</param>
690+
/// <param name="beginOffset">The offset into the stream where the boundary marker began.</param>
691+
/// <param name="lineNumber">The line number where the boundary marker exists within the stream.</param>
692+
/// <param name="cancellationToken">The cancellation token.</param>
693+
protected override void OnMultipartEndBoundaryRead (byte[] buffer, int startIndex, int count, long beginOffset, int lineNumber, CancellationToken cancellationToken)
694+
{
695+
var multipart = (Multipart) stack.Peek ();
696+
var rawEndBoundary = new byte[count];
697+
698+
Buffer.BlockCopy (buffer, startIndex, rawEndBoundary, 0, count);
699+
700+
multipart.RawEndBoundary = rawEndBoundary;
701+
}
702+
666703
/// <summary>
667704
/// Called when the beginning of the epilogue of a multipart is encountered in the stream.
668705
/// </summary>

MimeKit/MimeParser.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1720,8 +1720,11 @@ unsafe void ConstructMultipart (Multipart multipart, MimeEntityEndEventArgs args
17201720
//OnMultipartEndBoundaryBegin (multipart, GetEndOffset (inputIndex));
17211721

17221722
// consume the end boundary and read the epilogue (if there is one)
1723-
multipart.WriteEndBoundary = true;
17241723
SkipLine (inbuf, false, cancellationToken);
1724+
1725+
// FIXME: we should save the raw end boundary marker in case it contains trailing whitespace
1726+
multipart.RawEndBoundary = null;
1727+
17251728
PopBoundary ();
17261729

17271730
//OnMultipartEndBoundaryEnd (multipart, GetOffset (inputIndex));
@@ -1736,8 +1739,6 @@ unsafe void ConstructMultipart (Multipart multipart, MimeEntityEndEventArgs args
17361739
endOffset = GetEndOffset (inputIndex);
17371740
args.Lines = GetLineCount (beginLineNumber, beginOffset, endOffset);
17381741

1739-
multipart.WriteEndBoundary = false;
1740-
17411742
// We either found the end of the stream or we found a parent's boundary
17421743
PopBoundary ();
17431744
}

0 commit comments

Comments
 (0)