Beispiel #1
0
		public void TestBase64Encode ()
		{
			using (var original = new MemoryStream ()) {
				using (var file = File.OpenRead ("../../TestData/encoders/photo.b64"))
					file.CopyTo (original, 4096);

				using (var encoded = new MemoryStream ()) {
					using (var filtered = new FilteredStream (encoded)) {
						filtered.Add (EncoderFilter.Create (ContentEncoding.Base64));

						using (var file = File.OpenRead ("../../TestData/encoders/photo.jpg"))
							file.CopyTo (filtered, 4096);

						filtered.Flush ();
					}

					var buf0 = original.GetBuffer ();
					var buf1 = encoded.GetBuffer ();
					int n = (int) original.Length;

					Assert.AreEqual (original.Length, encoded.Length, "Encoded length is incorrect.");

					for (int i = 0; i < n; i++)
						Assert.AreEqual (buf0[i], buf1[i], "The byte at offset {0} does not match.", i);
				}
			}
		}
Beispiel #2
0
        /// <summary>
        /// Writes the <see cref="MimeKit.MimePart"/> to the specified stream.
        /// </summary>
        /// <param name="options">The formatting options.</param>
        /// <param name="stream">The stream.</param>
        /// <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 override void WriteTo(FormatOptions options, Stream stream)
        {
            base.WriteTo (options, stream);

            if (ContentObject == null)
                return;

            if (ContentObject.Encoding != encoding) {
                if (encoding == ContentEncoding.UUEncode) {
                    var begin = string.Format ("begin 0644 {0}", FileName ?? "unknown");
                    var buffer = Encoding.UTF8.GetBytes (begin);
                    stream.Write (buffer, 0, buffer.Length);
                    stream.Write (options.NewLineBytes, 0, options.NewLineBytes.Length);
                }

                // transcode the content into the desired Content-Transfer-Encoding
                using (var filtered = new FilteredStream (stream)) {
                    filtered.Add (EncoderFilter.Create (encoding));
                    filtered.Add (options.CreateNewLineFilter ());
                    ContentObject.DecodeTo (filtered);
                    filtered.Flush ();
                }

                if (encoding == ContentEncoding.UUEncode) {
                    var buffer = Encoding.ASCII.GetBytes ("end");
                    stream.Write (buffer, 0, buffer.Length);
                    stream.Write (options.NewLineBytes, 0, options.NewLineBytes.Length);
                }
            } else {
                using (var filtered = new FilteredStream (stream)) {
                    filtered.Add (options.CreateNewLineFilter ());
                    ContentObject.WriteTo (filtered);
                    filtered.Flush ();
                }
            }
        }
Beispiel #3
0
		/// <summary>
		/// Writes the message to the specified output stream.
		/// </summary>
		/// <remarks>
		/// Writes the message to the output stream using the provided formatting options.
		/// </remarks>
		/// <param name="options">The formatting options.</param>
		/// <param name="stream">The output 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="stream"/> is <c>null</c>.</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>
		public void WriteTo (FormatOptions options, Stream stream, CancellationToken cancellationToken = default (CancellationToken))
		{
			if (options == null)
				throw new ArgumentNullException ("options");

			if (stream == null)
				throw new ArgumentNullException ("stream");

			if (version == null && Body != null && Body.Headers.Count > 0)
				MimeVersion = new Version (1, 0);

			if (Body != null) {
				using (var filtered = new FilteredStream (stream)) {
					filtered.Add (options.CreateNewLineFilter ());

					foreach (var header in MergeHeaders ()) {
						if (options.HiddenHeaders.Contains (header.Id))
							continue;

						filtered.Write (header.RawField, 0, header.RawField.Length, cancellationToken);
						filtered.Write (new [] { (byte) ':' }, 0, 1, cancellationToken);
						filtered.Write (header.RawValue, 0, header.RawValue.Length, cancellationToken);
					}

					filtered.Flush (cancellationToken);
				}

				var cancellable = stream as ICancellableStream;

				if (cancellable != null) {
					cancellable.Write (options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken);
				} else {
					cancellationToken.ThrowIfCancellationRequested ();
					stream.Write (options.NewLineBytes, 0, options.NewLineBytes.Length);
				}

				try {
					Body.Headers.Suppress = true;
					Body.WriteTo (options, stream, cancellationToken);
				} finally {
					Body.Headers.Suppress = false;
				}
			} else {
				Headers.WriteTo (options, stream, cancellationToken);
			}
		}
Beispiel #4
0
		/// <summary>
		/// Digitally sign the message using a DomainKeys Identified Mail (DKIM) signature.
		/// </summary>
		/// <remarks>
		/// Digitally signs the message using a DomainKeys Identified Mail (DKIM) signature.
		/// </remarks>
		/// <param name="options">The formatting options.</param>
		/// <param name="signer">The DKIM signer.</param>
		/// <param name="headers">The list of header fields to sign.</param>
		/// <param name="headerCanonicalizationAlgorithm">The header canonicalization algorithm.</param>
		/// <param name="bodyCanonicalizationAlgorithm">The body canonicalization algorithm.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="options"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="signer"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="headers"/> is <c>null</c>.</para>
		/// </exception>
		/// <exception cref="System.ArgumentException">
		/// <para><paramref name="headers"/> does not contain the 'From' header.</para>
		/// <para>-or-</para>
		/// <para><paramref name="headers"/> contains one or more of the following headers: Return-Path,
		/// Received, Comments, Keywords, Bcc, Resent-Bcc, or DKIM-Signature.</para>
		/// </exception>
		void Sign (FormatOptions options, DkimSigner signer, IList<HeaderId> headers, DkimCanonicalizationAlgorithm headerCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple, DkimCanonicalizationAlgorithm bodyCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple)
		{
			if (options == null)
				throw new ArgumentNullException ("options");

			if (signer == null)
				throw new ArgumentNullException ("signer");

			if (headers == null)
				throw new ArgumentNullException ("headers");

			if (!headers.Contains (HeaderId.From))
				throw new ArgumentException ("The list of headers to sign MUST include the 'From' header.");

			var fields = new string[headers.Count];
			for (int i = 0; i < headers.Count; i++) {
				if (DkimShouldNotInclude.Contains (headers[i]))
					throw new ArgumentException (string.Format ("The list of headers to sign SHOULD NOT include the '{0}' header.", headers[i].ToHeaderName ()));

				fields[i] = headers[i].ToHeaderName ().ToLowerInvariant ();
			}

			if (version == null && Body != null && Body.Headers.Count > 0)
				MimeVersion = new Version (1, 0);

			Prepare (EncodingConstraint.SevenBit, 78);

			var t = DateTime.Now - DateUtils.UnixEpoch;
			var value = new StringBuilder ("v=1");
			byte[] signature, hash;
			Header dkim;

			options = options.Clone ();
			options.NewLineFormat = NewLineFormat.Dos;

			switch (signer.SignatureAlgorithm) {
			case DkimSignatureAlgorithm.RsaSha256:
				value.Append ("; a=rsa-sha256");
				break;
			default:
				value.Append ("; a=rsa-sha1");
				break;
			}

			value.AppendFormat ("; d={0}; s={1}", signer.Domain, signer.Selector);
			value.AppendFormat ("; c={0}/{1}",
				headerCanonicalizationAlgorithm.ToString ().ToLowerInvariant (),
				bodyCanonicalizationAlgorithm.ToString ().ToLowerInvariant ());
			if (!string.IsNullOrEmpty (signer.QueryMethod))
				value.AppendFormat ("; q={0}", signer.QueryMethod);
			if (!string.IsNullOrEmpty (signer.AgentOrUserIdentifier))
				value.AppendFormat ("; i={0}", signer.AgentOrUserIdentifier);
			value.AppendFormat ("; t={0}", (long) t.TotalSeconds);

			using (var stream = new DkimSignatureStream (DkimGetDigestSigner (signer.SignatureAlgorithm, signer.PrivateKey))) {
				using (var filtered = new FilteredStream (stream)) {
					filtered.Add (options.CreateNewLineFilter ());

					// write the specified message headers
					DkimWriteHeaders (options, fields, headerCanonicalizationAlgorithm, filtered);

					value.AppendFormat ("; h={0}", string.Join (":", fields.ToArray ()));

					hash = DkimHashBody (options, signer.SignatureAlgorithm, bodyCanonicalizationAlgorithm, -1);
					value.AppendFormat ("; bh={0}", Convert.ToBase64String (hash));
					value.Append ("; b=");

					dkim = new Header (HeaderId.DkimSignature, value.ToString ());
					Headers.Insert (0, dkim);

					switch (headerCanonicalizationAlgorithm) {
					case DkimCanonicalizationAlgorithm.Relaxed:
						DkimWriteHeaderRelaxed (options, filtered, dkim);
						break;
					default:
						DkimWriteHeaderSimple (options, filtered, dkim);
						break;
					}

					filtered.Flush ();
				}

				signature = stream.GenerateSignature ();

				dkim.Value += Convert.ToBase64String (signature);
			}
		}
Beispiel #5
0
		/// <summary>
		/// Creates a new <see cref="MultipartSigned"/>.
		/// </summary>
		/// <remarks>
		/// Cryptographically signs the entity using the supplied signer in order
		/// to generate a detached signature and then adds the entity along with
		/// the detached signature data to a new multipart/signed part.
		/// </remarks>
		/// <returns>A new <see cref="MultipartSigned"/> instance.</returns>
		/// <param name="ctx">The S/MIME context to use for signing.</param>
		/// <param name="signer">The signer.</param>
		/// <param name="entity">The entity to sign.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="ctx"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="signer"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="entity"/> is <c>null</c>.</para>
		/// </exception>
		/// <exception cref="Org.BouncyCastle.Cms.CmsException">
		/// An error occurred in the cryptographic message syntax subsystem.
		/// </exception>
		public static MultipartSigned Create (SecureMimeContext ctx, CmsSigner signer, MimeEntity entity)
		{
			if (ctx == null)
				throw new ArgumentNullException ("ctx");

			if (signer == null)
				throw new ArgumentNullException ("signer");

			if (entity == null)
				throw new ArgumentNullException ("entity");

			PrepareEntityForSigning (entity);

			using (var memory = new MemoryBlockStream ()) {
				using (var filtered = new FilteredStream (memory)) {
					// Note: see rfc3156, section 3 - second note
					filtered.Add (new ArmoredFromFilter ());

					// Note: see rfc3156, section 5.4 (this is the main difference between rfc2015 and rfc3156)
					filtered.Add (new TrailingWhitespaceFilter ());

					// Note: see rfc2015 or rfc3156, section 5.1
					filtered.Add (new Unix2DosFilter ());

					entity.WriteTo (filtered);
					filtered.Flush ();
				}

				memory.Position = 0;

				// Note: we need to parse the modified entity structure to preserve any modifications
				var parser = new MimeParser (memory, MimeFormat.Entity);
				var parsed = parser.ParseEntity ();
				memory.Position = 0;

				// sign the cleartext content
				var micalg = ctx.GetDigestAlgorithmName (signer.DigestAlgorithm);
				var signature = ctx.Sign (signer, memory);
				var signed = new MultipartSigned ();

				// set the protocol and micalg Content-Type parameters
				signed.ContentType.Parameters["protocol"] = ctx.SignatureProtocol;
				signed.ContentType.Parameters["micalg"] = micalg;

				// add the modified/parsed entity as our first part
				signed.Add (parsed);

				// add the detached signature as the second part
				signed.Add (signature);

				return signed;
			}
		}
Beispiel #6
0
        /// <summary>
        /// Decodes the content stream into another stream.
        /// </summary>
        /// <remarks>
        /// Uses the <see cref="Encoding"/> to decode the content stream to the output stream.
        /// </remarks>
        /// <param name="stream">The output stream.</param>
        /// <param name="cancellationToken">A cancellation token.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="stream"/> is <c>null</c>.
        /// </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>
        public void DecodeTo(Stream stream, CancellationToken cancellationToken)
        {
            if (stream == null)
                throw new ArgumentNullException ("stream");

            using (var filtered = new FilteredStream (stream)) {
                filtered.Add (DecoderFilter.Create (Encoding));
                WriteTo (filtered, cancellationToken);
                filtered.Flush ();
            }
        }
Beispiel #7
0
		/// <summary>
		/// Calculates the most efficient content encoding given the specified constraint.
		/// </summary>
		/// <remarks>
		/// If no <see cref="ContentObject"/> is set, <see cref="ContentEncoding.SevenBit"/> will be returned.
		/// </remarks>
		/// <returns>The most efficient content encoding.</returns>
		/// <param name="constraint">The encoding constraint.</param>
		/// <param name="maxLineLength">The maximum allowable length for a line (not counting the CRLF). Must be between <c>72</c> and <c>998</c> (inclusive).</param>
		/// <param name="cancellationToken">A cancellation token.</param>
		/// <exception cref="System.ArgumentOutOfRangeException">
		/// <para><paramref name="maxLineLength"/> is not between <c>72</c> and <c>998</c> (inclusive).</para>
		/// <para>-or-</para>
		/// <para><paramref name="constraint"/> is not a valid value.</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>
		public ContentEncoding GetBestEncoding (EncodingConstraint constraint, int maxLineLength, CancellationToken cancellationToken = default (CancellationToken))
		{
			if (ContentObject == null)
				return ContentEncoding.SevenBit;

			using (var measure = new MeasuringStream ()) {
				using (var filtered = new FilteredStream (measure)) {
					var filter = new BestEncodingFilter ();

					filtered.Add (filter);
					ContentObject.DecodeTo (filtered, cancellationToken);
					filtered.Flush ();

					return filter.GetBestEncoding (constraint, maxLineLength);
				}
			}
		}
Beispiel #8
0
		public void TestUUEncode ()
		{
			using (var original = new MemoryStream ()) {
				using (var file = File.OpenRead ("../../TestData/encoders/photo.uu"))
					file.CopyTo (original, 4096);

				using (var encoded = new MemoryStream ()) {
					var begin = Encoding.ASCII.GetBytes ("begin 644 photo.jpg");
					var eol = FormatOptions.Default.NewLineBytes;
					var end = Encoding.ASCII.GetBytes ("end");

					encoded.Write (begin, 0, begin.Length);
					encoded.Write (eol, 0, eol.Length);

					using (var filtered = new FilteredStream (encoded)) {
						filtered.Add (EncoderFilter.Create (ContentEncoding.UUEncode));

						using (var file = File.OpenRead ("../../TestData/encoders/photo.jpg"))
							file.CopyTo (filtered, 4096);

						filtered.Flush ();
					}

					encoded.Write (end, 0, end.Length);
					encoded.Write (eol, 0, eol.Length);

					var buf0 = original.GetBuffer ();
					var buf1 = encoded.GetBuffer ();
					int n = (int) original.Length;

					Assert.AreEqual (original.Length, encoded.Length, "Encoded length is incorrect.");

					for (int i = 0; i < n; i++)
						Assert.AreEqual (buf0[i], buf1[i], "The byte at offset {0} does not match.", i);
				}
			}
		}
Beispiel #9
0
		static void ExtractAttachments (TnefReader reader, BodyBuilder builder)
		{
			var attachMethod = TnefAttachMethod.ByValue;
			var filter = new BestEncodingFilter ();
			var prop = reader.TnefPropertyReader;
			MimePart attachment = null;
			int outIndex, outLength;
			TnefAttachFlags flags;
			string[] mimeType;
			byte[] attachData;
			string text;

			do {
				if (reader.AttributeLevel != TnefAttributeLevel.Attachment)
					break;

				switch (reader.AttributeTag) {
				case TnefAttributeTag.AttachRenderData:
					attachMethod = TnefAttachMethod.ByValue;
					attachment = new MimePart ();
					break;
				case TnefAttributeTag.Attachment:
					if (attachment == null)
						break;

					while (prop.ReadNextProperty ()) {
						switch (prop.PropertyTag.Id) {
						case TnefPropertyId.AttachLongFilename:
							attachment.FileName = prop.ReadValueAsString ();
							break;
						case TnefPropertyId.AttachFilename:
							if (attachment.FileName == null)
								attachment.FileName = prop.ReadValueAsString ();
							break;
						case TnefPropertyId.AttachContentLocation:
							text = prop.ReadValueAsString ();
							if (Uri.IsWellFormedUriString (text, UriKind.Absolute))
								attachment.ContentLocation = new Uri (text, UriKind.Absolute);
							else if (Uri.IsWellFormedUriString (text, UriKind.Relative))
								attachment.ContentLocation = new Uri (text, UriKind.Relative);
							break;
						case TnefPropertyId.AttachContentBase:
							text = prop.ReadValueAsString ();
							attachment.ContentBase = new Uri (text, UriKind.Absolute);
							break;
						case TnefPropertyId.AttachContentId:
							attachment.ContentId = prop.ReadValueAsString ();
							break;
						case TnefPropertyId.AttachDisposition:
							text = prop.ReadValueAsString ();
							if (attachment.ContentDisposition == null)
								attachment.ContentDisposition = new ContentDisposition (text);
							else
								attachment.ContentDisposition.Disposition = text;
							break;
						case TnefPropertyId.AttachData:
							var stream = prop.GetRawValueReadStream ();
							var content = new MemoryStream ();
							var guid = new byte[16];

							if (attachMethod == TnefAttachMethod.EmbeddedMessage) {
								var tnef = new TnefPart ();

								foreach (var param in attachment.ContentType.Parameters)
									tnef.ContentType.Parameters[param.Name] = param.Value;

								if (attachment.ContentDisposition != null)
									tnef.ContentDisposition = attachment.ContentDisposition;

								attachment = tnef;
							}

							// read the GUID
							stream.Read (guid, 0, 16);

							// the rest is content
							using (var filtered = new FilteredStream (content)) {
								filtered.Add (filter);
								stream.CopyTo (filtered, 4096);
								filtered.Flush ();
							}

							content.Position = 0;

							attachment.ContentTransferEncoding = filter.GetBestEncoding (EncodingConstraint.SevenBit);
							attachment.ContentObject = new ContentObject (content);
							filter.Reset ();

							builder.Attachments.Add (attachment);
							break;
						case TnefPropertyId.AttachMethod:
							attachMethod = (TnefAttachMethod) prop.ReadValueAsInt32 ();
							break;
						case TnefPropertyId.AttachMimeTag:
							mimeType = prop.ReadValueAsString ().Split ('/');
							if (mimeType.Length == 2) {
								attachment.ContentType.MediaType = mimeType[0].Trim ();
								attachment.ContentType.MediaSubtype = mimeType[1].Trim ();
							}
							break;
						case TnefPropertyId.AttachFlags:
							flags = (TnefAttachFlags) prop.ReadValueAsInt32 ();
							if ((flags & TnefAttachFlags.RenderedInBody) != 0) {
								if (attachment.ContentDisposition == null)
									attachment.ContentDisposition = new ContentDisposition (ContentDisposition.Inline);
								else
									attachment.ContentDisposition.Disposition = ContentDisposition.Inline;
							}
							break;
						case TnefPropertyId.AttachSize:
							if (attachment.ContentDisposition == null)
								attachment.ContentDisposition = new ContentDisposition ();

							attachment.ContentDisposition.Size = prop.ReadValueAsInt64 ();
							break;
						case TnefPropertyId.DisplayName:
							attachment.ContentType.Name = prop.ReadValueAsString ();
							break;
						}
					}
					break;
				case TnefAttributeTag.AttachCreateDate:
					if (attachment != null) {
						if (attachment.ContentDisposition == null)
							attachment.ContentDisposition = new ContentDisposition ();

						attachment.ContentDisposition.CreationDate = prop.ReadValueAsDateTime ();
					}
					break;
				case TnefAttributeTag.AttachModifyDate:
					if (attachment != null) {
						if (attachment.ContentDisposition == null)
							attachment.ContentDisposition = new ContentDisposition ();

						attachment.ContentDisposition.ModificationDate = prop.ReadValueAsDateTime ();
					}
					break;
				case TnefAttributeTag.AttachTitle:
					if (attachment != null && string.IsNullOrEmpty (attachment.FileName))
						attachment.FileName = prop.ReadValueAsString ();
					break;
				case TnefAttributeTag.AttachMetaFile:
					if (attachment == null)
						break;

					// TODO: what to do with the meta data?
					break;
				case TnefAttributeTag.AttachData:
					if (attachment == null || attachMethod != TnefAttachMethod.ByValue)
						break;

					attachData = prop.ReadValueAsBytes ();
					filter.Flush (attachData, 0, attachData.Length, out outIndex, out outLength);
					attachment.ContentTransferEncoding = filter.GetBestEncoding (EncodingConstraint.EightBit);
					attachment.ContentObject = new ContentObject (new MemoryStream (attachData, false));
					filter.Reset ();

					builder.Attachments.Add (attachment);
					break;
				}
			} while (reader.ReadNextAttribute ());
		}
Beispiel #10
0
		byte[] DkimHashBody (FormatOptions options, DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm bodyCanonicalizationAlgorithm, int maxLength)
		{
			using (var stream = new DkimHashStream (signatureAlgorithm, maxLength)) {
				using (var filtered = new FilteredStream (stream)) {
					DkimBodyFilter dkim;

					if (bodyCanonicalizationAlgorithm == DkimCanonicalizationAlgorithm.Relaxed)
						dkim = new DkimRelaxedBodyFilter ();
					else
						dkim = new DkimSimpleBodyFilter ();

					filtered.Add (options.CreateNewLineFilter ());
					filtered.Add (dkim);

					if (Body != null) {
						try {
							Body.EnsureNewLine = compliance == RfcComplianceMode.Strict;
							Body.Headers.Suppress = true;
							Body.WriteTo (options, filtered, CancellationToken.None);
						} finally {
							Body.Headers.Suppress = false;
							Body.EnsureNewLine = false;
						}
					}

					filtered.Flush ();

					if (!dkim.LastWasNewLine)
						stream.Write (options.NewLineBytes, 0, options.NewLineBytes.Length);
				}

				return stream.GenerateHash ();
			}
		}
Beispiel #11
0
        /// <summary>
        /// Writes the message to the specified stream.
        /// </summary>
        /// <param name="options">The formatting options.</param>
        /// <param name="stream">The stream.</param>
        /// <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 WriteTo(FormatOptions options, Stream stream)
        {
            if (options == null)
                throw new ArgumentNullException ("options");

            if (stream == null)
                throw new ArgumentNullException ("stream");

            if (!Headers.Contains ("Date"))
                Date = DateTimeOffset.Now;

            if (messageId == null)
                MessageId = MimeUtils.GenerateMessageId ();

            if (version == null && Body != null && Body.Headers.Count > 0)
                MimeVersion = new Version (1, 0);

            if (Body == null) {
                Headers.WriteTo (stream);

                stream.Write (options.NewLineBytes, 0, options.NewLineBytes.Length);
            } else {
                using (var filtered = new FilteredStream (stream)) {
                    filtered.Add (options.CreateNewLineFilter ());

                    foreach (var header in MergeHeaders ()) {
                        var name = Encoding.ASCII.GetBytes (header.Field);

                        filtered.Write (name, 0, name.Length);
                        filtered.WriteByte ((byte) ':');
                        filtered.Write (header.RawValue, 0, header.RawValue.Length);
                    }

                    filtered.Flush ();
                }

                options.WriteHeaders = false;
                Body.WriteTo (options, stream);
            }
        }
Beispiel #12
0
		public void TestImapClientGMail ()
		{
			var commands = new List<ImapReplayCommand> ();
			commands.Add (new ImapReplayCommand ("", "gmail.greeting.txt"));
			commands.Add (new ImapReplayCommand ("A00000000 CAPABILITY\r\n", "gmail.capability.txt"));
			commands.Add (new ImapReplayCommand ("A00000001 AUTHENTICATE PLAIN AHVzZXJuYW1lAHBhc3N3b3Jk\r\n", "gmail.authenticate.txt"));
			commands.Add (new ImapReplayCommand ("A00000002 NAMESPACE\r\n", "gmail.namespace.txt"));
			commands.Add (new ImapReplayCommand ("A00000003 LIST \"\" \"INBOX\"\r\n", "gmail.list-inbox.txt"));
			commands.Add (new ImapReplayCommand ("A00000004 XLIST \"\" \"*\"\r\n", "gmail.xlist.txt"));
			commands.Add (new ImapReplayCommand ("A00000005 LIST \"\" \"%\"\r\n", "gmail.list-personal.txt"));
			commands.Add (new ImapReplayCommand ("A00000006 CREATE UnitTests\r\n", "gmail.create-unittests.txt"));
			commands.Add (new ImapReplayCommand ("A00000007 LIST \"\" UnitTests\r\n", "gmail.list-unittests.txt"));
			commands.Add (new ImapReplayCommand ("A00000008 SELECT UnitTests (CONDSTORE)\r\n", "gmail.select-unittests.txt"));

			for (int i = 0; i < 50; i++) {
				using (var stream = new MemoryStream ()) {
					using (var resource = GetResourceStream (string.Format ("common.message.{0}.msg", i))) {
						using (var filtered = new FilteredStream (stream)) {
							filtered.Add (new Unix2DosFilter ());
							resource.CopyTo (filtered, 4096);
							filtered.Flush ();
						}

						stream.Position = 0;
					}
					var message = MimeMessage.Load (stream);
					long length = stream.Length;
					string latin1;

					stream.Position = 0;
					using (var reader = new StreamReader (stream, Latin1))
						latin1 = reader.ReadToEnd ();

					var command = string.Format ("A{0:D8} APPEND UnitTests (\\Seen) ", i + 9);
					command += "{" + length + "}\r\n";

					commands.Add (new ImapReplayCommand (command, "gmail.go-ahead.txt"));
					commands.Add (new ImapReplayCommand (latin1 + "\r\n", string.Format ("gmail.append.{0}.txt", i + 1)));
				}
			}

			commands.Add (new ImapReplayCommand ("A00000059 UID SEARCH RETURN () CHARSET US-ASCII OR TO nsb CC nsb\r\n", "gmail.search.txt"));
			commands.Add (new ImapReplayCommand ("A00000060 UID FETCH 1:3,5,7:9,11:14,26:29,31,34,41:43,50 (UID FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY)\r\n", "gmail.search-summary.txt"));
			commands.Add (new ImapReplayCommand ("A00000061 UID FETCH 1 (BODY.PEEK[])\r\n", "gmail.fetch.1.txt"));
			commands.Add (new ImapReplayCommand ("A00000062 UID FETCH 2 (BODY.PEEK[])\r\n", "gmail.fetch.2.txt"));
			commands.Add (new ImapReplayCommand ("A00000063 UID FETCH 3 (BODY.PEEK[])\r\n", "gmail.fetch.3.txt"));
			commands.Add (new ImapReplayCommand ("A00000064 UID FETCH 5 (BODY.PEEK[])\r\n", "gmail.fetch.5.txt"));
			commands.Add (new ImapReplayCommand ("A00000065 UID FETCH 7 (BODY.PEEK[])\r\n", "gmail.fetch.7.txt"));
			commands.Add (new ImapReplayCommand ("A00000066 UID FETCH 8 (BODY.PEEK[])\r\n", "gmail.fetch.8.txt"));
			commands.Add (new ImapReplayCommand ("A00000067 UID FETCH 9 (BODY.PEEK[])\r\n", "gmail.fetch.9.txt"));
			commands.Add (new ImapReplayCommand ("A00000068 UID FETCH 11 (BODY.PEEK[])\r\n", "gmail.fetch.11.txt"));
			commands.Add (new ImapReplayCommand ("A00000069 UID FETCH 12 (BODY.PEEK[])\r\n", "gmail.fetch.12.txt"));
			commands.Add (new ImapReplayCommand ("A00000070 UID FETCH 13 (BODY.PEEK[])\r\n", "gmail.fetch.13.txt"));
			commands.Add (new ImapReplayCommand ("A00000071 UID FETCH 14 (BODY.PEEK[])\r\n", "gmail.fetch.14.txt"));
			commands.Add (new ImapReplayCommand ("A00000072 UID FETCH 26 (BODY.PEEK[])\r\n", "gmail.fetch.26.txt"));
			commands.Add (new ImapReplayCommand ("A00000073 UID FETCH 27 (BODY.PEEK[])\r\n", "gmail.fetch.27.txt"));
			commands.Add (new ImapReplayCommand ("A00000074 UID FETCH 28 (BODY.PEEK[])\r\n", "gmail.fetch.28.txt"));
			commands.Add (new ImapReplayCommand ("A00000075 UID FETCH 29 (BODY.PEEK[])\r\n", "gmail.fetch.29.txt"));
			commands.Add (new ImapReplayCommand ("A00000076 UID FETCH 31 (BODY.PEEK[])\r\n", "gmail.fetch.31.txt"));
			commands.Add (new ImapReplayCommand ("A00000077 UID FETCH 34 (BODY.PEEK[])\r\n", "gmail.fetch.34.txt"));
			commands.Add (new ImapReplayCommand ("A00000078 UID FETCH 41 (BODY.PEEK[])\r\n", "gmail.fetch.41.txt"));
			commands.Add (new ImapReplayCommand ("A00000079 UID FETCH 42 (BODY.PEEK[])\r\n", "gmail.fetch.42.txt"));
			commands.Add (new ImapReplayCommand ("A00000080 UID FETCH 43 (BODY.PEEK[])\r\n", "gmail.fetch.43.txt"));
			commands.Add (new ImapReplayCommand ("A00000081 UID FETCH 50 (BODY.PEEK[])\r\n", "gmail.fetch.50.txt"));
			commands.Add (new ImapReplayCommand ("A00000082 UID STORE 1:3,5,7:9,11:14,26:29,31,34,41:43,50 FLAGS (\\Answered \\Seen)\r\n", "gmail.set-flags.txt"));
			commands.Add (new ImapReplayCommand ("A00000083 UID STORE 1:3,5,7:9,11:14,26:29,31,34,41:43,50 -FLAGS.SILENT (\\Answered)\r\n", "gmail.remove-flags.txt"));
			commands.Add (new ImapReplayCommand ("A00000084 UID STORE 1:3,5,7:9,11:14,26:29,31,34,41:43,50 +FLAGS.SILENT (\\Deleted)\r\n", "gmail.add-flags.txt"));
			commands.Add (new ImapReplayCommand ("A00000085 UNSELECT\r\n", "gmail.unselect-unittests.txt"));
			commands.Add (new ImapReplayCommand ("A00000086 DELETE UnitTests\r\n", "gmail.delete-unittests.txt"));
			commands.Add (new ImapReplayCommand ("A00000087 LOGOUT\r\n", "gmail.logout.txt"));

			using (var client = new ImapClient ()) {
				try {
					client.ReplayConnect ("localhost", new ImapReplayStream (commands, false), CancellationToken.None);
				} catch (Exception ex) {
					Assert.Fail ("Did not expect an exception in Connect: {0}", ex);
				}

				Assert.IsTrue (client.IsConnected, "Client failed to connect.");

				Assert.AreEqual (GMailInitialCapabilities, client.Capabilities);
				Assert.AreEqual (4, client.AuthenticationMechanisms.Count);
				Assert.IsTrue (client.AuthenticationMechanisms.Contains ("XOAUTH"), "Expected SASL XOAUTH auth mechanism");
				Assert.IsTrue (client.AuthenticationMechanisms.Contains ("XOAUTH2"), "Expected SASL XOAUTH2 auth mechanism");
				Assert.IsTrue (client.AuthenticationMechanisms.Contains ("PLAIN"), "Expected SASL PLAIN auth mechanism");
				Assert.IsTrue (client.AuthenticationMechanisms.Contains ("PLAIN-CLIENTTOKEN"), "Expected SASL PLAIN-CLIENTTOKEN auth mechanism");

				try {
					var credentials = new NetworkCredential ("username", "password");

					// Note: Do not try XOAUTH2
					client.AuthenticationMechanisms.Remove ("XOAUTH2");

					client.Authenticate (credentials, CancellationToken.None);
				} catch (Exception ex) {
					Assert.Fail ("Did not expect an exception in Authenticate: {0}", ex);
				}

				Assert.AreEqual (GMailAuthenticatedCapabilities, client.Capabilities);

				var inbox = client.Inbox;
				Assert.IsNotNull (inbox, "Expected non-null Inbox folder.");
				Assert.AreEqual (FolderAttributes.Inbox | FolderAttributes.HasNoChildren, inbox.Attributes, "Expected Inbox attributes to be \\HasNoChildren.");

				foreach (var special in Enum.GetValues (typeof (SpecialFolder)).OfType<SpecialFolder> ()) {
					var folder = client.GetFolder (special);

					if (special != SpecialFolder.Archive) {
						var expected = GetSpecialFolderAttribute (special) | FolderAttributes.HasNoChildren;

						Assert.IsNotNull (folder, "Expected non-null {0} folder.", special);
						Assert.AreEqual (expected, folder.Attributes, "Expected {0} attributes to be \\HasNoChildren.", special);
					} else {
						Assert.IsNull (folder, "Expected null {0} folder.", special);
					}
				}

				var personal = client.GetFolder (client.PersonalNamespaces[0]);
				var folders = personal.GetSubfolders (false, CancellationToken.None).ToList ();
				Assert.AreEqual (client.Inbox, folders[0], "Expected the first folder to be the Inbox.");
				Assert.AreEqual ("[Gmail]", folders[1].FullName, "Expected the second folder to be [Gmail].");
				Assert.AreEqual (FolderAttributes.NoSelect | FolderAttributes.HasChildren, folders[1].Attributes, "Expected [Gmail] folder to be \\Noselect \\HasChildren.");

				var created = personal.Create ("UnitTests", true, CancellationToken.None);

				Assert.IsNotNull (created.ParentFolder, "The ParentFolder property should not be null.");

				const MessageFlags ExpectedPermanentFlags = MessageFlags.Answered | MessageFlags.Flagged | MessageFlags.Draft | MessageFlags.Deleted | MessageFlags.Seen | MessageFlags.UserDefined;
				const MessageFlags ExpectedAcceptedFlags = MessageFlags.Answered | MessageFlags.Flagged | MessageFlags.Draft | MessageFlags.Deleted | MessageFlags.Seen;
				var access = created.Open (FolderAccess.ReadWrite, CancellationToken.None);
				Assert.AreEqual (FolderAccess.ReadWrite, access, "The UnitTests folder was not opened with the expected access mode.");
				Assert.AreEqual (ExpectedPermanentFlags, created.PermanentFlags, "The PermanentFlags do not match the expected value.");
				Assert.AreEqual (ExpectedAcceptedFlags, created.AcceptedFlags, "The AcceptedFlags do not match the expected value.");

				for (int i = 0; i < 50; i++) {
					using (var stream = GetResourceStream (string.Format ("common.message.{0}.msg", i))) {
						var message = MimeMessage.Load (stream);

						var uid = created.Append (message, MessageFlags.Seen, CancellationToken.None);
						Assert.IsTrue (uid.HasValue, "Expected a UID to be returned from folder.Append().");
						Assert.AreEqual ((uint) (i + 1), uid.Value.Id, "The UID returned from the APPEND command does not match the expected UID.");
					}
				}

				var query = SearchQuery.ToContains ("nsb").Or (SearchQuery.CcContains ("nsb"));
				var matches = created.Search (query, CancellationToken.None);

				const MessageSummaryItems items = MessageSummaryItems.Full | MessageSummaryItems.UniqueId;
				var summaries = created.Fetch (matches, items, CancellationToken.None);

				foreach (var summary in summaries) {
					if (summary.UniqueId.HasValue)
						created.GetMessage (summary.UniqueId.Value, CancellationToken.None);
					else
						created.GetMessage (summary.Index, CancellationToken.None);
				}

				created.SetFlags (matches, MessageFlags.Seen | MessageFlags.Answered, false, CancellationToken.None);
				created.RemoveFlags (matches, MessageFlags.Answered, true, CancellationToken.None);
				created.AddFlags (matches, MessageFlags.Deleted, true, CancellationToken.None);

				created.Close (false, CancellationToken.None);
				created.Delete (CancellationToken.None);

				client.Disconnect (true, CancellationToken.None);
			}
		}
		/// <summary>
		/// Converts the contents of <paramref name="source"/> from the <see cref="InputFormat"/> to the
		/// <see cref="OutputFormat"/> and writes the resulting text to <paramref name="destination"/>.
		/// </summary>
		/// <remarks>
		/// Converts the contents of <paramref name="source"/> from the <see cref="InputFormat"/> to the
		/// <see cref="OutputFormat"/> and writes the resulting text to <paramref name="destination"/>.
		/// </remarks>
		/// <param name="source">The source stream.</param>
		/// <param name="destination">The destination stream.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="source"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="destination"/> is <c>null</c>.</para>
		/// </exception>
		public void Convert (Stream source, Stream destination)
		{
			if (source == null)
				throw new ArgumentNullException ("source");

			if (destination == null)
				throw new ArgumentNullException ("destination");

			using (var filtered = new FilteredStream (destination)) {
				filtered.Add (this);
				source.CopyTo (filtered, 4096);
				filtered.Flush ();
			}
		}
        private void ExtractMapiProperties(TnefReader reader, MimeMessage message, BodyBuilder builder)
        {
            var tnefPropertyReader = reader.TnefPropertyReader;
            while (tnefPropertyReader.ReadNextProperty())
            {
                switch (tnefPropertyReader.PropertyTag.Id)
                {
                    case TnefPropertyId.RtfCompressed:
                        var memoryStream = new MemoryStream();
                        if (tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.String8 || tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.Unicode || tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.Binary)
                        {
                            var textPart = new TextPart("rtf");
                            textPart.ContentType.Name = "body.rtf";
                            RtfCompressedToRtf rtfCompressedToRtf = new RtfCompressedToRtf();
                            //var memoryStream = new MemoryStream();
                            using (var filteredStream = new FilteredStream(memoryStream))
                            {
                                filteredStream.Add(rtfCompressedToRtf);
                                using (Stream rawValueReadStream = tnefPropertyReader.GetRawValueReadStream())
                                {
                                    rawValueReadStream.CopyTo(filteredStream, 4096);
                                    filteredStream.Flush();
                                }
                            }

                            textPart.ContentObject = new ContentObject(memoryStream, ContentEncoding.Default);
                            memoryStream.Position = 0L;
                            builder.Attachments.Add(textPart);
                            continue;
                        }

                        memoryStream.Dispose();
                        continue;
                    case TnefPropertyId.BodyHtml:
                        if (tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.String8 || tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.Unicode || tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.Binary)
                        {
                            var textPart = new TextPart("html");
                            textPart.ContentType.Name = "body.html";
                            textPart.Text = tnefPropertyReader.ReadValueAsString();
                            builder.Attachments.Add(textPart);
                            continue;
                        }

                        continue;
                    case TnefPropertyId.InternetMessageId:
                        if (tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.String8 || tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.Unicode)
                        {
                            message.MessageId = tnefPropertyReader.ReadValueAsString();
                            continue;
                        }

                        continue;
                    case TnefPropertyId.Subject:
                        if (tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.String8 || tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.Unicode)
                        {
                            message.Subject = tnefPropertyReader.ReadValueAsString();
                            continue;
                        }

                        continue;
                    case TnefPropertyId.Body:
                        if (tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.String8 || tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.Unicode || tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.Binary)
                        {
                            var textPart = new TextPart("plain");
                            textPart.ContentType.Name = "body.txt";
                            textPart.Text = tnefPropertyReader.ReadValueAsString();
                            builder.Attachments.Add(textPart);
                            continue;
                        }

                        continue;
                    case TnefPropertyId.ConversationTopic:
                        if (tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.String8 || tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.Unicode)
                        {
                            message.Subject = tnefPropertyReader.ReadValueAsString();
                            continue;
                        }

                        continue;
                    case TnefPropertyId.SenderName:
                        if (tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.String8 || tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.Unicode)
                        {
                            var sender = new MailboxAddress(string.Empty, tnefPropertyReader.ReadValueAsString());
                            message.Sender = sender;
                        }

                        continue;
                    case (TnefPropertyId)Mapi.ID.PR_PRIMARY_SEND_ACCOUNT:
                        if (tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.String8 || tnefPropertyReader.PropertyTag.ValueTnefType == TnefPropertyType.Unicode)
                        {
                            var senderEmail = new MailboxAddress(string.Empty, tnefPropertyReader.ReadValueAsString());
                            message.Sender = senderEmail;
                        }

                        continue;
                    default:
                        try
                        {
                            tnefPropertyReader.ReadValue();
                            continue;
                        }
                        catch
                        {
                            continue;
                        }
                }
            }
        }
		/// <summary>
		/// Creates a new <see cref="MultipartEncrypted"/>.
		/// </summary>
		/// <remarks>
		/// Encrypts the entity to the specified recipients, encapsulating the result in a
		/// new multipart/encrypted part.
		/// </remarks>
		/// <returns>A new <see cref="MimeKit.Cryptography.MultipartEncrypted"/> instance containing
		/// the encrypted version of the specified entity.</returns>
		/// <param name="ctx">The OpenPGP cryptography context to use for encrypting.</param>
		/// <param name="recipients">The recipients for the encrypted entity.</param>
		/// <param name="entity">The entity to sign and encrypt.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="ctx"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="recipients"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="entity"/> is <c>null</c>.</para>
		/// </exception>
		/// <exception cref="System.ArgumentException">
		/// One or more of the recipient keys cannot be used for encrypting.
		/// </exception>
		public static MultipartEncrypted Create (OpenPgpContext ctx, IEnumerable<PgpPublicKey> recipients, MimeEntity entity)
		{
			if (ctx == null)
				throw new ArgumentNullException ("ctx");

			if (recipients == null)
				throw new ArgumentNullException ("recipients");

			if (entity == null)
				throw new ArgumentNullException ("entity");

			using (var memory = new MemoryStream ()) {
				using (var filtered = new FilteredStream (memory)) {
					filtered.Add (new Unix2DosFilter ());

					PrepareEntityForEncrypting (entity);
					entity.WriteTo (filtered);
					filtered.Flush ();
				}

				memory.Position = 0;

				var encrypted = new MultipartEncrypted ();
				encrypted.ContentType.Parameters["protocol"] = ctx.EncryptionProtocol;

				// add the protocol version part
				encrypted.Add (new ApplicationPgpEncrypted ());

				// add the encrypted entity as the second part
				encrypted.Add (ctx.Encrypt (recipients, memory));

				return encrypted;
			}
		}
Beispiel #16
0
		public void TestSimpleYEncMessage ()
		{
			using (var file = File.OpenRead ("../../TestData/yenc/simple.msg")) {
				var message = MimeMessage.Load (file);

				using (var decoded = new MemoryStream ()) {
					var ydec = new YDecoder ();

					using (var filtered = new FilteredStream (decoded)) {
						filtered.Add (new DecoderFilter (ydec));

						((MimePart) message.Body).ContentObject.WriteTo (filtered);
						filtered.Flush ();
					}

					decoded.Position = 0;

					Assert.AreEqual (584, decoded.Length, "The decoded size does not match.");
					Assert.AreEqual (0xded29f4f, ydec.Checksum ^ 0xffffffff, "The decoded checksum does not match.");

					// now re-encode it
					using (var encoded = new MemoryStream ()) {
						var ybegin = Encoding.ASCII.GetBytes ("-- \n=ybegin line=128 size=584 name=testfile.txt \n");
						var yend = Encoding.ASCII.GetBytes ("=yend size=584 crc32=ded29f4f \n");
						var yenc = new YEncoder ();

						encoded.Write (ybegin, 0, ybegin.Length);

						using (var filtered = new FilteredStream (encoded)) {
							filtered.Add (new EncoderFilter (yenc));

							decoded.CopyTo (filtered, 4096);
							filtered.Flush ();
						}

						encoded.Write (yend, 0, yend.Length);

						Assert.AreEqual (0xded29f4f, yenc.Checksum ^ 0xffffffff, "The encoded checksum does not match.");

						using (var original = new MemoryStream ()) {
							using (var filtered = new FilteredStream (original)) {
								filtered.Add (new Dos2UnixFilter ());

								((MimePart) message.Body).ContentObject.WriteTo (filtered);
								filtered.Flush ();
							}

							var latin1 = Encoding.GetEncoding ("iso-8859-1");
							var buf = original.GetBuffer ();

							var expected = latin1.GetString (buf, 0, (int) original.Length);

							buf = encoded.GetBuffer ();

							var actual = latin1.GetString (buf, 0, (int) encoded.Length);

							Assert.AreEqual (expected, actual, "Encoded value does not match original.");
						}
					}
				}
			}
		}
		/// <summary>
		/// Writes the message to the specified output stream.
		/// </summary>
		/// <remarks>
		/// Writes the message to the output stream using the provided formatting options.
		/// </remarks>
		/// <param name="options">The formatting options.</param>
		/// <param name="stream">The output 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="stream"/> is <c>null</c>.</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>
		public void WriteTo (FormatOptions options, Stream stream, CancellationToken cancellationToken = default (CancellationToken))
		{
			if (options == null)
				throw new ArgumentNullException ("options");

			if (stream == null)
				throw new ArgumentNullException ("stream");

			if (version == null && Body != null && Body.Headers.Count > 0)
				MimeVersion = new Version (1, 0);

			if (Body == null) {
				Headers.WriteTo (options, stream, cancellationToken);

				cancellationToken.ThrowIfCancellationRequested ();
				stream.Write (options.NewLineBytes, 0, options.NewLineBytes.Length);
			} else {
				cancellationToken.ThrowIfCancellationRequested ();

				using (var filtered = new FilteredStream (stream)) {
					filtered.Add (options.CreateNewLineFilter ());

					foreach (var header in MergeHeaders ()) {
						if (options.HiddenHeaders.Contains (header.Id))
							continue;

						cancellationToken.ThrowIfCancellationRequested ();

						var name = Encoding.ASCII.GetBytes (header.Field);

						filtered.Write (name, 0, name.Length);
						filtered.WriteByte ((byte) ':');
						filtered.Write (header.RawValue, 0, header.RawValue.Length);
					}

					filtered.Flush ();
				}

				options.WriteHeaders = false;
				Body.WriteTo (options, stream, cancellationToken);
			}
		}
Beispiel #18
0
        /// <summary>
        /// Gets the decoded text content using the provided charset to override
        /// the charset specified in the Content-Type parameters.
        /// </summary>
        /// <returns>The decoded text.</returns>
        /// <param name="charset">The charset encoding to use.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="charset"/> is <c>null</c>.
        /// </exception>
        public string GetText(Encoding charset)
        {
            if (charset == null)
                throw new ArgumentNullException ("charset");

            using (var memory = new MemoryStream ()) {
                using (var filtered = new FilteredStream (memory)) {
                    filtered.Add (new CharsetFilter (charset, Encoding.UTF8));

                    ContentObject.DecodeTo (filtered);
                    filtered.Flush ();

                    return Encoding.UTF8.GetString (memory.GetBuffer (), 0, (int) memory.Length);
                }
            }
        }
		/// <summary>
		/// Create a multipart/encrypted MIME part by encrypting the specified entity.
		/// </summary>
		/// <remarks>
		/// Encrypts the entity to the specified recipients, encapsulating the result in a
		/// new multipart/encrypted part.
		/// </remarks>
		/// <returns>A new <see cref="MultipartEncrypted"/> instance containing
		/// the encrypted version of the specified entity.</returns>
		/// <param name="ctx">The OpenPGP cryptography context to use for encrypting.</param>
		/// <param name="algorithm">The encryption algorithm.</param>
		/// <param name="recipients">The recipients for the encrypted entity.</param>
		/// <param name="entity">The entity to sign and encrypt.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="ctx"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="recipients"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="entity"/> is <c>null</c>.</para>
		/// </exception>
		/// <exception cref="System.ArgumentException">
		/// One or more of the recipient keys cannot be used for encrypting.
		/// </exception>
		/// <exception cref="System.NotSupportedException">
		/// THe specified encryption algorithm is not supported.
		/// </exception>
		public static MultipartEncrypted Encrypt (OpenPgpContext ctx, EncryptionAlgorithm algorithm, IEnumerable<MailboxAddress> recipients, MimeEntity entity)
		{
			if (ctx == null)
				throw new ArgumentNullException (nameof (ctx));

			if (recipients == null)
				throw new ArgumentNullException (nameof (recipients));

			if (entity == null)
				throw new ArgumentNullException (nameof (entity));

			using (var memory = new MemoryBlockStream ()) {
				using (var filtered = new FilteredStream (memory)) {
					filtered.Add (new Unix2DosFilter ());

					entity.WriteTo (filtered);
					filtered.Flush ();
				}

				memory.Position = 0;

				var encrypted = new MultipartEncrypted ();
				encrypted.ContentType.Parameters["protocol"] = ctx.EncryptionProtocol;

				// add the protocol version part
				encrypted.Add (new ApplicationPgpEncrypted ());

				// add the encrypted entity as the second part
				encrypted.Add (ctx.Encrypt (algorithm, recipients, memory));

				return encrypted;
			}
		}
Beispiel #20
0
		/// <summary>
		/// Writes the <see cref="MimeKit.MimePart"/> to the specified output stream.
		/// </summary>
		/// <remarks>
		/// Writes the MIME part to the output stream.
		/// </remarks>
		/// <param name="options">The formatting options.</param>
		/// <param name="stream">The output 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="stream"/> is <c>null</c>.</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>
		public override void WriteTo (FormatOptions options, Stream stream, CancellationToken cancellationToken = default (CancellationToken))
		{
			base.WriteTo (options, stream, cancellationToken);

			if (ContentObject == null)
				return;

			var cancellable = stream as ICancellableStream;

			if (ContentObject.Encoding != encoding) {
				if (encoding == ContentEncoding.UUEncode) {
					var begin = string.Format ("begin 0644 {0}", FileName ?? "unknown");
					var buffer = Encoding.UTF8.GetBytes (begin);

					if (cancellable != null) {
						cancellable.Write (buffer, 0, buffer.Length, cancellationToken);
						cancellable.Write (options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken);
					} else {
						cancellationToken.ThrowIfCancellationRequested ();
						stream.Write (buffer, 0, buffer.Length);
						stream.Write (options.NewLineBytes, 0, options.NewLineBytes.Length);
					}
				}

				// transcode the content into the desired Content-Transfer-Encoding
				using (var filtered = new FilteredStream (stream)) {
					filtered.Add (EncoderFilter.Create (encoding));

					if (encoding != ContentEncoding.Binary)
						filtered.Add (options.CreateNewLineFilter (true));

					ContentObject.DecodeTo (filtered, cancellationToken);
					filtered.Flush (cancellationToken);
				}

				if (encoding == ContentEncoding.UUEncode) {
					var buffer = Encoding.ASCII.GetBytes ("end");

					if (cancellable != null) {
						cancellable.Write (buffer, 0, buffer.Length, cancellationToken);
						cancellable.Write (options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken);
					} else {
						cancellationToken.ThrowIfCancellationRequested ();
						stream.Write (buffer, 0, buffer.Length);
						stream.Write (options.NewLineBytes, 0, options.NewLineBytes.Length);
					}
				}
			} else if (encoding != ContentEncoding.Binary) {
				using (var filtered = new FilteredStream (stream)) {
					// Note: if we are writing the top-level MimePart, make sure it ends with a new-line so that
					// MimeMessage.WriteTo() *always* ends with a new-line.
					filtered.Add (options.CreateNewLineFilter (Headers.Suppress));
					ContentObject.WriteTo (filtered, cancellationToken);
					filtered.Flush (cancellationToken);
				}
			} else {
				ContentObject.WriteTo (stream, cancellationToken);
			}
		}
Beispiel #21
0
		Stream GetResponseStream (ImapReplayCommand command)
		{
			MemoryStream memory;

			if (testUnixFormat) {
				memory = new MemoryStream ();

				using (var filtered = new FilteredStream (memory)) {
					filtered.Add (new Dos2UnixFilter ());
					filtered.Write (command.Response, 0, command.Response.Length);
					filtered.Flush ();
				}

				memory.Position = 0;
			} else {
				memory = new MemoryStream (command.Response, false);
			}

			return memory;
		}
Beispiel #22
0
		void Data (FormatOptions options, MimeMessage message, CancellationToken cancellationToken, ITransferProgress progress)
		{
			var response = SendCommand ("DATA", cancellationToken);

			if (response.StatusCode != SmtpStatusCode.StartMailInput)
				throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response);

			if (progress != null) {
				var ctx = new SendContext (progress, null);

				using (var stream = new ProgressStream (Stream, ctx.Update)) {
					using (var filtered = new FilteredStream (stream)) {
						filtered.Add (new SmtpDataFilter ());

						message.WriteTo (options, filtered, cancellationToken);
						filtered.Flush ();
					}
				}
			} else {
				using (var filtered = new FilteredStream (Stream)) {
					filtered.Add (new SmtpDataFilter ());

					message.WriteTo (options, filtered, cancellationToken);
					filtered.Flush ();
				}
			}

			Stream.Write (EndData, 0, EndData.Length, cancellationToken);
			Stream.Flush (cancellationToken);

			response = Stream.ReadResponse (cancellationToken);

			switch (response.StatusCode) {
			default:
				throw new SmtpCommandException (SmtpErrorCode.MessageNotAccepted, response.StatusCode, response.Response);
			case SmtpStatusCode.AuthenticationRequired:
				throw new ServiceNotAuthenticatedException (response.Response);
			case SmtpStatusCode.Ok:
				OnMessageSent (new MessageSentEventArgs (message, response.Response));
				break;
			}
		}
Beispiel #23
0
		/// <summary>
		/// Gets the decoded text content using the provided charset to override
		/// the charset specified in the Content-Type parameters.
		/// </summary>
		/// <remarks>
		/// Uses the provided charset encoding to convert the raw text content
		/// into a unicode string, overriding any charset specified in the
		/// Content-Type header.
		/// </remarks>
		/// <returns>The decoded text.</returns>
		/// <param name="charset">The charset encoding to use.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="charset"/> is <c>null</c>.
		/// </exception>
		public string GetText (Encoding charset)
		{
			if (charset == null)
				throw new ArgumentNullException ("charset");

			using (var memory = new MemoryStream ()) {
				using (var filtered = new FilteredStream (memory)) {
					filtered.Add (new CharsetFilter (charset, Encoding.UTF8));

					ContentObject.DecodeTo (filtered);
					filtered.Flush ();

#if PORTABLE
					var buffer = memory.ToArray ();
					int length = buffer.Length;
#else
					var buffer = memory.GetBuffer ();
					int length = (int) memory.Length;
#endif

					return Encoding.UTF8.GetString (buffer, 0, length);
				}
			}
		}
Beispiel #24
0
		byte[] DkimHashBody (FormatOptions options, DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm bodyCanonicalizationAlgorithm, int maxLength)
		{
			using (var stream = new DkimHashStream (signatureAlgorithm, maxLength)) {
				using (var filtered = new FilteredStream (stream)) {
					filtered.Add (options.CreateNewLineFilter ());

					if (bodyCanonicalizationAlgorithm == DkimCanonicalizationAlgorithm.Relaxed)
						filtered.Add (new DkimRelaxedBodyFilter ());
					else
						filtered.Add (new DkimSimpleBodyFilter ());

					if (Body != null) {
						try {
							Body.Headers.Suppress = true;
							Body.WriteTo (options, stream, CancellationToken.None);
						} finally {
							Body.Headers.Suppress = false;
						}
					}

					filtered.Flush ();
				}

				return stream.GenerateHash ();
			}
		}
Beispiel #25
0
        static void ExtractMapiProperties(TnefReader reader, MimeMessage message, BodyBuilder builder)
        {
            var prop = reader.TnefPropertyReader;

            while (prop.ReadNextProperty ()) {
                switch (prop.PropertyTag.Id) {
                case TnefPropertyId.InternetMessageId:
                    if (prop.PropertyTag.ValueTnefType == TnefPropertyType.String8 ||
                        prop.PropertyTag.ValueTnefType == TnefPropertyType.Unicode) {
                        message.MessageId = prop.ReadValueAsString ();
                        Console.WriteLine ("Message Property: {0} = {1}", prop.PropertyTag.Id, message.MessageId);
                    } else {
                        Assert.Fail ("Unknown property type for Message-Id: {0}", prop.PropertyTag.ValueTnefType);
                    }
                    break;
                case TnefPropertyId.Subject:
                    if (prop.PropertyTag.ValueTnefType == TnefPropertyType.String8 ||
                        prop.PropertyTag.ValueTnefType == TnefPropertyType.Unicode) {
                        message.Subject = prop.ReadValueAsString ();
                        Console.WriteLine ("Message Property: {0} = {1}", prop.PropertyTag.Id, message.Subject);
                    } else {
                        Assert.Fail ("Unknown property type for Subject: {0}", prop.PropertyTag.ValueTnefType);
                    }
                    break;
                case TnefPropertyId.RtfCompressed:
                    if (prop.PropertyTag.ValueTnefType == TnefPropertyType.String8 ||
                        prop.PropertyTag.ValueTnefType == TnefPropertyType.Unicode ||
                        prop.PropertyTag.ValueTnefType == TnefPropertyType.Binary) {
                        var rtf = new TextPart ("rtf");
                        rtf.ContentType.Name = "body.rtf";

                        var converter = new RtfCompressedToRtf ();
                        var content = new MemoryStream ();

                        using (var filtered = new FilteredStream (content)) {
                            filtered.Add (converter);

                            using (var compressed = prop.GetRawValueReadStream ()) {
                                compressed.CopyTo (filtered, 4096);
                                filtered.Flush ();
                            }
                        }

                        rtf.ContentObject = new ContentObject (content);
                        content.Position = 0;

                        builder.Attachments.Add (rtf);

                        Console.WriteLine ("Message Property: {0} = <compressed rtf data>", prop.PropertyTag.Id);
                    } else {
                        Assert.Fail ("Unknown property type for {0}: {1}", prop.PropertyTag.Id, prop.PropertyTag.ValueTnefType);
                    }
                    break;
                case TnefPropertyId.BodyHtml:
                    if (prop.PropertyTag.ValueTnefType == TnefPropertyType.String8 ||
                        prop.PropertyTag.ValueTnefType == TnefPropertyType.Unicode ||
                        prop.PropertyTag.ValueTnefType == TnefPropertyType.Binary) {
                        var html = new TextPart ("html");
                        html.ContentType.Name = "body.html";
                        html.Text = prop.ReadValueAsString ();

                        builder.Attachments.Add (html);

                        Console.WriteLine ("Message Property: {0} = {1}", prop.PropertyTag.Id, html.Text);
                    } else {
                        Assert.Fail ("Unknown property type for {0}: {1}", prop.PropertyTag.Id, prop.PropertyTag.ValueTnefType);
                    }
                    break;
                case TnefPropertyId.Body:
                    if (prop.PropertyTag.ValueTnefType == TnefPropertyType.String8 ||
                        prop.PropertyTag.ValueTnefType == TnefPropertyType.Unicode ||
                        prop.PropertyTag.ValueTnefType == TnefPropertyType.Binary) {
                        var plain = new TextPart ("plain");
                        plain.ContentType.Name = "body.txt";
                        plain.Text = prop.ReadValueAsString ();

                        builder.Attachments.Add (plain);

                        Console.WriteLine ("Message Property: {0} = {1}", prop.PropertyTag.Id, plain.Text);
                    } else {
                        Assert.Fail ("Unknown property type for {0}: {1}", prop.PropertyTag.Id, prop.PropertyTag.ValueTnefType);
                    }
                    break;
                default:
                    Console.WriteLine ("Message Property (unhandled): {0} = {1}", prop.PropertyTag.Id, prop.ReadValue ());
                    break;
                }
            }
        }
Beispiel #26
0
		/// <summary>
		/// Verify the specified DKIM-Signature header.
		/// </summary>
		/// <remarks>
		/// Verifies the specified DKIM-Signature header.
		/// </remarks>
		/// <param name="options">The formatting options.</param>
		/// <param name="dkimSignature">The DKIM-Signature header.</param>
		/// <param name="publicKeyLocator">The public key locator service.</param>
		/// <param name="cancellationToken">The cancellation token.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="options"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="dkimSignature"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="publicKeyLocator"/> is <c>null</c>.</para>
		/// </exception>
		/// <exception cref="System.ArgumentException">
		/// <paramref name="dkimSignature"/> is not a DKIM-Signature header.
		/// </exception>
		/// <exception cref="System.FormatException">
		/// The DKIM-Signature header value is malformed.
		/// </exception>
		/// <exception cref="System.OperationCanceledException">
		/// The operation was canceled via the cancellation token.
		/// </exception>
		bool Verify (FormatOptions options, Header dkimSignature, IDkimPublicKeyLocator publicKeyLocator, CancellationToken cancellationToken = default (CancellationToken))
		{
			if (options == null)
				throw new ArgumentNullException ("options");

			if (dkimSignature == null)
				throw new ArgumentNullException ("dkimSignature");

			if (dkimSignature.Id != HeaderId.DkimSignature)
				throw new ArgumentException ("The dkimSignature parameter MUST be a DKIM-Signature header.", "dkimSignature");

			if (publicKeyLocator == null)
				throw new ArgumentNullException ("publicKeyLocator");

			var parameters = ParseDkimSignature (dkimSignature.Value);
			DkimCanonicalizationAlgorithm headerAlgorithm, bodyAlgorithm;
			DkimSignatureAlgorithm signatureAlgorithm;
			AsymmetricKeyParameter key;
			string d, s, q, h, bh, b;
			int maxLength;

			ValidateDkimSignatureParameters (parameters, out signatureAlgorithm, out headerAlgorithm, out bodyAlgorithm,
				out d, out s, out q, out h, out bh, out b, out maxLength);

			key = publicKeyLocator.LocatePublicKey (q, d, s, cancellationToken);

			options = options.Clone ();
			options.NewLineFormat = NewLineFormat.Dos;

			// first check the body hash (if that's invalid, then the entire signature is invalid)
			var hash = Convert.ToBase64String (DkimHashBody (options, signatureAlgorithm, bodyAlgorithm, maxLength));

			if (hash != bh)
				return false;

			using (var stream = new DkimSignatureStream (DkimGetDigestSigner (signatureAlgorithm, key))) {
				using (var filtered = new FilteredStream (stream)) {
					filtered.Add (options.CreateNewLineFilter ());

					DkimWriteHeaders (options, h.Split (':'), headerAlgorithm, filtered);

					// now include the DKIM-Signature header that we are verifying,
					// but only after removing the "b=" signature value.
					var header = GetSignedDkimSignatureHeader (dkimSignature);

					switch (headerAlgorithm) {
					case DkimCanonicalizationAlgorithm.Relaxed:
						DkimWriteHeaderRelaxed (options, filtered, header);
						break;
					default:
						DkimWriteHeaderSimple (options, filtered, header);
						break;
					}

					filtered.Flush ();
				}

				return stream.VerifySignature (b);
			}
		}
Beispiel #27
0
		static void ExtractMapiProperties (TnefReader reader, MimeMessage message, BodyBuilder builder)
		{
			var prop = reader.TnefPropertyReader;

			while (prop.ReadNextProperty ()) {
				switch (prop.PropertyTag.Id) {
				case TnefPropertyId.InternetMessageId:
					if (prop.PropertyTag.ValueTnefType == TnefPropertyType.String8 ||
						prop.PropertyTag.ValueTnefType == TnefPropertyType.Unicode) {
						message.MessageId = prop.ReadValueAsString ();
					}
					break;
				case TnefPropertyId.Subject:
					if (prop.PropertyTag.ValueTnefType == TnefPropertyType.String8 ||
						prop.PropertyTag.ValueTnefType == TnefPropertyType.Unicode) {
						message.Subject = prop.ReadValueAsString ();
					}
					break;
				case TnefPropertyId.RtfCompressed:
					if (prop.PropertyTag.ValueTnefType == TnefPropertyType.String8 ||
						prop.PropertyTag.ValueTnefType == TnefPropertyType.Unicode ||
						prop.PropertyTag.ValueTnefType == TnefPropertyType.Binary) {
						var rtf = new TextPart ("rtf");
						rtf.ContentType.Name = "body.rtf";

						var converter = new RtfCompressedToRtf ();
						var content = new MemoryStream ();

						using (var filtered = new FilteredStream (content)) {
							filtered.Add (converter);

							using (var compressed = prop.GetRawValueReadStream ()) {
								compressed.CopyTo (filtered, 4096);
								filtered.Flush ();
							}
						}

						rtf.ContentObject = new ContentObject (content);
						content.Position = 0;

						builder.Attachments.Add (rtf);
					}
					break;
				case TnefPropertyId.BodyHtml:
					if (prop.PropertyTag.ValueTnefType == TnefPropertyType.String8 ||
						prop.PropertyTag.ValueTnefType == TnefPropertyType.Unicode ||
						prop.PropertyTag.ValueTnefType == TnefPropertyType.Binary) {
						var html = new TextPart ("html");
						html.ContentType.Name = "body.html";
						html.Text = prop.ReadValueAsString ();

						builder.Attachments.Add (html);
					}
					break;
				case TnefPropertyId.Body:
					if (prop.PropertyTag.ValueTnefType == TnefPropertyType.String8 ||
						prop.PropertyTag.ValueTnefType == TnefPropertyType.Unicode ||
						prop.PropertyTag.ValueTnefType == TnefPropertyType.Binary) {
						var plain = new TextPart ("plain");
						plain.ContentType.Name = "body.txt";
						plain.Text = prop.ReadValueAsString ();

						builder.Attachments.Add (plain);
					}
					break;
				}
			}
		}
Beispiel #28
0
        void Data(MimeMessage message, CancellationToken cancellationToken)
        {
            var response = SendCommand ("DATA", cancellationToken);

            if (response.StatusCode != SmtpStatusCode.StartMailInput)
                throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response);

            var options = FormatOptions.Default.Clone ();
            options.NewLineFormat = NewLineFormat.Dos;

            options.HiddenHeaders.Add (HeaderId.ContentLength);
            options.HiddenHeaders.Add (HeaderId.ResentBcc);
            options.HiddenHeaders.Add (HeaderId.Bcc);

            using (var filtered = new FilteredStream (stream)) {
                filtered.Add (new SmtpDataFilter ());
                message.WriteTo (options, filtered, cancellationToken);
                filtered.Flush ();
            }

            stream.Write (EndData, 0, EndData.Length);

            response = ReadResponse (cancellationToken);

            switch (response.StatusCode) {
            default:
                throw new SmtpCommandException (SmtpErrorCode.MessageNotAccepted, response.StatusCode, response.Response);
            case SmtpStatusCode.AuthenticationRequired:
                throw new UnauthorizedAccessException (response.Response);
            case SmtpStatusCode.Ok:
                break;
            }
        }
Beispiel #29
0
		public void TestJwzMbox ()
		{
			var summary = File.ReadAllText (Path.Combine (MboxDataDir, "jwz-summary.txt")).Replace ("\r\n", "\n");
			var options = FormatOptions.Default.Clone ();
			var original = new MemoryBlockStream ();
			var output = new MemoryBlockStream ();
			var builder = new StringBuilder ();
			var expected = new byte[4096];
			var buffer = new byte[4096];
			int nx, n;

			options.NewLineFormat = NewLineFormat.Unix;

			using (var stream = File.OpenRead (Path.Combine (MboxDataDir, "jwz.mbox.txt"))) {
				var parser = new MimeParser (stream, MimeFormat.Mbox);
				int count = 0;

				while (!parser.IsEndOfStream) {
					var message = parser.ParseMessage ();

					builder.AppendFormat ("{0}", parser.MboxMarker).Append ('\n');
					if (message.From.Count > 0)
						builder.AppendFormat ("From: {0}", message.From).Append ('\n');
					if (message.To.Count > 0)
						builder.AppendFormat ("To: {0}", message.To).Append ('\n');
					builder.AppendFormat ("Subject: {0}", message.Subject).Append ('\n');
					builder.AppendFormat ("Date: {0}", DateUtils.FormatDate (message.Date)).Append ('\n');
					DumpMimeTree (builder, message);
					builder.Append ('\n');

					var marker = Encoding.UTF8.GetBytes ((count > 0 ? "\n" : string.Empty) + parser.MboxMarker + "\n");
					output.Write (marker, 0, marker.Length);
					message.WriteTo (options, output);
					count++;
				}
			}

			string actual = builder.ToString ();

			// WORKAROUND: Mono's iso-2022-jp decoder breaks on this input in versions <= 3.2.3 but is fixed in 3.2.4+
			string iso2022jp = Encoding.GetEncoding ("iso-2022-jp").GetString (Convert.FromBase64String ("GyRAOjRGI0stGyhK"));
			if (iso2022jp != "佐藤豊")
				actual = actual.Replace (iso2022jp, "佐藤豊");

			Assert.AreEqual (summary, actual, "Summaries do not match for jwz.mbox");

			using (var stream = File.OpenRead (Path.Combine (MboxDataDir, "jwz.mbox.txt"))) {
				using (var filtered = new FilteredStream (original)) {
					filtered.Add (new Dos2UnixFilter ());
					stream.CopyTo (filtered);
					filtered.Flush ();
				}
			}

			original.Position = 0;
			output.Position = 0;

			Assert.AreEqual (original.Length, output.Length, "The length of the mbox did not match.");

			do {
				var position = original.Position;

				nx = original.Read (expected, 0, expected.Length);
				n = output.Read (buffer, 0, buffer.Length);

				if (nx == 0)
					break;

				for (int i = 0; i < nx; i++) {
					if (buffer[i] == expected[i])
						continue;

					var strExpected = CharsetUtils.Latin1.GetString (expected, 0, nx);
					var strActual = CharsetUtils.Latin1.GetString (buffer, 0, n);

					Assert.AreEqual (strExpected, strActual, "The mbox differs at position {0}", position + i);
				}
			} while (true);
		}