Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

How to create a spam filter or automatic category sort algorithm with your mail application

, 29 Jul 2012
This article describe about automatic category filters in mail applications.
SpamFilterSample-noexe.zip
SpamFilterSample
HigLabo.Mail.SampleApplication
Properties
HigLabo.Mail
Async
Common
HigLabo.Mail.csproj.user
HigLabo.Mail.csproj.vspscc
Imap
Command
Pop3
Command
Properties
Smtp
Command
SendMail
HigLabo.Net
Core
Extensions
HigLabo.Net.csproj.user
HigLabo.Net.csproj.vspscc
HigLabo.Net.Silverlight.csproj.user
HigLabo.Net.Silverlight.csproj.vspscc
HigLabo.Net.WindowsPhone7.csproj.user
HigLabo.Net.WindowsPhone7.csproj.vspscc
Http
OAuth
Properties
Socket
Reference
Properties
Reference.csproj.user
SpamFilterSample
bin
Debug
SpamFilterSample.vshost.exe.manifest
Properties
SpamFilterSample.csproj.user
SpamFilterSample.zip
bin
Debug
Release
HigLabo.Mail.dll
HigLabo.Mail.SampleApplication.exe
HigLabo.Net.dll
Newtonsoft.Json.dll
obj
x86
Debug
TempPE
Release
bin
Debug
Release
HigLabo.Mail.dll
HigLabo.Net.dll
Newtonsoft.Json.dll
HigLabo.Mail.csproj.user
HigLabo.Mail.csproj.vspscc
obj
Debug
TempPE
Release
bin
Debug
de
es
fr
it
ja
ko
ru
zh-Hans
zh-Hant
Release
de
System.Xml.Linq.resources.dll
es
System.Xml.Linq.resources.dll
fr
System.Xml.Linq.resources.dll
HigLabo.Net.dll
HigLabo.Net.Silverlight.dll
HigLabo.Net.WindowsPhone7.dll
it
System.Xml.Linq.resources.dll
ja
System.Xml.Linq.resources.dll
ko
System.Xml.Linq.resources.dll
Newtonsoft.Json.dll
Newtonsoft.Json.Silverlight.dll
Newtonsoft.Json.WindowsPhone7.dll
ru
System.Xml.Linq.resources.dll
System.Xml.Linq.dll
zh-Hans
System.Xml.Linq.resources.dll
zh-Hant
System.Xml.Linq.resources.dll
HigLabo.Net.csproj.user
HigLabo.Net.csproj.vspscc
HigLabo.Net.Silverlight.csproj.user
HigLabo.Net.Silverlight.csproj.vspscc
HigLabo.Net.WindowsPhone7.csproj.user
HigLabo.Net.WindowsPhone7.csproj.vspscc
obj
Debug
TempPE
Release
BayesianFilter.dll
bin
Debug
Reference.dll
Release
HtmlAgilityPack.dll
obj
Debug
TempPE
Reference.csproj.user
BayesianFilter.dll
HigLabo.Mail.dll
HigLabo.Net.dll
HtmlAgilityPack.dll
Lucene.Net.dll
Newtonsoft.Json.dll
SpamData
SpamFilterSample.exe
SpamFilterSample.vshost.exe
SpamFilterSample.vshost.exe.manifest
Release
obj
x86
Debug
TempPE
SpamFilterSample.csproj.user
using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Globalization;
using HigLabo.Net.Mail;

namespace HigLabo.Net
{
    /// Represent message defined RFC822,RFC2045-2049 as class.
    /// <summary>
    /// Represent message defined RFC822,RFC2045-2049 as class.
    /// </summary>
	public class InternetTextMessage 
	{
        private class RegexList
        {
            public static readonly ICollection<Regex> ContentEncodingCharset = new List<Regex>();
            public static readonly Regex HeaderParse = new Regex("^(?<key>[^:]*):[\\s]*(?<value>.*)");
            public static readonly Regex HeaderParse1 = new Regex("(?<value>[^;]*)[;]*");
            public static readonly Regex Attachment = new Regex("^attachment.*$");
            public static readonly Regex Image = new Regex("^image/.*$");
        }
        private List<Field> _Header;
		/// ヘッダーの文字列データ(US-ASCII)
		/// <summary>
		/// Field for header data (encoded by US-ASCII)
		/// ヘッダーの文字列データ(US-ASCII)
		/// </summary>
		private String _HeaderData = "";
		/// 本文の文字列データ(US-ASCII)
        /// <summary>
        /// Field for body data (encoded by US-ASCII)
        /// 本文の文字列データ(US-ASCII)
        /// </summary>
        private String _BodyData = "";
        private String _Data;
        private Boolean _DecodeHeaderText = true;
        private Encoding _ContentEncoding = Encoding.Default;
		/// <summary>
		/// 
		/// </summary>
		/// <param name="key"></param>
		/// <returns></returns>
		public String this[String key]
		{
			get 
			{
				Field f = InternetTextMessage.Field.FindField(this._Header, key);
				if (f == null)
				{
					return "";
				}
				else
				{
                    if (this._DecodeHeaderText == true)
                    {
                        return MailParser.DecodeFromMailHeaderLine(f.Value);
                    }
                    else
                    {
                        return f.Value;
                    }
				}
			}
			set 
			{
				Field f = InternetTextMessage.Field.FindField(this._Header, key);
				if (f == null)
				{
					f = new Field(key, value);
					this._Header.Add(f);
				}
				else
				{
					f.Value = value;
				}
			}
		}
        /// Fromを取得または設定します。
        /// <summary>
        /// Get from value.
        /// Fromを取得または設定します。
        /// </summary>
        public String From
        {
            get { return this["From"]; }
            set { this["From"] = value; }
        }
        /// Reply-Toを取得または設定します。
        /// <summary>
        /// Reply-Toを取得または設定します。
        /// </summary>
        public String ReplyTo
        {
            get { return this["Reply-To"]; }
            set { this["Reply-To"] = value; }
        }
        /// In-Reply-Toを取得または設定します。
        /// <summary>
        /// In-Reply-Toを取得または設定します。
        /// </summary>
        public String InReplyTo
        {
            get { return this["In-Reply-To"]; }
            set { this["In-Reply-To"] = value; }
        }
        /// 件名を取得または設定します。
        /// <summary>
        /// 件名を取得または設定します。
        /// </summary>
        public String Subject
        {
            get { return this["Subject"]; }
            set { this["Subject"] = value; }
        }
        /// Dateを取得または設定します。
        /// <summary>
        /// Dateを取得または設定します。
        /// </summary>
        public DateTimeOffset Date
        {
            get { return MailParser.ToDateTimeOffset(this["Date"]); }
            set { this["Date"] = MailParser.DateTimeOffsetString(value); }
        }
        /// MessageIDを取得または設定します。
        /// <summary>
        /// MessageIDを取得または設定します。
        /// </summary>
        public String MessageID
        {
            get { return this["Message-ID"]; }
            set { this["Message-ID"] = value; }
        }
        /// Referencesを取得または設定します。
        /// <summary>
        /// Referencesを取得または設定します。
        /// </summary>
        public String References
        {
            get { return this["References"]; }
            set { this["References"] = value; }
        }
        /// ContentTypeを取得します。
        /// <summary>
        /// ContentTypeを取得します。
        /// </summary>
        public ContentType ContentType
        {
            get 
            {
                ContentType ff = null;
                ff = InternetTextMessage.Field.FindField(this._Header, "content-type") as ContentType;
                if (ff == null)
                {
                    ff = new ContentType("text/plain");
                    this._Header.Add(ff);
                }
                return ff;
            }
        }
        /// Encodingを取得します。
        /// <summary>
        /// Encodingを取得します。
        /// </summary>
        public Encoding ContentEncoding
        {
            get { return this._ContentEncoding; }
            set { this._ContentEncoding = value; }
        }
        /// ContentDispositionを取得します。
        /// <summary>
        /// ContentDispositionを取得します。
        /// </summary>
        public ContentDisposition ContentDisposition
        {
            get
            {
                ContentDisposition ff = null;
                ff = InternetTextMessage.Field.FindField(this._Header, "content-disposition") as ContentDisposition;
                if (ff == null)
                {
                    ff = new ContentDisposition("");
                    this._Header.Add(ff);
                }
                return ff;
            }
        }
        /// MultiPartBoundaryの文字列を取得または設定します。
        /// <summary>
        /// MultiPartBoundaryの文字列を取得または設定します。
        /// </summary>
        public String MultiPartBoundary
        {
            get { return this.ContentType.Boundary; }
            set { this.ContentType.Boundary = value; }
        }
        /// Body部がMIMEで構成されているかどうかを示す値を取得します。
        /// <summary>
        /// Body部がMIMEで構成されているかどうかを示す値を取得します。
        /// </summary>
        public Boolean IsMultiPart
        {
            get { return Regex.IsMatch(this.ContentType.Value, ".*multipart/.*", RegexOptions.IgnoreCase); }
        }
        /// このインスタンスが本文部分のデータを表す場合、Trueを返します。
        /// <summary>
        /// このインスタンスが本文部分のデータを表す場合、Trueを返します。
        /// </summary>
        public Boolean IsBody
        {
            get
            {
                return (this.ContentType.Value.StartsWith("text/", StringComparison.OrdinalIgnoreCase) == true);
            }
        }
        /// このインスタンスがテキスト形式のデータを表す場合、Trueを返します。
        /// <summary>
        /// このインスタンスがテキスト形式のデータを表す場合、Trueを返します。
        /// </summary>
        public Boolean IsText
        {
            get
            {
                return (this.ContentType.Value.StartsWith("text/", StringComparison.OrdinalIgnoreCase) == true) ||
                    (this.ContentType.Value.Equals("application/xml", StringComparison.OrdinalIgnoreCase) == true) ||
                    (this.ContentType.Value.Equals("application/json", StringComparison.OrdinalIgnoreCase) == true);
            }
        }
        /// このインスタンスがHTML形式のテキストデータを表す場合、Trueを返します。
        /// <summary>
        /// このインスタンスがHTML形式のテキストデータを表す場合、Trueを返します。
        /// </summary>
        public Boolean IsHtml
        {
            get
            {
                return (this.ContentType.Value.StartsWith("text/html", StringComparison.OrdinalIgnoreCase) == true);
            }
        }
        /// このインスタンスが添付ファイルデータの場合、Trueを返します。
        /// <summary>
        /// このインスタンスが添付ファイルデータの場合、Trueを返します。
        /// </summary>
        public Boolean IsAttachment
        {
            get
            {
                if (String.IsNullOrEmpty(this.ContentDisposition.Value) == false)
                {
                    return RegexList.Attachment.Match(this.ContentDisposition.Value).Success ||
                        RegexList.Image.Match(this.ContentType.Value).Success;
                }
                return false;
            }
        }
        /// ContentDispositionを取得または設定します。
        /// <summary>
        /// ContentDispositionを取得または設定します。
        /// </summary>
        public String ContentDescription
        {
            get { return this["Content-Description"]; }
            set { this["Content-Description"] = value; }
        }
        /// ContentTransferEncodingの値を取得または設定します。
        /// <summary>
        /// ContentTransferEncodingの値を取得または設定します。
        /// </summary>
        public TransferEncoding ContentTransferEncoding
        {
            get { return MailParser.ToTransferEncoding(this["Content-Transfer-Encoding"]); }
            set { this["Content-Transfer-Encoding"] = MailParser.ToTransferEncoding(value); }
        }
        /// CharSetの値を取得します。
        /// <summary>
        /// CharSetの値を取得します。
        /// </summary>
        public String CharSet
        {
            get { return this.ContentEncoding.HeaderName; }
        }
        /// ヘッダーのコレクションを取得します。
        /// <summary>
        /// ヘッダーのコレクションを取得します。
        /// </summary>
        public List<Field> Header
        {
            get { return this._Header; }
        }
        /// ヘッダー部分のフィールドの値をデコードするかどうかを示す値を取得します。
        /// <summary>
        /// ヘッダー部分のフィールドの値をデコードするかどうかを示す値を取得します。
        /// </summary>
        public Boolean DecodeHeaderText
        {
            get { return this._DecodeHeaderText; }
            set { this._DecodeHeaderText = value; }
        }
		/// Header部分のデータを取得します。
		/// <summary>
		/// Header部分のデータを取得します。
		/// </summary>
		protected String HeaderData
		{
			get { return this._HeaderData; }
			set { this._HeaderData = value; }
		}
		/// Body部分のデータを取得します。
        /// <summary>
        /// Body部分のデータを取得します。
        /// </summary>
        protected String BodyData
        {
            get { return this._BodyData; }
            set { this._BodyData = value; }
        }
        /// このインスタンスを生成するときに使用した文字列データを取得します。
        /// <summary>
        /// Get text data used to create this instance.
        /// このインスタンスを生成するときに使用した文字列データを取得します。
        /// </summary>
        public String Data
        {
            get { return this._Data; }
            protected set { this._Data = value; }
        }
        static InternetTextMessage()
		{
			InternetTextMessage.Initialize();
		}
		private static void Initialize()
		{
			RegexList.ContentEncodingCharset.Add(new Regex(".*charset ?= ?[\"]*(?<Value>[^\";]*)[;\n\r]", RegexOptions.IgnoreCase));
			RegexList.ContentEncodingCharset.Add(new Regex(".*charset ?= ?[\"]*(?<Value>[^\"]*).*", RegexOptions.IgnoreCase));
		}
		/// <summary>
		/// 
		/// </summary>
        public InternetTextMessage()
        {
            this.Initialize("");
        }
		/// <summary>
		/// 
		/// </summary>
		/// <param name="text"></param>
        public InternetTextMessage(String text)
        {
            this.Initialize(text);
        }
        /// 初期化処理を行います。
        /// <summary>
        /// 初期化処理を行います。
        /// </summary>
        /// <param name="text"></param>
        private void Initialize(String text)
		{
			this._Header = new List<Field>();
            
            this.Date = DateTime.Now;
            this._Header.Add(new Field("From", ""));
            this._Header.Add(new Field("Subject", ""));
            this.ContentType.Value = "text/plain";
            this.ContentTransferEncoding = TransferEncoding.SevenBit;
            this.ContentDisposition.Value = "inline";
            this.SetDefaultContentEncoding();

            this.Parse(text);
        }
        /// 既定のContent-Encodingの値をセットします。
        /// <summary>
        /// 既定のContent-Encodingの値をセットします。
        /// </summary>
        private void SetDefaultContentEncoding()
        {
            if (System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName == "ja")
            {
                this.ContentEncoding = Encoding.GetEncoding("iso-2022-jp");
            }
        }
        /// テキストを解析します。
        /// <summary>
        /// テキストを解析します。
        /// </summary>
        /// <param name="text"></param>
        protected void Parse(String text)
        {
            StringReader sr = null;
            //複数行フィールドの場合の2行目以降の行のリスト
            List<String> l = new List<String>();
            String CurrentLine = "";
            String FirstLine = "";
            Boolean IsConcating = false;
            Int32 c = 0;
			StringBuilder sb = new StringBuilder(512);

            using (sr = new StringReader(text))
            {
                while (true)
                {
                    CurrentLine = sr.ReadLine();
					sb.Append(CurrentLine);
					sb.Append(MailParser.NewLine);
					if (IsConcating == true)
                    {
                        l.Add(CurrentLine);
                    }
                    else
                    {
                        l.Clear();
                        FirstLine = CurrentLine;
                        //ヘッダーとボディ部の区切り行かどうかチェック
                        if (FirstLine == "")
                        {
							_HeaderData = sb.ToString();
							//以降のデータはBody部のデータ
							sb = new StringBuilder(text.Length - _HeaderData.Length);
                            while (true)
                            {
                                CurrentLine = sr.ReadLine();
                                if (CurrentLine == null) { break; }
                                if (CurrentLine == ".") { break; }
                                if (CurrentLine.StartsWith("..") == true)
                                { CurrentLine = CurrentLine.Substring(1, CurrentLine.Length - 1); }
                                sb.Append(CurrentLine);
                                if (sr.Peek() == -1) { break; }
                                //Not append new line char after last line
                                //最終行には改行コードを追加しない
                                sb.Append(MailParser.NewLine);
                            }
                            this.BodyData = sb.ToString();
                            return;
                        }
                    }
                    //次の行の先頭の文字を取得
                    c = sr.Peek();
                    //次の行がなかったら終了
					if (c == -1)
					{
						_HeaderData = sb.ToString();
						break;
					}
                    //次の行の先頭の文字がタブ文字または半角スペースの場合、複数行のフィールドとして連結する
                    if (c == 9 || c == 32)
                    {
                        IsConcating = true;
                        continue;
                    }
                    else
                    {
                        IsConcating = false;
                        this.ParseHeaderField(FirstLine, l);
                        l.Clear();
                        IsConcating = false;
                    }
                }
            }
        }
        /// 行の文字列を解析し、フィールドのインスタンスを生成します。
        /// <summary>
        /// 行の文字列を解析し、フィールドのインスタンスを生成します。
        /// </summary>
        /// <param name="line"></param>
        /// <param name="lines"></param>
        /// <returns></returns>
        private void ParseHeaderField(String line, List<String> lines)
        {
            Match m = RegexList.HeaderParse.Match(line);
            Match m1 = null;
            Regex rx = RegexList.HeaderParse1;
            Field f = null;
            List<String> l = lines;
			Int32 size = 0;
			for (int i = 0; i < lines.Count; i++)
			{
				size += line.Length;
			}
            StringBuilder sb = new StringBuilder(size);

            if (String.IsNullOrEmpty(m.Groups["key"].Value) == false)
            {
                m1 = rx.Match(m.Groups["value"].Value);
                if (m.Groups["key"].Value.ToLower() == "content-type" ||
                    m.Groups["key"].Value.ToLower() == "content-disposition")
                {
                    sb.Append(line);
                    for (int i = 0; i < l.Count; i++)
                    {
                        sb.Append(l[i].TrimStart('\t'));
                    }
                    this.ParseContentEncoding(sb.ToString());

                    if (m.Groups["key"].Value.ToLower() == "content-type")
                    {
                        MailParser.ParseContentType(this.ContentType, sb.ToString());
                        this.ContentType.Value = m1.Groups["value"].Value;
                    }
                    else if (m.Groups["key"].Value.ToLower() == "content-disposition")
                    {
                        MailParser.ParseContentDisposition(this.ContentDisposition, sb.ToString());
                        this.ContentDisposition.Value = m1.Groups["value"].Value;
                    }
                }
                else
                {
                    f = Field.FindField(this._Header, m.Groups["key"].Value);
                    if (f == null)
                    {
                        f = new Field(m.Groups["key"].Value, m.Groups["value"].Value);
                        this.Header.Add(f);
                    }
                    else
                    {
                        f.Value = m.Groups["value"].Value;
                    }
                    for (int i = 0; i < l.Count; i++)
                    {
                        f.Value += l[i].TrimStart('\t');
                    }
                }
            }
        }
        /// Content-Encodingの解析を行います。
        /// <summary>
        /// Content-Encodingの解析を行います。
        /// </summary>
        /// <param name="line"></param>
        private void ParseContentEncoding(String line)
        {
            Match m = null;

            //charset=???;
            foreach (Regex rx in RegexList.ContentEncodingCharset)
            {
                m = rx.Match(line);
                if (String.IsNullOrEmpty(m.Groups["Value"].Value) == false)
                {
                    this._ContentEncoding = MailParser.GetEncoding(m.Groups["Value"].Value, this.ContentEncoding);
                    break;
                }
            }
        }
        /// バイナリデータをデコードして指定した物理パスに出力します。
        /// <summary>
        /// Decode binary data and output as file to specify file path.
        /// バイナリデータをデコードして指定した物理パスに出力します。
        /// </summary>
        /// <param name="filePath"></param>
        public void DecodeData(String filePath)
        {
            using (var stm = new FileStream(filePath, FileMode.Create))
            {
                this.DecodeData(stm, true);
            }
        }
        /// バイナリデータをデコードして指定したストリームに出力します。
        /// <summary>
        /// Decode binary data and output to specify stream.
        /// バイナリデータをデコードして指定したストリームに出力します。
        /// </summary>
        /// <param name="stream">書込み先のストリームオブジェクトです。</param>
        /// <param name="isClose">ストリームに書き込んだあとにストリームを閉じるかどうかを示す値を設定します。</param>
        public void DecodeData(Stream stream, Boolean isClose)
        {
            Byte[] bb = null;
            BinaryWriter sw = null;

            if (String.IsNullOrEmpty(this.ContentDisposition.Value) == true)
            { return; }

            if (this.ContentTransferEncoding == TransferEncoding.Base64)
            {
                bb = Convert.FromBase64String(this.BodyData.Replace("\n", "").Replace("\r", ""));
            }
            else if (this.ContentTransferEncoding == TransferEncoding.QuotedPrintable)
            {
                bb = MailParser.FromQuotedPrintableText(this.BodyData);
            }
            else if (this.ContentTransferEncoding == TransferEncoding.SevenBit)
            {
                bb = Encoding.ASCII.GetBytes(this.BodyData);
            }
            try
            {
                sw = new BinaryWriter(stream);
                sw.Write(bb);
                sw.Flush();
            }
            finally
            {
                if (isClose == true)
                {
                    sw.Close();
                }
            }
        }
        /// キーと値のセットで構成されるフィールドを表すクラスです。
        /// <summary>
        /// キーと値のセットで構成されるフィールドを表すクラスです。
        /// RFC822で定義されます。
        /// </summary>
        public class Field
        {
            private String _Key = "";
            private String _Value = "";
			/// <summary>
			/// 
			/// </summary>
            public String Key
            {
                get { return this._Key ?? ""; }
                set { this._Key = value; }
            }
			/// <summary>
			/// 
			/// </summary>
            public String Value
            {
                get { return this._Value ?? ""; }
                set { this._Value = value; }
            }
			/// <summary>
			/// 
			/// </summary>
			/// <param name="key"></param>
			/// <param name="value"></param>
            public Field(String key, String value)
            {
                this._Key = key;
                this._Value = value;
            }
			/// <summary>
			/// 
			/// </summary>
			/// <param name="fields"></param>
			/// <param name="key"></param>
			/// <returns></returns>
            public static Field FindField(List<Field> fields, String key)
            {
                List<Field> l = fields.FindAll(delegate(Field f) { return String.Equals(f.Key, key, StringComparison.InvariantCultureIgnoreCase); });
                if (l.Count > 0)
                {
                    return l[l.Count - 1];
                }
                return null;
            }
			/// <summary>
			/// 
			/// </summary>
			/// <returns></returns>
            public override string ToString()
            {
                return String.Format("{0}: {1}", this.Key, this.Value);
            }
        }
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Higty
Web Developer
Japan Japan
I'm Working at Software Company in Tokyo.

| Advertise | Privacy | Mobile
Web03 | 2.8.140821.2 | Last Updated 30 Jul 2012
Article Copyright 2012 by Higty
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid