// 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 #include #include #include #include #include #include #include #include #include #include #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" #if _SECURE_ATL && !defined(_ATL_MIN_CRT) 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" #else wsprintfA(szDate, "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" #endif 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 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 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 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 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 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) { return FALSE; } if ((pdwLen != NULL) && (*pdwLen < GetRequiredRecipientsStringLength())) { *pdwLen = GetRequiredRecipientsStringLength(); return FALSE; } DWORD dwMaxLen = 0; if (pdwLen != NULL) { dwMaxLen = *pdwLen; *pdwLen = 0; } DWORD dwLen = 0; DWORD dwTotalLen = 0; if (m_strTo.GetLength() > 0) { if (AtlMimeMakeRecipientsString(m_strTo, szRecip, &dwLen) != TRUE) { return FALSE; } szRecip+= dwLen; dwTotalLen = dwLen; } if (m_strCc.GetLength() > 0) { if (dwTotalLen) { *szRecip++ = ','; 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++; } Checked::memcpy_s(szRecip, dwMaxLen-dwTotalLen, m_strBcc, dwLen); szRecip+= dwLen; dwTotalLen+= dwLen; } *szRecip = '\0'; if (pdwLen != NULL) { *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 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 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 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(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(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(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(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 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 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 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 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 spData; if (!spData.Allocate(dwToGet+1)) return FALSE; // if double buffering is defined, create two buffers #ifdef ATLSMTP_DOUBLE_BUFFERED CHeapPtr buffer1; if (!buffer1.Allocate(nRequiredLength+3)) return FALSE; CHeapPtr buffer2; if (!buffer2.Allocate(nRequiredLength+3)) return FALSE; char* currBuffer = buffer1; char* prevBuffer = NULL; int nCurrBuffer = 0; DWORD dwPrevLength = 0; #else CHeapPtr 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 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 buffer1; if (!buffer1.Allocate(nRequiredLength+3)) return FALSE; CHeapPtr buffer2; if (!buffer2.Allocate(nRequiredLength+3)) return FALSE; char* currBuffer = buffer1; char* prevBuffer = NULL; int nCurrBuffer = 0; DWORD dwPrevLength = 0; #else CHeapPtr 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 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 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 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 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 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 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 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) { #if _SECURE_ATL int nBufLen = sprintf_s(szBuf, ATL_MIME_BOUNDARYLEN+(sizeof("\r\n\r\n--%s--\r\n")), "\r\n\r\n--%s--\r\n", szBoundaryBuf); #else int nBufLen = _snprintf(szBuf, ATL_MIME_BOUNDARYLEN+(sizeof("\r\n\r\n--%s--\r\n")), "\r\n\r\n--%s--\r\n", szBoundaryBuf); #endif 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 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 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 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 { #if _SECURE_ATL int ret = sprintf_s(szBoundary, nBufLen, "------=_Next_Part_%.10u.%.3u", GetTickCount(), rand()%1000); #else int ret = _snprintf(szBoundary, nBufLen, "------=_Next_Part_%.10u.%.3u", GetTickCount(), rand()%1000); #endif 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__