具有发送、验证、BASE64编码功能,已经修改调试完成
想要具体说明请发信 [email protected]
// CSmtp.cpp: implementation of the CSmtp class. // //////////////////////////////////////////////////////////////////////
#include "stdafx.h" #include "CSmtp.h"
#ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif
////////////////////////////////////////////////////////////////////// // CMailMessage // Formats a message compliant with RFC 822.
////////////////////////////////////////////////////////////////////// // Construction/Destruction //////////////////////////////////////////////////////////////////////
CMailMessage::CMailMessage() { m_sMailerName = IDS_APPNAME; SetCharsPerLine(76); }
CMailMessage::~CMailMessage() { }
BOOL CMailMessage::AddRecipient(LPCTSTR szEmailAddress, LPCTSTR szFriendlyName) { ASSERT(szEmailAddress != NULL); ASSERT(szFriendlyName != NULL); CRecipient to; to.m_sEmailAddress = szEmailAddress; to.m_sFriendlyName = szFriendlyName; m_Recipients.Add(to); return TRUE; }
// sEmailAddress and sFriendlyName are OUTPUT parameters. // If the function fails, it will return FALSE, and the OUTPUT // parameters will not be touched. BOOL CMailMessage::GetRecipient(CString & sEmailAddress, CString & sFriendlyName, int nIndex) { CRecipient to; if(nIndex < 0 || nIndex > m_Recipients.GetUpperBound()) return FALSE; to = m_Recipients[nIndex]; sEmailAddress = to.m_sEmailAddress; sFriendlyName = to.m_sFriendlyName; return TRUE; }
int CMailMessage::GetNumRecipients() { return m_Recipients.GetSize(); }
BOOL CMailMessage::AddMultipleRecipients(LPCTSTR szRecipients) { TCHAR* buf; UINT pos; UINT start; CString sTemp; CString sEmail; CString sFriendly; UINT length; int nMark; int nMark2;
ASSERT(szRecipients != NULL);
// Add Recipients length = strlen(szRecipients); buf = new TCHAR[length + 1]; // Allocate a work area (don't touch parameter itself) strcpy(buf, szRecipients); for(pos = 0, start = 0; pos <= length; pos++) { if(buf[pos] == ';' || buf[pos] == 0) { // First, pick apart the sub-strings (separated by ';') // Store it in sTemp. buf[pos] = 0; // Redundant when at the end of string, but who cares. sTemp = &buf[start]; // Now divide the substring into friendly names and e-mail addresses. nMark = sTemp.Find('<'); if(nMark >= 0) { sFriendly = sTemp.Left(nMark); nMark2 = sTemp.Find('>'); if(nMark2 < nMark) { delete[] buf; return FALSE; } // End of mark at closing bracket or end of string nMark2 > -1 ? nMark2 = nMark2 : nMark2 = sTemp.GetLength() - 1; sEmail = sTemp.Mid(nMark + 1, nMark2 - (nMark + 1)); } else { sEmail = sTemp; sFriendly = _T(""); } AddRecipient(sEmail, sFriendly); start = pos + 1; } } delete[] buf; return TRUE; }
void CMailMessage::FormatMessage() { start_header(); prepare_header(); end_header(); prepare_body(); }
void CMailMessage::SetCharsPerLine(UINT nCharsPerLine) { m_nCharsPerLine = nCharsPerLine; }
UINT CMailMessage::GetCharsPerLine() { return m_nCharsPerLine; }
// Create header as per RFC 822 // void CMailMessage::prepare_header() { CString sTemp; sTemp = _T("");
// From: sTemp = _T("From: ") + m_sFrom; add_header_line((LPCTSTR) sTemp);
// To: sTemp = _T("To: "); CString sEmail = _T(""); CString sFriendly = _T(""); for(int i = 0; i < GetNumRecipients(); i++) { GetRecipient(sEmail, sFriendly, i); sTemp += (i > 0 ? _T(",") : _T("")); sTemp += sFriendly; sTemp += _T("<"); sTemp += sEmail; sTemp += _T(">"); } add_header_line((LPCTSTR) sTemp);
// Date: m_tDateTime = m_tDateTime.GetCurrentTime();
// Format: Mon, 01 Jun 98 01:10:30 GMT sTemp = _T("Date: "); sTemp += m_tDateTime.Format("%a, %d %b %y %H:%M:%S %Z"); add_header_line((LPCTSTR) sTemp);
// Subject: sTemp = _T("Subject: ") + m_sSubject; add_header_line((LPCTSTR) sTemp);
// X-Mailer sTemp = _T("X-Mailer: ") + m_sMailerName; add_header_line((LPCTSTR) sTemp); }
void CMailMessage::prepare_body() { // Append a CR/LF to body if necessary. if(m_sBody.Right(2) != _T("\r\n")) m_sBody += _T("\r\n");
}
void CMailMessage::start_header() { m_sHeader = _T(""); }
void CMailMessage::end_header() { m_sHeader += _T("\r\n"); }
void CMailMessage::add_header_line(LPCTSTR szHeaderLine) { CString sTemp; sTemp.Format(_T("%s\r\n"), szHeaderLine); m_sHeader += sTemp; }
////////////////////////////////////////////////////////////////////// // CMIMEContentAgent //////////////////////////////////////////////////////////////////////
CMIMEContentAgent::CMIMEContentAgent(int nMIMEType) { m_nMIMETypeIHandle = nMIMEType; }
CMIMEContentAgent::~CMIMEContentAgent() {}
BOOL CMIMEContentAgent::QueryType(int nContentType) { return nContentType == m_nMIMETypeIHandle ? TRUE : FALSE; }
////////////////////////////////////////////////////////////////////// // CMIMECode //////////////////////////////////////////////////////////////////////
CMIMECode::CMIMECode() { }
CMIMECode::~CMIMECode() { }
////////////////////////////////////////////////////////////////////// // CBase64 ////////////////////////////////////////////////////////////////////// // Static Member Initializers //
// The 7-bit alphabet used to encode binary information CString CBase64::m_sBase64Alphabet = _T( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" );
int CBase64::m_nMask[] = { 0, 1, 3, 7, 15, 31, 63, 127, 255 };
CBase64::CBase64() { }
CBase64::~CBase64() { }
CString CBase64::Encode(LPCTSTR szEncoding, int nSize) { CString sOutput = _T( "" ); int nNumBits = 6; UINT nDigit; int lp = 0;
ASSERT( szEncoding != NULL ); if( szEncoding == NULL ) return sOutput; m_szInput = szEncoding; m_nInputSize = nSize;
m_nBitsRemaining = 0; nDigit = read_bits( nNumBits, &nNumBits, lp ); while( nNumBits > 0 ) { sOutput += m_sBase64Alphabet[ (int)nDigit ]; nDigit = read_bits( nNumBits, &nNumBits, lp ); } // Pad with '=' as per RFC 1521 while( sOutput.GetLength() % 4 != 0 ) { sOutput += '='; } return sOutput; }
// The size of the output buffer must not be less than // 3/4 the size of the input buffer. For simplicity, // make them the same size. int CBase64::Decode(LPCTSTR szDecoding, LPTSTR szOutput) { CString sInput; int c, lp =0; int nDigit; int nDecode[ 256 ];
ASSERT( szDecoding != NULL ); ASSERT( szOutput != NULL ); if( szOutput == NULL ) return 0; if( szDecoding == NULL ) return 0; sInput = szDecoding; if( sInput.GetLength() == 0 ) return 0;
// Build Decode Table // for( int i = 0; i < 256; i++ ) nDecode[i] = -2; // Illegal digit for( i=0; i < 64; i++ ) { nDecode[ m_sBase64Alphabet[ i ] ] = i; nDecode[ m_sBase64Alphabet[ i ] | 0x80 ] = i; // Ignore 8th bit nDecode[ '=' ] = -1; nDecode[ '=' | 0x80 ] = -1; // Ignore MIME padding char }
// Clear the output buffer memset( szOutput, 0, sInput.GetLength() + 1 );
// Decode the Input // for( lp = 0, i = 0; lp < sInput.GetLength(); lp++ ) { c = sInput[ lp ]; nDigit = nDecode[ c & 0x7F ]; if( nDigit < -1 ) { return 0; } else if( nDigit >= 0 ) // i (index into output) is incremented by write_bits() write_bits( nDigit & 0x3F, 6, szOutput, i ); } return i; }
UINT CBase64::read_bits(int nNumBits, int * pBitsRead, int& lp) { ULONG lScratch; while( ( m_nBitsRemaining < nNumBits ) && ( lp < m_nInputSize ) ) { int c = m_szInput[ lp++ ]; m_lBitStorage <<= 8; m_lBitStorage |= (c & 0xff); m_nBitsRemaining += 8; } if( m_nBitsRemaining < nNumBits ) { lScratch = m_lBitStorage << ( nNumBits - m_nBitsRemaining ); *pBitsRead = m_nBitsRemaining; m_nBitsRemaining = 0; } else { lScratch = m_lBitStorage >> ( m_nBitsRemaining - nNumBits ); *pBitsRead = nNumBits; m_nBitsRemaining -= nNumBits; } return (UINT)lScratch & m_nMask[nNumBits]; }
void CBase64::write_bits(UINT nBits, int nNumBits, LPTSTR szOutput, int& i) { UINT nScratch;
m_lBitStorage = (m_lBitStorage << nNumBits) | nBits; m_nBitsRemaining += nNumBits; while( m_nBitsRemaining > 7 ) { nScratch = m_lBitStorage >> (m_nBitsRemaining - 8); szOutput[ i++ ] = nScratch & 0xFF; m_nBitsRemaining -= 8; } }
////////////////////////////////////////////////////////////////////// // CAppOctetStream //////////////////////////////////////////////////////////////////////
// IMPORTANT: The number of bytes we read must be // a multiple of 3 because CBase64's Encode() // method will append padding characters ('=') // to make the output's size a multiple of 4. // (Base64 treats 3 8-bit bytes as 4 6-bit 'bytes'). // MIME decoders are free to treat '=' as a signal // that there's no more data, so we don't want to pad // until we're supposed to. // When at the end of the file, the # of bytes read // may not be a multiple of 3, but that's okay // because we DO want the padding chars then.
#define BYTES_TO_READ 54 // This number guarantess output won't // won't exceed line-length limit
CAppOctetStream::CAppOctetStream(int nContentType) :CMIMEContentAgent(nContentType) { }
CAppOctetStream::~CAppOctetStream() { }
BOOL CAppOctetStream::AppendPart(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath, CString & sDestination) { CStdioFile fAttachment;
ASSERT(szContent != NULL); // This class handles only file attachments, so // it ignores the bPath parameter. if(szContent == NULL) return FALSE; if(!fAttachment.Open(szContent, (CFile::modeRead|CFile::shareDenyWrite|CFile::typeBinary))) return FALSE; sDestination += build_sub_header(szContent, szParameters, nEncoding, TRUE); attach_file(&fAttachment, CMIMEMessage::BASE64, sDestination ); fAttachment.Close(); return TRUE; }
CString CAppOctetStream::build_sub_header(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath) { CString sSubHeader; CString sTemp; TCHAR szFName[ _MAX_FNAME ]; TCHAR szExt[ _MAX_EXT ];
_tsplitpath( szContent, NULL, NULL, szFName, szExt );
// This class ignores szParameters and nEncoding. // It controls its own parameters and only handles // Base64 encoding. if( bPath ) sTemp.Format( "; file=%s%s", szFName, szExt ); else sTemp = _T( "" ); sSubHeader.Format( _T( "Content-Type: %s%s\r\n" ), (LPCTSTR)GetContentTypeString(), (LPCTSTR)sTemp ); sSubHeader += _T( "Content-Transfer-Encoding: base64\r\n" ); sTemp.Format( _T( "Content-Disposition: attachment; filename=%s%s\r\n" ), szFName, szExt ); sSubHeader += sTemp; // Signal end of sub-header. sSubHeader += _T( "\r\n" ); // Warning: numerous concatenations // are inefficient. return sSubHeader; }
CString CAppOctetStream::GetContentTypeString() { CString s; s = _T( "application/octet-stream" ); return s; }
// Caller is responsible for opening and closing the file void CAppOctetStream::attach_file(CStdioFile* pFileAtt, int nEncoding, CString & sDestination) { CMIMECode* pEncoder; int nBytesRead; TCHAR szBuffer[ BYTES_TO_READ + 1 ];
ASSERT( pFileAtt != NULL ); if( pFileAtt == NULL ) return; switch( nEncoding ) { // This class handles only Base64 encoding, but others // may be added here. default: // Fall through to... case CMIMEMessage::BASE64: try { pEncoder = new CBase64; } catch( CMemoryException* e ) { delete e; return; } } if( pEncoder == NULL ) // Old habits are hard to break return; do { try { nBytesRead = pFileAtt->Read( szBuffer, BYTES_TO_READ ); } catch( CFileException* e ) { delete e; break; } szBuffer[ nBytesRead ] = 0; // Terminate the string sDestination += pEncoder->Encode( szBuffer, nBytesRead ); sDestination += _T( "\r\n" ); } while( nBytesRead == BYTES_TO_READ ); sDestination += _T( "\r\n" ); delete pEncoder; }
////////////////////////////////////////////////////////////////////// // CTextPlain //////////////////////////////////////////////////////////////////////
CTextPlain::CTextPlain( int nContentType, UINT nWrapPos ) :CMIMEContentAgent( nContentType ) { m_nWrapPos = nWrapPos; }
CTextPlain::~CTextPlain() {
}
CString CTextPlain::GetContentTypeString() { CString s; s = _T( "text/html" ); return s; }
BOOL CTextPlain::AppendPart(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath, CString & sDestination) { CString sSubHeader; CString sWrapped; sSubHeader = build_sub_header( szContent, szParameters, nEncoding, bPath ); sWrapped = wrap_text( szContent ); sDestination += (sSubHeader + sWrapped); return TRUE; }
CString CTextPlain::build_sub_header(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath) { CString sSubHeader; sSubHeader.Format( _T( "Content-Type: %s%s\r\n" ), (LPCTSTR)GetContentTypeString(), szParameters ); sSubHeader += _T( "Content-Transfer-Encoding: " ); switch( nEncoding ) { // This class handles only 7bit encoding, but others // may be added here. default: case CMIMEMessage::_7BIT: sSubHeader += _T( "7Bit" ); } sSubHeader += _T( "\r\n\r\n" ); return sSubHeader; }
CString CTextPlain::wrap_text(LPCTSTR szText) { CString sTemp; CString sLeft; CString sRight; int lp = 0; UINT nCount = 0; int nSpacePos = 0;
ASSERT( szText != NULL ); if( szText == NULL ) return sTemp; sTemp = szText; while( lp < sTemp.GetLength() ) { if( sTemp[ lp ] == ' ' ) nSpacePos = lp; // Reset counter on newline if( sTemp.Mid( lp, 2 ) == _T( "\r\n" ) ) nCount = 0; // Wrap text at last found space if( nCount > m_nWrapPos ) { sLeft = sTemp.Left( nSpacePos ); sRight = sTemp.Right( sTemp.GetLength() - nSpacePos ); sLeft.TrimRight(); sRight.TrimLeft(); sLeft += _T( "\r\n" ); sTemp = sLeft + sRight; nCount = 0; } else nCount++; lp++; } return sTemp; }
////////////////////////////////////////////////////////////////////// // CMIMEMessage //////////////////////////////////////////////////////////////////////
// Static Member Initializers CMIMEMessage::CMIMETypeManager CMIMEMessage::m_MIMETypeManager;
CMIMEMessage::CMIMEMessage() { m_sMIMEContentType = _T( "multipart/mixed"); m_sPartBoundary = _T( "WC_MAIL_PaRt_BoUnDaRy_05151998" ); m_sNoMIMEText = _T( "This is a multi-part message in MIME format." );
// Register the MIME types handled by this class // CMIMEContentAgent* pType; // These objects are deleted by CMIMTypeManager's destructor pType = new CTextPlain( TEXT_PLAIN, GetCharsPerLine() ); register_mime_type( pType ); pType = new CAppOctetStream( APPLICATION_OCTETSTREAM ); register_mime_type( pType ); }
CMIMEMessage::~CMIMEMessage() { }
// This implementation adds the part to the part-list used // to build the body. BOOL CMIMEMessage::AddMIMEPart(LPCTSTR szContent, int nContentType, LPCTSTR szParameters, int nEncoding, BOOL bPath ) { CMIMEPart part; part.m_nContentType = nContentType; part.m_sParameters = szParameters; part.m_nEncoding = nEncoding; part.m_bPath = bPath; part.m_sContent = szContent; part.m_sContent.TrimLeft(); part.m_sContent.TrimRight(); if( nContentType == TEXT_PLAIN ) m_MIMEPartList.AddHead( part ); else m_MIMEPartList.AddTail( part ); return TRUE; }
void CMIMEMessage::prepare_header() { CString sTemp;
// Let the base class add its headers CMailMessage::prepare_header(); add_header_line( _T( "MIME-Version: 1.0" ) ); sTemp.Format( _T( "Content-Type: %s; boundary=%s" ), (LPCTSTR)m_sMIMEContentType, (LPCTSTR)m_sPartBoundary ); add_header_line( (LPCTSTR)sTemp ); }
void CMIMEMessage::prepare_body() { // Class user may have assigned body text directly. // Convert it to just another MIME part to be processed. // If this default Content-Type isn't good enough for the // class user, he or she should have used AddMIMEPart() instead. if( m_sBody != _T( "" ) ) AddMIMEPart( (LPCTSTR)m_sBody, TEXT_PLAIN, "", _7BIT, FALSE );
// Initialize the body (replace current contents). m_sBody = m_sNoMIMEText; m_sBody += _T( "\r\n\r\n" ); append_mime_parts(); insert_message_end( m_sBody );
// Let the base class take me to Funky Town CMailMessage::prepare_body(); }
void CMIMEMessage::insert_boundary( CString& sText ) { CString sTemp; if( sText.Right( 2 ) != _T( "\r\n" ) ) sText += _T( "\r\n" ); sTemp.Format( _T( "--%s\r\n" ), (LPCTSTR)m_sPartBoundary ); sText += sTemp; }
void CMIMEMessage::insert_message_end( CString& sText ) { CString sTemp; if( sText.Right( 2 ) != _T( "\r\n" ) ) sText += _T( "\r\n" ); sTemp.Format( _T( "--%s--\r\n" ), (LPCTSTR)m_sPartBoundary ); sText += sTemp; }
void CMIMEMessage::register_mime_type(CMIMEContentAgent* pMIMEType) { ASSERT( pMIMEType != NULL ); if( pMIMEType == NULL ) return; m_MIMETypeManager.RegisterMIMEType( pMIMEType ); }
void CMIMEMessage::append_mime_parts() { POSITION part_position; CMIMEPart* pMIMEPart = NULL; CMIMEContentAgent* pMIMEType = NULL;
part_position = m_MIMEPartList.GetHeadPosition(); // Get each part from the list, retrieve a handler for it, // and let the handler do its thing. while( part_position != NULL ) { pMIMEPart = & m_MIMEPartList.GetNext( part_position ); pMIMEType = m_MIMETypeManager.GetHandler( pMIMEPart->m_nContentType ); if( pMIMEType != NULL ) { insert_boundary( m_sBody ); pMIMEType->AppendPart( pMIMEPart->m_sContent, pMIMEPart->m_sParameters, pMIMEPart->m_nEncoding, pMIMEPart->m_bPath, m_sBody ); } }
}
////////////////////////////////////////////////////////////////////// // CMIMETypeManager Implementation //////////////////////////////////////////////////////////////////////
CMIMEMessage::CMIMETypeManager::CMIMETypeManager() { }
CMIMEMessage::CMIMETypeManager::~CMIMETypeManager() { POSITION pos; CMIMEContentAgent* p; m_csAccess.Lock(); pos = m_MIMETypeList.GetHeadPosition(); while( pos != NULL ) { p = m_MIMETypeList.GetNext( pos ); delete p; } }
void CMIMEMessage::CMIMETypeManager::RegisterMIMEType(CMIMEContentAgent *pMIMEType) { ASSERT( pMIMEType != NULL ); if( pMIMEType == NULL ) return; m_csAccess.Lock(); m_MIMETypeList.AddTail( pMIMEType ); }
CMIMEContentAgent* CMIMEMessage::CMIMETypeManager::GetHandler(int nContentType) { POSITION pos; CMIMEContentAgent* pType = NULL;
m_csAccess.Lock(); pos = m_MIMETypeList.GetHeadPosition(); while( pos != NULL ) { pType = m_MIMETypeList.GetNext( pos ); if( pType->QueryType( nContentType ) == TRUE ) break; } return pType; }
////////////////////////////////////////////////////////////////////// // Construction/Destruction //////////////////////////////////////////////////////////////////////
// Static member initializers //
// Note: the order of the entries is important. // They must be synchronized with eResponse entries. CSmtp::response_code CSmtp::response_table[] = { // GENERIC_SUCCESS {250, _T("SMTP server error")}, // CONNECT_SUCCESS {220, _T("SMTP server not available")}, // AUTHQUE_SUCCESS {334, _T("SMTP server authentication error")}, // AUTH_SUCCESS {235, _T("Error username or password")}, // DATA_SUCCESS {354, _T("SMTP server not ready for data")}, // QUIT_SUCCESS {221, _T("SMTP server didn't terminate session")} };
////////////////////////////////////////////////////////////////////// // Construction/Destruction //////////////////////////////////////////////////////////////////////
CSmtp::CSmtp(LPCTSTR szSMTPServerName, UINT nPort) { ASSERT(szSMTPServerName != NULL); AfxSocketInit(); m_sSMTPServerHostName = szSMTPServerName; m_nPort = nPort; m_bConnected = FALSE; m_sError = _T("OK"); response_buf = NULL; }
CSmtp::~CSmtp() { Disconnect(); }
CString CSmtp::GetServerHostName() { return m_sSMTPServerHostName; }
BOOL CSmtp::Connect() { CString sHello; TCHAR local_host[80]; // Warning: arbitrary size if(m_bConnected) return TRUE;
try { // This will be deleted in Disconnect(); response_buf = new TCHAR[RESPONSE_BUFFER_SIZE]; // I can't count on all class users' applications // to have exception-throwing operator-new implementations, // so I'll soul-kiss the ones that don't. if(response_buf == NULL) { m_sError = _T("Not enough memory"); return FALSE; } } catch(CException* e) { response_buf = NULL; m_sError = _T("Not enough memory"); delete e; return FALSE; }
if(!m_wsSMTPServer.Create()) { m_sError = _T("Unable to create the socket"); delete response_buf; response_buf = NULL; return FALSE; } if( !m_wsSMTPServer.Connect(GetServerHostName(), GetPort())) { m_sError = _T("Unable to connect to server"); m_wsSMTPServer.Close(); delete response_buf; response_buf = NULL; return FALSE; } if(!get_response(CONNECT_SUCCESS)) { m_sError = _T( "Server didn't respond" ); m_wsSMTPServer.Close(); delete response_buf; response_buf = NULL; return FALSE; } gethostname(local_host, 80); sHello.Format(_T( "HELO %s\r\n" ), local_host); m_wsSMTPServer.Send((LPCTSTR)sHello, sHello.GetLength()); if(!get_response(GENERIC_SUCCESS)) { m_wsSMTPServer.Close(); delete response_buf; response_buf = NULL; return FALSE; } m_bConnected = TRUE; return TRUE; }
BOOL CSmtp::Auth() { CString sAuth; if(!m_bConnected) return FALSE; try { // This will be deleted in Disconnect(); response_buf = new TCHAR[RESPONSE_BUFFER_SIZE]; // I can't count on all class users' applications // to have exception-throwing operator-new implementations, // so I'll soul-kiss the ones that don't. if(response_buf == NULL) { m_sError = _T("Not enough memory"); return FALSE; } } catch(CException* e) { response_buf = NULL; m_sError = _T("Not enough memory"); delete e; return FALSE; }
sAuth.Format(_T( "auth login\r\n" )); file://construct auth quest m_wsSMTPServer.Send((LPCTSTR)sAuth, sAuth.GetLength()); if(!get_response(AUTHQUE_SUCCESS)) { m_sError="SMTP server with no auth"; m_bAuthed=TRUE; return TRUE; }
sAuth.Empty();
sAuth.Format(_T( "%s\r\n" ), m_sSMTPUser); file://m_sSMTPUser is an string encoded with CBASE64 m_wsSMTPServer.Send((LPCTSTR)sAuth, sAuth.GetLength()); if(!get_response(AUTHQUE_SUCCESS)) { m_sError="Unknown Error"; m_wsSMTPServer.Close(); delete response_buf; response_buf = NULL; return FALSE; }
sAuth.Empty();
sAuth.Format(_T( "%s\r\n" ), m_sSMTPPass); file://m_sSMTPPass is an string encoded with CBASE64 m_wsSMTPServer.Send((LPCTSTR)sAuth, sAuth.GetLength()); if(!get_response(AUTH_SUCCESS)) { m_wsSMTPServer.Close(); delete response_buf; response_buf = NULL; return FALSE; }
m_bAuthed = TRUE; return TRUE;
}
BOOL CSmtp::Disconnect() { BOOL ret; if(!m_bConnected) return TRUE; // Disconnect gracefully from the server and close the socket CString sQuit = _T("QUIT\r\n"); m_wsSMTPServer.Send((LPCTSTR)sQuit, sQuit.GetLength());
// No need to check return value here. // If it fails, the message is available with GetLastError ret = get_response(QUIT_SUCCESS); m_wsSMTPServer.Close();
if(response_buf != NULL) { delete[] response_buf; response_buf = NULL; } m_bConnected = FALSE; return ret; }
UINT CSmtp::GetPort() { return m_nPort; }
CString CSmtp::GetLastError() { return m_sError; }
BOOL CSmtp::SendMessage(CMailMessage* msg) { ASSERT(msg != NULL); if(!m_bConnected) { m_sError = _T("Must be connected"); return FALSE; } if(!m_bAuthed) { m_sError = _T("Must be authed"); return FALSE; } if(FormatMailMessage(msg) == FALSE) { return FALSE; } if(transmit_message(msg) == FALSE) { return FALSE; } return TRUE; }
BOOL CSmtp::FormatMailMessage(CMailMessage* msg) { ASSERT(msg != NULL); if(msg -> GetNumRecipients() == 0) { m_sError = _T("No Recipients"); return FALSE; } msg -> FormatMessage(); return TRUE; }
void CSmtp::SetServerProperties(LPCTSTR szSMTPServerName, UINT nPort) { ASSERT(szSMTPServerName != NULL); // Needs to be safe in non-debug too if(szSMTPServerName == NULL) return; m_sSMTPServerHostName = szSMTPServerName; m_nPort = nPort; }
CString CSmtp::cook_body(CMailMessage* msg) { ASSERT(msg != NULL); CString sTemp; CString sCooked = _T(""); LPTSTR szBad = _T("\r\n.\r\n"); LPTSTR szGood = _T("\r\n..\r\n"); int nPos; int nStart = 0; int nBadLength = strlen(szBad); sTemp = msg -> m_sBody; if(sTemp.Left(3) == _T(".\r\n")) sTemp = _T(".") + sTemp; // // This is a little inefficient because it beings a search // at the beginning of the string each time. This was // the only thing I could think of that handled ALL variations. // In particular, the sequence "\r\n.\r\n.\r\n" is troublesome. // (Even CStringEx's FindReplace wouldn't handle that situation // with the global flag set.) // while((nPos = sTemp.Find(szBad)) > -1) { sCooked = sTemp.Mid(nStart, nPos); sCooked += szGood; sTemp = sCooked + sTemp.Right(sTemp.GetLength() - (nPos + nBadLength)); } return sTemp; }
BOOL CSmtp::transmit_message(CMailMessage* msg) { CString sFrom; CString sTo; CString sTemp; CString sEmail;
ASSERT(msg != NULL); if(!m_bConnected) { m_sError = _T("Must be connected"); return FALSE; }
if(!m_bAuthed) { m_sError = _T("Must be authed"); return FALSE; }
// Send the MAIL command sFrom.Format(_T( "MAIL From: <%s>\r\n" ), (LPCTSTR)msg->m_sFrom); m_wsSMTPServer.Send((LPCTSTR)sFrom, sFrom.GetLength()); if(!get_response(GENERIC_SUCCESS)) return FALSE;
// Send RCPT commands (one for each recipient) for(int i = 0; i < msg->GetNumRecipients(); i++) { msg->GetRecipient(sEmail, sTemp, i); sTo.Format(_T("RCPT TO: <%s>\r\n"), (LPCTSTR)sEmail); m_wsSMTPServer.Send((LPCTSTR)sTo, sTo.GetLength()); get_response(GENERIC_SUCCESS); }
// Send the DATA command sTemp = _T("DATA\r\n"); m_wsSMTPServer.Send((LPCTSTR)sTemp, sTemp.GetLength()); if( !get_response(DATA_SUCCESS)) { return FALSE; }
// Send the header m_wsSMTPServer.Send((LPCTSTR)msg -> m_sHeader, msg -> m_sHeader.GetLength());
// Send the body sTemp = cook_body(msg); m_wsSMTPServer.Send((LPCTSTR)sTemp, sTemp.GetLength());
// Signal end of data sTemp = _T("\r\n.\r\n"); m_wsSMTPServer.Send((LPCTSTR)sTemp, sTemp.GetLength()); if( !get_response(GENERIC_SUCCESS)) { return FALSE; } return TRUE; }
BOOL CSmtp::get_response(UINT response_expected) { ASSERT(response_expected >= GENERIC_SUCCESS); ASSERT(response_expected < LAST_RESPONSE);
CString sResponse; UINT response; response_code* pResp; // Shorthand
if(m_wsSMTPServer.Receive(response_buf, RESPONSE_BUFFER_SIZE) == SOCKET_ERROR) { m_sError = _T("Socket Error"); return FALSE; } sResponse = response_buf; sscanf((LPCTSTR)sResponse.Left(3), _T("%d"), &response); pResp = &response_table[response_expected]; if(response != pResp -> nResponse) { m_sError.Format( _T("%d:%s"), response, (LPCTSTR)pResp -> sMessage); return FALSE; } return TRUE; }
// CSmtp.h: interface for the CSmtp class. // //////////////////////////////////////////////////////////////////////
#if !defined(AFX_CSMTP_H__A0D4A072_65DE_11D2_9816_9523BDBAF506__INCLUDED_) #define AFX_CSMTP_H__A0D4A072_65DE_11D2_9816_9523BDBAF506__INCLUDED_
#if _MSC_VER >= 1000 #pragma once #endif // _MSC_VER >= 1000
////////////////////////////////////////////////////////////////////// // CMailMessage // Formats a message compliant with RFC 822 #include <afxtempl.h>
class CMailMessage { public: CMailMessage(); virtual ~CMailMessage();
void FormatMessage(); int GetNumRecipients(); BOOL GetRecipient(CString& sEmailAddress, CString& sFriendlyName, int nIndex = 0); BOOL AddRecipient(LPCTSTR szEmailAddress, LPCTSTR szFriendlyName = ""); BOOL AddMultipleRecipients(LPCTSTR szRecipients = NULL); UINT GetCharsPerLine(); void SetCharsPerLine(UINT nCharsPerLine);
CString m_sFrom; CString m_sSubject; CString m_sEnvelope; CString m_sMailerName; CString m_sHeader; CTime m_tDateTime; CString m_sBody; CString m_sUser; CString m_sPass; private: UINT m_nCharsPerLine; class CRecipient { public: CString m_sEmailAddress; CString m_sFriendlyName; }; CArray <CRecipient, CRecipient&> m_Recipients; protected: // When overriding prepare_header(), call base class // version first, then add specialized // add_header_line calls. // This ensures that the base class has a chance to // create the header lines it needs. virtual void prepare_header(); virtual void prepare_body(); virtual void end_header(); virtual void start_header();
// This rarely needs overwriting, but is virtual just in case. // Do not include the trailing CR/LF in parameter. virtual void add_header_line(LPCTSTR szHeaderLine); };
// CMIMEContentAgent // Abstract base class. Content agents support MIME // content types on behalf of CMIMEMessage // class CMIMEContentAgent { public: CMIMEContentAgent(int nMIMEType); virtual ~CMIMEContentAgent(); BOOL QueryType(int nContentType); virtual BOOL AppendPart(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath, CString& sDestination) = 0; virtual CString GetContentTypeString() = 0;
protected: virtual CString build_sub_header(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath) = 0;
private: int m_nMIMETypeIHandle; };
////////////////////////////////////////////////////////////////////// // CMIMECode
class CMIMECode { public: CMIMECode(); virtual ~CMIMECode();
virtual int Decode( LPCTSTR szDecoding, LPTSTR szOutput ) = 0; virtual CString Encode( LPCTSTR szEncoding, int nSize ) = 0; };
////////////////////////////////////////////////////////////////////// // CBase64 // An encoding agent that handles Base64 class CBase64 : public CMIMECode { public: CBase64(); virtual ~CBase64();
// Override the base class mandatory functions virtual int Decode( LPCTSTR szDecoding, LPTSTR szOutput ); virtual CString Encode( LPCTSTR szEncoding, int nSize );
protected: void write_bits( UINT nBits, int nNumBts, LPTSTR szOutput, int& lp ); UINT read_bits( int nNumBits, int* pBitsRead, int& lp );
int m_nInputSize; int m_nBitsRemaining; ULONG m_lBitStorage; LPCTSTR m_szInput;
static int m_nMask[]; static CString m_sBase64Alphabet; private: };
////////////////////////////////////////////////////////////////////// // CAppOctetStream
class CAppOctetStream : public CMIMEContentAgent { public: virtual CString GetContentTypeString(); CAppOctetStream( int nContentType ); virtual ~CAppOctetStream();
virtual BOOL AppendPart( LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath, CString& sDestination );
protected: virtual void attach_file( CStdioFile* pFileAtt, int nEncoding, CString& sDestination ); virtual CString build_sub_header( LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath ); };
// CTextPlain // A MIME content agent that handles the "text/plain" // content type class CTextPlain : public CMIMEContentAgent { public: CTextPlain(int nContentType, UINT nWrapPos = 72); virtual ~CTextPlain(); virtual BOOL AppendPart(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath, CString& sDestination); virtual CString GetContentTypeString();
protected: UINT m_nWrapPos; CString wrap_text(LPCTSTR szText); virtual CString build_sub_header(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath); };
#include <afxmt.h>
// CMIMEMessage // Formats a message using the MIME specification.
class CMIMEMessage : public CMailMessage { public: CMIMEMessage(); virtual ~CMIMEMessage(); // MIME Type Codes enum eMIMETypeCode { TEXT_PLAIN = 0, APPLICATION_OCTETSTREAM, NEXT_FREE_MIME_CODE }; enum eMIMEEncodingCode { _7BIT = 0, _8BIT, BINARY, QUOTED_PRINTABLE, BASE64, NEXT_FREE_ENCODING_CODE }; BOOL AddMIMEPart(LPCTSTR szContent, int nContentType = APPLICATION_OCTETSTREAM, LPCTSTR szParameters = _T(""), int nEncoding = BASE64, BOOL bPath = TRUE); protected: void insert_message_end(CString& sText); void register_mime_type(CMIMEContentAgent* pMIMEType); void insert_boundary(CString& sText);
virtual void append_mime_parts(); virtual void prepare_header(); virtual void prepare_body();
CString m_sNoMIMEText; CString m_sPartBoundary; CString m_sMIMEContentType; private: class CMIMEPart { public: int m_nEncoding; int m_nContentType; CString m_sParameters; BOOL m_bPath; CString m_sContent; }; CList <CMIMEPart, CMIMEPart&> m_MIMEPartList;
class CMIMETypeManager { public: CMIMEContentAgent* GetHandler(int nContentType); void RegisterMIMEType(CMIMEContentAgent* pMIMEType); virtual ~CMIMETypeManager(); CMIMETypeManager(); private: CCriticalSection m_csAccess; CList <CMIMEContentAgent*, CMIMEContentAgent*> m_MIMETypeList; };
static CMIMETypeManager m_MIMETypeManager; };
////////////////////////////////////////////////////////////////////// // CSmtp // Main class for SMTP
#include <afxsock.h>
#define SMTP_PORT 25 // Standard port for SMTP servers #define RESPONSE_BUFFER_SIZE 1024
class CSmtp { public: CSmtp(LPCTSTR szSMTPServerName, UINT nPort = SMTP_PORT); virtual ~CSmtp();
void SetServerProperties(LPCTSTR szSMTPServerName, UINT nPort = SMTP_PORT); CString GetLastError(); UINT GetPort(); BOOL Disconnect(); BOOL Connect(); virtual BOOL FormatMailMessage(CMailMessage* msg); BOOL SendMessage(CMailMessage* msg); CString GetServerHostName();
private: BOOL get_response(UINT response_expected); CString cook_body(CMailMessage* msg);
CString m_sError; BOOL m_bConnected; BOOL m_bAuthed; UINT m_nPort; CString m_sSMTPServerHostName; CSocket m_wsSMTPServer;
protected: virtual BOOL transmit_message(CMailMessage* msg);
// // Helper Code // struct response_code { UINT nResponse; // Response we're looking for TCHAR* sMessage; // Error message if we don't get it };
enum eResponse { GENERIC_SUCCESS = 0, CONNECT_SUCCESS, DATA_SUCCESS, QUIT_SUCCESS, // Include any others here LAST_RESPONSE // Do not add entries past this one }; TCHAR* response_buf; static response_code response_table[]; };
#endif // !defined(AFX_CSMTP_H__A0D4A072_65DE_11D2_9816_9523BDBAF506__INCLUDED_)

|