⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 pop3mimeclient.cs

📁 《征服Ajax》原书的例题源码
💻 CS
📖 第 1 页 / 共 2 页
字号:
// POP3 Email Client
// =================
//
// copyright by Peter Huber, Singapore, 2006
// this code is provided as is, bugs are probable, free for any use at own risk, no 
// responsibility accepted. All rights, title and interest in and to the accompanying content retained.  :-)
//
// based on Standard for ARPA Internet Text Messages, http://rfc.net/rfc822.html
// based on MIME Standard,  Internet Message Bodies, http://rfc.net/rfc2045.html
// based on MIME Standard, Media Types, http://rfc.net/rfc2046.html
// based on QuotedPrintable Class from ASP emporium, http://www.aspemporium.com/classes.aspx?cid=6

// based on MIME Standard, E-mail Encapsulation of HTML (MHTML), http://rfc.net/rfc2110.html
// based on MIME Standard, Multipart/Related Content-type, http://rfc.net/rfc2112.html


// ?? RFC 2557       MIME Encapsulation of Aggregate Documents http://rfc.net/rfc2557.html

using System;
using System.Collections.Generic;
using System.Runtime;
using System.Globalization;
using System.IO;
using System.Net.Mail;
using System.Net.Mime;
using System.Text;


namespace Mail {
  // POP3 Client Email
  // =================

  /// <summary>
  /// Reads POP3 / MIME based emails 
  /// </summary>
  public class Pop3MimeClient:Pop3MailClient {

    //character array 'constants' used for analysing POP3 / MIME
    //----------------------------------------------------------
    private static char[] BlankChars        = { ' ' };
    private static char[] BracketChars      = { '(', ')' };
    private static char[] ColonChars        = { ':' };
    private static char[] CommaChars        = { ',' };
    private static char[] EqualChars        = { '=' };
    private static char[] ForwardSlashChars = { '/' };
    private static char[] SemiColonChars    = { ';' };

    private static char[] WhiteSpaceChars   = { ' ', '\t' };
    private static char[] NonValueChars     = { '"', '(', ')' };


    //Help for debugging
    //------------------
    /// <summary>
    /// used for debugging. Collects all unknown header lines for all (!) emails received
    /// </summary>
    public static bool isCollectUnknowHeaderLines = true;
    /// <summary>
    /// list of all unknown header lines received, for all (!) emails 
    /// </summary>
    public static List<string> AllUnknowHeaderLines = new List<string>();


    /// <summary>
    /// Set this flag, if you would like to get also the email in the raw US-ASCII format
    /// as received.
    /// Good for debugging, but takes quiet some space.
    /// </summary>
    public bool IsCollectRawEmail {
      get { return isGetRawEmail; }
      set { isGetRawEmail = value; }
    }
    private bool isGetRawEmail = false;


    // Pop3MimeClient Constructor
    //---------------------------

    /// <summary>
    /// constructor
    /// </summary>
    public Pop3MimeClient(
      string PopServer, 
      int Port,
      bool useSSL,
      string Username,
	  string Password)
		: base(PopServer, Port, useSSL, Username, Password)
    { }


    /// <summary>
    /// Gets 1 email from POP3 server and processes it.
    /// </summary>
    /// <param name="MessageNo">Email Id to be fetched from POP3 server</param>
    /// <param name="Message">decoded email</param>
    /// <returns>false: no email received or email not properly formatted</returns>
    public bool GetEmail(int MessageNo, out RxMailMessage Message) {
      Message = null;

      //request email, send RETRieve command to POP3 server
      if (!SendRetrCommand(MessageNo)) {
        return false;
      }

      //prepare message, set defaults as specified in RFC 2046
      //although they get normally overwritten, we have to make sure there are at least defaults
      Message = new RxMailMessage();
      Message.ContentTransferEncoding = TransferEncoding.SevenBit;
      Message.TransferType = "7bit";
      this.messageNo = MessageNo;

      //raw email tracing
      if (isGetRawEmail) {
        isTraceRawEmail = true;
        if (RawEmailSB==null){
          RawEmailSB = new StringBuilder(100000);
        }else{
          RawEmailSB.Length = 0;
        }
      }

      //convert received email into RxMailMessage
      MimeEntityReturnCode MessageMimeReturnCode = ProcessMimeEntity(Message, "");
      if (isGetRawEmail) {
        //add raw email version to message
        Message.RawContent = RawEmailSB.ToString();
        isTraceRawEmail = false;
      }

      if (MessageMimeReturnCode==MimeEntityReturnCode.bodyComplete ||
        MessageMimeReturnCode==MimeEntityReturnCode.parentBoundaryEndFound) {
        TraceFrom("email with {0} body chars received", Message.Body.Length);
        return true;
      }
      return false;
    }


    private int messageNo;

    private void callGetEmailWarning(string warningText, params object[] warningParameters) {
      string warningString;
      try {
        warningString = string.Format(warningText, warningParameters);
      } catch (Exception) {
        //some strange email address can give string.Format() a problem
        warningString = warningText;
      }
      CallWarning("GetEmail", "", "Problem EmailNo {0}: " + warningString, messageNo);
    }


    /// <summary>
    /// indicates the reason how a MIME entity processing has terminated
    /// </summary>
    private enum MimeEntityReturnCode {
      undefined = 0, //meaning like null
      bodyComplete, //end of message line found
      parentBoundaryStartFound,
      parentBoundaryEndFound,
      problem //received message doesn't follow MIME specification
    }


    //buffer used by every ProcessMimeEntity() to store  MIME entity
    StringBuilder MimeEntitySB = new StringBuilder(100000);

    /// <summary>
    /// Process a MIME entity
    /// 
    /// A MIME entity consists of header and body.
    /// Separator lines in the body might mark children MIME entities
    /// </summary>
    private MimeEntityReturnCode ProcessMimeEntity(RxMailMessage message, string parentBoundaryStart) {
      bool hasParentBoundary = parentBoundaryStart.Length>0;
      string parentBoundaryEnd = parentBoundaryStart + "--";
      MimeEntityReturnCode boundaryMimeReturnCode;

      //some format fields are inherited from parent, only the default for
      //ContentType needs to be set here, otherwise the boundary parameter would be
      //inherited too !
      message.SetContentTypeFields("text/plain; charset=us-ascii");

      //get header
      //----------
      string completeHeaderField = null;     //consists of one start line and possibly several continuation lines
      string response;

      // read header lines until empty line is found (end of header)
      while (true) {
        if (!readMultiLine(out response)) {
          //POP3 server has not send any more lines
          callGetEmailWarning("incomplete MIME entity header received");
          //empty this message
          while (readMultiLine(out response)) { }
          System.Diagnostics.Debugger.Break(); //didn't have a sample email to test this
          return MimeEntityReturnCode.problem;
        }
          
        if (response.Length<1) {
          //empty line found => end of header
          if (completeHeaderField!=null) {
            ProcessHeaderField(message, completeHeaderField);
          }else{
            //there was only an empty header.
          }
          break;
        }

        //check if there is a parent boundary in the header (wrong format!)
        if (hasParentBoundary && parentBoundaryFound(response, parentBoundaryStart, parentBoundaryEnd, out boundaryMimeReturnCode)){
          callGetEmailWarning("MIME entity header  prematurely ended by parent boundary");
          //empty this message
          while (readMultiLine(out response)) { }
          System.Diagnostics.Debugger.Break(); //didn't have a sample email to test this
          return boundaryMimeReturnCode;
        }
        //read header field
        //one header field can extend over one start line and multiple continuation lines
        //a continuation line starts with at least 1 blank (' ') or tab
        if (response[0]==' ' || response[0]=='\t') {
          //continuation line found.
          if (completeHeaderField==null){
            callGetEmailWarning("Email header starts with continuation line");
            //empty this message
            while (readMultiLine(out response)) { }
            System.Diagnostics.Debugger.Break(); //didn't have a sample email to test this
            return MimeEntityReturnCode.problem;
          }else {
            // append space, if needed, and continuation line
            if (completeHeaderField[completeHeaderField.Length-1]!=' ') {
              //previous line did not end with a whitespace
              //need to replace CRLF with a ' '
              completeHeaderField += ' ' + response.TrimStart(WhiteSpaceChars);
            } else {
              //previous line did end with a whitespace
              completeHeaderField += response.TrimStart(WhiteSpaceChars);
            }
          }

        } else {
          //a new header field line found
          if (completeHeaderField==null) {
            //very first field, just copy it and then check for continuation lines
            completeHeaderField = response;
          } else {
            //new header line found
            ProcessHeaderField(message, completeHeaderField);

            //save the beginning of the next line
            completeHeaderField = response;
          }
        }
      }//end while read header lines


      //process body
      //------------

      MimeEntitySB.Length=0;  //empty StringBuilder. For speed reasons, reuse StringBuilder defined as member of class
      string BoundaryDelimiterLineStart = null;
      bool isBoundaryDefined = false;
      if (message.ContentType.Boundary!=null) {
        isBoundaryDefined = true;
        BoundaryDelimiterLineStart = "--" + message.ContentType.Boundary;
      }
      //prepare return code for the case there is no boundary in the body
      boundaryMimeReturnCode = MimeEntityReturnCode.bodyComplete;

      //read body lines
      while (readMultiLine(out response)) {
        //check if there is a boundary line from this entity itself in the body
        if (isBoundaryDefined && response.TrimEnd()==BoundaryDelimiterLineStart) {
          //boundary line found.
          //stop the processing here and start a delimited body processing
          return ProcessDelimitedBody(message, BoundaryDelimiterLineStart, parentBoundaryStart, parentBoundaryEnd);
        }

        //check if there is a parent boundary in the body
        if (hasParentBoundary && 
          parentBoundaryFound(response, parentBoundaryStart, parentBoundaryEnd, out boundaryMimeReturnCode)) {
          //a parent boundary is found. Decode the content of the body received so far, then end this MIME entity
          //note that boundaryMimeReturnCode is set here, but used in the return statement
          break;
        }

        //process next line
        MimeEntitySB.Append(response + CRLF);
      }

      //a complete MIME body read
      //convert received US ASCII characters to .NET string (Unicode)
      string TransferEncodedMessage = MimeEntitySB.ToString();
      switch (message.ContentTransferEncoding) {
      case TransferEncoding.SevenBit:
        //nothing to do
        saveMessageBody(message, TransferEncodedMessage);
        break;

      case TransferEncoding.Base64:
        //convert base 64 -> byte[]
        byte[] bodyBytes = System.Convert.FromBase64String(TransferEncodedMessage);
        message.ContentStream = new MemoryStream(bodyBytes, false);

        if (message.MediaMainType=="text") {
          //convert byte[] -> string
          message.Body = DecodeByteArryToString(bodyBytes, message.BodyEncoding);

        } else if (message.MediaMainType=="image" || message.MediaMainType=="application") {
          SaveAttachment(message);
        }
        break;

      case TransferEncoding.QuotedPrintable:
        saveMessageBody(message, QuotedPrintable.Decode(TransferEncodedMessage));
        break;

      default:
        saveMessageBody(message, TransferEncodedMessage);
        //no need to raise a warning here, the warning was done when analising the header
        break;
      }
      return boundaryMimeReturnCode;
    }


    /// <summary>
    /// Check if the response line received is a parent boundary 
    /// </summary>
    private bool parentBoundaryFound(string response, string parentBoundaryStart, string parentBoundaryEnd, out MimeEntityReturnCode boundaryMimeReturnCode) {
      boundaryMimeReturnCode = MimeEntityReturnCode.undefined;
      if (response==null || response.Length<2 || response[0]!='-' || response[1]!='-') {
        //quick test: reponse doesn't start with "--", so cannot be a separator line
        return false;
      }
      if (response==parentBoundaryStart) {
        boundaryMimeReturnCode = MimeEntityReturnCode.parentBoundaryStartFound;
        return true;
      } else if (response==parentBoundaryEnd) {
        boundaryMimeReturnCode = MimeEntityReturnCode.parentBoundaryEndFound;
        return true;
      }
      return false;
    }


    /// <summary>
    /// Convert one MIME header field and update message accordingly
    /// </summary>
    private void ProcessHeaderField(RxMailMessage message, string headerField) {
      string headerLineType;
      string headerLineContent;
      int separatorPosition = headerField.IndexOf(':');
      if (separatorPosition<1) {
        // header field type not found, skip this line
        callGetEmailWarning("character ':' missing in header format field: '{0}'", headerField);
      } else {

        //process header field type
        headerLineType = headerField.Substring(0, separatorPosition).ToLowerInvariant();
        headerLineContent = headerField.Substring(separatorPosition+1).Trim(WhiteSpaceChars);
        if (headerLineType==""|| headerLineContent=="") {
          //1 of the 2 parts missing, drop the line
          return;
        }
        // add header line to headers
        message.Headers.Add(headerLineType, headerLineContent);

        //interpret if possible
        switch (headerLineType) {
          case "bcc":
            AddMailAddresses(headerLineContent, message.Bcc);
            break;
          case "cc":
            AddMailAddresses(headerLineContent, message.CC);
            break;
          case "content-description":
            message.ContentDescription = headerLineContent;
            break;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -