📄 smtp.cpp
字号:
//////////////////////////////////////////////////////////////////////
/*
Smtp.cpp: implementation of the CSmtp and CSmtpMessage classes
Written by Robert Simpson (robert@blackcastlesoft.com)
Created 11/1/2000
Version 1.7 -- Last Modified 06/18/2001
1.7 - Modified the code that gets the GMT offset and the code that
parses the date/time as per Noa Karsten's suggestions on
codeguru.
- Added an FD_ZERO(&set) to the last part of SendCmd(), since
I use the set twice and only zero it out once. Submitted by
Marc Allen.
- Removed the requirement that a message have a body and/or an
attachment. This allows for sending messages with only a
subject line. Submitted by Marc Allen.
1.6 - Apparently older versions of the STL do not have the clear()
method for basic_string's. I modified the code to use
erase() instead.
- Added #include <atlbase.h> to the smtp.h file, which will
allow any app to use these classes without problems.
1.5 - Guess I should have checked EncodeQuotedPrintable() as well,
since it did the same thing BreakMessage() did in adding an
extranneous CRLF to the end of any text it processed. Fixed.
1.4 - BreakMesage() added an extranneous CRLF to the end of any
text it processed, which is now fixed. Certainly not a big
deal, but it caused text attachments to not be 100% identical
to the original.
1.3 - Added a new class, CSmtpMimePart, to which the CSmtpAttachment
and CSmtpMessageBody classes inherit. This was done for
future expansion. CSmtpMimePart has a new ContentId string
value for optionally assigning a unique content ID value to
body parts and attachments. This was done to support the
multipart/related enhancement
- Support for multipart/related messages, which can be used
for sending html messages with embedded images.
- Modifed CSmtpMessage, adding a new MimeType member variable
so the user can specify a certain type of MIME format to use
when coding the message.
- Fixed a bug where multipart/alternative messages with multiple
message bodies were not properly processed when attachments
were also included in the message.
- Some small optimizations during the CSmtpMessage::Parse routine
1.2 - Vastly improved the time it takes to break a message,
which was dog slow with large attachments. My bad.
- Added another overridable, SmtpProgress() which is
called during the CSmtp::SendCmd() function when there
is a large quantity of data being sent over the wire.
Added CMD_BLOCK_SIZE to support the above new feature
- Added support for UNICODE
- Added the CSmtpAttachment class for better control and
expandability for attachments.
- Added alternative implementations for CSmtp::SendMessage
which make it easier to send simple messages via a single
function call.
- Added a constructor to CSmtpAddress for assigning default
values during initialization.
- Added a #pragma comment(lib,"wsock32.lib") to the smtp.h
file so existing projects don't have to have their linker
options modified.
1.1 - Rearranged the headers so they are written out as:
From,To,Subject,Date,MimeVersion,
followed by all remaining headers
- Modified the class to support multipart/alternative with
multiple message bodies.
Note that CSimpleMap does not sort the key values, and CSmtp
takes advantage of this by writing the headers out in the reverse
order of how they will be parsed before being sent to the SMTP
server. If you modify the code to use std::map or any other map
class, the headers may be alphabetized by key, which may cause
some mail clients to show the headers in the body of the message
or cause other undesirable results when viewing the message.
*/
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "Smtp.h"
#include "..\flag.h"
#pragma warning( disable : 4996 )
//////////////////////////////////////////////////////////////////////
// Construction/Destruction for CSmtpMessageBody
//////////////////////////////////////////////////////////////////////
CSmtpMessageBody::CSmtpMessageBody(LPCTSTR pszBody, LPCTSTR pszEncoding, LPCTSTR pszCharset, EncodingEnum encode)
{
// Set the default message encoding method
// To transfer html messages, make Encoding = _T("text/html")
if (pszEncoding) Encoding = pszEncoding;
if (pszCharset) Charset = pszCharset;
if (pszBody) Data = pszBody;
TransferEncoding = encode;
}
const CSmtpMessageBody& CSmtpMessageBody::operator=(LPCTSTR pszBody)
{
Data = pszBody;
return *this;
}
const CSmtpMessageBody& CSmtpMessageBody::operator=(const String& strBody)
{
Data = strBody;
return *this;
}
//////////////////////////////////////////////////////////////////////
// Construction/Destruction for CSmtpAttachment
//////////////////////////////////////////////////////////////////////
CSmtpAttachment::CSmtpAttachment(LPCTSTR pszFilename, LPCTSTR pszAltName, BOOL bIsInline, LPCTSTR pszEncoding, LPCTSTR pszCharset, EncodingEnum encode)
{
if (pszFilename) FileName = pszFilename;
if (pszAltName) AltName = pszAltName;
if (pszEncoding) Encoding = pszEncoding;
if (pszCharset) Charset = pszCharset;
TransferEncoding = encode;
Inline = bIsInline;
}
const CSmtpAttachment& CSmtpAttachment::operator=(LPCTSTR pszFilename)
{
FileName = pszFilename;
return *this;
}
const CSmtpAttachment& CSmtpAttachment::operator=(const String& strFilename)
{
FileName = strFilename;
return *this;
}
//////////////////////////////////////////////////////////////////////
// Construction/Destruction for CSmtpAddress
//////////////////////////////////////////////////////////////////////
CSmtpAddress::CSmtpAddress(LPCTSTR pszAddress, LPCTSTR pszName)
{
if (pszAddress) Address = pszAddress;
if (pszName) Name = pszName;
}
const CSmtpAddress& CSmtpAddress::operator=(LPCTSTR pszAddress)
{
Address = pszAddress;
return *this;
}
const CSmtpAddress& CSmtpAddress::operator=(const String& strAddress)
{
Address = strAddress;
return *this;
}
//////////////////////////////////////////////////////////////////////
// Construction/Destruction for CSmtpMessage
//////////////////////////////////////////////////////////////////////
CSmtpMessage::CSmtpMessage()
{
TIME_ZONE_INFORMATION tzi;
DWORD dwRet;
long Offset;
// Get local time and timezone offset
GetLocalTime(&Timestamp);
GMTOffset = 0;
dwRet = GetTimeZoneInformation(&tzi);
Offset = tzi.Bias;
if (dwRet == TIME_ZONE_ID_STANDARD) Offset += tzi.StandardBias;
if (dwRet == TIME_ZONE_ID_DAYLIGHT) Offset += tzi.DaylightBias;
GMTOffset = -((Offset / 60) * 100 + (Offset % 60));
MimeType = mimeGuess;
}
// Write all the headers to the e-mail message.
// This is done just before sending it, when we're sure the user wants it to go out.
void CSmtpMessage::CommitHeaders()
{
TCHAR szTime[64];
TCHAR szDate[64];
TCHAR szOut[1024];
String strHeader;
String strValue;
int n;
// Assign a few standard headers to the message
strHeader = _T("X-Priority");
strValue = _T("3 (Normal)");
// Only add the key if it doesn't exist already in the headers map
if (Headers.FindKey(strHeader) == -1) Headers.Add(strHeader,strValue);
strHeader = _T("X-MSMail-Priority");
strValue = _T("Normal");
if (Headers.FindKey(strHeader) == -1) Headers.Add(strHeader,strValue);
strHeader = _T("X-Mailer");
strValue = _T("Internet Explorer Assistant");
if (Headers.FindKey(strHeader) == -1) Headers.Add(strHeader,strValue);
strHeader = _T("Importance");
strValue = _T("Normal");
if (Headers.FindKey(strHeader) == -1) Headers.Add(strHeader,strValue);
// Get the time/date stamp and GMT offset for the Date header.
GetDateFormat(MAKELCID(LANG_ENGLISH, SORT_DEFAULT),0,&Timestamp,_T("ddd, d MMM yyyy"),szDate,64);
GetTimeFormat(MAKELCID(LANG_ENGLISH, SORT_DEFAULT),0,&Timestamp,_T("H:mm:ss"),szTime,64);
// Add the date/time stamp to the message headers
wsprintf(szOut,_T("%s %s %c%4.4d"),szDate,szTime,(GMTOffset>0)?'+':'-',GMTOffset);
strHeader = _T("Date");
strValue = szOut;
Headers.Remove(strHeader);
Headers.Add(strHeader,strValue);
// Write out the subject header
strHeader = _T("Subject");
strValue = Subject;
Headers.Remove(strHeader);
Headers.Add(strHeader,strValue);
// Write out the TO header
strValue.erase();
strHeader = _T("To");
if (Recipient.Name.length())
{
wsprintf(szOut,_T("\"%s\" "),Recipient.Name.c_str());
strValue += szOut;
}
if (Recipient.Address.length())
{
wsprintf(szOut,_T("<%s>"),Recipient.Address.c_str());
strValue += szOut;
}
// Write out all the CC'd names
for (n = 0;n < CC.GetSize();n++)
{
if (strValue.length()) strValue += _T(",\r\n\t");
if (CC[n].Name.length())
{
wsprintf(szOut,_T("\"%s\" "),CC[n].Name.c_str());
strValue += szOut;
}
wsprintf(szOut,_T("<%s>"),CC[n].Address.c_str());
strValue += szOut;
}
Headers.Remove(strHeader);
Headers.Add(strHeader,strValue);
// Write out the FROM header
strValue.erase();
strHeader = _T("From");
if (Sender.Name.length())
{
wsprintf(szOut,_T("\"%s\" "),Sender.Name.c_str());
strValue += szOut;
}
wsprintf(szOut,_T("<%s>"),Sender.Address.c_str());
strValue += szOut;
Headers.Remove(strHeader);
Headers.Add(strHeader,strValue);
}
// Parse a message into a single string
void CSmtpMessage::Parse(String& strDest)
{
String strHeader;
String strValue;
String strTemp;
String strBoundary;
String strInnerBoundary;
TCHAR szOut[1024];
int n;
strDest.erase();
// Get a count of the sections to see if this will be a multipart message
n = Message.GetSize();
n += Attachments.GetSize();
// Remove this header in case the message is being reused
strHeader = _T("Content-Type");
Headers.Remove(strHeader);
// If we have more than one section, then this is a multipart MIME message
if (n > 1)
{
wsprintf(szOut,_T("CSmtpMsgPart123X456_000_%8.8X"),GetTickCount());
strBoundary = szOut;
lstrcpy(szOut,_T("multipart/"));
if (MimeType == mimeGuess)
{
if (Attachments.GetSize() == 0) MimeType = mimeAlternative;
else MimeType = mimeMixed;
}
switch(MimeType)
{
case mimeAlternative:
lstrcat(szOut,_T("alternative"));
break;
case mimeMixed:
lstrcat(szOut,_T("mixed"));
break;
case mimeRelated:
lstrcat(szOut,_T("related"));
break;
}
lstrcat(szOut,_T(";\r\n\tboundary=\""));
lstrcat(szOut,strBoundary.c_str());
lstrcat(szOut,_T("\""));
strValue = szOut;
Headers.Add(strHeader,strValue);
}
strHeader = _T("MIME-Version");
strValue = MIME_VERSION;
Headers.Remove(strHeader);
Headers.Add(strHeader,strValue);
// Remove any message ID in the header and replace it with this message ID, if it exists
strHeader = _T("Message-ID");
Headers.Remove(strHeader);
if (MessageId.length())
{
wsprintf(szOut,_T("<%s>"),MessageId.c_str());
strValue = szOut;
Headers.Add(strHeader,strValue);
}
// Finalize the message headers
CommitHeaders();
// Write out all the message headers -- done backwards on purpose!
for (n = Headers.GetSize();n > 0;n--)
{
wsprintf(szOut,_T("%s: %s\r\n"),Headers.GetKeyAt(n-1).c_str(),Headers.GetValueAt(n-1).c_str());
strDest += szOut;
}
if (strBoundary.length())
{
wsprintf(szOut,_T("\r\n%s\r\n"),MULTIPART_MESSAGE);
strDest += szOut;
}
// If we have attachments and multiple message bodies, create a new multipart section
// This is done so we can display our multipart/alternative section separate from the
// main multipart/mixed environment, and support both attachments and alternative bodies.
if (Attachments.GetSize() && Message.GetSize() > 1 && strBoundary.length())
{
wsprintf(szOut,_T("CSmtpMsgPart123X456_001_%8.8X"),GetTickCount());
strInnerBoundary = szOut;
wsprintf(szOut,_T("\r\n--%s\r\nContent-Type: multipart/alternative;\r\n\tboundary=\"%s\"\r\n"),strBoundary.c_str(),strInnerBoundary.c_str());
strDest += szOut;
}
for (n = 0;n < Message.GetSize();n++)
{
// If we're multipart, then write the boundary line
if (strBoundary.length() || strInnerBoundary.length())
{
strDest += _T("\r\n--");
// If we have an inner boundary, write that one. Otherwise write the outer one
if (strInnerBoundary.length()) strDest += strInnerBoundary;
else strDest += strBoundary;
strDest += _T("\r\n");
}
strValue.erase();
strDest += _T("Content-Type: ");
strDest += Message[n].Encoding;
// Include the character set if the message is text
if (_tcsnicmp(Message[n].Encoding.c_str(),_T("text/"),5) == 0)
{
wsprintf(szOut,_T(";\r\n\tcharset=\"%s\""),Message[n].Charset.c_str());
strDest += szOut;
}
strDest += _T("\r\n");
// Encode the message
strValue = Message[n].Data;
EncodeMessage(Message[n].TransferEncoding,strValue,strTemp);
// Write out the encoding method used and the encoded message
strDest += _T("Content-Transfer-Encoding: ");
strDest += strTemp;
// If the message body part has a content ID, write it out
if (Message[n].ContentId.length())
{
wsprintf(szOut,_T("\r\nContent-ID: <%s>"),Message[n].ContentId.c_str());
strDest += szOut;
}
strDest += _T("\r\n\r\n");
strDest += strValue;
}
// If we have multiple message bodies, write out the trailing inner end sequence
if (strInnerBoundary.length())
{
wsprintf(szOut,_T("\r\n--%s--\r\n"),strInnerBoundary.c_str());
strDest += szOut;
}
// Process any attachments
for (n = 0;n < Attachments.GetSize();n++)
{
HANDLE hFile;
LPBYTE pData;
LPTSTR pszFile;
LPTSTR pszExt;
DWORD dwSize;
DWORD dwBytes;
CRegKey cKey;
TCHAR szType[MAX_PATH];
TCHAR szFilename[MAX_PATH];
// Get the filename of the attachment
strValue = Attachments[n].FileName;
// Open the file
lstrcpy(szFilename,strValue.c_str());
hFile = CreateFile(szFilename,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
// Get the size of the file, allocate the memory and read the contents.
dwSize = GetFileSize(hFile,NULL);
pData = (LPBYTE)malloc(dwSize + 1);
ZeroMemory(pData,dwSize+1);
if (ReadFile(hFile,pData,dwSize,&dwBytes,NULL))
{
// Write out our boundary marker
if (strBoundary.length())
{
wsprintf(szOut,_T("\r\n--%s\r\n"),strBoundary.c_str());
strDest += szOut;
}
// If no alternate name is supplied, strip the path to get the base filename
if (!Attachments[n].AltName.length())
{
// Strip the path from the filename
pszFile = _tcsrchr(szFilename,'\\');
if (!pszFile) pszFile = szFilename;
else pszFile ++;
}
else pszFile = (LPTSTR)Attachments[n].AltName.c_str();
// Set the content type for the attachment.
lstrcpy(szType,_T("application/octet-stream"));
// Check the registry for a content type that overrides the above default
pszExt = _tcschr(pszFile,'.');
if (pszExt)
{
if (!cKey.Open(HKEY_CLASSES_ROOT,pszExt,KEY_READ))
{
DWORD dwSize = MAX_PATH;
cKey.QueryValue(szType,_T("Content Type"),&dwSize);
cKey.Close();
}
}
// If the attachment has a specific encoding method, use it instead
if (Attachments[n].Encoding.length())
lstrcpy(szType,Attachments[n].Encoding.c_str());
// Write out the content type and attachment types to the message
wsprintf(szOut,_T("Content-Type: %s"),szType);
strDest += szOut;
// If the content type is text, write the charset
if (_tcsnicmp(szType,_T("text/"),5) == 0)
{
wsprintf(szOut,_T(";\r\n\tcharset=\"%s\""),Attachments[n].Charset.c_str());
strDest += szOut;
}
wsprintf(szOut,_T(";\r\n\tname=\"%s\"\r\n"),pszFile);
strDest += szOut;
// Encode the attachment
EncodeMessage(Attachments[n].TransferEncoding,strValue,strTemp,pData,dwSize);
// Write out the transfer encoding method
wsprintf(szOut,_T("Content-Transfer-Encoding: %s\r\n"),strTemp.c_str());
strDest += szOut;
// Write out the attachment's disposition
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -