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)));
			}
		}
Exemple #2
0
        /// <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 ();
            }
        }
Exemple #3
0
		/// <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;
		}
Exemple #4
0
		/// <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);
		}
Exemple #6
0
		/// <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 ();
			}
		}
Exemple #7
0
		MimeEntity ParseEntity (ChainedStream stream, CancellationToken cancellationToken)
		{
			try {
				return Engine.ParseEntity (stream, true, cancellationToken);
			} catch {
				stream.Dispose ();
				throw;
			}
		}
Exemple #8
0
        /// <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);
				}
			}
		}
Exemple #10
0
        /// <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;
        }