/// <summary> /// Asynchronously parses a message from the stream. /// </summary> /// <remarks> /// Parses a message from the stream. /// </remarks> /// <returns>The parsed message.</returns> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.FormatException"> /// There was an error parsing the message. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public async Task <MimeMessage> ParseMessageAsync(CancellationToken cancellationToken = default(CancellationToken)) { // Note: if a previously parsed MimePart's content has been read, // then the stream position will have moved and will need to be // reset. if (persistent && stream.Position != position) { stream.Seek(position, SeekOrigin.Begin); } // scan the from-line if we are parsing an mbox while (state != MimeParserState.MessageHeaders) { switch (await StepAsync(cancellationToken).ConfigureAwait(false)) { case MimeParserState.Error: throw new FormatException("Failed to find mbox From marker."); case MimeParserState.Eos: throw new FormatException("End of stream."); } } toplevel = true; // parse the headers var beginLineNumber = lineNumber; if (state < MimeParserState.Content && await StepAsync(cancellationToken).ConfigureAwait(false) == MimeParserState.Error) { throw new FormatException("Failed to parse message headers."); } var message = new MimeMessage(options, headers, RfcComplianceMode.Loose); var messageArgs = new MimeMessageEndEventArgs(message) { HeadersEndOffset = headerBlockEnd, BeginOffset = headerBlockBegin, LineNumber = beginLineNumber }; OnMimeMessageBegin(messageArgs); if (format == MimeFormat.Mbox && options.RespectContentLength) { contentEnd = 0; for (int i = 0; i < headers.Count; i++) { if (headers[i].Id != HeaderId.ContentLength) { continue; } var value = headers[i].RawValue; int index = 0; if (!ParseUtils.SkipWhiteSpace(value, ref index, value.Length)) { continue; } if (!ParseUtils.TryParseInt32(value, ref index, value.Length, out int length)) { continue; } contentEnd = GetOffset(inputIndex) + length; break; } } var type = GetContentType(null); var entity = options.CreateEntity(type, headers, true, 0); var entityArgs = new MimeEntityEndEventArgs(entity) { HeadersEndOffset = headerBlockEnd, BeginOffset = headerBlockBegin, LineNumber = beginLineNumber }; OnMimeEntityBegin(entityArgs); message.Body = entity; if (entity is Multipart) { await ConstructMultipartAsync((Multipart)entity, entityArgs, 0, cancellationToken).ConfigureAwait(false); } else if (entity is MessagePart) { await ConstructMessagePartAsync((MessagePart)entity, entityArgs, 0, cancellationToken).ConfigureAwait(false); } else { await ConstructMimePartAsync((MimePart)entity, entityArgs, cancellationToken).ConfigureAwait(false); } var endOffset = GetEndOffset(inputIndex); messageArgs.HeadersEndOffset = entityArgs.HeadersEndOffset = Math.Min(entityArgs.HeadersEndOffset, endOffset); messageArgs.EndOffset = entityArgs.EndOffset = endOffset; if (boundary != BoundaryType.Eos) { if (format == MimeFormat.Mbox) { state = MimeParserState.MboxMarker; } else { state = MimeParserState.Complete; } } else { state = MimeParserState.Eos; } OnMimeEntityEnd(entityArgs); OnMimeMessageEnd(messageArgs); return(message); }
async Task ConstructMessagePartAsync(MessagePart rfc822, MimeEntityEndEventArgs args, int depth, CancellationToken cancellationToken) { var beginOffset = GetOffset(inputIndex); var beginLineNumber = lineNumber; if (bounds.Count > 0) { int atleast = Math.Max(ReadAheadSize, GetMaxBoundaryLength()); if (await ReadAheadAsync(atleast, 0, cancellationToken).ConfigureAwait(false) <= 0) { boundary = BoundaryType.Eos; return; } unsafe { fixed(byte *inbuf = input) { byte *start = inbuf + inputIndex; byte *inend = inbuf + inputEnd; byte *inptr = start; *inend = (byte)'\n'; while (*inptr != (byte)'\n') { inptr++; } boundary = CheckBoundary(inputIndex, start, (int)(inptr - start)); switch (boundary) { case BoundaryType.ImmediateEndBoundary: case BoundaryType.ImmediateBoundary: case BoundaryType.ParentBoundary: return; case BoundaryType.ParentEndBoundary: // ignore "From " boundaries, broken mailers tend to include these... if (!IsMboxMarker(start)) { return; } break; } } } } // parse the headers... state = MimeParserState.MessageHeaders; if (await StepAsync(cancellationToken).ConfigureAwait(false) == MimeParserState.Error) { // Note: this either means that StepHeaders() found the end of the stream // or an invalid header field name at the start of the message headers, // which likely means that this is not a valid MIME stream? boundary = BoundaryType.Eos; return; } var message = new MimeMessage(options, headers, RfcComplianceMode.Loose); var messageArgs = new MimeMessageEndEventArgs(message, rfc822) { HeadersEndOffset = headerBlockEnd, BeginOffset = headerBlockBegin, LineNumber = beginLineNumber }; OnMimeMessageBegin(messageArgs); if (preHeaderBuffer.Length > 0) { message.MboxMarker = new byte[preHeaderLength]; Buffer.BlockCopy(preHeaderBuffer, 0, message.MboxMarker, 0, preHeaderLength); } var type = GetContentType(null); var entity = options.CreateEntity(type, headers, true, depth); var entityArgs = new MimeEntityEndEventArgs(entity) { HeadersEndOffset = headerBlockEnd, BeginOffset = headerBlockBegin, LineNumber = beginLineNumber }; OnMimeEntityBegin(entityArgs); message.Body = entity; if (entity is Multipart) { await ConstructMultipartAsync((Multipart)entity, entityArgs, depth + 1, cancellationToken).ConfigureAwait(false); } else if (entity is MessagePart) { await ConstructMessagePartAsync((MessagePart)entity, entityArgs, depth + 1, cancellationToken).ConfigureAwait(false); } else { await ConstructMimePartAsync((MimePart)entity, entityArgs, cancellationToken).ConfigureAwait(false); } rfc822.Message = message; var endOffset = GetEndOffset(inputIndex); messageArgs.HeadersEndOffset = entityArgs.HeadersEndOffset = Math.Min(entityArgs.HeadersEndOffset, endOffset); messageArgs.EndOffset = entityArgs.EndOffset = endOffset; OnMimeEntityEnd(entityArgs); OnMimeMessageEnd(messageArgs); args.Lines = GetLineCount(beginLineNumber, beginOffset, endOffset); }