// This is a part of the Active Template Library.
// Copyright (C) Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Active Template Library Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Active Template Library product.

#ifndef __ATLMIME_H__
#define __ATLMIME_H__

#pragma once

#ifdef _WIN32_WCE
	#error atlmime.h is not supported on Windows CE (_WIN32_WCE is defined)
#endif //_WIN32_WCE

#include <tchar.h>
#include <time.h>
#include <atlbase.h>
#include <mlang.h>
#include <atlfile.h>
#include <atlcoll.h>
#include <atlstr.h>
#include <atlsmtputil.h>
#include <atlenc.h>
#include <atlspriv.h>

#pragma warning(push)
#pragma warning(disable: 4625) // copy constructor could not be generated because a base class copy constructor is inaccessible
#pragma warning(disable: 4626) // assignment operator could not be generated because a base class assignment operator is inaccessible

#ifndef _CPPUNWIND
#pragma warning (push)
#pragma warning(disable: 4702) // unreachable code
#endif // _CPPUNWIND

#pragma pack(push,_ATL_PACKING)
namespace ATL {

#ifndef ATLMIME_SEPARATOR
#define ATLMIME_SEPARATOR "\r\n\r\n--"
#endif//ATLMIME_SEPARATOR

#ifndef ATLMIME_VERSION 
#define ATLMIME_VERSION "MIME-Version: 1.0"
#endif//ATLMIME_VERSION

#ifndef ATLMIME_EMAIL
#define ATLMIME_EMAIL "email"
#endif//ATLMIME_EMAIL

extern __declspec(selectany) const DWORD ATL_MIME_BOUNDARYLEN = 32;
extern __declspec(selectany) const DWORD ATL_MIME_DATE_LEN    = 64;

// Called when message is sent - sets the "Date:" field
inline size_t SetRfc822Time(__out_ecount_part_z_opt(dwLen, return) LPSTR szDate, __in size_t dwLen) throw()
{
	// Max buffer size required(including NULL) - 38
	const size_t s_dwMaxBufferLen = 38;
	if (szDate == NULL)
	{
		return s_dwMaxBufferLen;
	}
	
	if (dwLen < 38)
	{
		return 0;
	}
	static const LPCSTR s_months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 
							   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

	static const LPCSTR s_days[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

	SYSTEMTIME st;
	DWORD      dwTimeZoneId=TIME_ZONE_ID_UNKNOWN;
	CHAR       cDiff;
	LONG       ltzBias=0;
	LONG       ltzHour;
	LONG       ltzMinute;
	TIME_ZONE_INFORMATION tzi;

	GetLocalTime(&st);

	// Gets TIME_ZONE_INFORMATION
	memset(&tzi, 0, sizeof(tzi));
	dwTimeZoneId = GetTimeZoneInformation(&tzi);
	switch (dwTimeZoneId)
	{
	case TIME_ZONE_ID_STANDARD:
		ltzBias = tzi.Bias + tzi.StandardBias;
		break;

	case TIME_ZONE_ID_DAYLIGHT:
		ltzBias = tzi.Bias + tzi.DaylightBias;
		break;

	case TIME_ZONE_ID_UNKNOWN:
	default:
		ltzBias = tzi.Bias;
		break;
	}

	// Set Hour Minutes and time zone dif
	ltzHour = ltzBias / 60;
	ltzMinute = ltzBias % 60;
	cDiff = (ltzHour < 0) ? '+' : '-';

	int nDay = (st.wDayOfWeek > 6) ? 0 : st.wDayOfWeek;
	int nMonth = st.wMonth = (WORD)((st.wMonth < 1 || st.wMonth > 12) ? 0 : st.wMonth - 1);


	// Constructs RFC 822 format: "ddd, dd mmm yyyy hh:mm:ss +/- hhmm\0"
	sprintf_s(szDate, dwLen, "Date: %3s, %d %3s %4d %02d:%02d:%02d %c%02d%02d",
					  s_days[nDay],                            // "ddd"
					  st.wDay,                                 // "dd"
					  s_months[nMonth],                        // "mmm"
					  st.wYear,                                // "yyyy"
					  st.wHour,                                // "hh"
					  st.wMinute,                              // "mm"
					  st.wSecond,                              // "ss"
					  cDiff,                                   // "+" / "-"
					  abs (ltzHour),                           // "hh"
					  abs (ltzMinute));                        // "mm"
	return s_dwMaxBufferLen;
}

inline DWORD GetContentTypeFromFileName(LPCTSTR szFileName, CSimpleString& strContentType) throw()
{
	if (szFileName == NULL)
	{
		return ERROR_INVALID_DATA;
	}

	DWORD dwErr = ERROR_PATH_NOT_FOUND;
	_ATLTRY
	{
		// get the file extension
		TCHAR szExt[_MAX_EXT];
		Checked::tsplitpath_s(szFileName, NULL, 0, NULL, 0, NULL, 0, szExt, _countof(szExt));
		if (*szExt)
		{
			// Query the content type from the registry
			CRegKey rkContentType;
			dwErr = rkContentType.Open(HKEY_CLASSES_ROOT, szExt, KEY_READ);
			if (dwErr == ERROR_SUCCESS)
			{
				ULONG nChars=0;
				dwErr = rkContentType.QueryStringValue(_T("Content Type"), NULL, &nChars);
				if (dwErr == ERROR_SUCCESS)
				{
					LPTSTR szBuf = strContentType.GetBuffer(nChars);
					dwErr = rkContentType.QueryStringValue(_T("Content Type"), szBuf, &nChars);
					strContentType.ReleaseBuffer(nChars);
				}
			}
		}

		if (dwErr != ERROR_SUCCESS)
		{
			// default to application/octet-stream
			strContentType.SetString(_T("application/octet-stream"), sizeof("application/octet-stream")-1);
		}
	}
	_ATLCATCHALL()
	{
		dwErr = ERROR_OUTOFMEMORY;
	}

	return dwErr;
}

// CMimeBodyPart is an abstract base class for the body parts
// CMimeAttachment, CMimeText, CMimeHeader.
class CMimeBodyPart
{
public:

	virtual ~CMimeBodyPart() = 0 {}

	// WriteData - pure virtual method to dump the data for a body part.
	virtual BOOL WriteData(HANDLE hFile, LPOVERLAPPED pOverlapped, LPCSTR szBoundary, DWORD dwFlags = 0) = 0;

	// GetContentType - pure virtual method to get the content of a body part
	virtual LPCSTR GetContentType() = 0;

	// GetCharset - virtual method to get the character set of a body part
	// (defaults to ATLSMTP_DEFAULT_CSET).
	virtual LPCSTR GetCharset()
	{
		return ATLSMTP_DEFAULT_CSET;
	}

	virtual CMimeBodyPart* Copy() = 0;

protected:

	// MakeMimeHeader - pure virutal method to create a MIME header for a 
	// body part.
	virtual BOOL MakeMimeHeader(CStringA& header, LPCSTR szBoundary) = 0;
}; // class CMimeBodyPart


// This enum is used with the X-Priority part of the message header
enum ATL_MIME_PRIORITY 
{
	ATL_MIME_HIGH_PRIORITY   = 1, 
	ATL_MIME_NORMAL_PRIORITY = 3, 
	ATL_MIME_LOW_PRIORITY    = 5, 
	ATL_MIME_PRIORITY_ERROR  = 0
};


// CMimeHeader describes the basic RFC 822 message header.
// It also serves as the base class for the CMimeMessage object.
class CMimeHeader : public CMimeBodyPart
{
protected:

	// Pointer to MLANG's IMultiLanguage interface.
	// This is used in doing conversion from code pages
	// to MIME-compatible character sets.
	CComPtr<IMultiLanguage> m_spMultiLanguage;

	//Basic Header Parts
	CStringA        m_strFrom;
	CStringA        m_strTo;
	CStringA        m_strCc;
	CStringA        m_strBcc;
	CStringA        m_strSubject;

	//Extended Header Parts
	ATL_MIME_PRIORITY   m_nPriority;
	CStringA        m_XHeader;

	//Display Names
	CStringA        m_strSenderName;

	//MIME Character Sets
	char            m_szSubjectCharset[ATL_MAX_ENC_CHARSET_LENGTH];
	char            m_szSenderCharset[ATL_MAX_ENC_CHARSET_LENGTH];

	//Recipient and CC charsets are encoded in the Add methods

public:

	CMimeHeader() throw()
		:m_nPriority(ATL_MIME_NORMAL_PRIORITY)
	{
		m_szSubjectCharset[0] = '\0';
		m_szSenderCharset[0] = '\0';
	}

	~CMimeHeader() throw()
	{
	}

	// Initialize MLang for multilanguage support
	inline BOOL Initialize(IMultiLanguage* pMultiLanguage = NULL) throw()
	{
		if (pMultiLanguage != NULL)
		{
			m_spMultiLanguage = pMultiLanguage;
		}
		else
		{
			HRESULT hr = m_spMultiLanguage.CoCreateInstance(__uuidof(CMultiLanguage), NULL, CLSCTX_INPROC_SERVER);
			if (hr != S_OK)
				return FALSE;
		}
		return TRUE;
	}

	// Get the content type
	virtual inline LPCSTR GetContentType() throw()
	{
		return "text/plain";
	}

	// Get the character set
	virtual inline LPCSTR GetCharset() throw()
	{
		return "iso-8859-1";
	}

	virtual ATL_NOINLINE CMimeBodyPart* Copy() throw( ... )
	{
		CAutoPtr<CMimeHeader> pNewHeader;
		ATLTRY(pNewHeader.Attach(new CMimeHeader));
		if (pNewHeader)
			*pNewHeader = *this;

		return pNewHeader.Detach();
	}

	const CMimeHeader& operator=(const CMimeHeader& that) throw( ... )
	{
		if (this != &that)
		{
			m_spMultiLanguage = that.m_spMultiLanguage;
			m_strFrom = that.m_strFrom;
			m_strTo = that.m_strTo;
			m_strCc = that.m_strCc;
			m_strSubject = that.m_strSubject;

			m_nPriority = that.m_nPriority;
			m_XHeader = that.m_XHeader;

			m_strSenderName = that.m_strSenderName;

			Checked::strcpy_s(m_szSubjectCharset, ATL_MAX_ENC_CHARSET_LENGTH, that.m_szSubjectCharset);
			Checked::strcpy_s(m_szSenderCharset, ATL_MAX_ENC_CHARSET_LENGTH, that.m_szSenderCharset);
		}

		return *this;
	}

	// Set the priority of the message
	inline BOOL SetPriority(ATL_MIME_PRIORITY nPriority) throw()
	{
		if (nPriority < 0)
			return FALSE;
		m_nPriority = nPriority;
		return TRUE;
	}

	// Get the priority of the message
	inline ATL_MIME_PRIORITY GetPriority() throw()
	{
		return m_nPriority;
	}

	// Set the display (friendly) name for the header
	inline BOOL SetSenderName(LPCTSTR szName, UINT uiCodePage = 0) throw()
	{
		if (szName == NULL)
			return FALSE;

		CHeapPtr<char> szNamePtr;
		UINT nLen(0);

		BOOL bRet = AtlMimeConvertString(m_spMultiLanguage, uiCodePage, szName, &szNamePtr, &nLen);
		if (bRet)
		{
			_ATLTRY
			{
				m_strSenderName.Empty();
				m_strSenderName.Append(szNamePtr, (int) nLen);
			}
			_ATLCATCHALL()
			{
				return FALSE;
			}
			bRet = AtlMimeCharsetFromCodePage(m_szSenderCharset, uiCodePage, m_spMultiLanguage, ATL_MAX_ENC_CHARSET_LENGTH);
		}

		return bRet;
	}

	// Get the display (friendly) name for the sender
	inline LPCSTR GetSenderName() throw()
	{
		return m_strSenderName;
	}

	// Append a user defined header (should not contain CRLF)
	inline BOOL AppendUserDefinedHeader(LPCTSTR szHeaderName, LPCTSTR szHeader, UINT uiCodePage = 0) throw()
	{
		if ((szHeader == NULL) || (szHeaderName == NULL))
			return FALSE;

		_ATLTRY
		{
			CHeapPtr<char> szName;
			UINT nLen(0);

			BOOL bRet = AtlMimeConvertString(m_spMultiLanguage, uiCodePage, szHeader, &szName, &nLen);
			if (bRet)
			{
				// get the charset
				char szCharset[ATL_MAX_ENC_CHARSET_LENGTH];			
				bRet = AtlMimeCharsetFromCodePage(szCharset, uiCodePage, m_spMultiLanguage, ATL_MAX_ENC_CHARSET_LENGTH);

				if (bRet)
				{
					CStringA str;
					str.Append(szName, (int)nLen);

					// encode the string
					CHeapPtr<char> szBuf;
					DWORD dwReqLen = QEncodeGetRequiredLength(str.GetLength(), 
								ATL_MAX_ENC_CHARSET_LENGTH);

					if (szBuf.Allocate(dwReqLen) == false)
					{
						return FALSE;
					}

					DWORD dwLength(0);
					BOOL bEncoded = FALSE;
					if (!GetEncodedString(str, szCharset, szBuf, dwReqLen, dwLength, bEncoded))
					{
						return FALSE;
					}

					// add to m_XHeader
					m_XHeader += CT2CA(szHeaderName);
					m_XHeader.Append(": ", 2);
					m_XHeader.Append(szBuf, dwLength);
					m_XHeader.Append("\r\n", 2);
				}
			}

			return bRet;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}

	// Add a recipient ("To:" line)
	inline BOOL AddRecipient(LPCTSTR szAddress, LPCTSTR szName = NULL, UINT uiCodePage = 0) throw()
	{
		return AddRecipientHelper(m_strTo, szAddress, szName, uiCodePage);
	}

	// Get the recipients string ("To:" line)
	inline LPCSTR GetRecipients() throw()
	{
		return m_strTo;
	}

	// Clear all recipients ("To:" line)
	inline BOOL ClearRecipients() throw()
	{
		m_strTo.Empty();
		return TRUE;
	}

	// Add a recipient ("CC:" line)
	inline BOOL AddCc(LPCTSTR szAddress, LPCTSTR szName = NULL, UINT uiCodePage = 0) throw()
	{
		return AddRecipientHelper(m_strCc, szAddress, szName, uiCodePage);
	}

	// Get the recipients string ("CC:" line)
	inline LPCSTR GetCc() throw()
	{
		return m_strCc;
	}

	// Clear the recipients string ("CC:" line)
	inline BOOL ClearCc() throw()
	{
		m_strCc.Empty();
		return TRUE;
	}

	// Add a Bcc recipient (not output as part of message)
	inline BOOL AddBcc(LPCTSTR szAddress) throw()
	{
		if (szAddress == NULL)
		{
			return FALSE;
		}

		_ATLTRY
		{
			CStringA str = m_strBcc;

			if (m_strBcc.GetLength() > 0)
				str.Append(",", 1);

			str += CT2CA(szAddress);

			m_strBcc = str;

			return TRUE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}

	// Get the recipients string (Bcc part)
	inline LPCSTR GetBcc() throw()
	{
		return m_strBcc;
	}

	// Clear the recipients string (Bcc part)
	inline BOOL ClearBcc() throw()
	{
		m_strBcc.Empty();
		return TRUE;
	}


	inline DWORD GetRequiredRecipientsStringLength() throw()
	{
		DWORD dwRet = m_strTo.GetLength();
		if (m_strCc.GetLength())
		{
			dwRet += dwRet ? 1 : 0;
			dwRet += m_strCc.GetLength();
		}
		if (m_strBcc.GetLength())
		{
			dwRet += dwRet ? 1 : 0;
			dwRet += m_strBcc.GetLength();
		}
		dwRet++;
		return dwRet;
	}

	// returns the recipients string to be (addresses only, in comma separated format)
	ATL_NOINLINE BOOL GetRecipientsString(__out_ecount_part_z(*pdwLen, *pdwLen) LPSTR szRecip, __inout LPDWORD pdwLen) throw()
	{
		if ( (szRecip == NULL) || (pdwLen == NULL) )
		{
			return FALSE;
		}

		if ( *pdwLen < GetRequiredRecipientsStringLength())
		{
			*pdwLen = GetRequiredRecipientsStringLength();
			return FALSE;
		}

		DWORD dwMaxLen = *pdwLen;
		*pdwLen = 0;

		DWORD dwLen = 0;
		DWORD dwTotalLen = 0;
		if (m_strTo.GetLength() > 0)
		{
			dwLen = *pdwLen - dwTotalLen;
			if (AtlMimeMakeRecipientsString(m_strTo, szRecip, &dwLen) != TRUE)
			{
				return FALSE;
			}
			szRecip+= dwLen;
			dwTotalLen = dwLen;
		}

		if (m_strCc.GetLength() > 0)
		{
			if (dwTotalLen)
			{
				*szRecip++ = ',';
				dwTotalLen++;
			}
			dwLen = *pdwLen - dwTotalLen;
			if (AtlMimeMakeRecipientsString(m_strCc, szRecip, &dwLen) != TRUE)
			{
				return FALSE;
			}
			szRecip+= dwLen;
			dwTotalLen+= dwLen;
		}

		if (m_strBcc.GetLength() > 0)
		{
			dwLen = m_strBcc.GetLength();
			if (dwTotalLen)
			{
				*szRecip++ = ',';
				dwTotalLen++;
			}
			dwLen = *pdwLen - dwTotalLen;
			Checked::memcpy_s(szRecip, dwMaxLen-dwTotalLen, m_strBcc, dwLen);
			szRecip+= dwLen;
			dwTotalLen+= dwLen;
		}

		*szRecip = '\0';
		*pdwLen = dwTotalLen;

		return TRUE;
	}


	// Get the sender
	inline LPCSTR GetSender() throw()
	{
		return m_strFrom;
	}

	// Set the sender
	inline BOOL SetSender(LPCTSTR szSender) throw()
	{
		if (szSender == NULL)
			return FALSE;

		_ATLTRY
		{
			m_strFrom = CT2CA(szSender);
			return TRUE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}

	// Set the subject
	inline BOOL SetSubject(LPCTSTR szSubject, UINT uiCodePage = 0) throw()
	{
		if (szSubject == NULL)
			return FALSE;

		_ATLTRY
		{
			CHeapPtr<char> szName;
			UINT nLen(0);

			BOOL bRet = AtlMimeConvertString(m_spMultiLanguage, uiCodePage, szSubject, &szName, &nLen);
			if (bRet)
			{
				m_strSubject.Empty();
				m_strSubject.Append(szName, (int)nLen);
				bRet = AtlMimeCharsetFromCodePage(m_szSubjectCharset, uiCodePage, m_spMultiLanguage, ATL_MAX_ENC_CHARSET_LENGTH);
			}

			return bRet;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}

	// Get the subject
	inline LPCSTR GetSubject() throw()
	{
		return (LPCSTR)m_strSubject;
	}

	// Dump the header to hFile
	virtual inline BOOL WriteData(HANDLE hFile, LPOVERLAPPED pOverlapped, LPCSTR /*szBoundary*/, DWORD dwFlags = 0) throw()
	{
		if (pOverlapped == NULL)
		{
			return FALSE;
		}

		int nMaxSendLen = GetRequiredBufferSize(ATLSMTP_MAX_LINE_LENGTH-4);
		CHeapPtr<char> spSendBuffer;
		if (!spSendBuffer.Allocate(nMaxSendLen))
			return FALSE;
		
		// choose QEncode here, because the max QEncodeGetRequiredLength will always
		// return a value greater than BEncodeGetRequiredLength
		int nBufLen = __max(QEncodeGetRequiredLength(m_strSubject.GetLength(), 
								ATL_MAX_ENC_CHARSET_LENGTH),
						  QEncodeGetRequiredLength(m_strSenderName.GetLength(), 
								ATL_MAX_ENC_CHARSET_LENGTH)+m_strFrom.GetLength()+2);

		CHeapPtr<char> spBuf;
		if (!spBuf.Allocate(nBufLen))
			return FALSE;

		int nMaxLen = nBufLen;
		DWORD dwOffset = 0;

		char szDate[ATL_MIME_DATE_LEN];

		SetRfc822Time(szDate, ATL_MIME_DATE_LEN);
		char *pSendBuffer = spSendBuffer;

		DWORD dwLength = (DWORD) strlen(szDate);
		
		if(dwLength > ATLSMTP_MAX_LINE_LENGTH -2 -dwOffset )
			return FALSE;
		
		Checked::memcpy_s(pSendBuffer+dwOffset, nMaxSendLen-dwOffset, szDate, dwLength);
		dwOffset += dwLength;
		*(pSendBuffer+dwOffset++) = '\r';
		*(pSendBuffer+dwOffset++) = '\n';

		int dwHeaderPartLength = 0;
		*spBuf = '\0';

		// Get the sender name
		BOOL bRet = TRUE;
		BOOL bEncoded = FALSE;
		if (m_strSenderName.GetLength() > 0)
		{
			bRet = GetEncodedString(m_strSenderName, m_szSenderCharset, spBuf, nBufLen, dwLength, bEncoded);
			dwHeaderPartLength += dwLength;
		}

		// Get the sender email address
		if (bRet && m_strFrom.GetLength() > 0)
		{
				
			if (dwHeaderPartLength != 0)
			{
				if(dwHeaderPartLength + 1 > nBufLen)
					return FALSE;
			
				*(spBuf+dwHeaderPartLength++) = ' ';
			}
			
			if(dwHeaderPartLength + m_strFrom.GetLength() + 2 > nBufLen)
				return FALSE;

			*(spBuf+dwHeaderPartLength++) = '<';
			if (dwHeaderPartLength < 0 || dwHeaderPartLength > nMaxLen)
			{
				return FALSE;
			}
			Checked::memcpy_s(spBuf+dwHeaderPartLength, nMaxLen-dwHeaderPartLength, (LPCSTR)m_strFrom, m_strFrom.GetLength());
			dwHeaderPartLength+= m_strFrom.GetLength();
			*(spBuf+dwHeaderPartLength++) = '>';
		}

		// Output the "From: " line
		if (bRet && dwHeaderPartLength != 0)
		{
			const char szFrom[] = "From: ";
			if(sizeof(szFrom)/sizeof(szFrom[0])-1 > ATLSMTP_MAX_LINE_LENGTH -2 -dwOffset )
				return FALSE;
			if (dwOffset > static_cast<DWORD>(nMaxSendLen))
			{
				return FALSE;
			}
			Checked::memcpy_s(pSendBuffer+dwOffset, nMaxSendLen-dwOffset, szFrom, _countof(szFrom)-1);
			dwOffset+= (sizeof(szFrom)/sizeof(szFrom[0])-1) ;
			DWORD dwWritten = ATLSMTP_MAX_LINE_LENGTH - 2 - dwOffset;
			bRet = FormatField((LPBYTE)(char*)spBuf, dwHeaderPartLength, (LPBYTE)(pSendBuffer+dwOffset), &dwWritten, dwFlags);
			dwOffset += dwWritten;
			*(pSendBuffer+dwOffset++) = '\r';
			*(pSendBuffer+dwOffset++) = '\n';
		}

		// Output the subject
		if (bRet && m_strSubject.GetLength() > 0)
		{
			dwLength = 0;
			bRet = GetEncodedString(m_strSubject, m_szSubjectCharset, spBuf, nBufLen, dwLength, bEncoded);
			if (bRet && dwLength != 0)
			{
				const char szSubject[] = "Subject: ";
				if(sizeof(szSubject)/sizeof(szSubject[0])-1 > ATLSMTP_MAX_LINE_LENGTH -2 -dwOffset )
					return FALSE;
				if (dwOffset > static_cast<DWORD>(nMaxSendLen))
				{
					return FALSE;
				}
				Checked::memcpy_s(pSendBuffer+dwOffset, nMaxSendLen-dwOffset, szSubject, _countof(szSubject)-1);
				dwOffset+= (sizeof(szSubject)/sizeof(szSubject[0])-1);
				DWORD dwWritten = ATLSMTP_MAX_LINE_LENGTH - 2 - dwOffset;
				bRet = FormatField((LPBYTE)(char*)spBuf, dwLength, (LPBYTE)(pSendBuffer+dwOffset), &dwWritten, dwFlags);
				dwOffset += dwWritten;
				*(pSendBuffer+dwOffset++) = '\r';
				*(pSendBuffer+dwOffset++) = '\n';
			}
		}

		// Output the "To:" line
		if (bRet && m_strTo.GetLength() > 0)
		{
			const char szTo[] = "To: ";
			if(sizeof(szTo)/sizeof(szTo[0])-1 > ATLSMTP_MAX_LINE_LENGTH -2 -dwOffset )
					return FALSE;
			if (dwOffset > static_cast<DWORD>(nMaxSendLen))
			{
				return FALSE;
			}
			Checked::memcpy_s(pSendBuffer+dwOffset, nMaxSendLen-dwOffset, szTo, _countof(szTo)-1);
			dwOffset+= (sizeof(szTo)/sizeof(szTo[0]) -1);
			DWORD dwWritten = ATLSMTP_MAX_LINE_LENGTH - 2 - dwOffset;
			bRet = FormatRecipients((LPBYTE)((LPCSTR)m_strTo), m_strTo.GetLength(), (LPBYTE)(pSendBuffer+dwOffset), &dwWritten);
			dwOffset+= dwWritten;
			*(pSendBuffer+dwOffset++) = '\r';
			*(pSendBuffer+dwOffset++) = '\n';
		}

		// Output the "CC:" line
		if (bRet && m_strCc.GetLength() > 0)
		{
			const char szCC[] = "CC: ";
			if(sizeof(szCC)/sizeof(szCC[0])-1 > ATLSMTP_MAX_LINE_LENGTH -2 -dwOffset )
				return FALSE;
			if (dwOffset > static_cast<DWORD>(nMaxSendLen))
			{
				return FALSE;
			}
			Checked::memcpy_s(pSendBuffer+dwOffset, nMaxSendLen-dwOffset, szCC, _countof(szCC)-1);
			dwOffset+= (sizeof(szCC)/sizeof(szCC[0]) -1);
			DWORD dwWritten = ATLSMTP_MAX_LINE_LENGTH - 2 - dwOffset;
			bRet = FormatRecipients((LPBYTE)((LPCSTR)m_strCc), m_strCc.GetLength(), (LPBYTE)(pSendBuffer+dwOffset), &dwWritten);
			dwOffset+= dwWritten;
			*(pSendBuffer+dwOffset++) = '\r';
			*(pSendBuffer+dwOffset++) = '\n';
		}

		// Send the header
		if (bRet && dwOffset)
			bRet = AtlSmtpSendAndWait(hFile, pSendBuffer, dwOffset, pOverlapped);

		return bRet;
	}

protected:

	// Make the mime header
	virtual inline BOOL MakeMimeHeader(CStringA& /*header*/, LPCSTR /*szBoundary*/) throw()
	{
		// The message header does not have its own MIME header
		ATLASSERT(FALSE);
		return TRUE;
	}

	// Get an encoded string for a header field
	inline BOOL GetEncodedString(__in CStringA& headerString, __in LPCSTR szCharset, __out_ecount_part_z(nBufLen, dwLength) LPSTR szBuf, __in int nBufLen, __out DWORD& dwLength, __out BOOL& bEncoded) throw()
	{
//		BOOL bEncoded = FALSE;
		bEncoded = FALSE;
		if (m_spMultiLanguage.p)
		{
			// only encode if there are 8bit characters
			int nExtendedChars = GetExtendedChars(headerString, headerString.GetLength());
			if (nExtendedChars)
			{
				// choose smallest encoding
				if (((nExtendedChars*100)/headerString.GetLength()) < 17)
				{
					int nEncCnt = 0;
					if (!QEncode((LPBYTE)((LPCSTR)headerString), headerString.GetLength(), szBuf, &nBufLen, szCharset, &nEncCnt))
					{
						return FALSE;
					}

					//if no unsafe characters were encountered, just output it
					if (nEncCnt != 0)
					{
						bEncoded = TRUE;
					}
				}
				else
				{
					if (!BEncode((LPBYTE)((LPCSTR)headerString), headerString.GetLength(), szBuf, &nBufLen, szCharset))
					{
						return FALSE;
					}

					bEncoded = TRUE;
				}
			}
		}

		if (!bEncoded)
		{
			// there was no encoding
			dwLength = (DWORD) headerString.GetLength();
			if(dwLength > DWORD(nBufLen))
				return FALSE;
			Checked::memcpy_s(szBuf, nBufLen, headerString, dwLength);
		}
		else
		{
			dwLength = nBufLen;
		}
		return TRUE;
	}


	// Helper function for adding recipients
	inline BOOL AddRecipientHelper(CStringA& str, LPCTSTR szAddress, LPCTSTR szName = NULL, UINT uiCodePage = 0) throw()
	{
		if ((szAddress == NULL) && (szName == NULL))
		{
			return FALSE;
		}

		_ATLTRY
		{
			if (szName)
			{
				CHeapPtr<char> szNamePtr;
				UINT nLen(0);

				BOOL bRet = AtlMimeConvertString(m_spMultiLanguage, uiCodePage, szName, &szNamePtr, &nLen);
				if (bRet)
				{
					CStringA Name(szNamePtr, (int)nLen);

					char szCharset[ATL_MAX_ENC_CHARSET_LENGTH];

					if (!AtlMimeCharsetFromCodePage(szCharset, uiCodePage, m_spMultiLanguage, ATL_MAX_ENC_CHARSET_LENGTH))
					{
						return FALSE;
					}

					CFixedStringT<CStringA, 256> strBuf;

					int nBufLen = QEncodeGetRequiredLength(Name.GetLength(), 
								ATL_MAX_ENC_CHARSET_LENGTH)+1;

					char * szBuf = strBuf.GetBuffer(nBufLen);
					if (szBuf == NULL)
					{
						return FALSE;
					}

					DWORD dwLength = 0;
					BOOL bEncoded = FALSE;
					if (!GetEncodedString(Name, szCharset, szBuf, nBufLen, dwLength, bEncoded))
					{
						strBuf.ReleaseBuffer();
						return FALSE;
					}

					strBuf.ReleaseBuffer(dwLength);

					// append comma if there are existing recipients
					if (str.GetLength() != 0)
					{
						str.Append(", ", 2);
					}

					if (bEncoded == FALSE)
					{
						// need to escape the string if no encoding
						strBuf.Replace("\\", "\\\\");
						strBuf.Replace("\"", "\\\"");

						// wrap the unescaped name in quotes
						str.Append("\"", 1);
					}
					str += strBuf;
					if (bEncoded == FALSE)
					{
						// close quote
						str.Append("\"", 1);
					}
				}
				else
				{
					return bRet;
				}
			}

			if (szAddress)
			{
				if (szName)
				{
					str.Append(" ", 1);
				}
				else
				{
					// append comma if there are existing recipients
					if (str.GetLength() != 0)
					{
						str.Append(", ", 2);
					}
				}
				str.Append("<", 1);
				str += CT2CA(szAddress);
				str.Append(">", 1);
			}
			return TRUE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}

	// Get the formatted header information
	inline BOOL FormatField(LPBYTE pbSrcData, int nSrcLen, LPBYTE pbDest, 
		DWORD* pnBufLen, DWORD dwFlags = 0) throw()
	{
		if(pnBufLen == NULL)
			return FALSE;
			
		int nRead = 0;

		// 9 is the length of the maximum field name : "Subject :"
		// we set that here for simplicity
		int nLineLen = 9;
		DWORD nWritten = 0;

		//subtract 2 from these because it's easier for when we have
		//to break lines with a CRLF (and tab if necessary)
		int nMaxLineLength = ATLSMTP_MAX_LINE_LENGTH-3;
		while (nRead < nSrcLen)
		{
			//if we're at the end of the line, break it
			if (nLineLen == nMaxLineLength)
			{
				if( nWritten + 2  > *pnBufLen)
					return FALSE;
					
				*pbDest++ = '\r';
				*pbDest++ = '\n';
				nWritten+= 2;
				nLineLen = -1;

				if ((dwFlags & ATLSMTP_FORMAT_SMTP))
				{
					if(nWritten + 1 > *pnBufLen)
						return FALSE;
						
					*pbDest++ = '\t';
					nWritten++;
					nLineLen++;
				}
			}

			//if we hit a CRLF, reset nLineLen
			if (*pbSrcData == '\n' && nRead > 0 && *(pbSrcData-1) == '\r')
			{
				nLineLen = -1;
			}

			if(nWritten + 1 > *pnBufLen)
				return FALSE;
				
			*pbDest++ = *pbSrcData++;
			nRead++;
			nWritten++;
			nLineLen++;
		}

		*pnBufLen = (DWORD)nWritten;

		return TRUE;
	}


	// Get the formatted recipient information
	inline BOOL FormatRecipients(LPBYTE pbSrcData, int nSrcLen, LPBYTE pbDest, 
		DWORD* pnBufLen) throw()
	{
		
		if(pnBufLen == NULL)
			return FALSE;
			
		int nRead    = 0;
		DWORD nWritten = 0;

		while (nRead < nSrcLen)
		{
			if (*pbSrcData == ',')
			{
				if(nWritten + 4 > *pnBufLen)
					return FALSE;
					
				*pbDest++ = *pbSrcData++;
				nRead++;
				if (nRead+1 <= nSrcLen && *pbSrcData == ' ')
				{
					pbSrcData++;
					nRead++;
				}
				*pbDest++ = '\r';
				*pbDest++ = '\n';
				*pbDest++ = '\t';
				nWritten+= 4;

				continue;
			}

			if(nWritten + 1 > *pnBufLen)
				return FALSE;
				
			*pbDest++ = *pbSrcData++;
			nRead++;
			nWritten++;
		}

		*pnBufLen = nWritten;

		return TRUE;
	}

	// Get the required buffer size for the header
	inline int GetRequiredBufferSize(int nMaxLineLength) throw()
	{
		const static DWORD DATELINE    = 27;
		const static DWORD FROMLINE    = 10;
		const static DWORD TOLINE      = 6;
		const static DWORD CCLINE      = 6;
		const static DWORD SUBJECTLINE = 11;

		//data lengths (QEncoding potentially takes up more space than BEncoding,
		//so default to it)
		int nRequiredLength = QEncodeGetRequiredLength(m_strSenderName.GetLength(), ATL_MAX_ENC_CHARSET_LENGTH)
			+QEncodeGetRequiredLength(m_strSubject.GetLength(), ATL_MAX_ENC_CHARSET_LENGTH);
		nRequiredLength += m_strFrom.GetLength()+m_strTo.GetLength()+m_strCc.GetLength();

		//Add space for date
		nRequiredLength += DATELINE;

		//Add space for From: line
		nRequiredLength += FROMLINE;

		//Add space for To: line
		nRequiredLength += TOLINE;

		//Add space for Cc: line
		nRequiredLength += CCLINE;

		//Add space for Subject: line
		nRequiredLength += SUBJECTLINE;

		//Add space for line breaks and tabs
		nRequiredLength += 3*(nRequiredLength/nMaxLineLength);

		//Trailing CRLF
		nRequiredLength += 2;

		return nRequiredLength;
	}

}; // class CMimeHeader


// CMimeAttachment is an abstract base class for MIME message attachments.
// It serves as a base class for CMimeFileAttachment and CMimeRawAttachment
class CMimeAttachment : public CMimeBodyPart
{
protected:

	// the encoding scheme (ATLSMTP_BASE64_ENCODE, ATLSMTP_UUENCODE, ATLSMTP_QP_ENCODE)
	int      m_nEncodingScheme;

	// the content type of the attachment
	CStringA m_ContentType;

	// the character set
	char     m_szCharset[ATL_MAX_ENC_CHARSET_LENGTH];

	// the encode string ("base64", "quoted-printable", "uuencode")
	char     *m_pszEncodeString;

	// the display name of the attachment
	TCHAR    m_szDisplayName[_MAX_FNAME];

public:
	CMimeAttachment() throw()
		:m_nEncodingScheme(ATLSMTP_BASE64_ENCODE), m_pszEncodeString(NULL)
	{
		m_szCharset[0] = 0;
		m_szDisplayName[0] = 0;
	}

	virtual ~CMimeAttachment() throw()
	{
	}

	// CMimeFileAttachment and CMimeRawAttachment have to handle their own dumping
	virtual inline BOOL WriteData(HANDLE hFile, LPOVERLAPPED pOverlapped, LPCSTR szBoundary, DWORD dwFlags = 0) = 0;

	// Set the encoding scheme of the attachment
	inline BOOL SetEncodingScheme(int nScheme) throw()
	{
		if (nScheme != ATLSMTP_BASE64_ENCODE && nScheme != ATLSMTP_UUENCODE && nScheme != ATLSMTP_QP_ENCODE)
		{
			return FALSE;
		}

		m_nEncodingScheme = nScheme;
		return TRUE;
	}

	// Set the Content-Type of the attachment
	inline BOOL SetContentType(LPCTSTR szContent) throw()
	{
		_ATLTRY
		{
			m_ContentType = CT2CA(szContent);
			return TRUE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}

	// Get the content type of the attachment
	virtual inline LPCSTR GetContentType() throw()
	{
		return m_ContentType;
	}

	// Get the character set of the attachment
	virtual inline LPCSTR GetCharset() throw()
	{
		return m_szCharset;
	}

	virtual ATL_NOINLINE CMimeBodyPart* Copy() = 0;

	const CMimeAttachment& operator=(const CMimeAttachment& that) throw( ... )
	{
		if (this != &that)
		{
			m_nEncodingScheme = that.m_nEncodingScheme;
			m_ContentType = that.m_ContentType;
			Checked::strcpy_s(m_szCharset, ATL_MAX_ENC_CHARSET_LENGTH, that.m_szCharset);
			m_pszEncodeString = that.m_pszEncodeString;
			Checked::tcscpy_s(m_szDisplayName, _countof(m_szDisplayName), that.m_szDisplayName);
		}

		return *this;
	}

protected:

	// Make the MIME header for the attachment
	virtual inline BOOL MakeMimeHeader(CStringA& header, LPCSTR szBoundary) throw()
	{
		// if no display name is specified, default to "rawdata"
		return MakeMimeHeader(header, szBoundary, _T("rawdata"));
	}

	// Make the MIME header with the specified filename
	virtual inline BOOL MakeMimeHeader(CStringA& header, LPCSTR szBoundary, LPCTSTR szFileName)
	{
		ATLENSURE(szBoundary != NULL);
		ATLASSERT(szFileName != NULL);
		ATLASSUME(m_pszEncodeString != NULL);

		char szBegin[256];
		if (*szBoundary)
		{
			// this is not the only body part
			Checked::memcpy_s(szBegin, 256, ATLMIME_SEPARATOR, sizeof(ATLMIME_SEPARATOR));
			Checked::memcpy_s(szBegin+6, 250, szBoundary, ATL_MIME_BOUNDARYLEN);
			*(szBegin+(ATL_MIME_BOUNDARYLEN+6)) = '\0';
		}
		else
		{
			// this is the only body part, so output the MIME header
			Checked::memcpy_s(szBegin, 256, ATLMIME_VERSION, sizeof(ATLMIME_VERSION));
		}

		// Get file name with the path stripped out
		TCHAR szFile[MAX_PATH+1];
		TCHAR szExt[_MAX_EXT+1];
		Checked::tsplitpath_s(szFileName, NULL, 0, NULL, 0, szFile, _countof(szFile), szExt, _countof(szExt));
		Checked::tcscat_s(szFile, _countof(szFile), szExt);

		_ATLTRY
		{
			CT2CAEX<MAX_PATH+1> szFileNameA(szFile);

			CStringA szDisplayName(szFile);
			if (m_szDisplayName[0] != '\0')
			{
				szDisplayName = CT2CAEX<_MAX_FNAME+1>(m_szDisplayName);
			}

			header.Format("%s\r\nContent-Type: %s;\r\n\tcharset=\"%s\"\r\n\tname=\"%s\"\r\n"
				"Content-Transfer-Encoding: %s\r\nContent-Disposition: attachment;\r\n\tfilename=\"%s\"\r\n\r\n",
				szBegin, (LPCSTR) m_ContentType, m_szCharset, (LPCSTR) szDisplayName, m_pszEncodeString, (LPCSTR) szFileNameA); 
			return TRUE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}

	// Get encoding information
	inline BOOL GetEncodingInformation(int* pnRequiredLength, int* pnLineLength)
	{
		ATLENSURE(pnRequiredLength != NULL);
		ATLENSURE(pnLineLength != NULL);

		switch(m_nEncodingScheme)
		{
			case ATLSMTP_BASE64_ENCODE:
				m_pszEncodeString = "base64";
				*pnLineLength = ATLSMTP_MAX_BASE64_LINE_LENGTH;
				*pnRequiredLength = Base64EncodeGetRequiredLength(ATLSMTP_MAX_BASE64_LINE_LENGTH);
				break;
			case ATLSMTP_UUENCODE:
				m_pszEncodeString ="uuencode";
				*pnLineLength = ATLSMTP_MAX_UUENCODE_LINE_LENGTH;
				*pnRequiredLength = UUEncodeGetRequiredLength(ATLSMTP_MAX_UUENCODE_LINE_LENGTH);
				break;
			case ATLSMTP_QP_ENCODE:
				m_pszEncodeString = "quoted-printable";
				*pnLineLength = ATLSMTP_MAX_QP_LINE_LENGTH;
				*pnRequiredLength = QPEncodeGetRequiredLength(ATLSMTP_MAX_QP_LINE_LENGTH);
				break;
			default:
				return FALSE;
		}
		return TRUE;
	}

}; // class CMimeAttachment


// CMimeFileAttachment represents a MIME file attachment body part
class CMimeFileAttachment : public CMimeAttachment
{

protected:
	// The filename
	TCHAR m_szFileName[MAX_PATH+1];

public:
	CMimeFileAttachment() throw()
	{
		m_szFileName[0] = 0;
	}

	virtual ATL_NOINLINE CMimeBodyPart* Copy() throw( ... )
	{
		CAutoPtr<CMimeFileAttachment> pNewAttachment;
		ATLTRY(pNewAttachment.Attach(new CMimeFileAttachment));
		if (pNewAttachment)
			*pNewAttachment = *this;

		return pNewAttachment.Detach();
	}

	const CMimeFileAttachment& operator=(const CMimeFileAttachment& that) throw( ... )
	{
		if (this != &that)
		{
			CMimeAttachment::operator=(that);
			Checked::tcscpy_s(m_szFileName, _countof(m_szFileName), that.m_szFileName);
		}

		return *this;
	}


	// Initialize the file attachment
	// szFileName - the actual file name
	// szDisplayName - the display name for the file (optional)
	// pMultiLanguage - the IMulitLanguage pointer for codepage to charset conversion (optional)
	// uiCodePage - the code page (optional)
	inline BOOL Initialize(LPCTSTR szFileName, LPCTSTR szDisplayName = NULL, IMultiLanguage* pMultiLanguage = NULL, UINT uiCodePage = 0) throw()
	{
		if (!AtlMimeCharsetFromCodePage(m_szCharset, uiCodePage, pMultiLanguage, ATL_MAX_ENC_CHARSET_LENGTH))
			return FALSE;

		if( _tcslen(szFileName) > MAX_PATH )
		{
			return FALSE;
		}
		Checked::tcscpy_s(m_szFileName, _countof(m_szFileName), szFileName);

		if (szDisplayName)
		{
			// use the user-specified display name
			size_t nLen = _tcslen(szDisplayName)+1;
			if (nLen <= _countof(m_szDisplayName))
			{
				Checked::tcscpy_s(m_szDisplayName, _countof(m_szDisplayName), szDisplayName);
			}
			else
			{
				Checked::tcsncpy_s(m_szDisplayName, _countof(m_szDisplayName), szDisplayName, _countof(m_szDisplayName) - 4);
				Checked::tcscpy_s(m_szDisplayName + _countof(m_szDisplayName) - 4, 4, _T("..."));
			}
		}
		else
		{
			// otherwise there is no display name
			*m_szDisplayName = '\0';
		}
		return TRUE;
	}

	// Dump the data for the file attachment
	virtual inline BOOL WriteData(HANDLE hFile, LPOVERLAPPED pOverlapped, LPCSTR szBoundary, DWORD dwFlags = 0) throw()
	{
		if ((pOverlapped == NULL) || (szBoundary == NULL))
		{
			return FALSE;
		}

		int nLineLength = 0;
		int nRequiredLength = 0;

		if (!GetEncodingInformation(&nRequiredLength, &nLineLength))
			return FALSE;

		//Try to open the file that is being attached
		CAtlFile readFile;
		if (FAILED(readFile.Create(m_szFileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING)))
			return FALSE;

		//Make the mime header
		CStringA header;
		if (!MakeMimeHeader(header, szBoundary, m_szFileName))
		{
			return FALSE;
		}

		//Try to send the mime header
		if (!AtlSmtpSendAndWait(hFile, ((LPCSTR)header), header.GetLength(), pOverlapped))
		{
			return FALSE;
		}

		int nGetLines = ATLSMTP_GET_LINES;

		nRequiredLength *= nGetLines;

		//dwToGet is the total number of characters to attempt to get
		DWORD dwToGet = (DWORD)nGetLines*nLineLength;

		//allocate the data array
		CHeapPtr<BYTE> spData;
		if (!spData.Allocate(dwToGet+1))
			return FALSE;

// if double buffering is defined, create two buffers
#ifdef ATLSMTP_DOUBLE_BUFFERED
		CHeapPtr<char> buffer1;
		if (!buffer1.Allocate(nRequiredLength+3))
			return FALSE;

		CHeapPtr<char> buffer2;
		if (!buffer2.Allocate(nRequiredLength+3))
			return FALSE;

		char* currBuffer = buffer1;
		char* prevBuffer = NULL;
		int nCurrBuffer = 0;
		DWORD dwPrevLength = 0;
#else
		CHeapPtr<char> currBuffer;
		if (!currBuffer.Allocate(nRequiredLength+3))
			return FALSE;

#endif // ATLSMTP_DOUBLE_BUFFERED

		int nEncodedLength = nRequiredLength;
		BOOL bRet = FALSE;
		DWORD dwRead = 0;
		DWORD dwTotalRead = 0;
		DWORD dwCurrRead = 0;

		do
		{
			do 
			{
				//Read a chunk of data from the file increment buffer offsets and amount to read
				//based on what's already been read in this iteration of the loop
				HRESULT hr = readFile.Read(((LPBYTE)spData)+dwCurrRead, dwToGet-dwCurrRead, dwRead);
				if (FAILED(hr))
				{
					if (hr != AtlHresultFromWin32(ERROR_MORE_DATA))
					{
						return FALSE;
					}
				}
				dwCurrRead += dwRead;

			} while (dwRead != 0 && dwCurrRead < dwToGet);

			//reset nEncodedLength
			nEncodedLength = nRequiredLength;
			switch (m_nEncodingScheme)
			{
				case ATLSMTP_BASE64_ENCODE:
					//if we are at the end of input (dwCurrRead < dwToGet), output the trailing padding if necessary
					//(ATL_FLAG_NONE)
					bRet = Base64Encode(spData, dwCurrRead, currBuffer, &nEncodedLength, 
						(dwCurrRead < dwToGet ? ATL_BASE64_FLAG_NONE: ATL_BASE64_FLAG_NOPAD));
					//Base64Encoding needs explicit CRLF added
					if (dwCurrRead < dwToGet)
					{
						currBuffer[nEncodedLength++] = '\r';
						currBuffer[nEncodedLength++] = '\n';
					}
					break;
				case ATLSMTP_UUENCODE:
					//if we are at the beginning of the input, output the header (ATL_UUENCODE_HEADER)
					//if we are the end of input (dwCurrRead < dwToGet), output the 'end'
					//we are encoding for purposes of sending mail, so stuff dots (ATL_UUENCODE_DOT)
					bRet = UUEncode(spData, dwCurrRead, currBuffer, &nEncodedLength, m_szFileName,
									(dwTotalRead > 0 ? 0 : ATLSMTP_UUENCODE_HEADER) | 
									(dwCurrRead < dwToGet ? ATLSMTP_UUENCODE_END : 0) | 
									((dwFlags & ATLSMTP_FORMAT_SMTP) ? ATLSMTP_UUENCODE_DOT : 0));
					break;
				case ATLSMTP_QP_ENCODE:
					//we are encoding for purposes of sending mail, so stuff dots
					bRet = QPEncode(spData, dwCurrRead, currBuffer, &nEncodedLength, 
									((dwFlags & ATLSMTP_FORMAT_SMTP) ? ATLSMTP_QPENCODE_DOT : 0) |
									(dwCurrRead < dwToGet ? 0 : ATLSMTP_QPENCODE_TRAILING_SOFT));
					break;
			}
			//try to send the encoded data
#ifdef ATLSMTP_DOUBLE_BUFFERED
			if (bRet)
			{
				bRet = AtlSmtpSendOverlapped(hFile, currBuffer, nEncodedLength, 
					prevBuffer, dwPrevLength, pOverlapped);
			}

			//swap the buffers
			dwPrevLength = nEncodedLength;
			prevBuffer = currBuffer;
			currBuffer = (nCurrBuffer == 0 ? buffer2 : buffer1);
			nCurrBuffer = (nCurrBuffer == 0 ? 1 : 0);
#else
			if (bRet)
			{
				bRet = AtlSmtpSendAndWait(hFile, currBuffer, nEncodedLength, pOverlapped);
			}
#endif // ATLSMTP_DOUBLE_BUFFERED

			dwTotalRead += dwCurrRead;
			if (dwRead != 0)
				dwCurrRead = 0;

			nEncodedLength = nRequiredLength;

		} while (dwRead != 0 && bRet);

		//ensure that the last Send sent all the data
#ifdef ATLSMTP_DOUBLE_BUFFERED
		DWORD dwWritten = 0, dwErr = 0;
		if (!GetOverlappedResult(hFile, pOverlapped, &dwWritten, TRUE))
		{
			if ((dwErr = GetLastError()) != ERROR_IO_PENDING && dwErr != ERROR_IO_INCOMPLETE)
			{
				bRet = FALSE;
			}
			else if (dwWritten < dwPrevLength)
			{
				bRet = AtlSmtpSendAndWait(hFile, prevBuffer+dwWritten, 
					dwPrevLength-dwWritten, pOverlapped);
			}
		}
#endif // ATLSMTP_DOUBLE_BUFFERED

		//for uuencoding, if the last chunk read was of size dwToGet, but it was also the end of the file,
		//the "end" keyword will not get encoded, so a check is necessary
		if (m_nEncodingScheme == ATLSMTP_UUENCODE && dwCurrRead == dwToGet)
		{
			bRet = UUEncode(spData, 0, currBuffer, &nEncodedLength, m_szFileName, 
							(dwFlags & ATLSMTP_FORMAT_SMTP ? ATLSMTP_UUENCODE_DOT : 0) |
							ATLSMTP_UUENCODE_END);
			if (bRet)
			{
				bRet = AtlSmtpSendAndWait(hFile, currBuffer, nEncodedLength, pOverlapped);
			}
		}

		return bRet;
	}
}; // class CMimeFileAttachment

// CMimeRawAttachment represents a file attachment MIME body part.
// The data provided is not a file, but a blob of raw data.
class CMimeRawAttachment : public CMimeAttachment
{
protected:

	//the raw data
	void* m_pvRaw;

	//the length
	DWORD m_dwLength;

	//whether or not we own it
	bool  m_bShared;

public:
	CMimeRawAttachment() throw()
		:m_dwLength(0), m_bShared(false), m_pvRaw(NULL)
	{
	}

	~CMimeRawAttachment() throw()
	{
		//If we own the raw data, free it
		if (!m_bShared && m_pvRaw)
			free(m_pvRaw);
	}

	virtual ATL_NOINLINE CMimeBodyPart* Copy() throw( ... )
	{
		CAutoPtr<CMimeRawAttachment> pNewAttachment;
		ATLTRY(pNewAttachment.Attach(new CMimeRawAttachment));
		if (pNewAttachment)
			*pNewAttachment = *this;

		return pNewAttachment.Detach();
	}

	const CMimeRawAttachment& operator=(const CMimeRawAttachment& that) throw( ... )
	{
		if (this != &that)
		{
			CMimeAttachment::operator=(that);
			if (!m_bShared && m_pvRaw)
				free(m_pvRaw);

			m_bShared = that.m_bShared;
			m_dwLength = that.m_dwLength;

			if (m_bShared)
			{
				m_pvRaw = that.m_pvRaw;
			}
			else
			{
				m_pvRaw = malloc(m_dwLength);
				if (m_pvRaw)
				{
					Checked::memcpy_s(m_pvRaw, m_dwLength, that.m_pvRaw, m_dwLength);
				}
			}
		}

		return *this;
	}

	// Initialize the attachment
	// pData - the data
	// nDataLength - the size of pData in BYTEs
	// bCopyData - flag specifying whether CMimeRawAttachment should make a copy of the data (optional)
	// pMultiLanguage - the IMultiLanguage pointer for codepage to character set conversion (optional)
	// uiCodePage - the codepage (optional)
	inline BOOL Initialize(void* pData, DWORD nDataLength, BOOL bCopyData = TRUE, LPCTSTR szDisplayName = NULL, 
		IMultiLanguage* pMultiLanguage = NULL, UINT uiCodePage = 0) throw()
	{
		// if we're already attached to some data, and it's not shared, free it
		if (m_pvRaw && !m_bShared)
			free(m_pvRaw);
		m_pvRaw = NULL;

		m_dwLength = nDataLength;
		if (bCopyData)
		{
			m_pvRaw = calloc(sizeof(BYTE),m_dwLength);
			if (!m_pvRaw)
			{
				return FALSE;
			}
			Checked::memcpy_s(m_pvRaw, m_dwLength, pData, m_dwLength);
			m_bShared = false;
		}
		else
		{
			m_pvRaw = pData;
			m_bShared = true;
		}

		if (!AtlMimeCharsetFromCodePage(m_szCharset, uiCodePage, pMultiLanguage, ATL_MAX_ENC_CHARSET_LENGTH))
			return FALSE;

		if (szDisplayName)
		{
			// use the user-specified display name
			Checked::tcscpy_s(m_szDisplayName, _countof(m_szDisplayName), szDisplayName);
			m_szDisplayName[_countof(m_szDisplayName)-1] = 0;
		}
		else
		{
			// no display name
			*m_szDisplayName = '\0';
		}
		return TRUE;
	}

	// Output the data--similar to CFileAttachment::WriteData
	// See CFileAttachment::WriteData for comments
	virtual inline BOOL WriteData(HANDLE hFile, LPOVERLAPPED pOverlapped, LPCSTR szBoundary, DWORD dwFlags = 0) throw()
	{
		if ((pOverlapped == NULL) || (szBoundary == NULL))
		{
			return FALSE;
		}

		if (!m_pvRaw)
			return FALSE;

		int nLineLength = 0, nRequiredLength = 0;
		if (!GetEncodingInformation(&nRequiredLength, &nLineLength))
			return FALSE;

		CStringA header;

		if (!MakeMimeHeader(header, szBoundary))
		{
			return FALSE;
		}

		if (!AtlSmtpSendAndWait(hFile, ((LPCSTR)header), header.GetLength(), pOverlapped))
		{
			return FALSE;
		}

		int nGetLines = ATLSMTP_GET_LINES;
		DWORD dwCurrChunk = 0;
		nRequiredLength *= nGetLines;
		DWORD dwToGet = (DWORD)nGetLines*nLineLength;
		int nDestLen = nRequiredLength;
		BOOL bRet = FALSE;
		DWORD dwRead = 0;
#ifdef ATLSMTP_DOUBLE_BUFFERED
		CHeapPtr<char> buffer1;
		if (!buffer1.Allocate(nRequiredLength+3))
			return FALSE;

		CHeapPtr<char> buffer2;
		if (!buffer2.Allocate(nRequiredLength+3))
			return FALSE;

		char* currBuffer = buffer1;
		char* prevBuffer = NULL;
		int nCurrBuffer = 0;
		DWORD dwPrevLength = 0;
#else
		CHeapPtr<char> currBuffer;
		if (!currBuffer.Allocate(nRequiredLength+3))
			return FALSE;
#endif // ATLSMTP_DOUBLE_BUFFERED

		do 
		{
			if ((m_dwLength-dwRead) <= dwToGet)
				dwCurrChunk = m_dwLength-dwRead;
			else
				dwCurrChunk = dwToGet;
			switch(m_nEncodingScheme)
			{
				case ATLSMTP_BASE64_ENCODE:
					bRet = Base64Encode(((LPBYTE)(m_pvRaw))+dwRead, dwCurrChunk, currBuffer, &nDestLen, 
						(dwRead < m_dwLength) ? ATL_BASE64_FLAG_NONE : ATL_BASE64_FLAG_NOPAD);
					if (dwRead+dwCurrChunk == m_dwLength)
					{
						currBuffer[nDestLen++] = '\r';
						currBuffer[nDestLen++] = '\n';
					}
					break;
				case ATLSMTP_UUENCODE:
					bRet = UUEncode(((LPBYTE)(m_pvRaw))+dwRead, dwCurrChunk, currBuffer, &nDestLen, _T("rawdata"), 
									(dwRead > 0 ? 0 : ATLSMTP_UUENCODE_HEADER) | 
									(dwRead+dwCurrChunk == m_dwLength ? ATLSMTP_UUENCODE_END : 0) | 
									((dwFlags & ATLSMTP_FORMAT_SMTP) ? ATLSMTP_UUENCODE_DOT : 0));
					break;
				case ATLSMTP_QP_ENCODE:
					bRet = QPEncode(((LPBYTE)(m_pvRaw))+dwRead, dwCurrChunk, currBuffer, &nDestLen, 
									((dwFlags & ATLSMTP_FORMAT_SMTP) ? ATLSMTP_QPENCODE_DOT : 0) | 
									(dwRead+dwCurrChunk == m_dwLength ? 0 : ATLSMTP_QPENCODE_TRAILING_SOFT));
					break;
			}
			if (!bRet)
				break;
#ifdef ATLSMTP_DOUBLE_BUFFERED
			bRet = AtlSmtpSendOverlapped(hFile, currBuffer, nDestLen, prevBuffer, dwPrevLength, pOverlapped);
			dwPrevLength = (DWORD)nDestLen;
			prevBuffer = currBuffer;
			currBuffer = (nCurrBuffer == 0 ? buffer2 : buffer1);
			nCurrBuffer = (nCurrBuffer == 0 ? 1 : 0);
#else
			bRet = AtlSmtpSendAndWait(hFile, currBuffer, nDestLen, pOverlapped);
#endif // ATLSMTP_DOUBLE_BUFFERED

			nDestLen = nRequiredLength;
			dwRead += dwCurrChunk;
		} while (bRet && (dwRead < m_dwLength));

		//ensure all data is sent from prevBuffer
#ifdef ATLSMTP_DOUBLE_BUFFERED
		DWORD dwWritten = 0, dwErr = 0;
		if (!GetOverlappedResult(hFile, pOverlapped, &dwWritten, TRUE))
		{
			if ((dwErr = GetLastError()) != ERROR_IO_PENDING && dwErr != ERROR_IO_INCOMPLETE)
				bRet = FALSE;
			else if (dwWritten < dwPrevLength)
				bRet = AtlSmtpSendAndWait(hFile, prevBuffer+dwWritten, dwPrevLength-dwWritten, pOverlapped);
		}
#endif // ATLSMTP_DOUBLE_BUFFERED

		return bRet;
	}
}; // class CMimeRawAttachment


// CMimeText - represents a text body part in MIME body
class CMimeText : public CMimeBodyPart
{
protected:

	// the text
	CHeapPtr<char> m_szText;

	// the character set
	char     m_szCharset[ATL_MAX_ENC_CHARSET_LENGTH];

	// the text length
	int      m_nTextLen;

public:
	CMimeText() throw()
		:m_nTextLen(0)
	{
		Checked::strcpy_s(m_szCharset, ATL_MAX_ENC_CHARSET_LENGTH, ATLSMTP_DEFAULT_CSET);
	}

	virtual ~CMimeText() throw()
	{
	}

	// Get the content type
	virtual inline LPCSTR GetContentType() throw()
	{
		return "text/plain";
	}

	// Get the character set
	virtual inline LPCSTR GetCharset() throw()
	{
		return m_szCharset;
	}

	virtual ATL_NOINLINE CMimeBodyPart* Copy() throw( ... )
	{
		CAutoPtr<CMimeText> pNewText;
		ATLTRY(pNewText.Attach(new CMimeText));
		if (pNewText)
			*pNewText = *this;

		return pNewText.Detach();
	}

	const CMimeText& operator=(const CMimeText& that) throw( ... )
	{
		if (this != &that)
		{
			m_nTextLen = that.m_nTextLen;
			Checked::strcpy_s(m_szCharset, ATL_MAX_ENC_CHARSET_LENGTH, that.m_szCharset);
			m_szText.Free();
			if (m_szText.AllocateBytes(m_nTextLen) != false)
			{
				Checked::memcpy_s((char *)m_szText, m_nTextLen, (char *)that.m_szText, m_nTextLen);
			}
		}

		return *this;
	}

	// Initialize the body part
	// szText - the text (required)
	// nTextLen - the text length in bytes (optional--if not specified a _tcslen will be done)
	// pMultiLanguage - the IMultiLanguagte pointer for converting codepages to MIME character sets (optional)
	// uiCodePage - the codepage
	inline BOOL Initialize(LPCTSTR szText, int nTextLen = -1, IMultiLanguage* pMultiLanguage = NULL, UINT uiCodePage = 0) throw()
	{
		BOOL bRet = TRUE;

		// if IMultiLanguage is there, respect the codepage
		if (pMultiLanguage)
		{
			CHeapPtr<char> szTextPtr;
			UINT nLen(0);

			bRet = AtlMimeConvertString(pMultiLanguage, uiCodePage, szText, &szTextPtr, &nLen);
			if (bRet)
			{
				m_szText.Free();
				m_szText.Attach(szTextPtr.Detach());
				m_nTextLen = nLen;
			}
		}
		else // no multilanguage support
		{
			if (nTextLen < 0)
			{
				nTextLen = (int) _tcslen(szText);
				nTextLen*= sizeof(TCHAR);
			}

			m_szText.Free();
			if (m_szText.AllocateBytes(nTextLen) != false)
			{
				Checked::memcpy_s((char *)m_szText, nTextLen, szText, nTextLen);
				m_nTextLen = nTextLen;
			}
		}

		if (bRet)
		{
			bRet = AtlMimeCharsetFromCodePage(m_szCharset, uiCodePage, pMultiLanguage, ATL_MAX_ENC_CHARSET_LENGTH);
		}

		return bRet;
	}

	// Dump the data to hFile
	virtual inline BOOL WriteData(HANDLE hFile, LPOVERLAPPED pOverlapped, LPCSTR szBoundary, DWORD dwFlags = 0) throw()
	{
		if ((pOverlapped == NULL) || (szBoundary == NULL))
		{
			return FALSE;
		}

		CStringA strHeader;
		char sendBuffer[ATLSMTP_READBUFFER_SIZE];
		LPSTR pSendBuffer = sendBuffer;
		LPSTR szText = m_szText;

		if (!MakeMimeHeader(strHeader, szBoundary))
		{
			return FALSE;
		}

		//copy the header into the sendbuffer
		int nWritten = strHeader.GetLength();
		if(nWritten > ATLSMTP_READBUFFER_SIZE)
			return FALSE;
			
		Checked::memcpy_s(pSendBuffer, ATLSMTP_READBUFFER_SIZE, (LPCSTR)strHeader, nWritten);
		pSendBuffer+= nWritten;
		int nRead = 0;
		int nLineLen = 0;

		//subtract 2 from these because it's easier for when we have
		//to break lines with a CRLF
		int nMaxLineLength = ATLSMTP_MAX_LINE_LENGTH-2;
		int nMaxBufferSize = ATLSMTP_READBUFFER_SIZE-2;
		while (nRead <= m_nTextLen)
		{
			//if the buffer is full or we've reached the end of the text, 
			//send it
			if (nWritten >= nMaxBufferSize || nRead == m_nTextLen)
			{
				if (!AtlSmtpSendAndWait(hFile, sendBuffer, nWritten, pOverlapped))
					return FALSE;
				nWritten = 0;
				pSendBuffer = sendBuffer;
				if (nRead == m_nTextLen)
				{
					break; // job done, no need to run the code below
				}
			}

			//if we're at the end of the line, break it
			if (nLineLen == nMaxLineLength)
			{
				if(nWritten + 2 > ATLSMTP_READBUFFER_SIZE)
					return FALSE;
				*pSendBuffer++ = '\r';
				*pSendBuffer++ = '\n';
				nWritten+= 2;
				nLineLen = -1;
				continue;
			}

			//stuff dots at the start of the line
			if (nLineLen == 0 && (dwFlags & ATLSMTP_FORMAT_SMTP) && *szText == '.')
			{
				if(nWritten + 1 > ATLSMTP_READBUFFER_SIZE)
					return FALSE;
				*pSendBuffer++ = '.';
				nWritten++;
				nLineLen++;
				continue;
			}

			//if we hit a CRLF, reset nLineLen
			if (*szText == '\n' && nRead > 0 && *(szText-1) == '\r')
				nLineLen = -1;

			if(nWritten + 1 > ATLSMTP_READBUFFER_SIZE)
				return FALSE;
			*pSendBuffer++ = (*szText++);
			nRead++;
			nWritten++;
			nLineLen++;
		}

		return TRUE;
	}

protected:

	// Make the MIME header
	virtual inline BOOL MakeMimeHeader(CStringA& header, LPCSTR szBoundary) throw()
	{
		char szBegin[256];
		if (*szBoundary)
		{
			// this is not the only body part
			Checked::memcpy_s(szBegin, sizeof(szBegin), ATLMIME_SEPARATOR, sizeof(ATLMIME_SEPARATOR));
			Checked::memcpy_s(szBegin+6, sizeof(szBegin)-6, szBoundary, ATL_MIME_BOUNDARYLEN);
			*(szBegin+(ATL_MIME_BOUNDARYLEN+6)) = '\0';
		}
		else
		{
			// this is the only body part, so output the full MIME header
			Checked::memcpy_s(szBegin, sizeof(szBegin), ATLMIME_VERSION, sizeof(ATLMIME_VERSION));
		}

		_ATLTRY
		{
			header.Format("%s\r\nContent-Type: text/plain;\r\n\tcharset=\"%s\"\r\nContent-Transfer-Encoding: 8bit\r\n\r\n", 
				szBegin, m_szCharset);
			return TRUE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}
}; // class CMimeText


// CMimeMessage - the MIME message class.  Represents a full MIME message
class CMimeMessage : public CMimeHeader
{
protected:

	// The list of the MIME body parts
	CAutoPtrList<CMimeBodyPart> m_BodyParts;

	// The display name of the message
	char m_szDisplayName[MAX_PATH+1];

public:
	CMimeMessage(IMultiLanguage *pMultiLanguage = NULL) throw()
	{
		Initialize(pMultiLanguage);
		Checked::memcpy_s(m_szDisplayName, MAX_PATH+1, ATLMIME_EMAIL, sizeof(ATLMIME_EMAIL));
	}

	virtual ~CMimeMessage() throw()
	{
		RemoveParts();
	}

	void RemoveParts() throw()
	{
		m_BodyParts.RemoveAll();
	}


	virtual ATL_NOINLINE CMimeBodyPart* Copy() throw( ... )
	{
		CAutoPtr<CMimeMessage> pNewMessage;
		ATLTRY(pNewMessage.Attach(new CMimeMessage));
		if (pNewMessage)
			*pNewMessage = *this;

		return pNewMessage.Detach();
	}


	const CMimeMessage& operator=(const CMimeMessage& that) throw( ... )
	{
		if (this != &that)
		{
			CMimeHeader::operator=(that);
			Checked::strcpy_s(m_szDisplayName, MAX_PATH+1, that.m_szDisplayName);

			RemoveParts();
			POSITION pos = that.m_BodyParts.GetHeadPosition();
			while (pos != NULL)
			{
				CAutoPtr<CMimeBodyPart> pCopy(that.m_BodyParts.GetNext(pos)->Copy());
				if (pCopy)
				{
					m_BodyParts.AddTail(pCopy);
				}
			}
		}

		return *this;
	}

	// Set the display name of the message
	inline BOOL SetDisplayName(LPCTSTR szDisplayName) throw()
	{
		if (szDisplayName == NULL)
		{
			return FALSE;
		}

		_ATLTRY
		{
			CT2CA szDisplayNameA(szDisplayName);
		 	if (szDisplayNameA == NULL || strlen(szDisplayNameA) > MAX_PATH)
		 		return FALSE;
			Checked::strcpy_s(m_szDisplayName, MAX_PATH+1, szDisplayNameA);
			return TRUE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}

	// Add some text to the message at position nPos in the body parts list
	// szText - the text
	// nTextLen - the size of the text in bytes (optional - if not specified a _tcslen will be done)
	// nPos - the position in the message at which to insert the text (optional)
	// uiCodePage - the codepage (optional)
	inline BOOL AddText(LPCTSTR szText, int nTextLen = -1, int nPos = 1, UINT uiCodePage = 0) throw()
	{
		if (szText == NULL)
			return FALSE;

		if (nPos < 1)
		{
			nPos = 1;
		}

		CAutoPtr<CMimeBodyPart> spNewText;
		CMimeText *pNewText = NULL;
		ATLTRY(spNewText.Attach(pNewText = new CMimeText()));
		if (!spNewText || !pNewText)
			return FALSE;

		BOOL bRet = pNewText->Initialize(szText, nTextLen, m_spMultiLanguage, uiCodePage);
		if (bRet)
		{
			_ATLTRY
			{
				POSITION currPos = m_BodyParts.FindIndex(nPos-1);

					if (!currPos)
					{
						if (!m_BodyParts.AddTail(spNewText))
							bRet = FALSE;
					}
					else
					{
						if (!m_BodyParts.InsertBefore(currPos, spNewText))
							bRet = FALSE;
					}

			}
			_ATLCATCHALL()
			{
				bRet = FALSE;
			}
		}

		return bRet;
	}

	// Dump the data
	virtual BOOL WriteData(HANDLE hFile, LPOVERLAPPED pOverlapped, LPCSTR szBoundary=NULL, DWORD dwFlags = 0) throw()
	{	
		if (pOverlapped == NULL)
		{
			return FALSE;
		}

		// Make the MIME boundary for this message
		char szBoundaryBuf[ATL_MIME_BOUNDARYLEN+1];
		if(MakeBoundary(szBoundaryBuf,ATL_MIME_BOUNDARYLEN+1) == FALSE)
			return FALSE;

		// if the passed boundary is valid, this is an attached message
		if (szBoundary && *szBoundary != '\0')
		{
			_ATLTRY
			{
				// output the MIME header for a message attachment
				CStringA strHeader;
				strHeader.Format("\r\n\r\n--%s\r\nContent-Type: message/rfc822\r\n\tname=\"%s\"\r\nContent-Transfer-Encoding: 8bit\r\n"
					"Content-Disposition: attachment;\r\n\tfilename=\"%s\"\r\n\r\n", 
					szBoundary, m_szDisplayName, m_szDisplayName);

				if (!AtlSmtpSendAndWait(hFile, ((LPCSTR)strHeader), strHeader.GetLength(), pOverlapped))
				{
					return FALSE;
				}
			}
			_ATLCATCHALL()
			{
				return FALSE;
			}
		}

		if (!CMimeHeader::WriteData(hFile, pOverlapped, szBoundaryBuf, dwFlags))
			return FALSE;

		// Create and output the header
		CStringA strHeader;

		if (!MakeMimeHeader(strHeader, szBoundaryBuf))
		{
			return FALSE;
		}

		if (!AtlSmtpSendAndWait(hFile, ((LPCSTR)strHeader), strHeader.GetLength(), pOverlapped))
		{
			return FALSE;
		}

		CMimeBodyPart* pCurrPart;
		POSITION currPos = m_BodyParts.GetHeadPosition();

		//Dump the body parts
		while (currPos != NULL)
		{
			pCurrPart = m_BodyParts.GetAt(currPos);
			if (!pCurrPart->WriteData(hFile, pOverlapped, szBoundaryBuf, dwFlags))
			{
				return FALSE;
			}
			m_BodyParts.GetNext(currPos);
		}

		char szBuf[ATL_MIME_BOUNDARYLEN+(sizeof("\r\n\r\n--%s--\r\n"))];
		//output a trailing boundary
		if (*szBoundaryBuf)
		{
			int nBufLen = sprintf_s(szBuf, ATL_MIME_BOUNDARYLEN+(sizeof("\r\n\r\n--%s--\r\n")),
				"\r\n\r\n--%s--\r\n", szBoundaryBuf);
			if ((nBufLen < 0) || (!AtlSmtpSendAndWait(hFile, szBuf, nBufLen, pOverlapped)))
			{
				return FALSE;
			}
		}

		return TRUE;
	}

	// Attach a file.
	// szFileName - the filename
	// szDisplayName - the display name (optional)
	// szContentType - the content type (optional - defaults to NULL -- lookup will be attempted, otherwise default to application/octet-stream)
	// nEncodingScheme - the encoding scheme to use for the attachment (optional - defaults to base64
	// uiCodePage - the codepage (optional)
	inline BOOL AttachFile(LPCTSTR szFileName, LPCTSTR szDisplayName = NULL, LPCTSTR szContentType = NULL, 
		int nEncodingScheme = ATLSMTP_BASE64_ENCODE, UINT uiCodepage = 0)
	{
		if (szFileName == NULL)
			return FALSE;

		CAutoPtr<CMimeBodyPart> spFileAttach;
		CMimeFileAttachment* pFileAttach = NULL;
		ATLTRY(spFileAttach.Attach(pFileAttach = new CMimeFileAttachment()));
		if (!spFileAttach || !pFileAttach)
			return FALSE;

		BOOL bRet = pFileAttach->Initialize(szFileName, szDisplayName, m_spMultiLanguage, uiCodepage);

		if (bRet)
			bRet = pFileAttach->SetEncodingScheme(nEncodingScheme);

		CString strContentType;
		if (bRet && (szContentType == NULL))
		{
			if (GetContentTypeFromFileName(szFileName, strContentType) != ERROR_OUTOFMEMORY)
			{
				szContentType = strContentType;
			}
			else
			{
				bRet = FALSE;
			}
		}

		_ATLTRY
		{
			if (bRet)
			{
				bRet = pFileAttach->SetContentType(szContentType);
				if (bRet)
				{
					if (!m_BodyParts.AddTail(spFileAttach))
					{
						bRet = FALSE;
					}
				}
			}
		}
		_ATLCATCHALL()
		{
			bRet = FALSE;
		}

		return bRet;
	}

	// Attach some raw data
	// pRawData - the data
	// nDataLength - the size of the data in bytes
	// nEncodingScheme - the encoding scheme to use for the attachment (optional - defaults to base64
	// uiCodePage - the codepage (optional)
	inline BOOL AttachRaw(void* pRawData, DWORD dwDataLength, int nEncodingScheme = ATLSMTP_BASE64_ENCODE, BOOL bCopyData = TRUE, 
		LPCTSTR szDisplayName = NULL, LPCTSTR szContentType = _T("application/octet-stream"), UINT uiCodepage = 0)
	{
		if (!pRawData)
			return FALSE;

		CAutoPtr<CMimeBodyPart> spRawAttach;
		CMimeRawAttachment* pRawAttach;
		ATLTRY(spRawAttach.Attach(pRawAttach = new CMimeRawAttachment()));
		if (!spRawAttach)
		{
			return FALSE;
		}

		BOOL bRet = pRawAttach->Initialize(pRawData, dwDataLength, bCopyData, szDisplayName, m_spMultiLanguage, uiCodepage);

		if (bRet)
			bRet = pRawAttach->SetEncodingScheme(nEncodingScheme);
		if (bRet)
			bRet = pRawAttach->SetContentType(szContentType);

		_ATLTRY
		{
		if (bRet)
			if(!m_BodyParts.AddTail(spRawAttach))
				bRet = FALSE;
		}
		_ATLCATCHALL()
		{
			bRet = FALSE;
		}

		return bRet;
	}

	// Attach a CMimeMessage
	// pMsg - pointer to the Msg object
	inline BOOL AttachMessage(CMimeMessage* pMsg) throw( ... )
	{
		if (!pMsg)
			return FALSE;

		_ATLTRY
		{
			CAutoPtr<CMimeBodyPart> spMsg(pMsg->Copy());
			if (!m_BodyParts.AddTail(spMsg))
				return FALSE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}

		return TRUE;
	}

protected:
	// Make the MIME header
	virtual inline BOOL MakeMimeHeader(CStringA& header, LPCSTR szBoundary) throw()
	{
		_ATLTRY
		{
			if (!*szBoundary)
			{
				header.Format("X-Priority: %d\r\n%s", m_nPriority, (LPCSTR) m_XHeader);
			}
			else if (m_BodyParts.GetCount() > 1)
			{
				header.Format("X-Priority: %d\r\n%sMIME-Version: 1.0\r\nContent-Type: multipart/mixed;\r\n\tboundary=\"%s\"\r\n", 
					m_nPriority, (LPCSTR) m_XHeader, szBoundary);
			}
			return TRUE;
		}
		_ATLCATCHALL()
		{
			return FALSE;
		}
	}

	// Make the MIME boundary
	inline BOOL MakeBoundary(__out_ecount_z(nBufLen) LPSTR szBoundary, __in int nBufLen)
	{
		ATLENSURE(szBoundary != NULL);
			
		if(nBufLen < 1)
		{
			return FALSE;
		}

		if (m_BodyParts.GetCount() < 2)
		{
			*szBoundary = '\0';
		}
		else 
		{
			int ret = sprintf_s(szBoundary, nBufLen, "------=_Next_Part_%.10u.%.3u", GetTickCount(), rand()%1000); 
			if (ret == -1 || ret >= nBufLen)
				return FALSE;
		}
		return TRUE;
	}

}; // class CMimeMessage

} // namespace ATL
#pragma pack(pop)

#ifndef _CPPUNWIND
#pragma warning (pop)
#endif //_CPPUNWIND

#pragma warning(pop)

#endif // __ATLMIME_H__
