public async Task <MimePartSpan> ReadStructureAsync(Stream mailStream, CancellationToken cancellationToken) { using (UnencodedStreamReader reader = new UnencodedStreamReader(mailStream, leaveOpen: true)) { MimePartBuilder topLevelBuilder = new MimePartBuilder(); var currentBuilder = topLevelBuilder; StringBuilder currentHeader = new StringBuilder(); while ((await ReadLine(reader, cancellationToken)).TryGet(out Memory <byte> line)) { long position = reader.BytePositition; if (currentBuilder.Parent != null && currentBuilder.Parent.EndBoundary.Span.SequenceEqual(line.Span)) { currentBuilder = currentBuilder.Parent; continue; } if (currentBuilder.Parent != null && currentBuilder.Parent.Boundary.Span.SequenceEqual(line.Span)) { currentBuilder = new MimePartBuilder(currentBuilder.Parent, position); continue; } if (!currentBuilder.Boundary.IsEmpty && currentBuilder.Children == null && currentBuilder.Boundary.Span.SequenceEqual(line.Span)) { currentBuilder.Children = new List <MimePartBuilder>(); currentBuilder = new MimePartBuilder(currentBuilder, position); continue; } currentBuilder.End = position; if (!currentBuilder.HeaderComplete) { void ProcessPendingHeader() { if (currentHeader.Length == 0) { return; } string headerString = currentHeader.ToString(); currentHeader.Clear(); if (TestRegex(ContentTypeRegex, headerString, out var match)) { string type = match.Groups["type"].Value; if (type == "multipart") { CaptureCollection paramCaptures = match.Groups["param"].Captures; CaptureCollection valueCaptures = match.Groups["value"].Captures; for (int i = 0; i < paramCaptures.Count; i++) { switch (paramCaptures[i].Value.ToLowerInvariant()) { case "charset": break; case "boundary": ReadOnlySpan <char> value = valueCaptures[i].Value.AsSpan(); if (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"') { value = value.Slice(1, value.Length - 2); if (value.Contains("\\", StringComparison.Ordinal)) { value = Regex.Replace(value.ToString(), @"\\.", m => m.Value[1].ToString()); } } currentBuilder.EndBoundary = Encoding.ASCII.GetBytes("--" + value.ToString() + "--"); break; } } } } } if (line.Length == 0) { // header's over, process pending ones ProcessPendingHeader(); currentBuilder.ContentStart = position; continue; } if (IsImportantHeader(line)) { currentHeader.Append(Encoding.ASCII.GetString(line.Span)); continue; } if (currentHeader.Length > 0) { byte first = line.Span[0]; if (first == ' ' || first == '\t') { currentHeader.Append(Encoding.ASCII.GetString(line.Span)); continue; } // We found another header, process the current one. ProcessPendingHeader(); if (IsImportantHeader(line)) { currentHeader.Append(Encoding.ASCII.GetString(line.Span)); } } } } currentBuilder.End = reader.BytePositition; return(topLevelBuilder.ToMimePart()); } }
public MimePartBuilder(MimePartBuilder parent, long position) { Parent = parent; Start = position; parent?.Children.Add(this); }