SendMail.cls
上传用户:bcdefg2008
上传日期:2013-02-28
资源大小:144k
文件大小:96k
源码类别:

Email服务器

开发平台:

Visual Basic

  1. VERSION 1.0 CLASS
  2. BEGIN
  3.   MultiUse = -1  'True
  4.   Persistable = 0  'NotPersistable
  5.   DataBindingBehavior = 0  'vbNone
  6.   DataSourceBehavior  = 0  'vbNone
  7.   MTSTransactionMode  = 0  'NotAnMTSObject
  8. END
  9. Attribute VB_Name = "clsSendMail"
  10. Attribute VB_GlobalNameSpace = False
  11. Attribute VB_Creatable = True
  12. Attribute VB_PredeclaredId = False
  13. Attribute VB_Exposed = False
  14. Option Explicit
  15. Option Compare Text
  16. ' API Constants
  17. Private Const REG_SZ = 1&
  18. Private Const ERROR_SUCCESS     As Long = 0
  19. Private Const HKEY_CLASSES_ROOT = &H80000000
  20. Private Const WS_VERSION_REQD   As Long = &H101
  21. Private Const MIN_SOCKETS_REQD  As Long = 1
  22. Private Const DATA_SIZE = 32
  23. Private Const MAX_WSAD = 256
  24. Private Const MAX_WSAS = 128
  25. Private Const PING_TIMEOUT = 255
  26. Private Const TIME_ZONE_ID_UNKNOWN  As Long = 1
  27. Private Const TIME_ZONE_ID_STANDARD As Long = 1
  28. Private Const TIME_ZONE_ID_DAYLIGHT As Long = 2
  29. Private Const TIME_ZONE_ID_INVALID  As Long = &HFFFFFFFF
  30. ' Winsock API Type defs...
  31. Private Type ICMP_OPTIONS
  32.     Ttl                         As Byte
  33.     Tos                         As Byte
  34.     flags                       As Byte
  35.     OptionsSize                 As Byte
  36.     OptionsData                 As Long
  37. End Type
  38. Private Type ICMP_ECHO_REPLY
  39.     Address                     As Long
  40.     Status                      As Long
  41.     RoundTripTime               As Long
  42.     DataSize                    As Long
  43.     DataPointer                 As Long
  44.     options                     As ICMP_OPTIONS
  45.     Data                        As String * 250
  46. End Type
  47. Private Type HostEnt
  48.     hName                       As Long
  49.     hAliases                    As Long
  50.     hAddrType                   As Integer
  51.     hLen                        As Integer
  52.     hAddrList                   As Long
  53. End Type
  54. Private Type WSADATA
  55.     wVersion                    As Integer
  56.     wHighVersion                As Integer
  57.     szDescription(MAX_WSAD)     As Byte
  58.     szSystemStatus(MAX_WSAS)    As Byte
  59.     wMaxSockets                 As Integer
  60.     wMaxUDPDG                   As Integer
  61.     dwVendorInfo                As Long
  62. End Type
  63. ' SystemTime and TimeZone API Type defs...
  64. Private Type SYSTEMTIME
  65.     wYear                       As Integer
  66.     wMonth                      As Integer
  67.     wDayOfWeek                  As Integer
  68.     wDay                        As Integer
  69.     wHour                       As Integer
  70.     wMinute                     As Integer
  71.     wSecond                     As Integer
  72.     wMilliseconds               As Integer
  73. End Type
  74. Private Type TIME_ZONE_INFORMATION
  75.     Bias                        As Long
  76.     StandardName(63)            As Byte
  77.     StandardDate                As SYSTEMTIME
  78.     StandardBias                As Long
  79.     DaylightName(63)            As Byte
  80.     DaylightDate                As SYSTEMTIME
  81.     DaylightBias                As Long
  82. End Type
  83. ' Class Enum for host name string validation
  84. Public Enum VALIDATE_HOST_METHOD
  85.     VALIDATE_HOST_NONE = 0
  86.     VALIDATE_HOST_SYNTAX = 1
  87.     VALIDATE_HOST_PING = 2
  88.     VALIDATE_HOST_DNS = 3
  89. End Enum
  90. ' Class Enum for email address string validation
  91. Public Enum VALIDATE_METHOD
  92.     validate_none = 0
  93.     VALIDATE_SYNTAX = 1
  94. End Enum
  95. ' Class Enum for email encoding method
  96. Public Enum ENCODE_METHOD
  97.     MIME_ENCODE = 0
  98.     UU_ENCODE = 1
  99. End Enum
  100. ' Class Enum for mail priority
  101. Public Enum MAIL_PRIORITY
  102.     HIGH_PRIORITY = 1
  103.     NORMAL_PRIORITY = 3
  104.     LOW_PRIORITY = 5
  105. End Enum
  106. ' Structure to hold mail elements
  107. Private Type MAIL_DATA
  108.     sToAddr()                   As String           ' To: email address
  109.     sToDisplayName()            As String           ' To: display name
  110.     sCcAddr()                   As String           ' Cc: email address
  111.     sCcDisplayName()            As String           ' Cc: display name
  112.     sBccAddr()                  As String           ' Bcc: email address
  113.     sFromAddr                   As String           ' From: email address
  114.     sFromDisplayName            As String           ' From: display name
  115.     sReplyToAddr                As String           ' ReplyTo: email address
  116.     sSubject                    As String           ' Subject
  117.     sMailMessage                As String           ' email message body
  118.     sAttachment()               As String           ' attachment pathfilename
  119.     sAttachNameOnly()           As String           ' attachment name only
  120.     bAttachCID()                As Boolean          ' attachment has an assigned CID in an HTML document
  121.     lAttachNameSize             As Long             ' sum of the lenght of all attachment names
  122.     lAttachFileSize             As Long             ' sum of all file lenghts
  123.     lAttachCount                As Long             ' number of attachments
  124. End Type
  125. ' Class Property var's
  126. Private utMail                  As MAIL_DATA        ' see above type def
  127. Private etPriority              As MAIL_PRIORITY    ' mail priority, Normal - High - Low
  128. Private psDelimiter             As String           ' string to delimit multiple entries
  129. Private psSMTPHost              As String           ' remote host name or IP number
  130. Private plSMTPPort              As Long             ' remote host port number
  131. Private pbUseAuthentication     As Boolean          ' flag, use login authentication with host
  132. Private pbHtmlText              As Boolean          ' flag, send plain text / html text
  133. Private psContentBase           As String           ' Content base for HTML text
  134. Private plConnectTimeout        As Long             ' timeout value for connection attempts
  135. Private plConnectRetry          As Long             ' number of times to attempt a connection
  136. Private plMessageTimeOut        As Long             ' timeout value for sending a message
  137. Private pbPersistentSettings    As Long             ' flag, persistent/non-persistent settings
  138. Private etEncodeType            As ENCODE_METHOD    ' MIME / UUEncode flag
  139. Private etEmailValidation       As VALIDATE_METHOD  ' type of email address validation to use
  140. Private etSMTPHostValidation    As VALIDATE_METHOD  ' type of Host validation to use
  141. Private pbReceipt               As Boolean          ' flag, request a return receipt
  142. ' Class local var's
  143. Private psTimeZoneBias          As String           ' time zone offset bias
  144. Private pColErrors              As Collection       ' errors collection
  145. Private pbBase64Byt(0 To 63)    As Byte             ' base 64 encoder byte array
  146. Private psUUEncodeChr(0 To 63)  As String           ' UU encoder string array
  147. Private pb8BitMail              As Boolean          ' flag, 7/8 bit message body
  148. Private pbExitImmediately       As Boolean          ' flag - unrecoverable error
  149. Private pbConnected             As Boolean          ' flag, connection to host established
  150. Private pbManualDisconnect      As Boolean          ' flag, stay connected until 'Disconnect' called
  151. Private pbRequestAccepted       As Boolean          ' flag, host accepted request
  152. Private pbDataOK                As Boolean          ' flag, received "OK" from host
  153. Private pbAuthLoginSupported    As Boolean          ' flag, host supports auth login
  154. Private pbAuthMailFromOK        As Boolean          ' flag, host accepts 'mail from' auth
  155. Private pbAuthLoginSuccess      As Boolean          ' flag, Auth login accepted by remote host
  156. Private plBytesSent             As Long             ' running total of bytes sent
  157. Private plBytesRemaining        As Long             ' bytes remaining to be sent in sock send buffer
  158. Private pbSendProgress          As Boolean          ' flag indicating that the send progress event has fired
  159. Private plMailSize              As Long             ' total size of email session
  160. Private psUserName              As String           ' Auth username - optional, not supported by all servers
  161. Private psPassword              As String           ' Auth password - optional, not supported by all servers
  162. Private psPriority              As String           ' string version of priority Property for MSMail
  163. Private plPop3Status            As Long             ' POP3 connection status
  164. Private pbUsePopAuthentication  As Boolean          ' server requires Pop authorization (before SMTP)
  165. Private pbPopAuthOk             As Boolean          ' POP3 auth OK
  166. Private psPop3Host              As String           ' POP3 server name
  167. Private WithEvents sckMail      As CSocket          ' project must include the Winsock control
  168. Attribute sckMail.VB_VarHelpID = -1
  169. ' or a reference to the mswinsck.ocx
  170. Private psDay()                 As String           ' day name array
  171. Private psMonth()               As String           ' month name array
  172. ' Class Constants
  173. ' base 64 encoder string
  174. Private Const BASE64CHR As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
  175. ' error strings used with 'pColErrors' collection to report errors to the user
  176. Private Const ERR_INVALID_HOST = "Invalid or Missing SMTP Host Name"
  177. Private Const ERR_INVALID_POP_HOST = "Invalid or Missing POP3 Host Name"
  178. Private Const ERR_INVALID_PORT = "Invalid Remote Port"
  179. Private Const ERR_INVALID_REC_EMAIL = "Missing or Invalid Recipient E-mail Address"
  180. Private Const ERR_NO_REC_EMAIL = "No Recipient E-mail Address Specified"
  181. Private Const ERR_INVALID_CC_EMAIL = "Invalid Cc: Recipient E-mail Address"
  182. Private Const ERR_INVALID_BCC_EMAIL = "Invalid Bcc: Recipient E-mail Address"
  183. Private Const ERR_INVALID_SND_EMAIL = "Missing or Invalid Sender E-mail Address"
  184. Private Const ERR_TIMEOUT = "Timeout occurred: The SMTP Host did not respond to the request"
  185. Private Const ERR_FILE_NOT_EXIST = "The file you tried to attach does not exist"
  186. Private Const ERR_RECIPIENT_COUNT = "Too many recipients"
  187. Private Const ERR_HTML_REQUIRES_MIME = "Sending HTML requires MIME encoding"
  188. ' misc startup defaults
  189. Private Const CONNECT_TIMEOUT = 30                  ' seconds to wait before giving up
  190. Private Const CONNECT_RETRY = 4                     ' number of times to try before giving up
  191. Private Const MSG_TIMEOUT = 60                      ' seconds before timing out on message transmission
  192. Private Const REG_KEY = "vbSendMail"                ' registry key
  193. Private Const SETTINGS_KEY = "Settings"             ' registry sub key
  194. Private Const DEFAULT_PORT As Long = 25             ' default socket port for SMTP
  195. Private Const POP3_PORT As Long = 110               ' default socket port for POP3
  196. Private Const Q_CODE_HDR    As String = "=?ISO-8859-1?Q?"
  197. Private Const B_CODE_HDR    As String = "=?ISO-8859-1?B?"
  198. Private Const CODE_END      As String = "?="
  199. Private Const CHAR_SET      As String = "iso-8859-1"
  200. ' maximums per RFC 821...
  201. Private Const MAX_TEXTLINE_LEN = 1000               ' maximum total lenght of a text line
  202. Private Const MAX_RECIPIENTS = 100                  ' maximum number of recipients that must be buffered
  203. ' list of top level Domains, obtained from www.IANA.com.
  204. ' Can and will change, used in host name syntax checking
  205. Private Const TOP_DOMAINS = "COM ORG NET EDU GOV MIL INT AF AL DZ AS " & _
  206.                 "AD AO AI AQ AG AR AM AW AC AU AT AZ BS BH BD BB BY BZ BT BJ " & _
  207.                 "BE BM BO BA BW BV BR IO BN BG BF BI KH CM CA CV KY CF TD CL " & _
  208.                 "CN CX CC CO KM CD CG CK CR CI HR CU CY CZ DK DJ DM DO TP EC " & _
  209.                 "EG SV GQ ER EE ET FK FO FJ FI FR GF PF TF GA GM GE DE GH GI " & _
  210.                 "GR GL GD GP GU GT GG GN GW GY HT HM VA HN HK HU IS IN ID IR " & _
  211.                 "IQ IE IM IL IT JM JP JE JO KZ KE KI KP KR KW KG LA LV LB LS " & _
  212.                 "LR LY LI LT LU MO MK MG MW MY MV ML MT MH MQ MR MU YT MX FM " & _
  213.                 "MD MC MN MS MA MZ MM NA NR NP NL AN NC NZ NI NE NG NU NF MP " & _
  214.                 "NO OM PK PW PA PG PY PE PH PN PL PT PR QA RE RO RU RW KN LC " & _
  215.                 "VC WS SM ST SA SN SC SL SG SK SI SB SO ZA GS ES LK SH PM SD " & _
  216.                 "SR SJ SZ SE CH SY TW TJ TZ TH TG TK TO TT TN TR TM TC TV UG " & _
  217.                 "UA AE GB US UM UY UZ VU VE VN VG VI WF EH YE YU ZR ZM ZW UK"
  218. ' Class Events
  219. Public Event SendSuccesful()
  220. Public Event SendFailed(Explanation As String)
  221. Public Event Status(Status As String)
  222. Public Event Progress(PercentComplete As Long)
  223. ' API prototypes...
  224. ' winsock
  225. Private Declare Function WSAGetLastError Lib "wsock32.dll" () As Long
  226. Private Declare Function WSAStartup Lib "wsock32.dll" _
  227.                           (ByVal wVersionRequired As Long, lpWSAData As WSADATA) As Long
  228. Private Declare Function WSACleanup Lib "wsock32.dll" () As Long
  229. Private Declare Function gethostname Lib "wsock32.dll" _
  230.                           (ByVal szHost As String, ByVal dwHostLen As Long) As Long
  231. Private Declare Function gethostbyname Lib "wsock32.dll" (ByVal szHost As String) As Long
  232. Private Declare Function IcmpCreateFile Lib "icmp.dll" () As Long
  233. Private Declare Function IcmpCloseHandle Lib "icmp.dll" (ByVal IcmpHandle As Long) As Long
  234. Private Declare Function IcmpSendEcho Lib "icmp.dll" _
  235.                           (ByVal IcmpHandle As Long, ByVal DestinationAddress As Long, _
  236.                           ByVal RequestData As String, ByVal RequestSize As Long, _
  237.                           ByVal RequestOptions As Long, ReplyBuffer As ICMP_ECHO_REPLY, _
  238.                           ByVal ReplySize As Long, ByVal Timeout As Long) As Long
  239. ' registry
  240. Private Declare Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As Long) As Long
  241. Private Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" _
  242.                           (ByVal hKey As Long, ByVal lpSubKey As String, ByVal ulOptions As Long, _
  243.                           ByVal samDesired As Long, phkResult As Long) As Long
  244. Private Declare Function RegOpenKey Lib "advapi32.dll" Alias "RegOpenKeyA" _
  245.                           (ByVal hKey As Long, ByVal lpSubKey As String, phkResult As Long) As Long
  246. Private Declare Function RegQueryValueEx Lib "advapi32.dll" Alias "RegQueryValueExA" _
  247.                           (ByVal hKey As Long, ByVal lpValueName As String, ByVal lpReserved As Long, _
  248.                           lpType As Long, lpData As Any, lpcbData As Long) As Long
  249. Private Declare Function RegSetValueEx Lib "advapi32.dll" Alias "RegSetValueExA" _
  250.                           (ByVal hKey As Long, ByVal lpValueName As String, ByVal RESERVED As Long, _
  251.                           ByVal dwType As Long, lpData As Any, ByVal cbData As Long) As Long
  252. ' misc
  253. Private Declare Function GetTimeZoneInformation Lib "kernel32" _
  254.                           (lpTimeZoneInformation As TIME_ZONE_INFORMATION) As Long
  255. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
  256.                           (hpvDest As Any, hpvSource As Any, ByVal cbCopy As Long)
  257. Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
  258. Private Sub Class_Initialize()
  259.   Dim iPtr        As Integer                      ' loop counter
  260.   Dim utTZ        As TIME_ZONE_INFORMATION        ' api time zone type
  261.   Dim dwBias      As Long
  262.     ' instantiate the Error collection
  263.     Set pColErrors = New Collection
  264.     ' instantiate the Winsock Control
  265.     Set sckMail = New CSocket
  266.     ' alternate method of instantiating Winsock without a Form.
  267.     ' use a project Reference instead of the included frmSck & Winsock control
  268.     ' *** currently has unresolved deployment issues ***
  269.     'Set sckMail = New Winsock
  270.     ' initialize default values...
  271.     pbPersistentSettings = CLng(RegGet("PersistentSettings", "0"))
  272.     If pbPersistentSettings Then
  273.         ' load defaults from the registry
  274.         utMail.sFromAddr = RegGet("From", "")
  275.         utMail.sFromDisplayName = RegGet("FromDisplayName", "")
  276.         psPop3Host = RegGet("Pop3Host", "")
  277.         psSMTPHost = RegGet("RemoteHost", "")
  278.         plSMTPPort = CLng(RegGet("RemotePort", DEFAULT_PORT))
  279.         etSMTPHostValidation = RegGet("SMTPHostValidation", VALIDATE_HOST_DNS)
  280.         etEmailValidation = CLng(RegGet("EmailValidation", VALIDATE_SYNTAX))
  281.         plConnectTimeout = CLng(RegGet("ConnectTimeout", CONNECT_TIMEOUT))
  282.         plMessageTimeOut = CLng(RegGet("MessageTimeout", MSG_TIMEOUT))
  283.         plConnectRetry = CLng(RegGet("ConnectRetry", CONNECT_RETRY))
  284.         etEncodeType = RegGet("EncodeType", MIME_ENCODE)
  285.         psUserName = RegGet("Username", "")
  286.         pbUseAuthentication = RegGet("UseAuthentication", False)
  287.         pbUsePopAuthentication = RegGet("UsePopAuthentication", False)
  288.       Else
  289.         ' load standard defaults
  290.         plSMTPPort = DEFAULT_PORT
  291.         etSMTPHostValidation = VALIDATE_HOST_DNS
  292.         etEmailValidation = VALIDATE_SYNTAX
  293.         plConnectTimeout = CONNECT_TIMEOUT
  294.         plMessageTimeOut = MSG_TIMEOUT
  295.         plConnectRetry = CONNECT_RETRY
  296.         etEncodeType = MIME_ENCODE
  297.         pbHtmlText = False
  298.     End If
  299.     ' initialize the arrays for base64 & uu encoders
  300.     For iPtr = 0 To 63
  301.         pbBase64Byt(iPtr) = Asc(Mid$(BASE64CHR, iPtr + 1, 1))
  302.         psUUEncodeChr(iPtr) = Chr$(iPtr + &H20)
  303.     Next iPtr
  304.     psUUEncodeChr(0) = Chr$(&H60)
  305.     ' calculate the time zone offset bias
  306.     Select Case GetTimeZoneInformation(utTZ)
  307.       Case TIME_ZONE_ID_DAYLIGHT
  308.         dwBias = utTZ.Bias + utTZ.DaylightBias
  309.       Case Else
  310.         dwBias = utTZ.Bias + utTZ.StandardBias
  311.     End Select
  312.     psTimeZoneBias = Format$(-dwBias  60, "00") & Format$(Abs(dwBias - (dwBias  60) * 60), "00")
  313.     If InStr(psTimeZoneBias, "-") = 0 Then psTimeZoneBias = "+" & psTimeZoneBias
  314.     ' init mail recipient arrays (sets Ubound to -1)
  315.     utMail.sToAddr = Split("")
  316.     utMail.sToDisplayName = utMail.sToAddr
  317.     utMail.sCcAddr = utMail.sToAddr
  318.     utMail.sCcDisplayName = utMail.sToAddr
  319.     utMail.sBccAddr = utMail.sToAddr
  320.     utMail.sAttachment = utMail.sToAddr
  321.     ' set default delimiter
  322.     psDelimiter = ";"
  323.     ' set default priority
  324.     etPriority = NORMAL_PRIORITY
  325.     ' initialize the day/month arrays needed to support non-English systems.
  326.     ' some email clients/servers will not accept non-English words in the
  327.     ' date field so we need to guarantee that the day & month are English.
  328.     ' These arrays are used in the Send Sub to format the current time/date.
  329.     psDay() = Split(",Sun,Mon,Tue,Wed,Thu,Fri,Sat", ",")
  330.     psMonth() = Split(",Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", ",")
  331. End Sub
  332. Private Sub Class_Terminate()
  333.   ' make sure sckMail is closed
  334.     If sckMail.State <> sckClosed Then
  335.         DisconnectFromHost
  336.     End If
  337.     ' release memory
  338.     Set sckMail = Nothing
  339.     Set pColErrors = Nothing
  340. End Sub
  341. ' ******************************************************************************
  342. ' *      Class Properties                                                      *
  343. ' ******************************************************************************
  344. Public Property Get AsHTML() As Boolean
  345.   ' return the Property value
  346.     AsHTML = pbHtmlText
  347. End Property
  348. Public Property Let AsHTML(ByVal NewValue As Boolean)
  349.   ' save the new Property value
  350.     pbHtmlText = NewValue
  351. End Property
  352. Public Property Get Attachment() As String
  353.   ' return the Property value
  354.     Attachment = Join(utMail.sAttachment, psDelimiter)
  355. End Property
  356. Public Property Let Attachment(ByVal NewValue As String)
  357.   Dim sNameOnly()     As String
  358.   Dim lPtr            As Long
  359.     ' save the new Property value
  360.     utMail.sAttachment = Split(NewValue, psDelimiter)
  361.     ' reset the counters
  362.     utMail.lAttachCount = UBound(utMail.sAttachment) + 1
  363.     utMail.lAttachFileSize = 0
  364.     utMail.lAttachNameSize = 0
  365.     RemoveError ERR_FILE_NOT_EXIST
  366.     ' process all of the file names
  367.     If utMail.lAttachCount Then
  368.         ReDim utMail.sAttachNameOnly(utMail.lAttachCount - 1)
  369.         ReDim utMail.bAttachCID(utMail.lAttachCount - 1)
  370.         ' verify each entry...
  371.         For lPtr = 0 To UBound(utMail.sAttachment)
  372.             ' check that the file exists
  373.             If Dir(utMail.sAttachment(lPtr)) = "" Then
  374.                 AddError ERR_FILE_NOT_EXIST
  375.                 Exit For
  376.             End If
  377.             ' extract the file name
  378.             sNameOnly = Split(utMail.sAttachment(lPtr), "")
  379.             utMail.sAttachNameOnly(lPtr) = sNameOnly(UBound(sNameOnly))
  380.             ' add up the file sizes and name lengths for later...
  381.             utMail.lAttachFileSize = utMail.lAttachFileSize + FileLen(utMail.sAttachment(lPtr))
  382.             utMail.lAttachNameSize = utMail.lAttachNameSize + Len(utMail.sAttachNameOnly(lPtr))
  383.         Next lPtr
  384.     End If
  385. End Property
  386. Public Property Get BccRecipient() As String
  387.   ' return the Property value
  388.     BccRecipient = Join(utMail.sBccAddr, psDelimiter)
  389. End Property
  390. Public Property Let BccRecipient(ByVal NewValue As String)
  391.   ' save the new Property value
  392.     utMail.sBccAddr = Split(NewValue, psDelimiter)
  393.     TrimWhiteSpace utMail.sBccAddr
  394.     ValidateAddress NewValue, ERR_INVALID_BCC_EMAIL
  395. End Property
  396. Public Property Get CcDisplayName() As String
  397.   ' return the Property value
  398.     CcDisplayName = Join(utMail.sCcDisplayName, psDelimiter)
  399. End Property
  400. Public Property Let CcDisplayName(ByVal NewValue As String)
  401.   ' save the new Property value
  402.     utMail.sCcDisplayName = Split(NewValue, psDelimiter)
  403.     TrimWhiteSpace utMail.sCcDisplayName
  404. End Property
  405. Public Property Get CcRecipient() As String
  406.   ' return the Property value
  407.     CcRecipient = Join(utMail.sCcAddr, psDelimiter)
  408. End Property
  409. Public Property Let CcRecipient(ByVal NewValue As String)
  410.   ' save the new Property value
  411.     utMail.sCcAddr = Split(NewValue, psDelimiter)
  412.     TrimWhiteSpace utMail.sCcAddr
  413.     ValidateAddress NewValue, ERR_INVALID_CC_EMAIL
  414. End Property
  415. Public Property Get ConnectRetry() As Long
  416.   ' return the Property value
  417.     ConnectRetry = plConnectRetry
  418. End Property
  419. Public Property Let ConnectRetry(ByVal NewValue As Long)
  420.   ' save the new Property value
  421.     If NewValue > 0 And NewValue <= 20 Then plConnectRetry = NewValue
  422.     RegSave "ConnectRetry", Str$(NewValue)
  423. End Property
  424. Public Property Get ConnectTimeout() As Long
  425.   ' return the Property value
  426.     ConnectTimeout = plConnectTimeout
  427. End Property
  428. Public Property Let ConnectTimeout(ByVal NewValue As Long)
  429.   ' save the new Property value
  430.     If NewValue > 0 And NewValue <= 120 Then plConnectTimeout = NewValue
  431.     RegSave "ConnectTimeout", Str$(NewValue)
  432. End Property
  433. Public Property Get ContentBase() As String
  434.   ' return the Property value
  435.     ContentBase = psContentBase
  436. End Property
  437. Public Property Let ContentBase(ByVal NewValue As String)
  438.   ' save the new Property value
  439.   ' fix some common mistakes...
  440.     If Len(NewValue) Then
  441.         Replace$ NewValue, "", "/"
  442.         If InStr(1, NewValue, "http://", vbTextCompare) = 0 Then NewValue = "http://" & NewValue
  443.         If Right$(NewValue, 1) <> "/" Then NewValue = NewValue & "/"
  444.     End If
  445.     psContentBase = NewValue
  446. End Property
  447. Public Property Get Delimiter() As String
  448.   ' return the Property value
  449.     Delimiter = psDelimiter
  450. End Property
  451. Public Property Let Delimiter(ByVal NewValue As String)
  452.   ' save the new Property value
  453.     psDelimiter = Left$(NewValue, 1)
  454. End Property
  455. Public Property Get EmailAddressValidation() As VALIDATE_METHOD
  456.   ' return the Property value
  457.     EmailAddressValidation = etEmailValidation
  458. End Property
  459. Public Property Let EmailAddressValidation(ByVal NewValue As VALIDATE_METHOD)
  460.   ' save the new Property value
  461.     etEmailValidation = NewValue
  462.     RegSave "EmailValidation", Str$(NewValue)
  463. End Property
  464. Public Property Get EncodeType() As ENCODE_METHOD
  465.   ' return the Property value
  466.     EncodeType = etEncodeType
  467. End Property
  468. Public Property Let EncodeType(ByVal NewValue As ENCODE_METHOD)
  469.   ' save the new Property value
  470.     etEncodeType = NewValue
  471.     RegSave "EncodeType", Str$(NewValue)
  472. End Property
  473. Public Property Get from() As String
  474.   ' return the Property value
  475.     from = utMail.sFromAddr
  476. End Property
  477. Public Property Let from(ByVal NewValue As String)
  478.   ' save the new Property value
  479.     utMail.sFromAddr = Trim$(NewValue)
  480.     ValidateAddress NewValue, ERR_INVALID_SND_EMAIL
  481.     RegSave "From", NewValue
  482. End Property
  483. Public Property Get FromDisplayName() As String
  484.   ' return the Property value
  485.     FromDisplayName = utMail.sFromDisplayName
  486. End Property
  487. Public Property Let FromDisplayName(ByVal NewValue As String)
  488.   ' save the new Property value
  489.     utMail.sFromDisplayName = Trim$(NewValue)
  490.     RegSave "FromDisplayName", NewValue
  491. End Property
  492. Public Property Get Message() As String
  493.   ' return the Property value
  494.     Message = utMail.sMailMessage
  495. End Property
  496. Public Property Let Message(ByVal NewValue As String)
  497.   Dim lPtr        As Long
  498.   Dim bytTmp()    As Byte
  499.     ' save the new Property value
  500.     utMail.sMailMessage = FormatMail(NewValue)
  501.     ' check for any 8 bit characters
  502.     pb8BitMail = False
  503.     bytTmp() = StrConv(utMail.sMailMessage, vbFromUnicode)
  504.     For lPtr = 0 To UBound(bytTmp)
  505.         If bytTmp(lPtr) > 126 Then
  506.             pb8BitMail = True
  507.             Exit For
  508.         End If
  509.     Next lPtr
  510. End Property
  511. Public Property Get MessageTimeout() As Long
  512.   ' return the Property value
  513.     MessageTimeout = plMessageTimeOut
  514. End Property
  515. Public Property Let MessageTimeout(ByVal NewValue As Long)
  516.   ' save the new Property value
  517.     plMessageTimeOut = Abs(NewValue)
  518.     RegSave "MessageTimeout", Str$(NewValue)
  519. End Property
  520. Public Property Get Password() As String
  521.   ' return the Property value
  522.     Password = psPassword
  523. End Property
  524. Public Property Let Password(ByVal NewValue As String)
  525.   ' save the new Property value
  526.     psPassword = NewValue
  527. End Property
  528. Public Property Get PersistentSettings() As Boolean
  529.   ' return the Property value
  530.     PersistentSettings = pbPersistentSettings
  531. End Property
  532. Public Property Let PersistentSettings(ByVal NewValue As Boolean)
  533.   ' save the new Property value
  534.     pbPersistentSettings = NewValue
  535.     RegSave "PersistentSettings", CStr(CLng(NewValue))
  536. End Property
  537. Public Property Get Priority() As MAIL_PRIORITY
  538.   ' return the Property value
  539.     Priority = etPriority
  540. End Property
  541. Public Property Let Priority(ByVal NewValue As MAIL_PRIORITY)
  542.   ' save the new Property value
  543.     etPriority = NewValue
  544.     ' set the string version to match
  545.     Select Case etPriority
  546.       Case NORMAL_PRIORITY
  547.         psPriority = "Normal"
  548.       Case HIGH_PRIORITY
  549.         psPriority = "High"
  550.       Case LOW_PRIORITY
  551.         psPriority = "Low"
  552.     End Select
  553. End Property
  554. Public Property Get Receipt() As Boolean
  555.   ' return the Property value
  556.     Receipt = pbReceipt
  557. End Property
  558. Public Property Let Receipt(ByVal NewValue As Boolean)
  559.   ' save the new Property value
  560.     pbReceipt = NewValue
  561. End Property
  562. Public Property Get Recipient() As String
  563.   ' return the Property value
  564.     Recipient = Join(utMail.sToAddr, psDelimiter)
  565. End Property
  566. Public Property Let Recipient(ByVal NewValue As String)
  567.   ' save the new Property value
  568.     utMail.sToAddr = Split(NewValue, psDelimiter)
  569.     TrimWhiteSpace utMail.sToAddr
  570.     ValidateAddress NewValue, ERR_INVALID_REC_EMAIL
  571. End Property
  572. Public Property Get RecipientDisplayName() As String
  573.   ' return the Property value
  574.     RecipientDisplayName = Join(utMail.sToDisplayName, psDelimiter)
  575. End Property
  576. Public Property Let RecipientDisplayName(ByVal NewValue As String)
  577.   ' save the new Property value
  578.     utMail.sToDisplayName = Split(NewValue, psDelimiter)
  579.     TrimWhiteSpace utMail.sToDisplayName
  580. End Property
  581. Public Property Get ReplyToAddress() As String
  582.   ' return the Property value
  583.     ReplyToAddress = utMail.sReplyToAddr
  584. End Property
  585. Public Property Let ReplyToAddress(ByVal NewValue As String)
  586.   ' save the new Property value
  587.     utMail.sReplyToAddr = Trim$(NewValue)
  588. End Property
  589. Public Property Get POP3Host() As String
  590.   ' return the Property value
  591.     POP3Host = psPop3Host
  592. End Property
  593. Public Property Let POP3Host(NewValue As String)
  594.   Dim bValid      As Boolean
  595.     NewValue = Replace(NewValue, vbNullChar, vbNullString)
  596.     ' validate the new host name...
  597.     If Len(NewValue) Then
  598.         Select Case etSMTPHostValidation
  599.           Case VALIDATE_HOST_SYNTAX
  600.             bValid = IsValidIPHost(NewValue)
  601.           Case VALIDATE_HOST_PING
  602.             bValid = Ping(NewValue)
  603.           Case VALIDATE_HOST_DNS
  604.             If GetIPAddress(NewValue) <> "" Then bValid = True
  605.           Case Else
  606.             bValid = True
  607.         End Select
  608.       Else
  609.         bValid = True
  610.     End If
  611.     ' save the new Property value
  612.     If bValid Then
  613.         RegSave "Pop3Host", NewValue
  614.         RemoveError ERR_INVALID_POP_HOST
  615.         psPop3Host = NewValue
  616.       Else
  617.         AddError ERR_INVALID_POP_HOST
  618.     End If
  619. End Property
  620. Public Property Get SMTPHost() As String
  621.   ' return the Property value
  622.     SMTPHost = psSMTPHost
  623. End Property
  624. Public Property Let SMTPHost(NewValue As String)
  625.   Dim bValid      As Boolean
  626.     NewValue = Replace(NewValue, vbNullChar, vbNullString)
  627.     ' validate the new host name...
  628.     If Len(NewValue) Then
  629.         Select Case etSMTPHostValidation
  630.           Case VALIDATE_HOST_SYNTAX
  631.             bValid = IsValidIPHost(NewValue)
  632.           Case VALIDATE_HOST_PING
  633.             bValid = Ping(NewValue)
  634.           Case VALIDATE_HOST_DNS
  635.             If GetIPAddress(NewValue) <> "" Then bValid = True
  636.           Case Else
  637.             bValid = True
  638.         End Select
  639.       Else
  640.         bValid = True
  641.     End If
  642.     ' save the new Property value
  643.     If bValid Then
  644.         RegSave "RemoteHost", NewValue
  645.         RemoveError ERR_INVALID_HOST
  646.         psSMTPHost = NewValue
  647.       Else
  648.         AddError ERR_INVALID_HOST
  649.     End If
  650. End Property
  651. Public Property Get SMTPHostValidation() As VALIDATE_HOST_METHOD
  652.   ' return the Property value
  653.     SMTPHostValidation = etSMTPHostValidation
  654. End Property
  655. Public Property Let SMTPHostValidation(ByVal NewValue As VALIDATE_HOST_METHOD)
  656.   ' save the new Property value
  657.     etSMTPHostValidation = NewValue
  658.     RegSave "SMTPHostValidation", Str$(NewValue)
  659.     ' in case this is set after the host value is set
  660.     If psSMTPHost <> "" Then SMTPHost = psSMTPHost
  661. End Property
  662. Public Property Get SMTPPort() As Long
  663.   ' return the Property value
  664.     SMTPPort = plSMTPPort
  665. End Property
  666. Public Property Let SMTPPort(ByVal NewValue As Long)
  667.   ' save the new Property value
  668.     If NewValue < 1 Or NewValue > 65535 Then
  669.         AddError ERR_INVALID_PORT
  670.       Else
  671.         plSMTPPort = NewValue
  672.         RegSave "RemotePort", Str$(NewValue)
  673.         RemoveError ERR_INVALID_PORT
  674.     End If
  675. End Property
  676. Public Property Get Subject() As String
  677.   ' return the Property value
  678.     Subject = utMail.sSubject
  679. End Property
  680. Public Property Let Subject(ByVal NewValue As String)
  681.   ' save the new Property value
  682.     utMail.sSubject = NewValue
  683. End Property
  684. Public Property Get UseAuthentication() As Boolean
  685.   ' return the Property value
  686.     UseAuthentication = pbUseAuthentication
  687. End Property
  688. Public Property Let UseAuthentication(ByVal NewValue As Boolean)
  689.   ' save the new Property value
  690.     pbUseAuthentication = NewValue
  691.     RegSave "UseAuthentication", CStr(CLng(NewValue))
  692. End Property
  693. Public Property Get UsePopAuthentication() As Boolean
  694.   ' return the Property value
  695.     UsePopAuthentication = pbUsePopAuthentication
  696. End Property
  697. Public Property Let UsePopAuthentication(ByVal NewValue As Boolean)
  698.   ' save the new Property value
  699.     pbUsePopAuthentication = NewValue
  700.     RegSave "UsePopAuthentication", CStr(CLng(NewValue))
  701. End Property
  702. Public Property Get Username() As String
  703.   ' return the Property value
  704.     Username = psUserName
  705. End Property
  706. Public Property Let Username(ByVal NewValue As String)
  707.   ' save the new Property value
  708.     psUserName = NewValue
  709.     RegSave "Username", NewValue
  710. End Property
  711. ' ******************************************************************************
  712. ' *      Class Methods                                                         *
  713. ' ******************************************************************************
  714. Public Function Connect() As Boolean
  715.   ' public version of ConnectToHost
  716.   ' sets pbManualDisconnect flag so Send Sub
  717.   ' will not disconnect when finished....
  718.     pbManualDisconnect = True
  719.     Connect = ConnectToHost
  720. End Function
  721. Public Sub Disconnect()
  722.   ' public version of DisconnectFromHost
  723.   ' clears pbManualDisconnect flag
  724.     pbManualDisconnect = False
  725.     DisconnectFromHost
  726. End Sub
  727. Public Function GetContentType(ByVal strFile As String, Optional strDefault As String = "application/octet-stream") As String
  728.   ' ******************************************************************************
  729.   '
  730.   ' Synopsis:     Get the Content Type from the Registry.
  731.   '
  732.   ' Parameters:   strFile     - The filename to get the Content Type for
  733.   '               strDefault  - The default data to return if nothing is found
  734.   '
  735.   ' Return:       The Content Type string
  736.   '
  737.   ' Description:
  738.   ' The Content Type string for registered file extensions is located in
  739.   ' the system registry, in the root key HKEY_CLASSES_ROOT. Open the registry
  740.   ' key for the given file extension and read the 'Content Type' value. If the
  741.   ' key and/or value are not found, assign a default value of
  742.   ' 'application/octet-stream'
  743.   '
  744.   ' ******************************************************************************
  745.   Dim hKey                As Long                 ' key handle
  746.   Dim strBuff             As String               ' buffer for API to write to
  747.   Dim lBuffLen            As Long                 ' lenght of API return string
  748.   Dim lRet                As Long                 ' API return code
  749.   Dim lValueType          As Long                 ' data type for retun value
  750.   Dim iPtr                As Integer              ' scratch pointer
  751.   Dim strValueName        As String               ' registry 'value name
  752.   Dim strKeyName          As String               ' registry 'key name
  753.     If bInEXE Then On Local Error GoTo ERR_GetContentType
  754.     GetContentType = strDefault
  755.     ' registry value name
  756.     strValueName = "Content Type"
  757.     ' get the passed in key name. We only want
  758.     ' the file extension here e.g. .exe, .doc, etc.
  759.     ' if an extension is not found, assign default
  760.     ' value and return
  761.     iPtr = InStrRev(strFile, ".")
  762.     If iPtr Then
  763.         strKeyName = Mid$(strFile, iPtr)
  764.       Else
  765.         Exit Function
  766.     End If
  767.     ' open the Registry key, if key not found, return the defaut value
  768.     lRet = RegOpenKey(HKEY_CLASSES_ROOT, strKeyName, hKey)
  769.     If lRet <> ERROR_SUCCESS Then Exit Function
  770.     ' query the key value to get it's data type & length
  771.     lRet = RegQueryValueEx(hKey, strValueName, 0&, lValueType, 0&, lBuffLen)
  772.     ' should be type string...
  773.     If lValueType = REG_SZ Then
  774.         ' create a buffer & call the API again
  775.         strBuff = String$(lBuffLen, " ")
  776.         lRet = RegQueryValueEx(hKey, strValueName, 0&, lValueType, ByVal strBuff, lBuffLen)
  777.         ' get the string value, drop the trailing '0'
  778.         If lRet = ERROR_SUCCESS Then GetContentType = Left$(strBuff, lBuffLen - 1)
  779.     End If
  780.     ' close the key
  781.     If hKey Then lRet = RegCloseKey(hKey)
  782. Exit Function
  783. ERR_GetContentType:
  784.     If hKey Then lRet = RegCloseKey(hKey)
  785.     GetContentType = strDefault
  786. End Function
  787. Public Function GetIPAddress(sHostName As String) As String
  788.   ' Resolves host-name to an IP address (DNS)
  789.   '
  790.   ' THIS CODE IS BASED ON FUNCTIONS
  791.   ' WITHIN RICHARD DEEMING'S IP UTILITIES:
  792.   ' http://www.freevbcode.com
  793.   Dim lpHost          As Long
  794.   Dim HOST            As HostEnt
  795.   Dim dwIPAddr        As Long
  796.   Dim tmpIPAddr()     As Byte
  797.   Dim I               As Integer
  798.   Dim sIPAddr         As String
  799.     ' init winsock api
  800.     If Not SocketsInitialize() Then
  801.         GetIPAddress = ""
  802.         Exit Function
  803.     End If
  804.     ' if no name given, use local host
  805.     If sHostName = "" Then sHostName = GetIPHost
  806.     sHostName = Trim$(sHostName) & Chr$(0)
  807.     ' call api
  808.     lpHost = gethostbyname(sHostName)
  809.     If lpHost Then
  810.         ' extract the data...
  811.         CopyMemory HOST, ByVal lpHost, Len(HOST)
  812.         CopyMemory dwIPAddr, ByVal HOST.hAddrList, 4
  813.         ReDim tmpIPAddr(1 To HOST.hLen)
  814.         CopyMemory tmpIPAddr(1), ByVal dwIPAddr, HOST.hLen
  815.         ' convert format
  816.         For I = 1 To HOST.hLen
  817.             sIPAddr = sIPAddr & tmpIPAddr(I) & "."
  818.         Next I
  819.         ' set the return value
  820.         GetIPAddress = Mid$(sIPAddr, 1, Len(sIPAddr) - 1)
  821.       Else
  822.         WSAGetLastError
  823.         GetIPAddress = ""
  824.     End If
  825.     SocketsCleanup
  826. End Function
  827. Public Function GetIPHost() As String
  828.   ' Resolves the local host name
  829.   '
  830.   ' THIS CODE IS BASED ON FUNCTIONS
  831.   ' WITHIN RICHARD DEEMING'S IP UTILITIES:
  832.   ' http://www.freevbcode.com
  833.   Dim sHostName   As String
  834.   Dim iPtr        As Integer
  835.     ' create a buffer
  836.     sHostName = String$(256, Chr$(0))
  837.     ' init winsock api
  838.     If Not SocketsInitialize() Then Exit Function
  839.     ' get the loacal hosts name
  840.     If gethostname(sHostName, Len(sHostName)) = ERROR_SUCCESS Then
  841.         iPtr = InStr(sHostName, Chr$(0))
  842.         If iPtr > 1 Then GetIPHost = Mid$(sHostName, 1, iPtr - 1)
  843.     End If
  844.     SocketsCleanup
  845. End Function
  846. Public Function IsValidEmailAddress(AddressString As String)  ' As Boolean
  847.   Dim sTmp()      As String
  848.     ' assume failure
  849.     IsValidEmailAddress = False
  850.     ' sould have one "@"
  851.     sTmp = Split(AddressString, "@")
  852.     If UBound(sTmp) <> 1 Then Exit Function
  853.     IsValidEmailAddress = IsValidIPHost(sTmp(1))
  854. End Function
  855. Public Function MXQuery(Optional IPDomain As String = "") As String
  856.   Dim sDomain     As String
  857.     ' return the best server found in an MX Query
  858.     If bInEXE Then On Local Error GoTo Err_MXQuery
  859.     sDomain = Trim$(IPDomain)
  860.     If Len(sDomain) Then
  861.         RaiseEvent Status("Performing MX Query, Domain: " & sDomain)
  862.       Else
  863.         RaiseEvent Status("Performing MX Query")
  864.     End If
  865.     MXQuery = MX_Query(sDomain)
  866. Exit Function
  867. Err_MXQuery:
  868.     MXQuery = vbNullString
  869.     RaiseEvent Status(Err.Description)
  870. End Function
  871. Public Function Ping(Address As String, _
  872.                      Optional RoundTripTime As String = "", _
  873.                      Optional DataSize As String = "", _
  874.                      Optional DataMatch As Boolean = False) As Boolean
  875.   ' Ping a remote host
  876.   '
  877.   ' THIS CODE IS BASED ON FUNCTIONS
  878.   ' WITHIN RICHARD DEEMING'S IP UTILITIES:
  879.   ' http://www.freevbcode.com
  880.   Dim ECHO            As ICMP_ECHO_REPLY
  881.   Dim iPtr            As Integer
  882.   Dim Dt              As String
  883.   Dim sAddress        As String
  884.   Dim hPort           As Long
  885.   Dim lAddress        As Long
  886.   Dim bytAddr(3)      As Byte
  887.     If bInEXE Then On Local Error GoTo DPErr
  888.     ' assume failure
  889.     Ping = False
  890.     ' if passed a name, get the IP address
  891.     If Not IsDottedQuad(Address) Then
  892.         sAddress = GetIPAddress(Address)
  893.       Else
  894.         sAddress = Address
  895.     End If
  896.     If sAddress = "" Then Exit Function
  897.     If SocketsInitialize Then
  898.         ' build string of random characters
  899.         For iPtr = 1 To DATA_SIZE
  900.             Dt = Dt & Chr$(Rnd() * 254 + 1)
  901.         Next iPtr
  902.         ' ping an ip address, passing the
  903.         ' address and the ECHO structure
  904.         lAddress = AddressStringToLong(sAddress)
  905.         hPort = IcmpCreateFile()
  906.         IcmpSendEcho hPort, lAddress, Dt, Len(Dt), 0, ECHO, Len(ECHO), PING_TIMEOUT
  907.         IcmpCloseHandle hPort
  908.         ' get the results from the ECHO structure
  909.         RoundTripTime = ECHO.RoundTripTime
  910.         CopyMemory bytAddr(0), ECHO.Address, 4
  911.         Address = CStr(bytAddr(0)) & "." & _
  912.                   CStr(bytAddr(1)) & "." & _
  913.                   CStr(bytAddr(2)) & "." & _
  914.                   CStr(bytAddr(3))
  915.         DataSize = ECHO.DataSize & " bytes"
  916.         iPtr = InStr(ECHO.Data, Chr$(0))
  917.         If iPtr > 1 Then DataMatch = (Left$(ECHO.Data, iPtr - 1) = Dt)
  918.         If ECHO.Status = 0 And ECHO.Address = lAddress Then Ping = True
  919.         SocketsCleanup
  920.     End If
  921. Exit Function
  922. DPErr:
  923. End Function
  924. Public Sub send()
  925.   Dim sSenderName         As String
  926.   Dim sToHeader           As String
  927.   Dim sCcHeader           As String
  928.   Dim iCtr                As Integer
  929.   Dim sAuth               As String
  930.   Dim sTxt                As String
  931.   Dim strBoundry          As String
  932.   Dim bMimeMultiPart      As Boolean
  933.   Dim fStart              As Single
  934.   Dim fTimeOut            As Single
  935.   Dim lSendBuffSize       As Long
  936.   Dim bRelatedLinks       As Boolean
  937.     ' general catch all error handler only
  938.     ' works when running in stand alone EXE
  939.     If bInEXE Then On Local Error GoTo Err_Send
  940.     ' check for multipart MIME
  941.     If etEncodeType = MIME_ENCODE And utMail.lAttachCount > 0 Then
  942.         bMimeMultiPart = True
  943.       Else
  944.         bMimeMultiPart = False
  945.     End If
  946.     ' check sender
  947.     If Len(utMail.sFromAddr) = 0 Then AddError ERR_INVALID_SND_EMAIL
  948.     ' HTML & UU Encode are mutually exclusive
  949.     If pbHtmlText = True And etEncodeType = UU_ENCODE Then AddError ERR_HTML_REQUIRES_MIME
  950.     ' check recipient count
  951.     If UBound(utMail.sToAddr) + UBound(utMail.sCcAddr) + UBound(utMail.sBccAddr) = -3 Then AddError ERR_NO_REC_EMAIL
  952.     If UBound(utMail.sToAddr) + UBound(utMail.sCcAddr) + UBound(utMail.sBccAddr) + 3 > MAX_RECIPIENTS Then AddError ERR_RECIPIENT_COUNT
  953.     ' resize the display name arrays to match the recipient arrays
  954.     iCtr = UBound(utMail.sToAddr)
  955.     If iCtr >= 0 Then ReDim Preserve utMail.sToDisplayName(iCtr)
  956.     iCtr = UBound(utMail.sCcAddr)
  957.     If iCtr >= 0 Then ReDim Preserve utMail.sCcDisplayName(iCtr)
  958.     ' we won't try to send if there's already an error
  959.     If pColErrors.Count > 0 Then
  960.         SendFail
  961.         Exit Sub
  962.     End If
  963.     ' get the Content-Location for any linked objects
  964.     If utMail.lAttachCount Then bRelatedLinks = GetAttachCID
  965.     ' get the mail size
  966.     plMailSize = EstimateMailSize
  967.     ' this flag gets set when a socket error occurs or the host cannot process an
  968.     ' input command, see 'SendFail', 'sckMail_DataArrival' & 'WaitUntilTrue' Subs
  969.     pbExitImmediately = False
  970.     With sckMail
  971.         ' if not already conected then connect to the remote host
  972.         If .State <> sckConnected Then
  973.             If Not ConnectToHost Then Exit Sub
  974.         End If
  975.         ' reset the progress counter
  976.         plBytesSent = 0
  977.         ' tell the host who the mail is 'From
  978.         RaiseEvent Status("Sending Sender Information...")
  979.         pbRequestAccepted = False
  980.         If pbAuthMailFromOK Then sAuth = " AUTH=" & utMail.sFromAddr Else sAuth = vbNullString
  981.         .SendData "MAIL FROM: <" & utMail.sFromAddr & ">" & sAuth & vbCrLf
  982.         If Not WaitUntilTrue(pbRequestAccepted, plMessageTimeOut, True) Then Exit Sub
  983.         If pbExitImmediately Then Exit Sub
  984.         ' tell the host who the recipients are
  985.         ' build the 'To:' header string 'sToHeader' too
  986.         RaiseEvent Status("Sending Recipient Information...")
  987.         For iCtr = 0 To UBound(utMail.sToAddr)
  988.             ' send the recipient address & wait for a reply
  989.             pbRequestAccepted = False
  990.             .SendData "RCPT TO: <" & utMail.sToAddr(iCtr) & ">" & vbCrLf
  991.             If Not WaitUntilTrue(pbRequestAccepted, plMessageTimeOut, True) Then Exit Sub
  992.             If pbExitImmediately Then Exit Sub
  993.             ' build the 'To:' header string for later...
  994.             If Len(utMail.sToDisplayName(iCtr)) Then
  995.                 sToHeader = sToHeader & CText(utMail.sToDisplayName(iCtr), True)
  996.               Else
  997.                 sToHeader = sToHeader & """" & Trim$(utMail.sToAddr(iCtr)) & """"
  998.             End If
  999.             sToHeader = sToHeader & " <" & utMail.sToAddr(iCtr) & ">"
  1000.             If iCtr < UBound(utMail.sToAddr) Then sToHeader = sToHeader & ", " & vbCrLf & vbTab
  1001.         Next iCtr
  1002.         ' send Cc: recipient addresses (just more 'RCPT TO' addresses)
  1003.         ' build the 'Cc:' header string too
  1004.         For iCtr = 0 To UBound(utMail.sCcAddr)
  1005.             ' send the recipient address & wait for a reply
  1006.             pbRequestAccepted = False
  1007.             .SendData "RCPT TO: <" & utMail.sCcAddr(iCtr) & ">" & vbCrLf
  1008.             If Not WaitUntilTrue(pbRequestAccepted, plMessageTimeOut, True) Then Exit Sub
  1009.             If pbExitImmediately Then Exit Sub
  1010.             ' build the 'Cc:' header string for later...
  1011.             If Len(utMail.sCcDisplayName(iCtr)) Then
  1012.                 sCcHeader = sCcHeader & CText(utMail.sCcDisplayName(iCtr), True)
  1013.               Else
  1014.                 sCcHeader = sCcHeader & """" & Trim$(utMail.sCcAddr(iCtr)) & """"
  1015.             End If
  1016.             sCcHeader = sCcHeader & " <" & utMail.sCcAddr(iCtr) & ">"
  1017.             If iCtr < UBound(utMail.sCcAddr) Then sCcHeader = sCcHeader & ", " & vbCrLf & vbTab
  1018.         Next iCtr
  1019.         ' send Bcc: recipient addresses (more of the same)
  1020.         ' no display headers here, these are blind
  1021.         For iCtr = 0 To UBound(utMail.sBccAddr)
  1022.             ' send the recipient address & wait for a reply
  1023.             pbRequestAccepted = False
  1024.             .SendData "RCPT TO: <" & Trim$(utMail.sBccAddr(iCtr)) & ">" & vbCrLf
  1025.             If Not WaitUntilTrue(pbRequestAccepted, plMessageTimeOut, True) Then Exit Sub
  1026.             If pbExitImmediately Then Exit Sub
  1027.         Next iCtr
  1028.         ' tell the remote host we're ready to send data
  1029.         RaiseEvent Status("Sending Message...")
  1030.         pbDataOK = False
  1031.         .SendData "DATA" & vbCrLf
  1032.         If Not WaitUntilTrue(pbDataOK, plMessageTimeOut, True) Then Exit Sub
  1033.         If pbExitImmediately Then Exit Sub
  1034.         ' OK, the host is ready for data, this is where the mail message starts
  1035.         ' Send the mail headers (the ones displayed on the target email client)
  1036.         pbRequestAccepted = False
  1037.         ' from, to, cc & subject headers..
  1038.         If Len(Trim$(utMail.sFromDisplayName)) Then
  1039.             sSenderName = CText(utMail.sFromDisplayName, True)
  1040.           Else
  1041.             sSenderName = """" & utMail.sFromAddr & """"
  1042.         End If
  1043.         sSenderName = sSenderName & " <" & utMail.sFromAddr & ">"
  1044.         .SendData "From: " & sSenderName & vbCrLf
  1045.         .SendData "To: " & sToHeader & vbCrLf
  1046.         If Len(sCcHeader) Then .SendData "Cc: " & sCcHeader & vbCrLf
  1047.         .SendData "Subject: " & CText(utMail.sSubject) & vbCrLf
  1048.         If Len(utMail.sReplyToAddr) Then .SendData "Reply-to: <" & utMail.sReplyToAddr & ">" & vbCrLf
  1049.         ' send English foramted date/time string
  1050.         .SendData "Date: " & psDay(Weekday(Now)) & ", " & Day(Now) & " " & psMonth(Month(Now)) & _
  1051.                   Format$(Now, " yyyy hh:nn:ss ") & psTimeZoneBias & vbCrLf
  1052.         ' MIME headers...
  1053.         If etEncodeType = MIME_ENCODE Then
  1054.             ' create a Unique-Boundary string for multi-part MIME encoding
  1055.             strBoundry = "----_=_NextPart_000_" & Right$("00000000" & Hex$(Date), 8) & "." & Right$("00000000" & Hex$(CLng(Time * 10 ^ 8)), 8)
  1056.             
  1057.             .SendData "MIME-Version: 1.0" & vbCrLf
  1058.             If etPriority <> NORMAL_PRIORITY Then
  1059.                 .SendData "X-Priority: " & Trim$(Str$(etPriority)) & vbCrLf
  1060.                 .SendData "X-MSMail-Priority: " & psPriority & vbCrLf
  1061.             End If
  1062.             If pbReceipt Then .SendData "Disposition-Notification-To: " & sSenderName & vbCrLf
  1063.             ' if it's multi part send the boundry info
  1064.             If bMimeMultiPart Then
  1065.                 If bRelatedLinks Then
  1066.                     .SendData "Content-Type: multipart/related;" & vbCrLf
  1067.                   Else
  1068.                     .SendData "Content-Type: multipart/mixed;" & vbCrLf
  1069.                 End If
  1070.                 .SendData vbTab & "boundary=" & """" & strBoundry & """" & vbCrLf & vbCrLf
  1071.                 .SendData "This is a multi-part message in MIME format." & vbCrLf & vbCrLf
  1072.                 ' send the MIME boundry and content headers for the message body
  1073.                 .SendData "--" & strBoundry & vbCrLf
  1074.             End If
  1075.             ' plain or html text...
  1076.             If pbHtmlText Then sTxt = "text/html;" Else sTxt = "text/plain;"
  1077.             .SendData "Content-Type: " & sTxt & vbCrLf
  1078.             .SendData vbTab & "charset=" & """" & CHAR_SET & """" & vbCrLf
  1079.             If pb8BitMail Then sTxt = "8bit" Else sTxt = "7bit"
  1080.             .SendData "Content-Transfer-Encoding: " & sTxt & vbCrLf
  1081.             ' if we're sending html & the user supplied the content base then send it too
  1082.             If pbHtmlText Then If Len(psContentBase) Then .SendData "Content-Base: " & """" & psContentBase & """" & vbCrLf
  1083.         End If
  1084.         .SendData vbCrLf & vbCrLf
  1085.         ' Send the message body
  1086.         .SendData utMail.sMailMessage & vbCrLf & vbCrLf & vbCrLf
  1087.         ' Send attachments, if any...
  1088.         For iCtr = 0 To utMail.lAttachCount - 1
  1089.             If utMail.bAttachCID(iCtr) Then
  1090.                 RaiseEvent Status("Sending Embedded File, " & utMail.sAttachNameOnly(iCtr) & "...")
  1091.               Else
  1092.                 RaiseEvent Status("Sending Attachment, " & utMail.sAttachNameOnly(iCtr) & "...")
  1093.             End If
  1094.             If etEncodeType = MIME_ENCODE Then
  1095.                 ' send the next MIME boundry & content headers
  1096.                 .SendData "--" & strBoundry & vbCrLf
  1097.                 .SendData "Content-Type: " & GetContentType(utMail.sAttachNameOnly(iCtr)) & ";" & vbCrLf
  1098.                 .SendData vbTab & "name=" & """" & utMail.sAttachNameOnly(iCtr) & """" & vbCrLf
  1099.                 .SendData "Content-Transfer-Encoding: base64" & vbCrLf
  1100.                 .SendData "Content-Disposition: attachment;" & vbCrLf
  1101.                 .SendData vbTab & "filename=" & """" & utMail.sAttachNameOnly(iCtr) & """" & vbCrLf
  1102.                 If (bRelatedLinks And utMail.bAttachCID(iCtr)) Then
  1103.                     .SendData "Content-ID: <" & utMail.sAttachNameOnly(iCtr) & ">" & vbCrLf
  1104.                 End If
  1105.                 .SendData vbCrLf
  1106.                 ' send the encoded file
  1107.                 EncodeAndSendFile utMail.sAttachment(iCtr), MIME_ENCODE
  1108.                 If pbExitImmediately Then Exit Sub
  1109.                 .SendData vbCrLf
  1110.               Else
  1111.                 ' start a UUEncode session
  1112.                 .SendData "begin 600 " & utMail.sAttachNameOnly(iCtr) & vbCrLf
  1113.                 ' send the encoded file
  1114.                 EncodeAndSendFile utMail.sAttachment(iCtr), UU_ENCODE
  1115.                 If pbExitImmediately Then Exit Sub
  1116.                 ' send the ending sequence
  1117.                 .SendData "end" & vbCrLf
  1118.             End If
  1119.             ' the sckMail Send buffer now holds the current file
  1120.             ' if its a large file, wait here for the buffer to
  1121.             ' empty before loading the next one
  1122.             Do While plBytesRemaining > 4096
  1123.                 ' timeout code...
  1124.                 fStart = Timer
  1125.                 ' Deal with timer being reset at Midnight
  1126.                 If fStart + plMessageTimeOut < 86400 Then
  1127.                     fTimeOut = fStart + plMessageTimeOut
  1128.                   Else
  1129.                     fTimeOut = (fStart - 86400) + plMessageTimeOut
  1130.                 End If
  1131.                 ' wait for a change in the send buffer
  1132.                 ' if it's changing, everything is OK
  1133.                 lSendBuffSize = plBytesRemaining
  1134.                 Do Until lSendBuffSize <> plBytesRemaining
  1135.                     If plBytesRemaining < 4096 Then Exit Do
  1136.                     If Timer >= fTimeOut Then
  1137.                         Timeout
  1138.                         Exit Sub
  1139.                     End If
  1140.                     Sleep (10)
  1141.                     DoEvents
  1142.                 Loop
  1143.             Loop
  1144.         Next iCtr
  1145.         If bMimeMultiPart = True Then
  1146.             ' send the MIME closing boundry header
  1147.             'Sleep (20)
  1148.             sckMail.SendData "--" & strBoundry & "--" & vbCrLf
  1149.         End If
  1150.         ' Send the 'end of mail' string
  1151.         pbRequestAccepted = False
  1152.         .SendData "." & vbCrLf
  1153.         If Not WaitUntilTrue(pbRequestAccepted, plMessageTimeOut, True) Then Exit Sub
  1154.     End With
  1155.     ' send completion notifications...
  1156.     RaiseEvent Progress(100)
  1157.     RaiseEvent Status("Transmission Complete...")
  1158.     ' if the Public Function Connect() was called,
  1159.     ' stay connected to the host, otherwise disconnect
  1160.     If Not pbManualDisconnect Then DisconnectFromHost
  1161.     RaiseEvent SendSuccesful
  1162. Exit Sub
  1163. Err_Send:
  1164.     ' add the error to the error collection
  1165.     AddError Err.Description
  1166.     SendFail
  1167. End Sub
  1168. Public Sub shutdown()
  1169.   ' stub function, here to maintain binary
  1170.   ' compatibility with previous versions.
  1171. End Sub
  1172. ' ******************************************************************************
  1173. ' *      Private Class Functions                                               *
  1174. ' ******************************************************************************
  1175. Private Sub AddError(ByVal ErrStr As String)
  1176.   ' add error string to the error collection
  1177.     On Local Error Resume Next
  1178.       pColErrors.Add ErrStr, ErrStr
  1179. End Sub
  1180. Private Function AddressStringToLong(ByVal tmp As String) As Long
  1181.   ' convert an ip address string to a long value
  1182.   '
  1183.   ' THIS CODE IS BASED ON FUNCTIONS
  1184.   ' WITHIN RICHARD DEEMING'S IP UTILITIES:
  1185.   ' http://www.freevbcode.com
  1186.   Dim sParts()    As String
  1187.     sParts = Split(tmp, ".")
  1188.     If UBound(sParts) <> 3 Then
  1189.         AddressStringToLong = 0
  1190.         Exit Function
  1191.     End If
  1192.     ' build the long value out of the
  1193.     ' hex of the extracted strings
  1194.     AddressStringToLong = Val("&H" & Right$("00" & Hex$(sParts(3)), 2) & _
  1195.                           Right$("00" & Hex$(sParts(2)), 2) & _
  1196.                           Right$("00" & Hex$(sParts(1)), 2) & _
  1197.                           Right$("00" & Hex$(sParts(0)), 2))
  1198. End Function
  1199. Private Function bInEXE() As Boolean
  1200.   ' ******************************************************************************
  1201.   '
  1202.   ' Synopsis:     Check if application is running in the VB IDE or stand alone EXE.
  1203.   '
  1204.   ' Parameters:   none
  1205.   '
  1206.   ' Return:       True if running in EXE, False if running in IDE
  1207.   '
  1208.   ' Description:
  1209.   '
  1210.   ' Debug.print 1/0 will error produce a divide by zero error if running in IDE.
  1211.   ' If running in exe debug.print statement will be ignored
  1212.   '
  1213.   ' ******************************************************************************
  1214.   ' modified version of Brian Gillham's code
  1215.   ' sample available at www.freevbcode.com
  1216.     On Local Error GoTo ErrorHandler
  1217.     Debug.Print 1 / 0                               ' this line will fail in the IDE
  1218.     bInEXE = True                                   ' this line will execute only in EXE or dll
  1219. Exit Function
  1220. ErrorHandler:
  1221.     bInEXE = False
  1222. End Function
  1223. Private Function ConnectToHost() As Boolean
  1224.   Dim iCtr            As Integer
  1225.   Dim sHello          As String
  1226.     If bInEXE Then On Local Error GoTo Connect_Error
  1227.     ' already connected?
  1228.     If sckMail.State = sckConnected Then
  1229.         ConnectToHost = True
  1230.         Exit Function
  1231.       ElseIf sckMail.State <> sckClosed Then
  1232.         sckMail.CloseSocket
  1233.     End If
  1234.     ' check the SMTP host
  1235.     If Len(psSMTPHost) = 0 Then
  1236.         psSMTPHost = MXQuery
  1237.         If Len(psSMTPHost) = 0 Then
  1238.             AddError ERR_INVALID_HOST
  1239.             Exit Function
  1240.         End If
  1241.     End If
  1242.     ' Pop3 Authentication first?
  1243.     If pbUsePopAuthentication Then
  1244.         RaiseEvent Status("Connecting to POP3 Server (" & Me.POP3Host & ")...")
  1245.         pbExitImmediately = False
  1246.         pbConnected = False
  1247.         pbPopAuthOk = False
  1248.         plPop3Status = 0
  1249.         If Len(psPop3Host) = 0 Then
  1250.             AddError ERR_INVALID_POP_HOST
  1251.             SendFail
  1252.             Exit Function
  1253.         End If
  1254.         ' open POP3 connection
  1255.         With sckMail
  1256.             .RemoteHost = psPop3Host
  1257.             .RemotePort = POP3_PORT
  1258.             For iCtr = 1 To plConnectRetry
  1259.                 If .State <> sckConnected Then
  1260.                     If .State = sckClosed Then .Connect
  1261.                     If WaitUntilTrue(pbConnected, plConnectTimeout, False) Then Exit For
  1262.                     If pbExitImmediately Then Exit Function
  1263.                     If .State = sckError Then .CloseSocket
  1264.                   Else
  1265.                     pbConnected = True
  1266.                     Exit For
  1267.                 End If
  1268.             Next iCtr
  1269.             ' data arival event responds automatically
  1270.             WaitUntilTrue pbPopAuthOk, plConnectTimeout, False
  1271.             .CloseSocket
  1272.         End With
  1273.         DoEvents
  1274.         If pbExitImmediately Then Exit Function
  1275.         RaiseEvent Status("POP3 Authentication Successful...")
  1276.     End If
  1277.     ' reset var's
  1278.     pbRequestAccepted = False
  1279.     pbDataOK = False
  1280.     pbAuthLoginSupported = False
  1281.     pbAuthMailFromOK = False
  1282.     pbAuthLoginSuccess = False
  1283.     pbExitImmediately = False
  1284.     ConnectToHost = False
  1285.     pbConnected = False
  1286.     ' open an SMTP session...
  1287.     With sckMail
  1288.         ' setup the port
  1289.         If .State <> sckClosed Then .CloseSocket
  1290.         .RemoteHost = psSMTPHost
  1291.         .RemotePort = plSMTPPort
  1292.         ' open a connection with the remote host
  1293.         ' try 'plConnectRetry' times before giving up
  1294.         RaiseEvent Status("Connecting to SMTP Server (" & Me.SMTPHost & ")...")
  1295.         For iCtr = 1 To plConnectRetry
  1296.             If .State <> sckConnected Then
  1297.                 If .State = sckClosed Then .Connect
  1298.                 If WaitUntilTrue(pbConnected, plConnectTimeout, False) Then Exit For
  1299.                 If pbExitImmediately Then Exit Function
  1300.                 If .State = sckError Then .CloseSocket
  1301.               Else
  1302.                 pbConnected = True
  1303.                 Exit For
  1304.             End If
  1305.         Next iCtr
  1306.         ' if the connect attempt failed, exit
  1307.         If Not pbConnected Or Not WaitUntilTrue(pbRequestAccepted, plConnectTimeout, False) Then
  1308.             Timeout
  1309.             Exit Function
  1310.         End If
  1311.         ' once a connection is established, say 'hello
  1312.         RaiseEvent Status("Initializing Communications...")
  1313.         pbRequestAccepted = False
  1314.         ' EHLO is the extended (ESMTP) hello command, HELO is the standard hello command
  1315.         If pbUseAuthentication Then sHello = "EHLO " Else sHello = "HELO "
  1316.         .SendData sHello & Mid$(utMail.sFromAddr, InStr(utMail.sFromAddr, "@") + 1) & vbCrLf
  1317.         If Not WaitUntilTrue(pbRequestAccepted, plConnectTimeout, True) Then Exit Function
  1318.         ' Login Authentication ...
  1319.         ' the 'EHLO" command will cause the host to send a list of supported extensions
  1320.         ' via a series of 250 replies, wait to see if 'Auth Logon' is listed. The Sub
  1321.         ' sckMail_DataArrival will set pbUseAuthentication = True if Auth Login is
  1322.         ' supported by the remote host. If it is supported, Sub sckMail_DataArrival will
  1323.         ' respond to the host's Username & Password requests (psUserName, psPassword).
  1324.         If pbUseAuthentication = True Then
  1325.             If WaitUntilTrue(pbAuthLoginSupported, 5, False) Then
  1326.                 RaiseEvent Status("Sending Login Authentication...")
  1327.                 .SendData "AUTH Login" & vbCrLf
  1328.                 If WaitUntilTrue(pbAuthLoginSuccess, 5, False) Then
  1329.                     RaiseEvent Status("Host Login OK!")
  1330.                   Else
  1331.                     RaiseEvent Status("Host Login Failed!")
  1332.                     Exit Function
  1333.                 End If
  1334.                 If pbExitImmediately Then Exit Function
  1335.               Else
  1336.                 RaiseEvent Status("Login Not Supported by Host, Continuing...")
  1337.             End If
  1338.         End If
  1339.     End With
  1340.     ConnectToHost = True
  1341. Connect_Error:
  1342. End Function
  1343. Private Function CText(sIn As String, Optional bAddQuotesIfNotConverted As Boolean = False) As String
  1344.   '   'B' or 'Q' encode an ASCII string, defined in RFC 2047...
  1345.   '   The "B" encoding is identical to the "BASE64" encoding defined by RFC 1521.
  1346.   '   The "Q" encoding is similar to the "Quoted-Printable" content-
  1347.   '   transfer-encoding defined in RFC 1521.  It is designed to allow text
  1348.   '   containing mostly ASCII characters to be decipherable on an ASCII
  1349.   '   terminal without decoding.
  1350.   '   perform both & return the smaller of the two
  1351.   Dim iPtr            As Integer
  1352.   Dim bNeedsEncoding  As Boolean
  1353.   Dim iMax            As Integer
  1354.   Dim sChr            As String
  1355.   Dim sLine           As String
  1356.   Dim sQCode          As String
  1357.   Dim sBCode          As String
  1358.   Dim bytTmp()        As Byte
  1359.     If bInEXE Then On Local Error GoTo Err_Qtext
  1360.     ' scan for 8bit characters
  1361.     bytTmp() = StrConv(sIn, vbFromUnicode)
  1362.     For iPtr = 0 To UBound(bytTmp)
  1363.         If bytTmp(iPtr) > 126 Then
  1364.             bNeedsEncoding = True
  1365.             Exit For
  1366.         End If
  1367.     Next iPtr
  1368.     If Not bNeedsEncoding Then
  1369.         If bAddQuotesIfNotConverted Then
  1370.             ' if its part of an address string it needs
  1371.             ' to be quoted if it's returned as plain text
  1372.             CText = """" & sIn & """"
  1373.           Else
  1374.             CText = sIn
  1375.         End If
  1376.         Exit Function
  1377.     End If
  1378.     ' Q encode
  1379.     iMax = 54
  1380.     For iPtr = 1 To Len(sIn)
  1381.         sChr = Mid$(sIn, iPtr, 1)
  1382.         Select Case Asc(sChr)
  1383.             ' pass printable ascii as is, except "=" "?" "_" " "
  1384.           Case 33 To 60, 62, 64 To 94, 96 To 126
  1385.             sLine = sLine & sChr
  1386.             ' convert space to underscore (for readability)
  1387.           Case 32
  1388.             sLine = sLine & "_"
  1389.             ' Q Code everything else
  1390.           Case Else
  1391.             sLine = sLine & "=" & Right$("00" & Hex$(Asc(sChr)), 2)
  1392.         End Select
  1393.         If Len(sLine) >= iMax Then
  1394.             sQCode = sQCode & Q_CODE_HDR & sLine & CODE_END
  1395.             If iPtr < Len(sIn) Then sQCode = sQCode & vbCrLf & vbTab
  1396.             sLine = ""
  1397.         End If
  1398.     Next iPtr
  1399.     sQCode = sQCode & Q_CODE_HDR & sLine & CODE_END
  1400.     ' B encode
  1401.     iMax = 42
  1402.     sLine = sIn
  1403.     Do While Len(sLine)
  1404.         ' encode a line, maximun lenght is 76 characters
  1405.         ' <header><base64encoded text><end><CrLf>
  1406.         sBCode = sBCode & B_CODE_HDR & EncodeBase64String(Mid$(sLine, 1, iMax))
  1407.         ' strip off the CrLf & add END_CODE , CrLF & Tab
  1408.         sBCode = Mid$(sBCode, 1, Len(sBCode) - 2) & CODE_END
  1409.         ' get ready for the next line
  1410.         sLine = Mid$(sLine, iMax + 1)
  1411.         If Len(sLine) Then sBCode = sBCode & vbCrLf & vbTab
  1412.     Loop
  1413.     If Len(sQCode) < Len(sBCode) Then
  1414.         CText = sQCode
  1415.       Else
  1416.         CText = sBCode
  1417.     End If
  1418. Exit Function
  1419. Err_Qtext:
  1420.     CText = sIn
  1421. End Function
  1422. Public Function DecodeBase64String(ByVal str2Decode As String) As String
  1423.   ' ******************************************************************************
  1424.   '
  1425.   ' Synopsis:     Decode a Base 64 string
  1426.   '
  1427.   ' Parameters:   str2Decode  - The base 64 encoded input string
  1428.   '
  1429.   ' Return:       decoded string
  1430.   '
  1431.   ' Description:
  1432.   ' Coerce 4 base 64 encoded bytes into 3 decoded bytes by converting 4, 6 bit
  1433.   ' values (0 to 63) into 3, 8 bit values. Transform the 8 bit value into its
  1434.   ' ascii character equivalent. Stop converting at the end of the input string
  1435.   ' or when the first '=' (equal sign) is encountered.
  1436.   '
  1437.   ' ******************************************************************************
  1438.   Dim lPtr            As Long
  1439.   Dim iValue          As Integer
  1440.   Dim iLen            As Integer
  1441.   Dim iCtr            As Integer
  1442.   Dim Bits(1 To 4)    As Byte
  1443.   Dim strDecode       As String
  1444.     ' for each 4 character group....
  1445.     For lPtr = 1 To Len(str2Decode) Step 4
  1446.         iLen = 4
  1447.         For iCtr = 0 To 3
  1448.             ' retrive the base 64 value, 4 at a time
  1449.             iValue = InStr(1, BASE64CHR, Mid$(str2Decode, lPtr + iCtr, 1), vbBinaryCompare)
  1450.             Select Case iValue
  1451.                 ' A~Za~z0~9+/
  1452.               Case 1 To 64
  1453.                 Bits(iCtr + 1) = iValue - 1
  1454.                 ' =
  1455.               Case 65
  1456.                 iLen = iCtr
  1457.                 Exit For
  1458.                 ' not found
  1459.               Case 0
  1460.                 Exit Function
  1461.             End Select
  1462.         Next iCtr
  1463.         ' convert the 4, 6 bit values into 3, 8 bit values
  1464.         Bits(1) = Bits(1) * &H4 + (Bits(2) And &H30)  &H10
  1465.         Bits(2) = (Bits(2) And &HF) * &H10 + (Bits(3) And &H3C)  &H4
  1466.         Bits(3) = (Bits(3) And &H3) * &H40 + Bits(4)
  1467.         ' add the three new characters to the output string
  1468.         For iCtr = 1 To iLen - 1
  1469.             strDecode = strDecode & Chr$(Bits(iCtr))
  1470.         Next iCtr
  1471.     Next lPtr
  1472.     DecodeBase64String = strDecode
  1473. End Function
  1474. Private Sub DisconnectFromHost()
  1475.     With sckMail
  1476.         ' notify the user
  1477.         If .State <> sckClosed Then RaiseEvent Status("Closing Connection...")
  1478.         ' tell the host we're closing the connection...
  1479.         If .State = sckConnected Then
  1480.             pbRequestAccepted = False
  1481.             .SendData "QUIT" & vbCrLf
  1482.             WaitUntilTrue pbRequestAccepted, 2, False
  1483.         End If
  1484.         ' close the connection
  1485.         .CloseSocket
  1486.     End With
  1487. End Sub
  1488. Private Sub EncodeAndSendFile(ByVal strFile As String, ByVal Encode As ENCODE_METHOD)
  1489.   ' ******************************************************************************
  1490.   '
  1491.   ' Synopsis:     Send a file attachment via an open socket
  1492.   '
  1493.   ' Parameters:   strFile  - The input file name
  1494.   '               Encode   -  type of encoding to use; MIME or UU
  1495.   '
  1496.   ' Return:       nothing
  1497.   '
  1498.   ' Description:
  1499.   ' Open the file & read characters in. Send the characters through the
  1500.   ' appropriate encoder, either MIME (Base64) or UUEncode, before
  1501.   ' tranmission via an open socket.
  1502.   '
  1503.   ' ******************************************************************************
  1504.   Dim hFile               As Integer              ' file handle
  1505.   Dim sValue              As String               ' temp string buffer
  1506.   Dim bInFile()           As Byte                 ' byte array file buffer
  1507.   Dim lEventCtr           As Long                 ' counter
  1508.   Dim lChunkSize          As Long                 ' number of bytes to get
  1509.   Dim lNumBytes           As Long                 ' file pointer
  1510.     ' in case there's a file io error
  1511.     If bInEXE Then On Local Error GoTo File_Error
  1512.     ' open the file
  1513.     hFile = FreeFile
  1514.     Open strFile For Binary Access Read As #hFile
  1515.     ' bytes to read
  1516.     lNumBytes = LOF(hFile)
  1517.     If Encode = MIME_ENCODE Then
  1518.         Do While lNumBytes
  1519.             ' set input buffer size, MUST be a multiple of 57
  1520.             lChunkSize = IIf(lNumBytes > 11400, 11400, lNumBytes)
  1521.             ' set to true in sckMail.SendProgress Event
  1522.             pbSendProgress = False
  1523.             ' read & Base 64 encode a group of characters
  1524.             ' changed from 'InputB' to 'Get' to improve performance
  1525.             ' on Netware servers/clients, thanks to Richard Gatewood.
  1526.             'bInFile = InputB(lChunkSize, #hFile)       ' nw change (remove)
  1527.             ReDim bInFile(lChunkSize - 1)               ' nw change (add)
  1528.             Get #hFile, , bInFile()                     ' nw change (add)
  1529.             If sckMail.State = sckConnected Then
  1530.                 sckMail.SendData EncodeBase64Byte(bInFile)
  1531.               Else
  1532.                 Err.Raise 0, , "Socket not Open"
  1533.             End If
  1534.             ' adjust file pointer
  1535.             lNumBytes = lNumBytes - lChunkSize
  1536.             'DoEvents
  1537.             ' wait for sckMail.SendProgress Event to fire
  1538.             ' suggested by David Hill to fix an issue with a very fast machine
  1539.             WaitUntilTrue pbSendProgress, 2, False
  1540.         Loop
  1541.       ElseIf Encode = UU_ENCODE Then
  1542.         Do While lNumBytes
  1543.             ' set input buffer size, MUST be 45
  1544.             lChunkSize = IIf(lNumBytes > 45, 45, lNumBytes)
  1545.             ' read & UU encode a line of characters
  1546.             sValue = Input(lChunkSize, #hFile)
  1547.             If sckMail.State = sckConnected Then
  1548.                 sckMail.SendData UUEncodeString(sValue) & vbCrLf
  1549.               Else
  1550.                 Err.Raise 0, , "Socket not Open"
  1551.             End If
  1552.             ' adjust file pointer
  1553.             lNumBytes = lNumBytes - lChunkSize
  1554.             ' DoEvents (occasionally)
  1555.             lEventCtr = lEventCtr + 1
  1556.             If lEventCtr Mod 50 = 0 Then DoEvents
  1557.         Loop
  1558.     End If
  1559. File_Done:
  1560.     Close #hFile
  1561. Exit Sub
  1562. File_Error:
  1563.     AddError Err.Description
  1564.     SendFail
  1565.     pbExitImmediately = True
  1566.     Resume File_Done
  1567. End Sub
  1568. Private Function EncodeBase64Byte(InArray() As Byte) As Byte()
  1569.   '******************************************************************************
  1570.   '
  1571.   ' Synopsis:     Base 64 encode a byte array
  1572.   '
  1573.   ' Parameters:   InArray  - The input byte array
  1574.   '
  1575.   ' Return:       encoded byte array
  1576.   '
  1577.   ' Description:
  1578.   '   Convert a byte array to a Base 64 encoded byte array. Coerce 3 bytes into
  1579.   '   4 by converting 3, 8 bit bytes into 4, 6 bit values. Each 6 bit value
  1580.   '   (0 to 63) is then used as a pointer into a base64 byte array to derive a
  1581.   '   character.
  1582.   '
  1583.   '******************************************************************************
  1584.   Dim lInPtr              As Long         ' pointer into input array
  1585.   Dim lOutPtr             As Long         ' pointer into output array
  1586.   Dim OutArray()          As Byte         ' output byte array buffer
  1587.   Dim lLen                As Long         ' number of extra bytes past 3 byte boundry
  1588.   Dim iNewLine            As Long         ' line counter
  1589.     ' if size of input array is not a multiple of 3,
  1590.     ' increase it to the next multiple of 3
  1591.     lLen = (UBound(InArray) - LBound(InArray) + 1) Mod 3
  1592.     If lLen Then
  1593.         lLen = 3 - lLen
  1594.         ReDim Preserve InArray(UBound(InArray) + lLen)
  1595.     End If
  1596.     ' create an output buffer
  1597.     ReDim OutArray(UBound(InArray) * 2 + 100)
  1598.     ' step through the input array, 3 bytes at a time
  1599.     For lInPtr = 0 To UBound(InArray) Step 3
  1600.         ' add CrLf as required
  1601.         If iNewLine = 19 Then
  1602.             OutArray(lOutPtr) = 13
  1603.             OutArray(lOutPtr + 1) = 10
  1604.             lOutPtr = lOutPtr + 2
  1605.             iNewLine = 0
  1606.         End If
  1607.         ' convert 3 bytes into 4 base 64 encoded bytes
  1608.         OutArray(lOutPtr) = pbBase64Byt((InArray(lInPtr) And &HFC)  4)
  1609.         OutArray(lOutPtr + 1) = pbBase64Byt((InArray(lInPtr) And &H3) * &H10 + (InArray(lInPtr + 1) And &HF0)  &H10)
  1610.         OutArray(lOutPtr + 2) = pbBase64Byt((InArray(lInPtr + 1) And &HF) * 4 + (InArray(lInPtr + 2) And &HC0)  &H40)
  1611.         OutArray(lOutPtr + 3) = pbBase64Byt(InArray(lInPtr + 2) And &H3F)
  1612.         ' update pointers
  1613.         lOutPtr = lOutPtr + 4
  1614.         iNewLine = iNewLine + 1
  1615.     Next lInPtr
  1616.     ' add terminator '=' as required
  1617.     Select Case lLen
  1618.       Case 1
  1619.         OutArray(lOutPtr - 1) = 61
  1620.       Case 2
  1621.         OutArray(lOutPtr - 1) = 61
  1622.         OutArray(lOutPtr - 2) = 61
  1623.     End Select
  1624.     ' add CrLf if not already there
  1625.     If OutArray(lOutPtr - 2) <> 13 Then
  1626.         OutArray(lOutPtr) = 13
  1627.         OutArray(lOutPtr + 1) = 10
  1628.         lOutPtr = lOutPtr + 2
  1629.     End If
  1630.     ' resize output buffer and return
  1631.     ReDim Preserve OutArray(lOutPtr - 1)
  1632.     EncodeBase64Byte = OutArray
  1633. End Function
  1634. Private Function EncodeBase64String(ByRef str2Encode As String) As String
  1635.   ' ******************************************************************************
  1636.   '
  1637.   ' Synopsis:     Base 64 encode a string
  1638.   '
  1639.   ' Parameters:   str2Encode  - The input string
  1640.   '
  1641.   ' Return:       encoded string
  1642.   '
  1643.   ' Description:
  1644.   ' Convert a string to a byte array and pass to EncodeBase64Byte function (above)
  1645.   ' for Base64 conversion. Convert byte array back to a string and return.
  1646.   '
  1647.   ' ******************************************************************************
  1648.   Dim tmpByte()   As Byte
  1649.     If Len(str2Encode) Then
  1650.         ' convert string to byte array
  1651.         tmpByte = StrConv(str2Encode, vbFromUnicode)
  1652.         ' pass to the byte array encoder
  1653.         tmpByte = EncodeBase64Byte(tmpByte)
  1654.         ' convert back to string & return
  1655.         EncodeBase64String = StrConv(tmpByte, vbUnicode)
  1656.     End If
  1657. End Function
  1658. Private Function EstimateMailSize() As Long
  1659.   ' ******************************************************************************
  1660.   '
  1661.   ' Synopsis:     Estimate the size (number of bytes) of the mail message
  1662.   '
  1663.   ' Parameters:   none
  1664.   '
  1665.   ' Return:       long - number of bytes
  1666.   '
  1667.   ' Description:
  1668.   ' Estimate the size in bytes of the mail message being sent. Include the
  1669.   ' message body, headers, attachments, etc. Account for type of encoding.
  1670.   ' The result is used to calculate send progress.
  1671.   '
  1672.   ' ******************************************************************************
  1673.   Dim lNumBytes       As Long
  1674.   Dim iCtr            As Integer
  1675.     lNumBytes = 93
  1676.     ' Mail From
  1677.     lNumBytes = lNumBytes + Len(utMail.sFromAddr)
  1678.     ' login authentication
  1679.     If pbUseAuthentication Then
  1680.         lNumBytes = lNumBytes + 25 + Len(utMail.sFromAddr)
  1681.         If Len(psUserName) > 0 Then lNumBytes = lNumBytes + (Len(psUserName) * 4  3)
  1682.         If Len(psPassword) > 0 Then lNumBytes = lNumBytes + (Len(psPassword) * 4  3)
  1683.     End If
  1684.     ' To: recipients
  1685.     For iCtr = 0 To UBound(utMail.sToAddr)
  1686.         lNumBytes = lNumBytes + 15 + Len(utMail.sToAddr(iCtr)) * 2  ' sent twice, RCPT & 'To:' header
  1687.         If iCtr > 0 Then lNumBytes = lNumBytes + 6
  1688.     Next iCtr
  1689.     ' To Display
  1690.     For iCtr = 0 To UBound(utMail.sToDisplayName)
  1691.         lNumBytes = lNumBytes + Len(utMail.sToDisplayName(iCtr)) + 11
  1692.     Next iCtr
  1693.     ' Cc: recipients
  1694.     For iCtr = 0 To UBound(utMail.sCcAddr)
  1695.         lNumBytes = lNumBytes + 15 + Len(utMail.sCcAddr(iCtr)) * 2  ' sent twice, RCPT & 'Cc:' header
  1696.         If iCtr > 0 Then lNumBytes = lNumBytes + 6                  ' header
  1697.     Next iCtr
  1698.     ' Cc Display
  1699.     For iCtr = 0 To UBound(utMail.sCcDisplayName)
  1700.         lNumBytes = lNumBytes + Len(utMail.sCcDisplayName(iCtr)) + 11
  1701.     Next iCtr
  1702.     ' Bcc: recipients
  1703.     For iCtr = 0 To UBound(utMail.sBccAddr)
  1704.         lNumBytes = lNumBytes + 15 + Len(utMail.sBccAddr(iCtr))  ' RCPT & 'Bcc:' header
  1705.         If iCtr > 0 Then lNumBytes = lNumBytes + 6               ' header
  1706.     Next iCtr
  1707.     ' From:
  1708.     If Len(utMail.sFromDisplayName) Then lNumBytes = lNumBytes + Len(utMail.sFromDisplayName) + 3
  1709.     lNumBytes = lNumBytes + Len(utMail.sFromAddr)
  1710.     ' ReplyTo
  1711.     If Len(utMail.sReplyToAddr) Then lNumBytes = lNumBytes + Len(utMail.sReplyToAddr) + 14
  1712.     ' Subject
  1713.     lNumBytes = lNumBytes + Len(utMail.sSubject)
  1714.     ' Message body
  1715.     lNumBytes = lNumBytes + Len(utMail.sMailMessage)
  1716.     ' MIME headers....
  1717.     If etEncodeType = MIME_ENCODE Then
  1718.         lNumBytes = lNumBytes + 64
  1719.         If pbHtmlText = True And Len(psContentBase) > 0 Then lNumBytes = lNumBytes + 18 + Len(psContentBase)
  1720.         If pbReceipt Then lNumBytes = lNumBytes + 36 + Len(utMail.sFromDisplayName) + Len(utMail.sFromAddr)
  1721.     End If
  1722.     ' attachments
  1723.     If utMail.lAttachCount > 0 Then
  1724.         If etEncodeType = MIME_ENCODE Then
  1725.             lNumBytes = lNumBytes + utMail.lAttachFileSize * 4  3 + 42  ' length of encoded file
  1726.             lNumBytes = lNumBytes + (utMail.lAttachFileSize  57) * 2    ' add CrLf for each line
  1727.             lNumBytes = lNumBytes + utMail.lAttachNameSize * 2           ' add file name twice
  1728.             lNumBytes = lNumBytes + (utMail.lAttachCount * 182)          ' attachment header per file
  1729.             lNumBytes = lNumBytes + 290                                  ' additional MIME headers
  1730.           Else
  1731.             lNumBytes = lNumBytes + utMail.lAttachFileSize * 4  3       ' length of encoded file
  1732.             lNumBytes = lNumBytes + (utMail.lAttachFileSize  45) * 3    ' add length char + CrLf for each line
  1733.             lNumBytes = lNumBytes + utMail.lAttachNameSize               ' add file name once
  1734.             lNumBytes = lNumBytes + (utMail.lAttachCount * 20)           ' attachment header per file
  1735.         End If
  1736.     End If
  1737.     EstimateMailSize = lNumBytes
  1738. End Function
  1739. Private Function FormatMail(ByVal strIn As String) As String
  1740.   ' ******************************************************************************
  1741.   '
  1742.   ' Synopsis:     Re-format text lines per RFC 821
  1743.   '
  1744.   ' Parameters:   strIn   - The input string to be formated
  1745.   '
  1746.   ' Return:       re-formated string
  1747.   '
  1748.   ' Description:
  1749.   ' RFC 821 places the following restrictions on user text:
  1750.   ' 1) Before sending a line of mail text begining with a '.
  1751.   ' the sender will add an additional '.
  1752.   '
  1753.   ' 2) The receiver checks each line of mail text, if a line is single '.
  1754.   ' it is the end of the mail message. If the first character is
  1755.   ' a '.' and there are other characters on the line, the first '.
  1756.   ' is deleted.
  1757.   '
  1758.   ' 3) The maximum line lenght will not exceed 1000 characters
  1759.   '
  1760.   ' ******************************************************************************
  1761.   Dim sTextLine()     As String
  1762.   Dim sRemainder      As String
  1763.   Dim sNewLine        As String
  1764.   Dim sDelimiter      As String
  1765.   Dim lPtr            As Long
  1766.   Dim lSplit          As Long
  1767.     If Len(strIn) = 0 Then Exit Function
  1768.     ' Select the correct delimiter character
  1769.     If InStr(strIn, vbCrLf) Then
  1770.         sDelimiter = vbCrLf
  1771.       ElseIf InStr(strIn, vbCr) Then
  1772.         sDelimiter = vbCr
  1773.       Else
  1774.         sDelimiter = vbNullString
  1775.     End If
  1776.     ' split the text into seperate lines
  1777.     sTextLine() = Split(strIn, sDelimiter)
  1778.     ' process each line
  1779.     For lPtr = 0 To UBound(sTextLine)
  1780.         ' check for lines starting with a '.
  1781.         ' when found, add a second '.
  1782.         If Left$(sTextLine(lPtr), 1) = "." Then sTextLine(lPtr) = "." & sTextLine(lPtr)
  1783.         ' check that the line is not too long (account for 2 extra characters - vbCrLf)
  1784.         ' break into smaller elements as required
  1785.         If Len(sTextLine(lPtr)) > MAX_TEXTLINE_LEN - 2 Then
  1786.             sRemainder = sTextLine(lPtr)
  1787.             sNewLine = vbNullString
  1788.             If sDelimiter = vbNullString Then sDelimiter = vbCrLf
  1789.             Do While Len(sRemainder) > MAX_TEXTLINE_LEN - 2
  1790.                 ' try to split at a space character, if not then split at MAX_TEXTLINE_LEN - 2
  1791.                 lSplit = InStrRev(sRemainder, " ", MAX_TEXTLINE_LEN - 2)
  1792.                 If lSplit = 0 Then lSplit = MAX_TEXTLINE_LEN - 2
  1793.                 ' insert a vbCrLf at the split point
  1794.                 sNewLine = sNewLine & Mid$(sRemainder, 1, lSplit) & sDelimiter
  1795.                 sRemainder = Mid$(sRemainder, lSplit + 1)
  1796.             Loop
  1797.             sTextLine(lPtr) = sNewLine & sRemainder
  1798.         End If
  1799.     Next lPtr
  1800.     FormatMail = Join(sTextLine, sDelimiter)
  1801. End Function
  1802. Private Function GetAttachCID() As Boolean
  1803.   ' search the email body for tags with filenames that match the list of attached
  1804.   ' filenames, replace the path with a 'cid' and flag the array as having a valid CID
  1805.   ' example: <IMG SRC="/images/somefile.jpg"> is replaced with <IMG SRC="CID:somefile.jpg">
  1806.   Dim iCtr            As Integer
  1807.   Dim lPtr            As Long
  1808.   Dim lEndFirstPart   As Long
  1809.   Dim lStartLastPart  As Long
  1810.   Dim lQuotePos       As Long
  1811.   Dim lEqualPos       As Long
  1812.   Dim lNextPos        As Long
  1813.   Dim lGtPos          As Long
  1814.   Dim lLtPos          As Long
  1815.     If utMail.lAttachCount < 1 Then Exit Function
  1816.     If Not pbHtmlText Then Exit Function
  1817.     ' for each attached file
  1818.     For iCtr = 0 To utMail.lAttachCount - 1
  1819.         ' find the first occurance
  1820.         lPtr = InStr(1, utMail.sMailMessage, utMail.sAttachNameOnly(iCtr), vbTextCompare)
  1821.         Do While lPtr
  1822.             ' found an occurance of the file name,
  1823.             ' is it part of a tag?
  1824.             lLtPos = InStrRev(utMail.sMailMessage, "<", lPtr)
  1825.             lGtPos = IIf(lLtPos > 0, InStr(lLtPos, utMail.sMailMessage, ">"), 0)
  1826.             If lLtPos > 0 And lGtPos > 0 And lGtPos > lPtr And lLtPos < lPtr Then
  1827.                 ' yes it's part of an HTML tag
  1828.                 ' find the equal sign & quote if any exists
  1829.                 lEqualPos = InStrRev(utMail.sMailMessage, "=", lPtr)
  1830.                 lQuotePos = InStr(lEqualPos, utMail.sMailMessage, """")
  1831.                 ' first part
  1832.                 If lQuotePos > 0 And lQuotePos < lPtr Then
  1833.                     lEndFirstPart = lQuotePos
  1834.                   Else
  1835.                     lEndFirstPart = lEqualPos
  1836.                 End If
  1837.                 ' last part
  1838.                 lStartLastPart = lPtr + Len(utMail.sAttachNameOnly(iCtr))
  1839.                 ' replace with "CID:somefile.jpg"
  1840.                 utMail.sMailMessage = Mid$(utMail.sMailMessage, 1, lEndFirstPart) & _
  1841.                                       "cid:" & utMail.sAttachNameOnly(iCtr) & _
  1842.                                       Mid$(utMail.sMailMessage, lStartLastPart)
  1843.                 utMail.bAttachCID(iCtr) = True
  1844.                 GetAttachCID = True
  1845.                 lNextPos = lEndFirstPart + Len(utMail.sAttachNameOnly(iCtr)) + 4
  1846.               Else
  1847.                 lNextPos = lPtr + Len(utMail.sAttachNameOnly(iCtr))
  1848.             End If
  1849.             ' find the next one
  1850.             lPtr = InStr(lNextPos, utMail.sMailMessage, utMail.sAttachNameOnly(iCtr), vbTextCompare)
  1851.         Loop
  1852.     Next iCtr
  1853. End Function
  1854. Private Function IsDottedQuad(ByVal HostString As String) As Boolean
  1855.   ' verify that a string is 'xxx.xxx.xxx.xxx' format
  1856.   Dim sSplit()        As String
  1857.   Dim iCtr            As Integer
  1858.     ' split at the "."
  1859.     sSplit = Split(HostString, ".")
  1860.     ' should be 4 elements
  1861.     If UBound(sSplit) <> 3 Then Exit Function
  1862.     ' check each element
  1863.     For iCtr = 0 To 3
  1864.         ' should be numeric
  1865.         If Not IsNumeric(sSplit(iCtr)) Then Exit Function
  1866.         ' range check
  1867.         If iCtr = 0 Then
  1868.             If Val(sSplit(iCtr)) > 239 Then Exit Function
  1869.           Else
  1870.             If Val(sSplit(iCtr)) > 255 Then Exit Function
  1871.         End If
  1872.     Next iCtr
  1873.     IsDottedQuad = True
  1874. End Function
  1875. Private Function IsValidIPHost(ByVal HostString As String) As Boolean
  1876.   ' validate a host string
  1877.   Dim sHost               As String
  1878.   Dim sSplit()            As String
  1879.     sHost = UCase$(Trim$(HostString))
  1880.     ' if it's a dotted quad it's OK
  1881.     If IsDottedQuad(sHost) Then
  1882.         IsValidIPHost = True
  1883.         Exit Function
  1884.     End If
  1885.     sSplit = Split(sHost, ".")
  1886.     ' it's dotted quad, top level domain?
  1887.     If UBound(sSplit) > 0 And InStr(TOP_DOMAINS, sSplit(UBound(sSplit))) > 0 Then
  1888.         IsValidIPHost = True
  1889.         Exit Function
  1890.     End If
  1891. End Function
  1892. Private Function RegGet(ByVal sSettingName As String, ByVal sDefaultValue As String) As String
  1893.     If bInEXE Then On Local Error GoTo ERR_RegGet
  1894.     ' get registry setting
  1895.     RegGet = GetSetting(REG_KEY, SETTINGS_KEY, sSettingName, sDefaultValue)
  1896. Exit Function
  1897. ERR_RegGet:
  1898.     RegGet = sDefaultValue
  1899. End Function
  1900. Private Sub RegSave(ByVal sSettingName As String, ByVal sNewValue As String)
  1901.     If bInEXE Then On Local Error GoTo ERR_RegSave
  1902.     ' save registry setting
  1903.     If pbPersistentSettings Then SaveSetting REG_KEY, SETTINGS_KEY, sSettingName, sNewValue
  1904. ERR_RegSave:
  1905. End Sub
  1906. Private Sub RemoveError(ByVal ErrStr As String)
  1907.   ' remove an error string from the error collection
  1908.   Dim I   As Long
  1909.     On Local Error Resume Next
  1910.       ' walk the collection looking for the string to remove
  1911.       For I = 1 To pColErrors.Count
  1912.           If pColErrors(I) = ErrStr Then pColErrors.Remove ErrStr
  1913.       Next I
  1914. End Sub
  1915. Private Sub SendFail()
  1916.   Dim iCtr            As Integer
  1917.   Dim sErrorString    As String
  1918.     ' report all errors to the user
  1919.     For iCtr = 1 To pColErrors.Count
  1920.         sErrorString = sErrorString & pColErrors(iCtr) & vbCrLf
  1921.     Next iCtr
  1922.     RaiseEvent SendFailed(sErrorString)
  1923.     ' close the connection with the remote host
  1924.     If sckMail.State <> sckClosed Then DisconnectFromHost
  1925.     ' set flag to exit 'Send' Sub without further processing
  1926.     pbExitImmediately = True
  1927.     ' clear all errors
  1928.     Set pColErrors = New Collection
  1929. End Sub
  1930. Private Sub SocketsCleanup()
  1931.   ' Cleanup Windows sockets
  1932.   '
  1933.   ' THIS CODE IS BASED ON FUNCTIONS
  1934.   ' WITHIN RICHARD DEEMING'S IP UTILITIES:
  1935.   ' http://www.freevbcode.com
  1936.     WSACleanup
  1937. End Sub
  1938. Private Function SocketsInitialize() As Boolean
  1939.   ' Initialize Windows sockets
  1940.   '
  1941.   ' THIS CODE IS BASED ON FUNCTIONS
  1942.   ' WITHIN RICHARD DEEMING'S IP UTILITIES:
  1943.   ' http://www.freevbcode.com
  1944.   Dim WSAD            As WSADATA
  1945.     SocketsInitialize = False
  1946.     If WSAStartup(WS_VERSION_REQD, WSAD) <> ERROR_SUCCESS Then Exit Function
  1947.     If WSAD.wMaxSockets < MIN_SOCKETS_REQD Then Exit Function
  1948.     SocketsInitialize = True
  1949. End Function
  1950. Private Sub TrimWhiteSpace(sInArray() As String)
  1951.   Dim I   As Long
  1952.     For I = LBound(sInArray) To UBound(sInArray)
  1953.         sInArray(I) = Trim$(sInArray(I))
  1954.         sInArray(I) = Replace(sInArray(I), vbCrLf, vbNullString)
  1955.         sInArray(I) = Replace(sInArray(I), vbTab, vbNullString)
  1956.     Next I
  1957. End Sub
  1958. Private Sub Timeout()
  1959.   ' time out occured, add the 'Timeout' error
  1960.   ' to the error collection
  1961.     AddError ERR_TIMEOUT
  1962.     SendFail
  1963. End Sub
  1964. Private Function UUEncodeString(ByRef str2UUEncode As String) As String
  1965.   ' ******************************************************************************
  1966.   '
  1967.   ' Synopsis:     UUEncode a string
  1968.   '
  1969.   ' Parameters:   str2UUEncode  - The input string
  1970.   '
  1971.   ' Return:       encoded string
  1972.   '
  1973.   ' Description:
  1974.   ' UU Encode a string. Coerce 3 bytes into 4 by converting 3, 8 bit bytes into
  1975.   ' 4, 6 bit values. Each 6 bit value (0 to 63) is then used as a pointer into
  1976.   ' the UUEncode string array to derive the correct character. The string will
  1977.   ' be a multiple of 4 bytes in lenght after conversion, padded with '=' as
  1978.   ' required. The line length will be encoded as a leading character
  1979.   ' (same 0 to 63 encoding) in the return string.
  1980.   '
  1981.   ' ******************************************************************************
  1982.   Dim sValue              As String
  1983.   Dim lPtr                As Long
  1984.   Dim lCtr                As Long
  1985.   Dim lLen                As Long
  1986.   Dim lLineLen            As Long
  1987.   Dim sEncoded            As String
  1988.   Dim Bits8(1 To 3)       As Byte
  1989.   Dim Bits6(1 To 4)       As Byte
  1990.     lLineLen = Len(str2UUEncode)
  1991.     ' lines are limited to 63
  1992.     If lLineLen > 63 Then Exit Function
  1993.     For lCtr = 1 To Len(str2UUEncode) Step 3
  1994.         ' Get 3 characters
  1995.         sValue = Mid$(str2UUEncode, lCtr, 3)
  1996.         lLen = Len(sValue)
  1997.         ' Move string data into a byte array, then
  1998.         ' swap bits to create 4, 6 bit values (0-63)
  1999.         If lLen < 3 Then Erase Bits8
  2000.         CopyMemory Bits8(1), ByVal sValue, lLen
  2001.         Bits6(1) = (Bits8(1) And &HFC)  &H4
  2002.         Bits6(2) = (Bits8(1) And &H3) * &H10 + (Bits8(2) And &HF0)  &H10
  2003.         Bits6(3) = (Bits8(2) And &HF) * &H4 + (Bits8(3) And &HC0)  &H40
  2004.         Bits6(4) = Bits8(3) And &H3F
  2005.         ' Encode new 4 byte string by selecting a character from
  2006.         ' the array. Length is determined by 'lLen' to make sure
  2007.         ' the file attachment is the right length
  2008.         For lPtr = 1 To lLen + 1
  2009.             sEncoded = sEncoded & psUUEncodeChr(Bits6(lPtr))
  2010.         Next lPtr
  2011.     Next lCtr
  2012.     ' add the line length character
  2013.     sEncoded = psUUEncodeChr(lLineLen) & sEncoded
  2014.     ' The decoder expects the size to be a multiple of 4 bytes.
  2015.     ' Possible sizes for the last packet are: 2, 3 & 4.
  2016.     Select Case lLen + 1
  2017.       Case 2
  2018.         sEncoded = sEncoded & "=="          ' send two pad characters
  2019.       Case 3
  2020.         sEncoded = sEncoded & "="           ' send one pad character
  2021.         ' no pad characers needed
  2022.     End Select
  2023.     UUEncodeString = sEncoded
  2024. End Function
  2025. Private Sub ValidateAddress(ByVal sRecip As String, ByVal sError As String)
  2026.   ' Validate Recipient, Cc and Bcc email address
  2027.   ' Appropriate validation methods for are:
  2028.   ' VALIDATE_NONE, VALIDATE_SYNTAX
  2029.   Dim iPtr            As Integer
  2030.   Dim sRecipArray()   As String
  2031.     RemoveError sError
  2032.     ' if VALIDATE_SYNTAX...
  2033.     If etEmailValidation = VALIDATE_SYNTAX Then
  2034.         ' split components into an array
  2035.         sRecipArray = Split(sRecip, psDelimiter)
  2036.         For iPtr = 0 To UBound(sRecipArray)
  2037.             ' validate address...
  2038.             If IsValidEmailAddress(sRecipArray(iPtr)) = False Then
  2039.                 AddError sError
  2040.                 Exit For
  2041.             End If
  2042.         Next iPtr
  2043.     End If
  2044. End Sub
  2045. Private Function WaitUntilTrue(ByRef Flag As Boolean, ByVal SecondsToWait As Long, Optional ByVal bRaiseTimeOutError As Boolean = True) As Boolean
  2046.   Dim fStart              As Single
  2047.   Dim fTimetoQuit         As Single
  2048.     If SecondsToWait < 1 Then Exit Function
  2049.     fStart = Timer
  2050.     ' Deal with timer being reset at Midnight
  2051.     If fStart + SecondsToWait < 86400 Then
  2052.         fTimetoQuit = fStart + SecondsToWait
  2053.       Else
  2054.         fTimetoQuit = (fStart - 86400) + SecondsToWait
  2055.     End If
  2056.     Do Until Flag = True
  2057.         If Timer >= fTimetoQuit Then
  2058.             If bRaiseTimeOutError Then Timeout
  2059.             Exit Function
  2060.         End If
  2061.         If pbExitImmediately Then Exit Function
  2062.         DoEvents
  2063.         Sleep (10)                                  ' added to reduce CPU load during wait periods
  2064.     Loop
  2065.     WaitUntilTrue = Flag
  2066. End Function
  2067. ' ******************************************************************************
  2068. ' *      Private Winsock OCX Events                                            *
  2069. ' ******************************************************************************
  2070. Private Sub sckMail_OnClose()
  2071.   ' keep track of connection state
  2072.     pbConnected = False
  2073. End Sub
  2074. Private Sub sckMail_OnConnect()
  2075.   ' keep track of connection state
  2076.     pbConnected = True
  2077. End Sub
  2078. Private Sub sckMail_OnDataArrival(ByVal bytesTotal As Long)
  2079.   ' ********************************************************
  2080.   ' SMTP Reply codes, outlined in RFC 821
  2081.   ' ********************************************************
  2082.   ' 211 - System status/help reply
  2083.   ' 214 - Help message
  2084.   ' 220 - <domain> Service ready
  2085.   ' 221 - <domain> Service closing channel
  2086.   ' 250 - OK: action completed
  2087.   ' 251 - User not local, will forward to <domain>
  2088.   ' 354 - OK: Start mail input, end with <CrLf>.<CrLf>
  2089.   ' 421 - <domain> Service not available, closing channel
  2090.   ' 450 - Mailbox busy, action not taken
  2091.   ' 451 - Requested action aborted: error in processing
  2092.   ' 452 - Requested action not taken: insufficient system storage
  2093.   ' 500 - Syntax error, command unrecognized
  2094.   ' 501 - Syntax error in parameters or arguments
  2095.   ' 502 - Command not implimented
  2096.   ' 503 - Bad sequence of commands
  2097.   ' 504 - Command parameter not implimented
  2098.   ' 550 - Mailbox unavailable, action not taken
  2099.   ' 553 - Requested action not taken: mailbox name not allowed / invalid
  2100.   ' 551 - User not local, please try <forward-path>
  2101.   ' 552 - Requested action not taken: exceeds storage allocation
  2102.   ' 554 - Transaction failed
  2103.   ' ********************************************************
  2104.   ' ESMTP AUTHentication extensions, outlined in RFC 2554
  2105.   ' ********************************************************
  2106.   ' 235 - Authentication successful
  2107.   ' 334 - Server challenge / ready response
  2108.   ' 432 - A password transition is needed
  2109.   ' 454 - Temporary authentication failure
  2110.   ' 530 - Authentication required
  2111.   ' 534 - Authentication mechanism is too weak
  2112.   ' 535 - Server rejected authentication
  2113.   ' 538 - Encryption required for requested authentication mechanism
  2114.   ' ********************************************************
  2115.   '  POP3 Command Summary, outlined in RFC 1939
  2116.   ' ********************************************************
  2117.   '  USER name               valid in the AUTHORIZATION state
  2118.   '  PASS string
  2119.   '  QUIT
  2120.   '
  2121.   '  STAT                    valid in the TRANSACTION state
  2122.   '  List [msg]
  2123.   '  RETR msg
  2124.   '  DELE msg
  2125.   '  NOOP
  2126.   '  RSET
  2127.   '  QUIT
  2128.   '
  2129.   '  Optional POP3 Commands:
  2130.   '  APOP name digest        valid in the AUTHORIZATION state
  2131.   '  TOP msg n               valid in the TRANSACTION state
  2132.   '  UIDL [msg]
  2133.   '
  2134.   '  POP3 Replies:
  2135.   '  +OK
  2136.   '  -ERR
  2137.   '
  2138.   '  Note that with the exception of the STAT, LIST, and UIDL commands,
  2139.   '  the reply given by the POP3 server to any command is significant
  2140.   '  only to "+OK" and "-ERR".  Any text occurring after this reply
  2141.   '  may be ignored by the client.
  2142.   Dim strAns          As String
  2143.   Dim sMsg            As String
  2144.     If sckMail.State <> sckConnected Then Exit Sub
  2145.     sckMail.GetData strAns, vbString
  2146.     Select Case Left$(strAns, 3)
  2147.         ' Ready
  2148.       Case "220"
  2149.         pbRequestAccepted = True
  2150.         ' OK
  2151.       Case "221", "251"
  2152.         pbRequestAccepted = True
  2153.         ' OK, check for authentication support
  2154.       Case "250"
  2155.         pbRequestAccepted = True
  2156.         If InStr(1, strAns, "auth login", vbTextCompare) Then pbAuthLoginSupported = True
  2157.         If InStr(1, strAns, "auth=login", vbTextCompare) Then pbAuthMailFromOK = True
  2158.         ' Auth Login OK
  2159.       Case "235"
  2160.         pbAuthLoginSuccess = True
  2161.         ' mail host 'AUTH' challenge
  2162.       Case "334"
  2163.         ' clean up the message portion
  2164.         sMsg = Trim$(Mid$(strAns, 4))
  2165.         sMsg = Replace(sMsg, vbCrLf, vbNullString)
  2166.         ' username requested
  2167.         If InStr(1, DecodeBase64String(sMsg), "username", vbTextCompare) Then
  2168.             sckMail.SendData EncodeBase64String(psUserName)
  2169.             ' password requested
  2170.           ElseIf InStr(1, DecodeBase64String(sMsg), "password", vbTextCompare) Then
  2171.             sckMail.SendData EncodeBase64String(psPassword)
  2172.             ' unexpected or unsupported challenge, cancel Auth request
  2173.             ' which will result in a 501 error reply from the host
  2174.           Else
  2175.             sckMail.SendData vbCrLf & "*" & vbCrLf
  2176.         End If
  2177.         ' OK, send data
  2178.       Case "354"
  2179.         pbDataOK = True
  2180.         ' do nothing
  2181.       Case "211", "214"
  2182.         ' POP3 success
  2183.       Case "+OK"
  2184.         Select Case plPop3Status
  2185.           Case 0
  2186.             sckMail.SendData "USER " & psUserName & vbCrLf
  2187.             plPop3Status = plPop3Status + 1
  2188.           Case 1
  2189.             sckMail.SendData "PASS " & psPassword & vbCrLf
  2190.             plPop3Status = plPop3Status + 1
  2191.           Case 2
  2192.             sckMail.SendData "QUIT" & vbCrLf
  2193.             plPop3Status = plPop3Status + 1
  2194.           Case 3
  2195.             pbPopAuthOk = True
  2196.             plPop3Status = 0
  2197.         End Select
  2198.         ' POP3 error
  2199.       Case "-ER"
  2200.         pbPopAuthOk = False
  2201.         plPop3Status = 0
  2202.         pbExitImmediately = True
  2203.         AddError strAns
  2204.         SendFail
  2205.         ' host didn't like what we sent or couldn't process it
  2206.       Case Else
  2207.         AddError strAns  ''sMsg
  2208.         SendFail
  2209.     End Select
  2210. End Sub
  2211. Private Sub sckMail_OnError(ByVal Number As Integer, Description As String, _
  2212.                           ByVal sCode As Long, ByVal Source As String, ByVal HelpFile As String, _
  2213.                           ByVal HelpContext As Long, CancelDisplay As Boolean)
  2214.   ' socket error, add the error to the error collection
  2215.     AddError Description
  2216.     SendFail
  2217. End Sub
  2218. Private Sub sckMail_OnSendProgress(ByVal bytesSent As Long, ByVal bytesRemaining As Long)
  2219.   Dim lNewValue           As Long
  2220.   Static lProgressLast    As Long
  2221.     pbSendProgress = True
  2222.     ' add up sent bytes
  2223.     plBytesSent = plBytesSent + bytesSent
  2224.     ' calculate the percentage of the total
  2225.     If plMailSize > 0 Then lNewValue = CLng(CSng(plBytesSent / plMailSize) * 100)
  2226.     If lNewValue > 100 Then lNewValue = 100
  2227.     ' update if the value changed
  2228.     If lNewValue <> lProgressLast Then
  2229.         lProgressLast = lNewValue
  2230.         If sckMail.State = sckConnected Then RaiseEvent Progress(lNewValue)
  2231.     End If
  2232.     ' keep track of what's left
  2233.     plBytesRemaining = bytesRemaining
  2234. End Sub