Example #1
0
        private static MimePart GetMimePart(AttachmentBase item)
        {
            var      mimeType    = item.ContentType.ToString();
            var      contentType = ContentType.Parse(mimeType);
            var      attachment  = item as Attachment;
            MimePart part;

            if (contentType.MediaType.Equals("text", StringComparison.OrdinalIgnoreCase))
            {
                // Original: part = new TextPart(contentType);
                // Due to constructor of TextPart(ContentType contentType) being internal,
                // mimic the instantiation by using MimePart(ContentType contentType)
                part = new MimePart(contentType);
            }
            else
            {
                part = new MimePart(contentType);
            }

            if (attachment != null)
            {
                var disposition = attachment.ContentDisposition.ToString();
                part.ContentDisposition = ContentDisposition.Parse(disposition);
            }

            switch (item.TransferEncoding)
            {
            case TransferEncoding.QuotedPrintable:
                part.ContentTransferEncoding = ContentEncoding.QuotedPrintable;
                break;

            case TransferEncoding.Base64:
                part.ContentTransferEncoding = ContentEncoding.Base64;
                break;

            case TransferEncoding.SevenBit:
                part.ContentTransferEncoding = ContentEncoding.SevenBit;
                break;

            case TransferEncoding.EightBit:
                part.ContentTransferEncoding = ContentEncoding.EightBit;
                break;
            }

            if (item.ContentId != null)
            {
                part.ContentId = item.ContentId;
            }

            var stream = new MemoryBlockStream();

            item.ContentStream.CopyTo(stream);
            stream.Position = 0;

            part.ContentObject = new ContentObject(stream);

            return(part);
        }
		public void TestArgumentExceptions ()
		{
			var disposition = new ContentDisposition ();

			Assert.Throws<ArgumentNullException> (() => disposition.Disposition = null, "Setting the disposition to null value should throw.");
			Assert.Throws<ArgumentException> (() => disposition.Disposition = string.Empty, "Setting the disposition to an empty value should throw.");
			Assert.Throws<ArgumentException> (() => disposition.Disposition = "žádost", "Setting the disposition to a non-ascii value should throw.");
			Assert.Throws<ArgumentException> (() => disposition.Disposition = "two atoms", "Setting the disposition to multiple atom tokens should throw.");
		}
        private static MimePart GetMimePart(AttachmentBase item)
        {
            var mimeType    = item.ContentType.ToString();
            var contentType = ContentType.Parse(mimeType);
            var attachment  = item as Attachment;
            var part        = new MimePart(contentType);

            //
            if (attachment != null)
            {
                var disposition = attachment.ContentDisposition.ToString();
                part.ContentDisposition = ContentDisposition.Parse(disposition);
            }

            // Adjust the transfer encoding
            switch (item.TransferEncoding)
            {
            case TransferEncoding.QuotedPrintable:
                part.ContentTransferEncoding = ContentEncoding.QuotedPrintable;
                break;

            case TransferEncoding.Base64:
                part.ContentTransferEncoding = ContentEncoding.Base64;
                break;

            case TransferEncoding.SevenBit:
                part.ContentTransferEncoding = ContentEncoding.SevenBit;
                break;

            case TransferEncoding.EightBit:
                part.ContentTransferEncoding = ContentEncoding.EightBit;
                break;

            case TransferEncoding.Unknown:
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            // Adjust the attachment content identifier
            if (item.ContentId != null)
            {
                part.ContentId = item.ContentId;
            }

            // Copy the content of the attachment
            var stream = new MemoryBlockStream();

            item.ContentStream.CopyTo(stream);
            stream.Position = 0;

            part.Content = new MimeContent(stream);

            // Done
            return(part);
        }
		static void AssertParseResults (ContentDisposition disposition, ContentDisposition expected)
		{
			if (expected == null) {
				Assert.IsNull (disposition);
				return;
			}

			Assert.AreEqual (expected.Disposition, disposition.Disposition, "Disposition");
			Assert.AreEqual (expected.Parameters.Count, disposition.Parameters.Count, "Parameter count");

			for (int i = 0; i < expected.Parameters.Count; i++) {
				var encoding = expected.Parameters[i].Encoding;
				var value = expected.Parameters[i].Value;
				var name = expected.Parameters[i].Name;

				Assert.AreEqual (name, disposition.Parameters[i].Name);
				Assert.AreEqual (encoding.EncodingName, disposition.Parameters[i].Encoding.EncodingName);
				Assert.AreEqual (value, disposition.Parameters[i].Value);
				Assert.IsTrue (disposition.Parameters.Contains (name));
				Assert.AreEqual (expected.Parameters[name], disposition.Parameters[name]);
			}
		}
		/// <summary>
		/// Tries to parse the given input buffer into a new <see cref="MimeKit.ContentDisposition"/> instance.
		/// </summary>
		/// <remarks>
		/// Parses a Content-Disposition value from the specified buffer.
		/// </remarks>
		/// <returns><c>true</c>, if the disposition was successfully parsed, <c>false</c> otherwise.</returns>
		/// <param name="options">The parser options.</param>
		/// <param name="buffer">The input buffer.</param>
		/// <param name="disposition">The parsed disposition.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="options"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="buffer"/> is <c>null</c>.</para>
		/// </exception>
		public static bool TryParse (ParserOptions options, byte[] buffer, out ContentDisposition disposition)
		{
			if (options == null)
				throw new ArgumentNullException ("options");

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

			int index = 0;

			return TryParse (options, buffer, ref index, buffer.Length, false, out disposition);
		}
		public void TestChineseFilename2047 ()
		{
			const string expected = " attachment; filename=\"=?gb18030?b?suLK1M7Esb4udHh0?=\"\n";
			var disposition = new ContentDisposition (ContentDisposition.Attachment);
			disposition.Parameters.Add ("GB18030", "filename", "测试文本.txt");

			var format = FormatOptions.Default.Clone ();
			format.ParameterEncodingMethod = ParameterEncodingMethod.Rfc2047;
			format.NewLineFormat = NewLineFormat.Unix;

			var encoded = disposition.Encode (format, Encoding.UTF8);
			Parameter param;

			Assert.AreEqual (expected, encoded, "The encoded Chinese filename parameter does not match the expected value.");
			Assert.IsTrue (ContentDisposition.TryParse (encoded, out disposition), "Failed to parse Content-Disposition");
			Assert.AreEqual ("测试文本.txt", disposition.FileName, "The decoded Chinese filename does not match.");
			Assert.IsTrue (disposition.Parameters.TryGetValue ("filename", out param), "Failed to locate filename parameter.");
			Assert.AreEqual ("GB18030", param.Encoding.HeaderName, "The filename encoding did not match.");
		}
		public void TestDispositionParameters ()
		{
			const string expected = "Content-Disposition: attachment; filename=document.doc;\n" +
				"\tcreation-date=\"Sat, 04 Jan 1997 15:22:17 -0400\";\n" +
				"\tmodification-date=\"Thu, 04 Jan 2007 15:22:17 -0400\";\n" +
				"\tread-date=\"Wed, 04 Jan 2012 15:22:17 -0400\"; size=37001";
			var ctime = new DateTimeOffset (1997, 1, 4, 15, 22, 17, new TimeSpan (-4, 0, 0));
			var mtime = new DateTimeOffset (2007, 1, 4, 15, 22, 17, new TimeSpan (-4, 0, 0));
			var atime = new DateTimeOffset (2012, 1, 4, 15, 22, 17, new TimeSpan (-4, 0, 0));
			var disposition = new ContentDisposition ();
			var format = FormatOptions.Default.Clone ();
			const long size = 37001;
			Parameter param;
			string encoded;

			format.NewLineFormat = NewLineFormat.Unix;

			Assert.AreEqual (ContentDisposition.Attachment, disposition.Disposition, "The disposition should be 'attachment'.");
			Assert.IsTrue (disposition.IsAttachment, "IsAttachment should be true by default.");

			Assert.IsNull (disposition.FileName, "The filename should default to null.");
			Assert.IsNull (disposition.CreationDate, "The creation-date should default to null.");
			Assert.IsNull (disposition.ModificationDate, "The modification-date should default to null.");
			Assert.IsNull (disposition.ReadDate, "The read-date should default to null.");
			Assert.IsNull (disposition.Size, "The size should default to null.");

			disposition.FileName = "document.doc";
			disposition.CreationDate = ctime;
			disposition.ModificationDate = mtime;
			disposition.ReadDate = atime;
			disposition.Size = size;

			encoded = disposition.ToString (format, Encoding.UTF8, true);

			Assert.AreEqual (expected, encoded, "The encoded Content-Disposition does not match.");

			disposition = ContentDisposition.Parse (encoded.Substring ("Content-Disposition:".Length));

			Assert.AreEqual ("document.doc", disposition.FileName, "The filename parameter does not match.");
			Assert.AreEqual (ctime, disposition.CreationDate, "The creation-date parameter does not match.");
			Assert.AreEqual (mtime, disposition.ModificationDate, "The modification-date parameter does not match.");
			Assert.AreEqual (atime, disposition.ReadDate, "The read-date parameter does not match.");
			Assert.AreEqual (size, disposition.Size, "The size parameter does not match.");

			disposition.CreationDate = null;
			Assert.IsFalse (disposition.Parameters.TryGetValue ("creation-date", out param), "The creation-date parameter should have been removed.");

			disposition.ModificationDate = null;
			Assert.IsFalse (disposition.Parameters.TryGetValue ("modification-date", out param), "The modification-date parameter should have been removed.");

			disposition.ReadDate = null;
			Assert.IsFalse (disposition.Parameters.TryGetValue ("read-date", out param), "The read-date parameter should have been removed.");

			disposition.FileName = null;
			Assert.IsFalse (disposition.Parameters.TryGetValue ("filename", out param), "The filename parameter should have been removed.");

			disposition.Size = null;
			Assert.IsFalse (disposition.Parameters.TryGetValue ("size", out param), "The size parameter should have been removed.");

			disposition.IsAttachment = false;
			Assert.AreEqual (ContentDisposition.Inline, disposition.Disposition, "The disposition should be 'inline'.");
			Assert.IsFalse (disposition.IsAttachment, "IsAttachment should be false.");
		}
		public void TestMistakenlyQuotedEncodedParameterValues ()
		{
			const string text = "attachment;\n filename*0*=\"ISO-8859-2''%C8%50%50%20%2D%20%BE%E1%64%6F%73%74%20%6F%20%61%6B%63%65\";\n " +
				"filename*1*=\"%70%74%61%63%69%20%73%6D%6C%6F%75%76%79%20%31%32%2E%31%32%2E\";\n " +
				"filename*2*=\"%64%6F%63\"";
			const string filename = "ČPP - žádost o akceptaci smlouvy 12.12.doc";
			var expected = new ContentDisposition ("attachment");

			expected.Parameters.Add (Encoding.GetEncoding ("ISO-8859-2"), "filename", filename);

			AssertParse (text, expected);
		}
		public void TestInvalidDataAfterMDisposition ()
		{
			var expected = new ContentDisposition ("attachment");
			const string text = "attachment x";

			// TryParse will return false but will have a value to use
			AssertParse (text, expected, false, 11, 11);
		}
Example #10
0
 /// <summary>
 /// Tries to parse the given input buffer into a new <see cref="MimeKit.ContentDisposition"/> instance.
 /// </summary>
 /// <remarks>
 /// Parses a Content-Disposition value from the supplied buffer starting at the given index
 /// and spanning across the specified number of bytes.
 /// </remarks>
 /// <returns><c>true</c>, if the disposition was successfully parsed, <c>false</c> otherwise.</returns>
 /// <param name="buffer">The input buffer.</param>
 /// <param name="startIndex">The starting index of the input buffer.</param>
 /// <param name="length">The number of bytes in the input buffer to parse.</param>
 /// <param name="disposition">The parsed disposition.</param>
 /// <exception cref="System.ArgumentNullException">
 /// <paramref name="buffer"/> is <c>null</c>.
 /// </exception>
 /// <exception cref="System.ArgumentOutOfRangeException">
 /// <paramref name="startIndex"/> and <paramref name="length"/> do not specify
 /// a valid range in the byte array.
 /// </exception>
 public static bool TryParse(byte[] buffer, int startIndex, int length, out ContentDisposition disposition)
 {
     return(TryParse(ParserOptions.Default, buffer, startIndex, length, out disposition));
 }
Example #11
0
 /// <summary>
 /// Tries to parse the given input buffer into a new <see cref="MimeKit.ContentDisposition"/> instance.
 /// </summary>
 /// <remarks>
 /// Parses a Content-Disposition value from the specified buffer.
 /// </remarks>
 /// <returns><c>true</c>, if the disposition was successfully parsed, <c>false</c> otherwise.</returns>
 /// <param name="buffer">The input buffer.</param>
 /// <param name="disposition">The parsed disposition.</param>
 /// <exception cref="System.ArgumentNullException">
 /// <paramref name="buffer"/> is <c>null</c>.
 /// </exception>
 public static bool TryParse(byte[] buffer, out ContentDisposition disposition)
 {
     return(TryParse(ParserOptions.Default, buffer, out disposition));
 }
Example #12
0
		/// <summary>
		/// Tries to parse the given input buffer into a new <see cref="MimeKit.ContentDisposition"/> instance.
		/// </summary>
		/// <remarks>
		/// Parses a Content-Disposition value from the specified buffer.
		/// </remarks>
		/// <returns><c>true</c>, if the disposition was successfully parsed, <c>false</c> otherwise.</returns>
		/// <param name="options">The parser options.</param>
		/// <param name="buffer">The input buffer.</param>
		/// <param name="disposition">The parsed disposition.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="options"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="buffer"/> is <c>null</c>.</para>
		/// </exception>
		public static bool TryParse (ParserOptions options, byte[] buffer, out ContentDisposition disposition)
		{
			ParseUtils.ValidateArguments (options, buffer);

			int index = 0;

			return TryParse (options, buffer, ref index, buffer.Length, false, out disposition);
		}
Example #13
0
		/// <summary>
		/// Tries to parse the given input buffer into a new <see cref="MimeKit.ContentDisposition"/> instance.
		/// </summary>
		/// <remarks>
		/// Parses a Content-Disposition value from the specified buffer.
		/// </remarks>
		/// <returns><c>true</c>, if the disposition was successfully parsed, <c>false</c> otherwise.</returns>
		/// <param name="buffer">The input buffer.</param>
		/// <param name="disposition">The parsed disposition.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="buffer"/> is <c>null</c>.
		/// </exception>
		public static bool TryParse (byte[] buffer, out ContentDisposition disposition)
		{
			return TryParse (ParserOptions.Default, buffer, out disposition);
		}
Example #14
0
		/// <summary>
		/// Tries to parse the given input buffer into a new <see cref="MimeKit.ContentDisposition"/> instance.
		/// </summary>
		/// <remarks>
		/// Parses a Content-Disposition value from the supplied buffer starting at the given index
		/// and spanning across the specified number of bytes.
		/// </remarks>
		/// <returns><c>true</c>, if the disposition was successfully parsed, <c>false</c> otherwise.</returns>
		/// <param name="options">The parser options.</param>
		/// <param name="buffer">The input buffer.</param>
		/// <param name="startIndex">The starting index of the input buffer.</param>
		/// <param name="length">The number of bytes in the input buffer to parse.</param>
		/// <param name="disposition">The parsed disposition.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="options"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="buffer"/> is <c>null</c>.</para>
		/// </exception>
		/// <exception cref="System.ArgumentOutOfRangeException">
		/// <paramref name="startIndex"/> and <paramref name="length"/> do not specify
		/// a valid range in the byte array.
		/// </exception>
		public static bool TryParse (ParserOptions options, byte[] buffer, int startIndex, int length, out ContentDisposition disposition)
		{
			ParseUtils.ValidateArguments (options, buffer, startIndex, length);

			int index = startIndex;

			return TryParse (options, buffer, ref index, startIndex + length, false, out disposition);
		}
Example #15
0
		/// <summary>
		/// Tries to parse the given input buffer into a new <see cref="MimeKit.ContentDisposition"/> instance.
		/// </summary>
		/// <remarks>
		/// Parses a Content-Disposition value from the supplied buffer starting at the given index
		/// and spanning across the specified number of bytes.
		/// </remarks>
		/// <returns><c>true</c>, if the disposition was successfully parsed, <c>false</c> otherwise.</returns>
		/// <param name="buffer">The input buffer.</param>
		/// <param name="startIndex">The starting index of the input buffer.</param>
		/// <param name="length">The number of bytes in the input buffer to parse.</param>
		/// <param name="disposition">The parsed disposition.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="buffer"/> is <c>null</c>.
		/// </exception>
		/// <exception cref="System.ArgumentOutOfRangeException">
		/// <paramref name="startIndex"/> and <paramref name="length"/> do not specify
		/// a valid range in the byte array.
		/// </exception>
		public static bool TryParse (byte[] buffer, int startIndex, int length, out ContentDisposition disposition)
		{
			return TryParse (ParserOptions.Default, buffer, startIndex, length, out disposition);
		}
Example #16
0
		internal static bool TryParse (ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out ContentDisposition disposition)
		{
			string type;
			int atom;

			disposition = null;

			if (!ParseUtils.SkipCommentsAndWhiteSpace (text, ref index, endIndex, throwOnError))
				return false;

			atom = index;
			if (!ParseUtils.SkipAtom (text, ref index, endIndex)) {
				if (throwOnError)
					throw new ParseException (string.Format ("Invalid atom token at position {0}", atom), atom, index);

				return false;
			}

			type = Encoding.ASCII.GetString (text, atom, index - atom);

			if (!ParseUtils.SkipCommentsAndWhiteSpace (text, ref index, endIndex, throwOnError))
				return false;

			disposition = new ContentDisposition ();
			disposition.disposition = type;

			if (index >= endIndex)
				return true;

			if (text[index] != (byte) ';') {
				if (throwOnError)
					throw new ParseException (string.Format ("Expected ';' at position {0}", index), index, index);

				return false;
			}

			index++;

			if (!ParseUtils.SkipCommentsAndWhiteSpace (text, ref index, endIndex, throwOnError))
				return false;

			if (index >= endIndex)
				return true;

			ParameterList parameters;
			if (!ParameterList.TryParse (options, text, ref index, endIndex, throwOnError, out parameters))
				return false;

			disposition.Parameters = parameters;

			return true;
		}
Example #17
0
		/// <summary>
		/// Called when the headers change in some way.
		/// </summary>
		/// <remarks>
		/// <para>Whenever a header is added, changed, or removed, this method will
		/// be called in order to allow custom <see cref="MimeEntity"/> subclasses
		/// to update their state.</para>
		/// <para>Overrides of this method should call the base method so that their
		/// superclass may also update its own state.</para>
		/// </remarks>
		/// <param name="action">The type of change.</param>
		/// <param name="header">The header being added, changed or removed.</param>
		protected virtual void OnHeadersChanged (HeaderListChangedAction action, Header header)
		{
			MailboxAddress mailbox;
			int index = 0;
			string text;

			switch (action) {
			case HeaderListChangedAction.Added:
			case HeaderListChangedAction.Changed:
				switch (header.Id) {
				case HeaderId.ContentDisposition:
					if (disposition != null)
						disposition.Changed -= ContentDispositionChanged;

					if (ContentDisposition.TryParse (Headers.Options, header.RawValue, out disposition))
						disposition.Changed += ContentDispositionChanged;
					break;
				case HeaderId.ContentLocation:
					text = header.Value.Trim ();

					if (Uri.IsWellFormedUriString (text, UriKind.Absolute))
						location = new Uri (text, UriKind.Absolute);
					else if (Uri.IsWellFormedUriString (text, UriKind.Relative))
						location = new Uri (text, UriKind.Relative);
					else
						location = null;
					break;
				case HeaderId.ContentBase:
					text = header.Value.Trim ();

					if (Uri.IsWellFormedUriString (text, UriKind.Absolute))
						baseUri = new Uri (text, UriKind.Absolute);
					else
						baseUri = null;
					break;
				case HeaderId.ContentId:
					if (MailboxAddress.TryParse (Headers.Options, header.RawValue, ref index, header.RawValue.Length, false, out mailbox))
						contentId = mailbox.Address;
					else
						contentId = null;
					break;
				}
				break;
			case HeaderListChangedAction.Removed:
				switch (header.Id) {
				case HeaderId.ContentDisposition:
					if (disposition != null)
						disposition.Changed -= ContentDispositionChanged;

					disposition = null;
					break;
				case HeaderId.ContentLocation:
					location = null;
					break;
				case HeaderId.ContentBase:
					baseUri = null;
					break;
				case HeaderId.ContentId:
					contentId = null;
					break;
				}
				break;
			case HeaderListChangedAction.Cleared:
				if (disposition != null)
					disposition.Changed -= ContentDispositionChanged;

				disposition = null;
				contentId = null;
				location = null;
				baseUri = null;
				break;
			default:
				throw new ArgumentOutOfRangeException (nameof (action));
			}
		}
Example #18
0
		/// <summary>
		/// Tries to parse the given text into a new <see cref="MimeKit.ContentDisposition"/> instance.
		/// </summary>
		/// <remarks>
		/// Parses a Content-Disposition value from the supplied text.
		/// </remarks>
		/// <returns><c>true</c>, if the disposition was successfully parsed, <c>false</c> otherwise.</returns>
		/// <param name="text">The text to parse.</param>
		/// <param name="disposition">The parsed disposition.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="text"/> is <c>null</c>.
		/// </exception>
		public static bool TryParse (string text, out ContentDisposition disposition)
		{
			if (text == null)
				throw new ArgumentNullException ("text");

			var buffer = Encoding.UTF8.GetBytes (text);
			int index = 0;

			return TryParse (ParserOptions.Default, buffer, ref index, buffer.Length, false, out disposition);
		}
Example #19
0
 /// <summary>
 /// Tries to parse the given text into a new <see cref="MimeKit.ContentDisposition"/> instance.
 /// </summary>
 /// <remarks>
 /// Parses a Content-Disposition value from the supplied text.
 /// </remarks>
 /// <returns><c>true</c> if the disposition was successfully parsed; otherwise, <c>false</c>.</returns>
 /// <param name="text">The text to parse.</param>
 /// <param name="disposition">The parsed disposition.</param>
 /// <exception cref="System.ArgumentNullException">
 /// <paramref name="text"/> is <c>null</c>.
 /// </exception>
 public static bool TryParse(string text, out ContentDisposition disposition)
 {
     return(TryParse(ParserOptions.Default, text, out disposition));
 }
Example #20
0
		/// <summary>
		/// Tries to parse the given text into a new <see cref="MimeKit.ContentDisposition"/> instance.
		/// </summary>
		/// <remarks>
		/// Parses a Content-Disposition value from the supplied text.
		/// </remarks>
		/// <returns><c>true</c>, if the disposition was successfully parsed, <c>false</c> otherwise.</returns>
		/// <param name="options">The parser options.</param>
		/// <param name="text">The text to parse.</param>
		/// <param name="disposition">The parsed disposition.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="options"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="text"/> is <c>null</c>.</para>
		/// </exception>
		public static bool TryParse (ParserOptions options, string text, out ContentDisposition disposition)
		{
			ParseUtils.ValidateArguments (options, text);

			var buffer = Encoding.UTF8.GetBytes (text);
			int index = 0;

			return TryParse (ParserOptions.Default, buffer, ref index, buffer.Length, false, out disposition);
		}
Example #21
0
        internal static bool TryParse(ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out ContentDisposition disposition)
        {
            string type;
            int    atom;

            disposition = null;

            if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError))
            {
                return(false);
            }

            atom = index;
            if (!ParseUtils.SkipAtom(text, ref index, endIndex))
            {
                if (throwOnError)
                {
                    throw new ParseException(string.Format("Invalid atom token at position {0}", atom), atom, index);
                }

                return(false);
            }

            type = Encoding.ASCII.GetString(text, atom, index - atom);

            if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError))
            {
                return(false);
            }

            disposition             = new ContentDisposition();
            disposition.disposition = type;

            if (index >= endIndex)
            {
                return(true);
            }

            if (text[index] != (byte)';')
            {
                if (throwOnError)
                {
                    throw new ParseException(string.Format("Expected ';' at position {0}", index), index, index);
                }

                return(false);
            }

            index++;

            if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError))
            {
                return(false);
            }

            if (index >= endIndex)
            {
                return(true);
            }

            ParameterList parameters;

            if (!ParameterList.TryParse(options, text, ref index, endIndex, throwOnError, out parameters))
            {
                return(false);
            }

            disposition.Parameters = parameters;

            return(true);
        }
Example #22
0
		/// <summary>
		/// Tries to parse the given text into a new <see cref="MimeKit.ContentDisposition"/> instance.
		/// </summary>
		/// <remarks>
		/// Parses a Content-Disposition value from the supplied text.
		/// </remarks>
		/// <returns><c>true</c>, if the disposition was successfully parsed, <c>false</c> otherwise.</returns>
		/// <param name="text">The text to parse.</param>
		/// <param name="disposition">The parsed disposition.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="text"/> is <c>null</c>.
		/// </exception>
		public static bool TryParse (string text, out ContentDisposition disposition)
		{
			return TryParse (ParserOptions.Default, text, out disposition);
		}
Example #23
0
        /// <summary>
        /// Tries to parse the given input buffer into a new <see cref="MimeKit.ContentDisposition"/> instance.
        /// </summary>
        /// <remarks>
        /// Parses a Content-Disposition value from the supplied buffer starting at the specified index.
        /// </remarks>
        /// <returns><c>true</c>, if the disposition was successfully parsed, <c>false</c> otherwise.</returns>
        /// <param name="options">The parser options.</param>
        /// <param name="buffer">The input buffer.</param>
        /// <param name="startIndex">The starting index of the input buffer.</param>
        /// <param name="disposition">The parsed disposition.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <para><paramref name="options"/> is <c>null</c>.</para>
        /// <para>-or-</para>
        /// <para><paramref name="buffer"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// <paramref name="startIndex"/> is out of range.
        /// </exception>
        public static bool TryParse(ParserOptions options, byte[] buffer, int startIndex, out ContentDisposition disposition)
        {
            if (options == null)
            {
                throw new ArgumentNullException("options");
            }

            if (buffer == null)
            {
                throw new ArgumentNullException("buffer");
            }

            if (startIndex < 0 || startIndex >= buffer.Length)
            {
                throw new ArgumentOutOfRangeException("startIndex");
            }

            int index = startIndex;

            return(TryParse(options, buffer, ref index, buffer.Length, false, out disposition));
        }
Example #24
0
        static ContentDisposition ParseContentDisposition(ImapEngine engine, CancellationToken cancellationToken)
        {
            var token = engine.ReadToken (cancellationToken);

            if (token.Type == ImapTokenType.Nil)
                return null;

            if (token.Type != ImapTokenType.OpenParen)
                throw ImapEngine.UnexpectedToken (token, false);

            var dsp = ReadStringToken (engine, cancellationToken);
            var disposition = new ContentDisposition (dsp);

            token = engine.ReadToken (cancellationToken);

            if (token.Type == ImapTokenType.OpenParen)
                ParseParameterList (disposition.Parameters, engine, cancellationToken);
            else if (token.Type != ImapTokenType.Nil)
                throw ImapEngine.UnexpectedToken (token, false);

            token = engine.ReadToken (cancellationToken);

            if (token.Type != ImapTokenType.CloseParen)
                throw ImapEngine.UnexpectedToken (token, false);

            return disposition;
        }
		public void TestMultipleParametersWithIdenticalNames ()
		{
			const string text1 = "inline;\n filename=\"Filename.doc\";\n filename*0*=UTF-8''UnicodeFile;\n filename*1*=name.doc";
			const string text2 = "inline;\n filename*0*=UTF-8''UnicodeFile;\n filename*1*=name.doc;\n filename=\"Filename.doc\"";
			const string text3 = "inline;\n filename*0*=UTF-8''UnicodeFile;\n filename=\"Filename.doc\";\n filename*1*=name.doc";
			var expected = new ContentDisposition ("inline");

			expected.Parameters.Add ("filename", "UnicodeFilename.doc");

			AssertParse (text1, expected);
			AssertParse (text2, expected);
			AssertParse (text3, expected);
		}
Example #26
0
		static bool TryParse (string text, ref int index, out ContentDisposition disposition)
		{
			IList<Parameter> parameters;
			string value;

			disposition = null;

			while (index < text.Length && text[index] == ' ')
				index++;

			if (index >= text.Length)
				return false;

			if (text[index] != '(') {
				if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") {
					index += 3;
					return true;
				}

				return false;
			}

			index++;

			if (!TryParse (text, ref index, out value))
				return false;

			if (!TryParse (text, ref index, out parameters))
				return false;

			if (index >= text.Length || text[index] != ')')
				return false;

			index++;

			disposition = new ContentDisposition (value);

			foreach (var param in parameters)
				disposition.Parameters.Add (param);

			return true;
		}
		public void TestUnquotedFilenameParameterValues ()
		{
			const string text = " attachment; filename=Partnership Marketing Agreement\n Form - Mega Brands - Easter Toys - Week 11.pdf";
			const string filename = "Partnership Marketing Agreement Form - Mega Brands - Easter Toys - Week 11.pdf";
			var expected = new ContentDisposition ("attachment");

			expected.Parameters.Add ("filename", filename);

			AssertParse (text, expected);
		}
		static void AssertParse (string text, ContentDisposition expected, bool result = true, int tokenIndex = -1, int errorIndex = -1)
		{
			var buffer = Encoding.UTF8.GetBytes (text);
			var options = ParserOptions.Default;
			ContentDisposition disposition;

			Assert.AreEqual (result, ContentDisposition.TryParse (text, out disposition), "Unexpected result for TryParse: {0}", text);
			AssertParseResults (disposition, expected);

			Assert.AreEqual (result, ContentDisposition.TryParse (options, text, out disposition), "Unexpected result for TryParse: {0}", text);
			AssertParseResults (disposition, expected);

			Assert.AreEqual (result, ContentDisposition.TryParse (buffer, out disposition), "Unexpected result for TryParse: {0}", text);
			AssertParseResults (disposition, expected);

			Assert.AreEqual (result, ContentDisposition.TryParse (options, buffer, out disposition), "Unexpected result for TryParse: {0}", text);
			AssertParseResults (disposition, expected);

			Assert.AreEqual (result, ContentDisposition.TryParse (buffer, 0, out disposition), "Unexpected result for TryParse: {0}", text);
			AssertParseResults (disposition, expected);

			Assert.AreEqual (result, ContentDisposition.TryParse (options, buffer, 0, out disposition), "Unexpected result for TryParse: {0}", text);
			AssertParseResults (disposition, expected);

			Assert.AreEqual (result, ContentDisposition.TryParse (buffer, 0, buffer.Length, out disposition), "Unexpected result for TryParse: {0}", text);
			AssertParseResults (disposition, expected);

			Assert.AreEqual (result, ContentDisposition.TryParse (options, buffer, 0, buffer.Length, out disposition), "Unexpected result for TryParse: {0}", text);
			AssertParseResults (disposition, expected);

			try {
				disposition = ContentDisposition.Parse (text);
				if (tokenIndex != -1 && errorIndex != -1)
					Assert.Fail ("Parsing \"{0}\" should have failed.", text);
				AssertParseResults (disposition, expected);
			} catch (ParseException ex) {
				Assert.AreEqual (tokenIndex, ex.TokenIndex, "Unexpected token index");
				Assert.AreEqual (errorIndex, ex.ErrorIndex, "Unexpected error index");
			} catch (Exception e) {
				Assert.Fail ("Unexpected exception: {0}", e);
			}

			try {
				disposition = ContentDisposition.Parse (options, text);
				if (tokenIndex != -1 && errorIndex != -1)
					Assert.Fail ("Parsing \"{0}\" should have failed.", text);
				AssertParseResults (disposition, expected);
			} catch (ParseException ex) {
				Assert.AreEqual (tokenIndex, ex.TokenIndex, "Unexpected token index");
				Assert.AreEqual (errorIndex, ex.ErrorIndex, "Unexpected error index");
			} catch (Exception e) {
				Assert.Fail ("Unexpected exception: {0}", e);
			}

			try {
				disposition = ContentDisposition.Parse (buffer);
				if (tokenIndex != -1 && errorIndex != -1)
					Assert.Fail ("Parsing \"{0}\" should have failed.", text);
				AssertParseResults (disposition, expected);
			} catch (ParseException ex) {
				Assert.AreEqual (tokenIndex, ex.TokenIndex, "Unexpected token index");
				Assert.AreEqual (errorIndex, ex.ErrorIndex, "Unexpected error index");
			} catch (Exception e) {
				Assert.Fail ("Unexpected exception: {0}", e);
			}

			try {
				disposition = ContentDisposition.Parse (options, buffer);
				if (tokenIndex != -1 && errorIndex != -1)
					Assert.Fail ("Parsing \"{0}\" should have failed.", text);
				AssertParseResults (disposition, expected);
			} catch (ParseException ex) {
				Assert.AreEqual (tokenIndex, ex.TokenIndex, "Unexpected token index");
				Assert.AreEqual (errorIndex, ex.ErrorIndex, "Unexpected error index");
			} catch (Exception e) {
				Assert.Fail ("Unexpected exception: {0}", e);
			}

			try {
				disposition = ContentDisposition.Parse (buffer, 0);
				if (tokenIndex != -1 && errorIndex != -1)
					Assert.Fail ("Parsing \"{0}\" should have failed.", text);
				AssertParseResults (disposition, expected);
			} catch (ParseException ex) {
				Assert.AreEqual (tokenIndex, ex.TokenIndex, "Unexpected token index");
				Assert.AreEqual (errorIndex, ex.ErrorIndex, "Unexpected error index");
			} catch (Exception e) {
				Assert.Fail ("Unexpected exception: {0}", e);
			}

			try {
				disposition = ContentDisposition.Parse (options, buffer, 0);
				if (tokenIndex != -1 && errorIndex != -1)
					Assert.Fail ("Parsing \"{0}\" should have failed.", text);
				AssertParseResults (disposition, expected);
			} catch (ParseException ex) {
				Assert.AreEqual (tokenIndex, ex.TokenIndex, "Unexpected token index");
				Assert.AreEqual (errorIndex, ex.ErrorIndex, "Unexpected error index");
			} catch (Exception e) {
				Assert.Fail ("Unexpected exception: {0}", e);
			}

			try {
				disposition = ContentDisposition.Parse (buffer, 0, buffer.Length);
				if (tokenIndex != -1 && errorIndex != -1)
					Assert.Fail ("Parsing \"{0}\" should have failed.", text);
				AssertParseResults (disposition, expected);
			} catch (ParseException ex) {
				Assert.AreEqual (tokenIndex, ex.TokenIndex, "Unexpected token index");
				Assert.AreEqual (errorIndex, ex.ErrorIndex, "Unexpected error index");
			} catch (Exception e) {
				Assert.Fail ("Unexpected exception: {0}", e);
			}

			try {
				disposition = ContentDisposition.Parse (options, buffer, 0, buffer.Length);
				if (tokenIndex != -1 && errorIndex != -1)
					Assert.Fail ("Parsing \"{0}\" should have failed.", text);
				AssertParseResults (disposition, expected);
			} catch (ParseException ex) {
				Assert.AreEqual (tokenIndex, ex.TokenIndex, "Unexpected token index");
				Assert.AreEqual (errorIndex, ex.ErrorIndex, "Unexpected error index");
			} catch (Exception e) {
				Assert.Fail ("Unexpected exception: {0}", e);
			}
		}
		public void TestChineseFilename ()
		{
			const string expected = " attachment;\n\tfilename*=gb18030''%B2%E2%CA%D4%CE%C4%B1%BE.txt\n";
			var disposition = new ContentDisposition (ContentDisposition.Attachment);
			disposition.Parameters.Add ("GB18030", "filename", "测试文本.txt");

			var format = FormatOptions.Default.Clone ();
			format.NewLineFormat = NewLineFormat.Unix;

			var encoded = disposition.Encode (format, Encoding.UTF8);
			Parameter param;

			Assert.AreEqual (expected, encoded, "The encoded Chinese filename parameter does not match the expected value.");
			Assert.IsTrue (ContentDisposition.TryParse (encoded, out disposition), "Failed to parse Content-Disposition");
			Assert.AreEqual ("测试文本.txt", disposition.FileName, "The decoded Chinese filename does not match.");
			Assert.IsTrue (disposition.Parameters.TryGetValue ("filename", out param), "Failed to locate filename parameter.");
			Assert.AreEqual ("GB18030", param.Encoding.HeaderName, "The filename encoding did not match.");
		}
Example #30
0
		static ContentDisposition ParseContentDisposition (ImapEngine engine, CancellationToken cancellationToken)
		{
			var token = engine.ReadToken (cancellationToken);

			if (token.Type == ImapTokenType.Nil)
				return null;

			if (token.Type != ImapTokenType.OpenParen)
				throw ImapEngine.UnexpectedToken (token, false);

			var dsp = ReadStringToken (engine, cancellationToken);
			var builder = new StringBuilder (dsp);
			ContentDisposition disposition;

			token = engine.ReadToken (cancellationToken);

			if (token.Type == ImapTokenType.OpenParen)
				ParseParameterList (builder, engine, cancellationToken);
			else if (token.Type != ImapTokenType.Nil)
				throw ImapEngine.UnexpectedToken (token, false);

			token = engine.ReadToken (cancellationToken);

			if (token.Type != ImapTokenType.CloseParen)
				throw ImapEngine.UnexpectedToken (token, false);

			if (!ContentDisposition.TryParse (builder.ToString (), out disposition))
				disposition = new ContentDisposition (dsp);

			return disposition;
		}
		public void TestIssue239 ()
		{
			const string text = " attachment; size=1049971;\n\tfilename*=\"utf-8''SBD%20%C5%A0kodov%C3%A1k%2Ejpg\"";
			const string filename = "SBD Škodovák.jpg";
			var expected = new ContentDisposition ("attachment");

			expected.Parameters.Add ("size", "1049971");
			expected.Parameters.Add ("filename", filename);

			AssertParse (text, expected);
		}
Example #32
0
		internal static void Encode (StringBuilder builder, ContentDisposition disposition)
		{
			if (disposition == null) {
				builder.Append ("NIL");
				return;
			}

			builder.Append ('(');
			Encode (builder, disposition.Disposition);
			builder.Append (' ');
			Encode (builder, disposition.Parameters);
			builder.Append (')');
		}
Example #33
0
        /// <summary>
        /// Called when the headers change in some way.
        /// </summary>
        /// <param name="action">The type of change.</param>
        /// <param name="header">The header being added, changed or removed.</param>
        protected virtual void OnHeadersChanged(HeaderListChangedAction action, Header header)
        {
            switch (action) {
            case HeaderListChangedAction.Added:
            case HeaderListChangedAction.Changed:
                switch (header.Id) {
                case HeaderId.ContentDisposition:
                    if (disposition != null)
                        disposition.Changed -= ContentDispositionChanged;

                    if (ContentDisposition.TryParse (Headers.Options, header.RawValue, out disposition))
                        disposition.Changed += ContentDispositionChanged;
                    break;
                case HeaderId.ContentId:
                    contentId = MimeUtils.EnumerateReferences (header.RawValue, 0, header.RawValue.Length).FirstOrDefault ();
                    break;
                }
                break;
            case HeaderListChangedAction.Removed:
                switch (header.Id) {
                case HeaderId.ContentDisposition:
                    if (disposition != null)
                        disposition.Changed -= ContentDispositionChanged;

                    disposition = null;
                    break;
                case HeaderId.ContentId:
                    contentId = null;
                    break;
                }
                break;
            case HeaderListChangedAction.Cleared:
                if (disposition != null)
                    disposition.Changed -= ContentDispositionChanged;

                disposition = null;
                contentId = null;
                break;
            default:
                throw new ArgumentOutOfRangeException ();
            }
        }
Example #34
0
		/// <summary>
		/// Tries to parse the given input buffer into a new <see cref="MimeKit.ContentDisposition"/> instance.
		/// </summary>
		/// <remarks>
		/// Parses a Content-Disposition value from the supplied buffer starting at the specified index.
		/// </remarks>
		/// <returns><c>true</c>, if the disposition was successfully parsed, <c>false</c> otherwise.</returns>
		/// <param name="options">The parser options.</param>
		/// <param name="buffer">The input buffer.</param>
		/// <param name="startIndex">The starting index of the input buffer.</param>
		/// <param name="disposition">The parsed disposition.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="options"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="buffer"/> is <c>null</c>.</para>
		/// </exception>
		/// <exception cref="System.ArgumentOutOfRangeException">
		/// <paramref name="startIndex"/> is out of range.
		/// </exception>
		public static bool TryParse (ParserOptions options, byte[] buffer, int startIndex, out ContentDisposition disposition)
		{
			if (options == null)
				throw new ArgumentNullException ("options");

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

			if (startIndex < 0 || startIndex >= buffer.Length)
				throw new ArgumentOutOfRangeException ("startIndex");

			int index = startIndex;

			return TryParse (options, buffer, ref index, buffer.Length, false, out disposition);
		}
Example #35
0
		/// <summary>
		/// Called when the headers change in some way.
		/// </summary>
		/// <remarks>
		/// <para>Whenever a header is added, changed, or removed, this method will
		/// be called in order to allow custom <see cref="MimeEntity"/> subclasses
		/// to update their state.</para>
		/// <para>Overrides of this method should call the base method so that their
		/// superclass may also update its own state.</para>
		/// </remarks>
		/// <param name="action">The type of change.</param>
		/// <param name="header">The header being added, changed or removed.</param>
		protected virtual void OnHeadersChanged (HeaderListChangedAction action, Header header)
		{
			string text;

			switch (action) {
			case HeaderListChangedAction.Added:
			case HeaderListChangedAction.Changed:
				switch (header.Id) {
				case HeaderId.ContentDisposition:
					if (disposition != null)
						disposition.Changed -= ContentDispositionChanged;

					if (ContentDisposition.TryParse (Headers.Options, header.RawValue, out disposition))
						disposition.Changed += ContentDispositionChanged;
					break;
				case HeaderId.ContentLocation:
					text = header.Value.Trim ();

					if (Uri.IsWellFormedUriString (text, UriKind.Absolute))
						location = new Uri (text, UriKind.Absolute);
					else if (Uri.IsWellFormedUriString (text, UriKind.Relative))
						location = new Uri (text, UriKind.Relative);
					else
						location = null;
					break;
				case HeaderId.ContentBase:
					text = header.Value.Trim ();

					if (Uri.IsWellFormedUriString (text, UriKind.Absolute))
						baseUri = new Uri (text, UriKind.Absolute);
					else
						baseUri = null;
					break;
				case HeaderId.ContentId:
					contentId = MimeUtils.EnumerateReferences (header.RawValue, 0, header.RawValue.Length).FirstOrDefault ();
					break;
				}
				break;
			case HeaderListChangedAction.Removed:
				switch (header.Id) {
				case HeaderId.ContentDisposition:
					if (disposition != null)
						disposition.Changed -= ContentDispositionChanged;

					disposition = null;
					break;
				case HeaderId.ContentLocation:
					location = null;
					break;
				case HeaderId.ContentBase:
					baseUri = null;
					break;
				case HeaderId.ContentId:
					contentId = null;
					break;
				}
				break;
			case HeaderListChangedAction.Cleared:
				if (disposition != null)
					disposition.Changed -= ContentDispositionChanged;

				disposition = null;
				contentId = null;
				location = null;
				baseUri = null;
				break;
			default:
				throw new ArgumentOutOfRangeException ("action");
			}
		}
Example #36
0
        internal static bool TryParse(ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out ContentDisposition disposition)
        {
            string type;
            int    atom;

            disposition = null;

            if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError))
            {
                return(false);
            }

            if (index >= endIndex)
            {
                if (throwOnError)
                {
                    throw new ParseException(string.Format("Expected atom token at position {0}", index), index, index);
                }

                return(false);
            }

            atom = index;
            if (text[index] == '"')
            {
                if (throwOnError)
                {
                    throw new ParseException(string.Format("Unxpected qstring token at position {0}", atom), atom, index);
                }

                // Note: This is a work-around for broken mailers that quote the disposition value...
                //
                // See https://github.com/jstedfast/MailKit/issues/486 for details.
                if (!ParseUtils.SkipQuoted(text, ref index, endIndex, throwOnError))
                {
                    return(false);
                }

                type = CharsetUtils.ConvertToUnicode(options, text, atom, index - atom);
                type = MimeUtils.Unquote(type);

                if (string.IsNullOrEmpty(type))
                {
                    type = Attachment;
                }
            }
            else
            {
                if (!ParseUtils.SkipAtom(text, ref index, endIndex))
                {
                    if (throwOnError)
                    {
                        throw new ParseException(string.Format("Invalid atom token at position {0}", atom), atom, index);
                    }

                    // Note: this is a work-around for broken mailers that do not specify a disposition value...
                    //
                    // See https://github.com/jstedfast/MailKit/issues/486 for details.
                    if (index > atom || text[index] != (byte)';')
                    {
                        return(false);
                    }

                    type = Attachment;
                }
                else
                {
                    type = Encoding.ASCII.GetString(text, atom, index - atom);
                }
            }

            disposition             = new ContentDisposition();
            disposition.disposition = type;

            if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError))
            {
                return(false);
            }

            if (index >= endIndex)
            {
                return(true);
            }

            if (text[index] != (byte)';')
            {
                if (throwOnError)
                {
                    throw new ParseException(string.Format("Expected ';' at position {0}", index), index, index);
                }

                return(false);
            }

            index++;

            if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError))
            {
                return(false);
            }

            if (index >= endIndex)
            {
                return(true);
            }

            ParameterList parameters;

            if (!ParameterList.TryParse(options, text, ref index, endIndex, throwOnError, out parameters))
            {
                return(false);
            }

            disposition.Parameters = parameters;

            return(true);
        }
Example #37
0
        /// <summary>
        /// Tries to parse the given input buffer into a new <see cref="MimeKit.ContentDisposition"/> instance.
        /// </summary>
        /// <remarks>
        /// Parses a Content-Disposition value from the supplied buffer starting at the specified index.
        /// </remarks>
        /// <returns><c>true</c> if the disposition was successfully parsed; otherwise, <c>false</c>.</returns>
        /// <param name="options">The parser options.</param>
        /// <param name="buffer">The input buffer.</param>
        /// <param name="startIndex">The starting index of the input buffer.</param>
        /// <param name="disposition">The parsed disposition.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <para><paramref name="options"/> is <c>null</c>.</para>
        /// <para>-or-</para>
        /// <para><paramref name="buffer"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// <paramref name="startIndex"/> is out of range.
        /// </exception>
        public static bool TryParse(ParserOptions options, byte[] buffer, int startIndex, out ContentDisposition disposition)
        {
            ParseUtils.ValidateArguments(options, buffer, startIndex);

            int index = startIndex;

            return(TryParse(options, buffer, ref index, buffer.Length, false, out disposition));
        }
Example #38
0
        /// <summary>
        /// Called when the headers change in some way.
        /// </summary>
        /// <remarks>
        /// <para>Whenever a header is added, changed, or removed, this method will
        /// be called in order to allow custom <see cref="MimeEntity"/> subclasses
        /// to update their state.</para>
        /// <para>Overrides of this method should call the base method so that their
        /// superclass may also update its own state.</para>
        /// </remarks>
        /// <param name="action">The type of change.</param>
        /// <param name="header">The header being added, changed or removed.</param>
        protected virtual void OnHeadersChanged(HeaderListChangedAction action, Header header)
        {
            string text;

            switch (action)
            {
            case HeaderListChangedAction.Added:
            case HeaderListChangedAction.Changed:
                switch (header.Id)
                {
                case HeaderId.ContentDisposition:
                    if (disposition != null)
                    {
                        disposition.Changed -= ContentDispositionChanged;
                    }

                    if (ContentDisposition.TryParse(Headers.Options, header.RawValue, out disposition))
                    {
                        disposition.Changed += ContentDispositionChanged;
                    }
                    break;

                case HeaderId.ContentLocation:
                    text = header.Value.Trim();

                    if (Uri.IsWellFormedUriString(text, UriKind.Absolute))
                    {
                        location = new Uri(text, UriKind.Absolute);
                    }
                    else if (Uri.IsWellFormedUriString(text, UriKind.Relative))
                    {
                        location = new Uri(text, UriKind.Relative);
                    }
                    else
                    {
                        location = null;
                    }
                    break;

                case HeaderId.ContentBase:
                    text = header.Value.Trim();

                    if (Uri.IsWellFormedUriString(text, UriKind.Absolute))
                    {
                        baseUri = new Uri(text, UriKind.Absolute);
                    }
                    else
                    {
                        baseUri = null;
                    }
                    break;

                case HeaderId.ContentId:
                    contentId = MimeUtils.EnumerateReferences(header.RawValue, 0, header.RawValue.Length).FirstOrDefault();
                    break;
                }
                break;

            case HeaderListChangedAction.Removed:
                switch (header.Id)
                {
                case HeaderId.ContentDisposition:
                    if (disposition != null)
                    {
                        disposition.Changed -= ContentDispositionChanged;
                    }

                    disposition = null;
                    break;

                case HeaderId.ContentLocation:
                    location = null;
                    break;

                case HeaderId.ContentBase:
                    baseUri = null;
                    break;

                case HeaderId.ContentId:
                    contentId = null;
                    break;
                }
                break;

            case HeaderListChangedAction.Cleared:
                if (disposition != null)
                {
                    disposition.Changed -= ContentDispositionChanged;
                }

                disposition = null;
                contentId   = null;
                location    = null;
                baseUri     = null;
                break;

            default:
                throw new ArgumentOutOfRangeException("action");
            }
        }