public void Setup () { var bytes = new byte[10 * 1024]; int position = 0; random = new Random (); random.NextBytes (bytes); // this is our master stream, all operations on the chained stream // should match the results on this stream master = new MemoryStream (bytes); cbuf = new byte[4096]; mbuf = new byte[4096]; // make a handful of smaller streams based on master to chain together chained = new ChainedStream (); while (position < bytes.Length) { int n = Math.Min (bytes.Length - position, random.Next () % 4096); var segment = new byte[n]; Buffer.BlockCopy (bytes, position, segment, 0, n); lengths.Add (n); position += n; chained.Add (new ReadOneByteStream (new MemoryStream (segment))); } }
/// <summary> /// Join the specified message/partial parts into the complete message. /// </summary> /// <param name="options">The parser options to use.</param> /// <param name="partials">The list of partial message parts.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="partials"/>is <c>null</c>.</para> /// </exception> public static MimeMessage Join(ParserOptions options, IEnumerable<MessagePartial> partials) { if (options == null) throw new ArgumentNullException ("options"); if (partials == null) throw new ArgumentNullException ("partials"); var parts = partials.ToList (); if (parts.Count == 0) return null; parts.Sort (PartialCompare); if (!parts[parts.Count - 1].Total.HasValue) throw new ArgumentException ("partials"); int total = parts[parts.Count - 1].Total.Value; if (parts.Count != total) throw new ArgumentException ("partials"); string id = parts[0].Id; using (var chained = new ChainedStream ()) { // chain all of the partial content streams... for (int i = 0; i < parts.Count; i++) { int number = parts[i].Number.Value; if (number != i + 1) throw new ArgumentException ("partials"); var content = parts[i].ContentObject; content.Stream.Seek (0, SeekOrigin.Begin); var filtered = new FilteredStream (content.Stream); filtered.Add (DecoderFilter.Create (content.Encoding)); chained.Add (filtered); } var parser = new MimeParser (options, chained); return parser.ParseMessage (); } }
/// <summary> /// Gets the specified body part. /// </summary> /// <remarks> /// Gets the specified body part. /// </remarks> /// <returns>The body part.</returns> /// <param name="index">The index of the message.</param> /// <param name="partSpecifier">The body part specifier.</param> /// <param name="headersOnly"><c>true</c> if only the headers should be downloaded; otherwise, <c>false</c>></param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress reporting mechanism.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="partSpecifier"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="index"/> is out of range. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="MessageNotFoundException"> /// The IMAP server did not return the requested message. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public MimeEntity GetBodyPart (int index, string partSpecifier, bool headersOnly, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException ("index"); if (partSpecifier == null) throw new ArgumentNullException ("partSpecifier"); CheckState (true, false); string[] tags; var command = string.Format ("FETCH {0} ({1})\r\n", index + 1, GetBodyPartQuery (partSpecifier, headersOnly, out tags)); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamContext (progress); ChainedStream chained; bool dispose = false; Stream stream; ic.RegisterUntaggedHandler ("FETCH", FetchStream); ic.UserData = ctx; Engine.QueueCommand (ic); try { Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); chained = new ChainedStream (); foreach (var tag in tags) { if (!ctx.Sections.TryGetValue (tag, out stream)) throw new MessageNotFoundException ("The IMAP server did not return the requested body part."); if (!(stream is MemoryStream || stream is MemoryBlockStream)) dispose = true; chained.Add (stream); } foreach (var tag in tags) ctx.Sections.Remove (tag); } finally { ctx.Dispose (); } var entity = ParseEntity (chained, dispose, cancellationToken); if (partSpecifier.Length == 0) { for (int i = entity.Headers.Count; i > 0; i--) { var header = entity.Headers[i - 1]; if (!header.Field.StartsWith ("Content-", StringComparison.OrdinalIgnoreCase)) entity.Headers.RemoveAt (i - 1); } } return entity; }
/// <summary> /// Load a <see cref="MimeEntity"/> from the specified content stream. /// </summary> /// <remarks> /// This method is mostly meant for use with APIs such as <see cref="System.Net.HttpWebResponse"/> /// where the headers are parsed separately from the content. /// </remarks> /// <returns>The parsed MIME entity.</returns> /// <param name="options">The parser options.</param> /// <param name="contentType">The Content-Type of the stream.</param> /// <param name="content">The content stream.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="contentType"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="content"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.FormatException"> /// There was an error parsing the entity. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public static MimeEntity Load (ParserOptions options, ContentType contentType, Stream content, CancellationToken cancellationToken = default (CancellationToken)) { if (options == null) throw new ArgumentNullException ("options"); if (contentType == null) throw new ArgumentNullException ("contentType"); if (content == null) throw new ArgumentNullException ("content"); var format = FormatOptions.Default.Clone (); format.NewLineFormat = NewLineFormat.Dos; var encoded = contentType.Encode (format, Encoding.UTF8); var header = string.Format ("Content-Type:{0}\r\n", encoded); var chained = new ChainedStream (); chained.Add (new MemoryStream (Encoding.UTF8.GetBytes (header), false)); chained.Add (content); return Load (options, chained, cancellationToken); }
public void TestChainedHeadersAndContent () { var buf = Encoding.ASCII.GetBytes ("Content-Type: text/plain\r\n\r\n"); var headers = new MemoryStream (); var content = new MemoryStream (); headers.Write (buf, 0, buf.Length); headers.Position = 0; buf = Encoding.ASCII.GetBytes ("Hello, world!\r\n"); content.Write (buf, 0, buf.Length); content.Position = 0; var chained = new ChainedStream (); chained.Add (headers); chained.Add (content); var entity = MimeEntity.Load (chained, true) as TextPart; Assert.AreEqual ("Hello, world!\r\n", entity.Text); }
/// <summary> /// Joins the specified message/partial parts into the complete message. /// </summary> /// <remarks> /// Combines all of the message/partial fragments into its original, /// complete, message. /// </remarks> /// <returns>The re-combined message.</returns> /// <param name="options">The parser options to use.</param> /// <param name="partials">The list of partial message parts.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="partials"/>is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para>The last partial does not have a Total.</para> /// <para>-or-</para> /// <para>The number of partials provided does not match the expected count.</para> /// <para>-or-</para> /// <para>One or more partials is missing.</para> /// </exception> public static MimeMessage Join (ParserOptions options, IEnumerable<MessagePartial> partials) { if (options == null) throw new ArgumentNullException ("options"); if (partials == null) throw new ArgumentNullException ("partials"); var parts = partials.ToList (); if (parts.Count == 0) return null; parts.Sort (PartialCompare); if (!parts[parts.Count - 1].Total.HasValue) throw new ArgumentException ("The last partial does not have a Total.", "partials"); int total = parts[parts.Count - 1].Total.Value; if (parts.Count != total) throw new ArgumentException ("The number of partials provided does not match the expected count.", "partials"); string id = parts[0].Id; using (var chained = new ChainedStream ()) { // chain all of the partial content streams... for (int i = 0; i < parts.Count; i++) { int number = parts[i].Number.Value; if (number != i + 1) throw new ArgumentException ("One or more partials is missing.", "partials"); var content = parts[i].ContentObject; chained.Add (content.Open ()); } var parser = new MimeParser (options, chained); return parser.ParseMessage (); } }
MimeEntity ParseEntity (ChainedStream stream, CancellationToken cancellationToken) { try { return Engine.ParseEntity (stream, true, cancellationToken); } catch { stream.Dispose (); throw; } }
/// <summary> /// Gets the specified body part. /// </summary> /// <remarks> /// Gets the specified body part. /// </remarks> /// <returns>The body part.</returns> /// <param name="index">The index of the message.</param> /// <param name="part">The body part.</param> /// <param name="headersOnly"><c>true</c> if only the headers should be downloaded; otherwise, <c>false</c>></param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="part"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="index"/> is out of range. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="System.InvalidOperationException"> /// <para>The <see cref="ImapClient"/> is not connected.</para> /// <para>-or-</para> /// <para>The <see cref="ImapClient"/> is not authenticated.</para> /// <para>-or-</para> /// <para>The folder is not currently open.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override MimeEntity GetBodyPart(int index, BodyPart part, bool headersOnly, CancellationToken cancellationToken = default (CancellationToken)) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException ("index"); if (part == null) throw new ArgumentNullException ("part"); CheckState (true, false); string[] tags; var command = string.Format ("FETCH {0} ({1})\r\n", index + 1, GetBodyPartQuery (part, headersOnly, out tags)); var ic = new ImapCommand (Engine, cancellationToken, this, command); var streams = new Dictionary<string, Stream> (); Stream stream; ic.RegisterUntaggedHandler ("FETCH", FetchMessageBody); ic.UserData = streams; Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Result != ImapCommandResult.Ok) throw ImapCommandException.Create ("FETCH", ic); var chained = new ChainedStream (); foreach (var tag in tags) { if (!streams.TryGetValue (tag, out stream)) return null; chained.Add (stream); } var entity = Engine.ParseEntity (chained, true, cancellationToken); if (part.PartSpecifier.Length == 0) { for (int i = entity.Headers.Count; i > 0; i--) { var header = entity.Headers[i - 1]; if (!header.Field.StartsWith ("Content-", StringComparison.OrdinalIgnoreCase)) entity.Headers.RemoveAt (i - 1); } } return entity; }
public void TestStreamArguments () { using (var stream = new MeasuringStream ()) AssertStreamArguments (stream); using (var stream = new MemoryBlockStream ()) AssertStreamArguments (stream); using (var memory = new MemoryStream ()) { using (var stream = new FilteredStream (memory)) AssertStreamArguments (stream); } using (var memory = new MemoryStream ()) { using (var stream = new BoundStream (memory, 0, -1, true)) AssertStreamArguments (stream); } using (var memory = new MemoryStream ()) { using (var stream = new ChainedStream ()) { stream.Add (memory); AssertStreamArguments (stream); } } }
/// <summary> /// Gets the specified body part. /// </summary> /// <returns>The body part.</returns> /// <param name="index">The index of the message.</param> /// <param name="part">The body part.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="part"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="index"/> is out of range. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="System.InvalidOperationException"> /// <para>The <see cref="ImapClient"/> is not connected.</para> /// <para>-or-</para> /// <para>The <see cref="ImapClient"/> is not authenticated.</para> /// <para>-or-</para> /// <para>The folder is not currently open.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public MimeEntity GetBodyPart(int index, BodyPart part, CancellationToken cancellationToken) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException ("index"); if (part == null) throw new ArgumentNullException ("part"); CheckState (true, false); var tags = new string[2]; if (part.PartSpecifier.Length > 0) { tags[0] = part.PartSpecifier + ".MIME"; tags[1] = part.PartSpecifier; } else { tags[0] = "HEADER"; tags[1] = "TEXT"; } var command = string.Format ("UID FETCH {0} (BODY.PEEK[{1}] BODY.PEEK[{2}])\r\n", index + 1, tags[0], tags[1]); var ic = new ImapCommand (Engine, cancellationToken, this, command); var streams = new Dictionary<string, Stream> (); Stream stream; ic.RegisterUntaggedHandler ("FETCH", FetchMessageBody); ic.UserData = streams; Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Result != ImapCommandResult.Ok) throw new ImapCommandException ("FETCH", ic.Result); var chained = new ChainedStream (); foreach (var tag in tags) { if (!streams.TryGetValue (tag, out stream)) return null; chained.Add (stream); } var entity = MimeEntity.Load (chained, cancellationToken); if (part.PartSpecifier.Length == 0) { for (int i = entity.Headers.Count; i > 0; i--) { var header = entity.Headers[i - 1]; if (!header.Field.StartsWith ("Content-", StringComparison.OrdinalIgnoreCase)) entity.Headers.RemoveAt (i - 1); } } return entity; }