POP3Client
Frank Dzaebel, erstellt am: 1.3.2006, zuletzt geändert:  28.6.2006
Kategorie: POP3, .NET-Version: 1.1, [Download]

Eine kleine Library zum Zugriff auf POP3. Andere Links:
A POP3 Client in C# .NET
Retrieve Mail From a POP3 Server Using C#
Google-Suche: POP3 C#
Versenden und Empfangen von E-Mail

namespace POP3Client
{
  using System.IO;
  using System.Net;
  using System.Net.Sockets;
  using System.Text;
  using System.Diagnostics;
  using System;
  using System.Windows.Forms;
  using System.Text.RegularExpressions;


  // Ein Delegat-Typ um Status-Änderungs-Ereignisse anzuhängen
  public delegate void StatusÄnderungsHandler(string NeuerStatus);
  // Ein Delegat-Typ um WarteStatus-Änderungs-Ereignisse anzuhängen
  public delegate void WarteStatusÄnderungsHandler(WStatus NeuerWarteStatus);

  #region VStatus, WStatus
  /// <summary>Verbindungsstatus zum POP3-Server</summary>
  public enum VStatus
  {
    /// <summary>Die Verbindung zum PO3Server ist 'getrennt'.
    /// Die Reihenfolge ist GETRENNT, AUTHORISIERUNG, TRANSAKTION, MODIFIKATION. </summary>
    GETRENNT = 0,
    /// <summary>Die Verbindung zum PO3Server ist im Zustand 'Authorisierung des Benutzers'. 
    /// Die Reihenfolge ist GETRENNT, AUTHORISIERUNG, TRANSAKTION, MODIFIKATION. </summary>
    AUTHORISIERUNG = 1,
    /// <summary>Die Verbindung zum PO3Server ist im Zustand 'TRANSAKTION'.
    /// Die Reihenfolge ist GETRENNT, AUTHORISIERUNG, TRANSAKTION, MODIFIKATION. </summary>
    TRANSAKTION = 2,
    /// <summary>Die Verbindung zum PO3Server ist im Zustand 'MODIFIKATION'.
    /// Die Reihenfolge ist GETRENNT, AUTHORISIERUNG, TRANSAKTION, MODIFIKATION. </summary>
    MODIFIKATION = 3
  };

  /// <summary>WarteStatus. WarteStatus der Verbindung zum POP3-Server (für einen Client-Progressbar). Bei Beginn eines Warte-Vorganges auf WStatus.BEGONNEN, bei Ende auf WStatus.BEENDET</summary>
  public enum WStatus
  {
    /// <summary>Der Pop3Server wartet auf die Beendigung eine Anfrage</summary>
    BEGONNEN = 0,
    /// <summary>Das Warten auf die Beendigung eine Anfrage an den POP3Server ist beendet.</summary>
    BEENDET = 1
  };
  #endregion

  public class POP3client
  {
    #region Öffentliche globale Variablen
    /// <summary>Port (Bei Mails standardmäßig = 110). Die Anschlussnummer des Remotehosts, mit dem eine Verbindung hergestellt werden soll. </summary>
    public int SERVER_PORT = 110;

    /// <summary>Globaler Benutzername</summary>
    public string user;

    /// <summary>Globales Passwort</summary>
    public string pwd;

    /// <summary>Globaler Hostname</summary>
    public string pop;

    /// <summary>Globaler Fehler-Status</summary>
    public bool Fehler;
    #endregion

    #region Private globale Variablen

    /// <summary>Membervariable zu [Status]. Status der Verbindung. Anfangs VStatus.GETRENNT</summary>
    private VStatus m_Status = VStatus.GETRENNT;
    /// <summary>Membervariable zu [WarteStatus]. WarteStatus der Verbindung (für einen Client-Progressbar). Bei Beginn eines Warte-Vorganges auf WStatus.BEGONNEN, bei Ende auf WStatus.BEENDET</summary>
    private WStatus m_WarteStatus = WStatus.BEENDET;

    private TcpClient Server;
    private NetworkStream NetStrm;
    private StreamReader RdStrm;
    private string Data;
    private byte[] szData;
    private string CRLF = "\r\n";
    private Regex RegX;
    #endregion

    public POP3client()
    {
    }

    public POP3client(string pop_server,string user_name,string password)
    {
      pop = pop_server; user = user_name; pwd = password;
    }


    // Ein Ereignis, das der Client benutzen kann um Änderungen am Status der Verbindung zu behandeln.
    public event StatusÄnderungsHandler StatusGeändert;
    // Ein Ereignis, das der Client benutzen kann um Änderungen am WarteStatus einer mail-Funktion z.B. über eine ProgressBar darzustellen.
    public event WarteStatusÄnderungsHandler WarteStatusGeändert;

    #region Connect() - Methode
    public string connect(string pop_server)
    {
      pop = pop_server; return (connect());
    }

    /// <summary>Verbindung zum 'pop_server' wird aufgebaut. </summary>
    /// <returns>Beispiel: "+OK POP3 server ready <1896.697170952@dbc.mtview.ca.us>"</returns>
    public string connect()
    {
      WarteStatus = WStatus.BEGONNEN;
      try
      {  // Initialisierung
        Server = new TcpClient(pop,SERVER_PORT);
        NetStrm = Server.GetStream();
        RdStrm = new StreamReader(Server.GetStream());

        //The pop session is now in the AUTHORISIERUNG Status
        Status = VStatus.AUTHORISIERUNG;
        return (RdStrm.ReadLine());
      }
      catch (Exception Ex)
      {
        return ("Fehler: " + Ex.ToString());
      }
      finally { WarteStatus = WStatus.BEENDET; }
    }
    #endregion

    #region Private Methoden

    /// <summary>Wenn (Status != stat) ist, wird einen Fehlerstring zurückgegeben, der besagt, dass der VStatus 'stat' im augenblicklichen Zustand nicht erlaubt ist </summary>
    /// <param name="stat">Augenblicklicher VStatus</param>
    /// <returns>Fehlerstring</returns>
    private bool Ist_Status(VStatus stat,ref string Meldung)
    {
      if (Status != stat)
      {
        StackFrame CallStack = new StackFrame(1,true);
        Meldung = String.Format(
          "Bei Aufruf von Kommando \"{0}\" (Zeile {1}) ist der Zustand [{2}] nicht erlaubt!\r\nMomentaner Zustand: [{3}]",
          CallStack.GetMethod().Name,CallStack.GetFileLineNumber(),Enum.GetName(typeof(VStatus),stat),Enum.GetName(typeof(VStatus),Status));
      }
      else Meldung = "";
      return (Status == stat);
    }

    private string disconnect()
    {
      string temp = "Verbindung getrennt.";
      if (Status != VStatus.GETRENNT)
      {  //Verbindung trennen
        NetStrm.Close(); RdStrm.Close();
        Status = VStatus.GETRENNT;
      }
      else
      {
        temp = "Nicht verbunden.";
      }
      return (temp);
    }

    private void issue_command(string command)
    {
      //Sendet das Kommando zum POP Server. 
      Data = command + CRLF;
      szData = System.Text.Encoding.ASCII.GetBytes(Data.ToCharArray());
      NetStrm.Write(szData,0,szData.Length);
    }

    private string read_single_line_response()
    {
      //read the response of the pop server. 
      string temp;
      try
      {
        temp = RdStrm.ReadLine();
        was_pop_error(temp);
        return (temp);
      }
      catch (Exception Ex)
      {
        Fehler = true;
        return ("Fehler in read_single_line_response(): " + Ex.ToString());
      }
    }


    private string read_multi_line_response()
    {
      //read the response of the pop server. 
      string temp = "";
      string szTemp;

      try
      {
        szTemp = RdStrm.ReadLine();
        was_pop_error(szTemp);
        if (!Fehler)
        {
          while (szTemp != ".")
          {
            temp += szTemp + CRLF;
            szTemp = RdStrm.ReadLine();
          }
        }
        else
        {
          temp = szTemp;
        }
        return (temp);
      }
      catch (InvalidOperationException err)
      {
        return ("Fehler in read_multi_line_response(): " + err.ToString());
      }
    }

    /// <summary>Setzt Fehler auf true, wenn der POP-Server einen Fehler während des letzten Client-Befehls entdeckt. </summary>
    /// <param name="response"></param>
    private void was_pop_error(string response)
    {
      if (response.StartsWith("-"))
      {  ///<comment> Wenn der erste Buchstabe der Antwort ein "-" ist, dann hat der POP-Server einen Fehler während des letzten Client-Befehls entdeckt <comment>
        Fehler = true;
      }
      else Fehler = false; //Erfolg
    }

    #endregion

    #region POP commands
    /// <summary>DELE löscht eine Meldung. Sie wird tatsächlich aber erst gelöscht, wenn das QUIT-Kommando aufgerufen wird. Deswegen kann man auch eine Meldung undeleten mit RSET (z.B. bei einem Fehler).</summary>
    /// <param name="msg_number">MeldungsNr die zu löschen ist</param>
    /// <returns>Beispiel1: "DELE 1" -> "S: +OK message 1 deleted" 
    /// Beispiel2: "DELE 2" -> "S: -ERR message 2 already deleted" </returns>
    public string DELE(int msg_number)
    {
      string temp = "";
      if (Ist_Status(VStatus.TRANSAKTION,ref temp))
      {
        issue_command("DELE " + msg_number.ToString());
        temp = read_single_line_response();
      }
      return (temp);
    }

    /// <summary>
    /// LIST listet Meldungsnummer(n) mit deren Byte-Größe der Mailbox auf. 
    /// Das tut es, indem es das "Standard-POP3-Multiline-Antwortformat" nutzt. 
    /// Hier ein Beispiel:
    /// LIST
    /// +OK Mailbox contents follow
    /// 1 7774
    /// 2 513
    /// 3 10493
    /// .
    /// Die letzte Zeile ist eine einzelne 'Punkt-Zeile', was Standard bei allgemeinen Mail-Protokollen ist. 
    /// Jede Zeile enthält die MeldungsNr gefolgt von der Größe in Bytes.
    /// Wenn man eine MeldungsNr löscht (DELE), würde sie hier nicht erscheinen, deswegen tappen Sie nicht in die Falle,
    /// dass Nummern sequentiell sind und deswegen ignoriert werden könnten!
    /// </summary>
    public string LIST()
    {
      string temp = "";
      if (Ist_Status(VStatus.TRANSAKTION,ref temp))
      {
        issue_command("LIST");
        temp = read_multi_line_response();
      }
      return (temp);
    }

    /// <summary>
    /// LIST listet Meldungsnummer(n) mit deren Byte-Größe der Mailbox auf. 
    /// Das tut es, indem es das "Standard-POP3-Multiline-Antwortformat" nutzt. 
    /// Hier ein Beispiel:
    /// LIST
    /// +OK Mailbox contents follow
    /// 1 7774
    /// 2 513
    /// 3 10493
    /// .
    /// Die letzte Zeile ist eine einzelne 'Punkt-Zeile', was Standard bei allgemeinen Mail-Protokollen ist. 
    /// Jede Zeile enthält die MeldungsNr gefolgt von der Größe in Bytes.
    /// Wenn man eine MeldungsNr löscht (DELE), würde sie hier nicht erscheinen, deswegen tappen Sie nicht in die Falle,
    /// dass Nummern sequentiell sind und deswegen ignoriert werden könnten!
    /// </summary>
    public string LIST(int msg_number)
    {
      string temp = "";

      if (Ist_Status(VStatus.TRANSAKTION,ref temp))
      {
        issue_command("LIST " + msg_number.ToString());
        temp = read_single_line_response();  //when the message number is supplied, expect a single line response
      }
      return (temp);
    }

    /// <summary>Der POP3Server tut nichts, außer, dass er eine positive Antwort ("+OK") ausgibt.</summary>
    /// <returns></returns>
    public string NOOP()
    {
      string temp = "";
      if (Ist_Status(VStatus.TRANSAKTION,ref temp))
      {
        issue_command("NOOP");
        temp = read_single_line_response();
      }
      return (temp);

    }
    /// <summary>Dem Pop3Client wird das Passwort mitgeteilt. </summary>
    /// <returns></returns>
    public string PASS()
    {
      string temp = "";
      if (Ist_Status(VStatus.AUTHORISIERUNG,ref temp))
      {
        if (pwd != null)
        {
          issue_command("PASS " + pwd);
          temp = read_single_line_response();

          if (!Fehler)
          {
            //Übergang zu VStatus.TRANSAKTION
            Status = VStatus.TRANSAKTION;
          }
        }
        else
        {
          temp = "Kein Passwort gesetzt.";
        }
      }
      return (temp);
    }

    /// <summary>Dem Pop3Client wird das Passwort mitgeteilt.</summary>
    /// <param name="password">Mail-Passwort</param>
    /// <returns></returns>
    public string PASS(string password)
    {
      pwd = password;   //put the supplied password into the appropriate property
      return (PASS()); //call PASS() with no arguement
    }

    /// <summary>QUIT terminiert die Session und löscht jede mit DELE markierte Meldung.</summary>
    /// <returns>Beispiel: "+OK dewey POP3 server signing off (2 messages left)"</returns>
    public string QUIT()
    {
      //QUIT is valid in all pop states

      string temp;
      if (Status != VStatus.GETRENNT)
      {
        issue_command("QUIT");
        temp = read_single_line_response();
        temp += CRLF + disconnect();

      }
      else
      {
        temp = "Not Connected.";
      }
      return (temp);
    }

    /// <summary>RETR ruft eine Meldung vom Pop-Server ab. Nutzen Sie die MeldungsNr vom List-Kommando. Sie erhalten den realen Text der Meldung, Header-Zeilen gefolgt von einer Leerzeile gefolgt von den 'body'-Zeilen, gefolgt von einem Punkt auf einer einzelnen neuen Zeile. Enthält die Nachricht schon so eine Zeile, wird sie dubliziert. Der Client muß sie dann zurück setzen.</summary>
    public string RETR(int msg)
    {
      string temp = "";
      if (Ist_Status(VStatus.TRANSAKTION,ref temp))
      {
        // Retrieve: Abruf der Mail mit Nummer Mail Parameter
        issue_command("RETR " + msg.ToString());
        temp = read_multi_line_response();
      }
      return (temp);
    }

    /// <summary>Wenn Meldungen (durch DELE MedlungsNr) als gelöscht markiert wurden, werden diese Löschung(en) wieder aufgehoben. Der POP3 server antwortet z.B. mit "+OK maildrop has 2 messages (320 octets)". </summary>
    /// <returns></returns>
    public string RSET()
    {
      string temp = "";
      if (Ist_Status(VStatus.TRANSAKTION,ref temp))
      {
        issue_command("RSET");
        temp = read_single_line_response();
      }
      return (temp);
    }

    /// <summary> STAT antwortet einfach mit einer einzelnen Zeile, die aus zwei Nummern besteht: Die Anzahl von Meldungen in der MailBox, und der Gesamt-Größe der Meldungen in Bytes. Ähnlich ist das LIST Kommando</summary>
    /// <param name="Anzahl">Die Anzahl von Meldungen in der MailBox</param>
    /// <param name="Bytes">Gesamt-Größe der Meldungungen in Bytes</param>
    /// <returns>[string] Beispiel-Rückgabe: "+OK 23 1352"</returns>
    public string STAT(ref int Anzahl,ref int Bytes)
    {
      string temp = ""; Anzahl = 0; Bytes = 0;
      if (Ist_Status(VStatus.TRANSAKTION,ref temp))
      {
        issue_command("STAT");
        temp = read_single_line_response();
        if (!Fehler)
        {
          try //ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1031/cpgenref/html/cpconregularexpressionslanguageelements.htm
          {
            RegX = new Regex(@"\+OK\s*(?'Anzahl'\d+)\s*(?'Bytes'\d+)");
            Anzahl = int.Parse(RegX.Match(temp).Result("${Anzahl}"));
            Bytes = int.Parse(RegX.Match(temp).Result("${Bytes}"));
          }
          catch (Exception Ex)
          {
            temp = Ex.Message;
          }
        }
      }
      return (temp);
    }

    /// <summary>TOP gibt die Kopfzeilen der Meldung 'Msg' und 'Zeilen' Meldungszeilen des body's der Meldung zurück. Wenn 'Zeilen'=0 ist, erhält man natürlich nur die Kopfzeilen, was für für Filter-Zwecke benutzt wird, ohne gleich die ganze Meldung downzuloaden. TOP ist zwar ein optionales Kommando, aber die meisten POP-Server unterstützen es. </summary>
    /// <param name="Msg">MeldungsNr, deren Kopfzeilen erhalten werden sollen</param>
    /// <param name="Zeilen">Anzahl der Zeilen des body's der Meldung, die zusätzlich angefügt werden sollen.</param>
    /// <returns></returns>
    public string TOP(int Msg,int Zeilen)
    {
      string temp = "";
      if (Ist_Status(VStatus.TRANSAKTION,ref temp))
      {
        issue_command(String.Format("TOP {0} {1}",Msg.ToString(),Zeilen.ToString()));
        temp = read_multi_line_response();
      }
      return (temp);
    }

    /// <summary>Dem Pop3Client wird der Benutzername mitgeteilt. </summary>
    /// <returns></returns>
    public string USER()
    {
      string temp = "";
      if (Ist_Status(VStatus.AUTHORISIERUNG,ref temp))
      {
        if (user != null)
        {
          issue_command("USER " + user);
          temp = read_single_line_response();
        }
        else
        {
          temp = "Es wurde kein Benutzer definiert.";
        }
      }
      return (temp);
    }

    /// <summary>Dem Pop3Client wird der Benutzername mitgeteilt. </summary>
    /// <returns></returns>
    public string USER(string user_name)
    {
      user = user_name;
      return (USER());  //Aufruf: USER ohne Argumente
    }
    #endregion

    /// <summary>Globaler Verbindungs-Status </summary>
    public VStatus Status
    {
      get { return m_Status; }
      set
      {
        m_Status = value;
if (StatusGeändert != null) StatusGeändert(Enum.GetName(typeof(VStatus),m_Status)); //Ereignis auslösen } } /// <summary>Globaler Warte-Status [WStatus.BEGONNEN, BEENDET, KEIN_WARTEN] </summary> public WStatus WarteStatus { get { return m_WarteStatus; } set { m_WarteStatus = value;
if (WarteStatusGeändert != null) WarteStatusGeändert(m_WarteStatus); //Ereignis auslösen } } } }