public MimeContent GetNextContent() { string key = ""; string value = ""; MimeContent mime = new MimeContent(); MemoryStream ms = new MemoryStream(); do { switch (state) { case MimeParserState.ReadBoundary: if (buffer[idx] == '\r') idx++; else if (buffer[idx] != '\n') value += (char)buffer[idx++]; else { idx++; value = ""; state = MimeParserState.ReadHeaderKey; } break; case MimeParserState.ReadHeaderKey: if (buffer[idx] == '\r') idx++; else if (buffer[idx] == '\n') { idx++; state = MimeParserState.ReadContent; } else if (buffer[idx] == ':') idx++; else if (buffer[idx] != ' ') key += (char)buffer[idx++]; else { idx++; value = ""; state = MimeParserState.ReadHeaderValue; } break; case MimeParserState.ReadHeaderValue: if (buffer[idx] == '\r') idx++; else if (buffer[idx] != '\n') value += (char)buffer[idx++]; else { idx++; mime.Headers.Add(key, value); key = ""; state = MimeParserState.ReadHeaderKey; } break; case MimeParserState.ReadContent: if (buffer[idx] == '\n' && idx < buffer.Length - _boundaryBytes.Length - 2) { // detect if next line is boundary line or content bool foundBound = true; for (int i = 0; i < _boundaryBytes.Length; i++) { if (_boundaryBytes[i] != buffer[idx + i + 1]) { foundBound = false; break; } } if (!foundBound) { ms.WriteByte(buffer[idx++]); continue; } else { idx++; state = MimeParserState.ReadBoundary; if (ms.ToArray().Length > 1) { mime.Content = new byte[ms.ToArray().Length - 1]; Array.Copy(ms.ToArray(), mime.Content, ms.ToArray().Length - 1); } return mime; } } else { ms.WriteByte(buffer[idx++]); } break; } } while (idx < buffer.Length); return null; }
unsafe MimeEntity ParseEntity(byte* inbuf) { state = MimeParserState.Headers; while (state < MimeParserState.Content) { if (Step (inbuf) == MimeParserState.Error) throw new Exception ("Failed to parse entity headers."); } var type = GetContentType (null); BoundaryType found; // Note: we pass 'false' as the 'toplevel' argument here because // we want the entity to consume all of the headers. var entity = MimeEntity.Create (options, type, headers, false); if (entity is Multipart) found = ConstructMultipart ((Multipart) entity, inbuf); else if (entity is MessagePart) found = ConstructMessagePart ((MessagePart) entity, inbuf); else found = ConstructMimePart ((MimePart) entity, inbuf); if (found != BoundaryType.Eos) state = MimeParserState.Complete; else state = MimeParserState.Eos; return entity; }
unsafe MimeMessage ParseMessage(byte* inbuf) { BoundaryType found; // scan the from-line if we are parsing an mbox while (state != MimeParserState.MessageHeaders) { switch (Step (inbuf)) { case MimeParserState.Error: throw new Exception ("Failed to find mbox From marker."); case MimeParserState.Eos: throw new Exception ("End of stream."); } } // parse the headers while (state < MimeParserState.Content) { if (Step (inbuf) == MimeParserState.Error) throw new Exception ("Failed to parse message headers."); } var message = new MimeMessage (options, headers); if (format == MimeFormat.Mbox && options.RespectContentLength) { bounds[0].ContentEnd = -1; for (int i = 0; i < headers.Count; i++) { if (icase.Compare (headers[i].Field, "Content-Length") != 0) continue; var value = headers[i].RawValue; int length, index = 0; if (!ParseUtils.TryParseInt32 (value, ref index, value.Length, out length)) continue; long endOffset = GetOffset (inputIndex) + length; bounds[0].ContentEnd = endOffset; break; } } var type = GetContentType (null); var entity = MimeEntity.Create (options, type, headers, true); message.Body = entity; if (entity is Multipart) found = ConstructMultipart ((Multipart) entity, inbuf); else if (entity is MessagePart) found = ConstructMessagePart ((MessagePart) entity, inbuf); else found = ConstructMimePart ((MimePart) entity, inbuf); if (found != BoundaryType.Eos) { if (format == MimeFormat.Mbox) state = MimeParserState.MboxMarker; else state = MimeParserState.Complete; } else { state = MimeParserState.Eos; } return message; }
unsafe BoundaryType ConstructMessagePart(MessagePart part, byte* inbuf) { BoundaryType found; if (bounds.Count > 0) { int atleast = Math.Max (ReadAheadSize, GetMaxBoundaryLength ()); if (ReadAhead (inbuf, atleast, 0) <= 0) return BoundaryType.Eos; byte* start = inbuf + inputIndex; byte* inend = inbuf + inputEnd; byte* inptr = start; *inend = (byte) '\n'; while (*inptr != (byte) '\n') inptr++; found = CheckBoundary (inputIndex, start, (int) (inptr - start)); switch (found) { case BoundaryType.ImmediateEndBoundary: case BoundaryType.ImmediateBoundary: case BoundaryType.ParentBoundary: return found; case BoundaryType.ParentEndBoundary: // ignore "From " boundaries, broken mailers tend to include these... if (!IsMboxMarker (start)) return found; break; } } // parse the headers... state = MimeParserState.Headers; if (Step (inbuf) == 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? return BoundaryType.Eos; } var message = new MimeMessage (options, headers); var type = GetContentType (null); var entity = MimeEntity.Create (options, type, headers, true); message.Body = entity; if (entity is Multipart) found = ConstructMultipart ((Multipart) entity, inbuf); else if (entity is MessagePart) found = ConstructMessagePart ((MessagePart) entity, inbuf); else found = ConstructMimePart ((MimePart) entity, inbuf); part.Message = message; return found; }
unsafe BoundaryType MultipartScanSubparts(Multipart multipart, byte* inbuf) { BoundaryType found; do { // skip over the boundary marker if (!SkipLine (inbuf)) return BoundaryType.Eos; // parse the headers state = MimeParserState.Headers; if (Step (inbuf) == MimeParserState.Error) return BoundaryType.Eos; //if (state == ParserState.Complete && headers.Count == 0) // return BoundaryType.EndBoundary; var type = GetContentType (multipart.ContentType); var entity = MimeEntity.Create (options, type, headers, false); if (entity is Multipart) found = ConstructMultipart ((Multipart) entity, inbuf); else if (entity is MessagePart) found = ConstructMessagePart ((MessagePart) entity, inbuf); else found = ConstructMimePart ((MimePart) entity, inbuf); multipart.Add (entity); } while (found == BoundaryType.ImmediateBoundary); return found; }
/// <summary> /// Sets the stream to parse. /// </summary> /// <param name="options">The parser options.</param> /// <param name="stream">The stream to parse.</param> /// <param name="format">The format of the stream.</param> /// <param name="persistent"><c>true</c> if the stream is persistent; otherwise <c>false</c>.</param> /// <remarks> /// <para>If <paramref name="persistent"/> is <c>true</c> and <paramref name="stream"/> is seekable, then /// the <see cref="MimeParser"/> will not copy the content of <see cref="MimePart"/>s into memory. Instead, /// it will use a <see cref="MimeKit.IO.BoundStream"/> to reference a substream of <paramref name="stream"/>. /// This has the potential to not only save mmeory usage, but also improve <see cref="MimeParser"/> /// performance.</para> /// <para>It should be noted, however, that disposing <paramref name="stream"/> will make it impossible /// for <see cref="ContentObject"/> to read the content.</para> /// </remarks> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> public void SetStream(ParserOptions options, Stream stream, MimeFormat format, bool persistent) { if (options == null) throw new ArgumentNullException ("options"); if (stream == null) throw new ArgumentNullException ("stream"); this.persistent = persistent && stream.CanSeek; this.options = options.Clone (); this.format = format; this.stream = stream; inputIndex = inputStart; inputEnd = inputStart; mboxMarkerOffset = 0; mboxMarkerLength = 0; offset = stream.CanSeek ? stream.Position : 0; headers.Clear (); headerOffset = 0; headerIndex = 0; bounds.Clear (); if (format == MimeFormat.Mbox) { bounds.Add (Boundary.CreateMboxBoundary ()); mboxMarkerBuffer = new byte[ReadAheadSize]; state = MimeParserState.MboxMarker; } else { state = MimeParserState.Initialized; } }
unsafe void StepMboxMarker(byte* inbuf) { bool complete = false; bool needInput; int left = 0; mboxMarkerLength = 0; do { if (ReadAhead (inbuf, Math.Max (ReadAheadSize, left), 0) <= left) { // failed to find a From line; EOF reached state = MimeParserState.Error; inputIndex = inputEnd; return; } needInput = false; byte* inptr = inbuf + inputIndex; byte* inend = inbuf + inputEnd; *inend = (byte) '\n'; while (inptr < inend) { byte* start = inptr; // scan for the end of the line while (*inptr != (byte) '\n') inptr++; long length = inptr - start; // consume the '\n' inptr++; if (inptr >= inend) { // we don't have enough input data inputIndex = (int) (start - inbuf); left = (int) length; needInput = true; break; } if (length >= 5 && IsMboxMarker (start)) { long startIndex = start - inbuf; mboxMarkerOffset = GetOffset ((int) startIndex); mboxMarkerLength = (int) length; if (mboxMarkerBuffer.Length < mboxMarkerLength) Array.Resize (ref mboxMarkerBuffer, mboxMarkerLength); Array.Copy (input, startIndex, mboxMarkerBuffer, 0, length); complete = true; break; } } if (!needInput) { inputIndex = (int) (inptr - inbuf); left = 0; } } while (!complete); state = MimeParserState.MessageHeaders; }
unsafe void StepHeaders(byte* inbuf) { bool scanningFieldName = true; bool checkFolded = false; bool midline = false; bool valid = true; int left = 0; long length; bool eoln; ResetRawHeaderData (); headers.Clear (); do { if (ReadAhead (inbuf, Math.Max (ReadAheadSize, left), 0) <= left) { // failed to find the end of the headers; EOF reached state = MimeParserState.Error; inputIndex = inputEnd; return; } byte* inptr = inbuf + inputIndex; byte* inend = inbuf + inputEnd; bool needInput = false; *inend = (byte) '\n'; while (inptr < inend) { byte* start = inptr; // if we are scanning a new line, check for a folded header if (!midline && checkFolded && !(*inptr).IsBlank ()) { ParseAndAppendHeader (); headerOffset = GetOffset ((int) (inptr - inbuf)); scanningFieldName = true; checkFolded = false; valid = true; } eoln = IsEoln (inptr); if (scanningFieldName && !eoln) { // scan and validate the field name if (*inptr != (byte) ':') { *inend = (byte) ':'; while (*inptr != (byte) ':') { if (IsBlankOrControl (*inptr)) { valid = false; break; } inptr++; } if (inptr == inend) { // we don't have enough input data left = (int) (inend - start); inputIndex = (int) (start - inbuf); needInput = true; break; } *inend = (byte) '\n'; } else { valid = false; } if (!valid) { length = inptr - start; if (format == MimeFormat.Mbox && length == 4 && IsMboxMarker (start)) { // we've found the start of the next message... inputIndex = (int) (start - inbuf); state = MimeParserState.Complete; headerIndex = 0; return; } if (state == MimeParserState.MessageHeaders && headers.Count == 0) { // ignore From-lines that might appear at the start of a message if (length != 4 || !IsMboxMarker (start)) { inputIndex = (int) (start - inbuf); state = MimeParserState.Error; headerIndex = 0; return; } } } } scanningFieldName = false; while (*inptr != (byte) '\n') inptr++; if (inptr == inend) { // we didn't manage to slurp up a full line, save what we have and refill our input buffer length = inptr - start; if (inptr > start) { // Note: if the last byte we got was a '\r', rewind a byte inptr--; if (*inptr == (byte) '\r') length--; else inptr++; } AppendRawHeaderData ((int) (start - inbuf), (int) length); inputIndex = (int) (inptr - inbuf); left = (int) (inend - inptr); needInput = true; midline = true; break; } // check to see if we've reached the end of the headers if (!midline && IsEoln (start)) { inputIndex = (int) (inptr - inbuf) + 1; state = MimeParserState.Content; ParseAndAppendHeader (); headerIndex = 0; return; } length = (inptr + 1) - start; AppendRawHeaderData ((int) (start - inbuf), (int) length); checkFolded = true; midline = false; inptr++; } if (!needInput) { inputIndex = (int) (inptr - inbuf); left = (int) (inend - inptr); } } while (true); }
unsafe MimeParserState Step(byte* inbuf) { switch (state) { case MimeParserState.Error: break; case MimeParserState.Initialized: state = format == MimeFormat.Mbox ? MimeParserState.MboxMarker : MimeParserState.MessageHeaders; break; case MimeParserState.MboxMarker: StepMboxMarker (inbuf); break; case MimeParserState.MessageHeaders: case MimeParserState.Headers: StepHeaders (inbuf); break; case MimeParserState.Content: break; case MimeParserState.Complete: break; case MimeParserState.Eos: break; default: throw new ArgumentOutOfRangeException (); } return state; }
public MimeContent GetNextContent() { string key = ""; string value = ""; MimeContent mime = new MimeContent(); MemoryStream ms = new MemoryStream(); do { switch (state) { case MimeParserState.ReadBoundary: if (buffer[idx] == '\r') { idx++; } else if (buffer[idx] != '\n') { value += (char)buffer[idx++]; } else { idx++; value = ""; state = MimeParserState.ReadHeaderKey; } break; case MimeParserState.ReadHeaderKey: if (buffer[idx] == '\r') { idx++; } else if (buffer[idx] == '\n') { idx++; state = MimeParserState.ReadContent; } else if (buffer[idx] == ':') { idx++; } else if (buffer[idx] != ' ') { key += (char)buffer[idx++]; } else { idx++; value = ""; state = MimeParserState.ReadHeaderValue; } break; case MimeParserState.ReadHeaderValue: if (buffer[idx] == '\r') { idx++; } else if (buffer[idx] != '\n') { value += (char)buffer[idx++]; } else { idx++; mime.Headers.Add(key, value); key = ""; state = MimeParserState.ReadHeaderKey; } break; case MimeParserState.ReadContent: if (buffer[idx] == '\n' && idx < buffer.Length - _boundaryBytes.Length - 2) { // detect if next line is boundary line or content bool foundBound = true; for (int i = 0; i < _boundaryBytes.Length; i++) { if (_boundaryBytes[i] != buffer[idx + i + 1]) { foundBound = false; break; } } if (!foundBound) { ms.WriteByte(buffer[idx++]); continue; } else { idx++; state = MimeParserState.ReadBoundary; if (ms.ToArray().Length > 1) { mime.Content = new byte[ms.ToArray().Length - 1]; Array.Copy(ms.ToArray(), mime.Content, ms.ToArray().Length - 1); } return(mime); } } else { ms.WriteByte(buffer[idx++]); } break; } }while (idx < buffer.Length); return(null); }