/* * b o d y u t i l . c p p * * Purpose: * utility functions for body * * History * August '96: brettm - created * * Copyright (C) Microsoft Corp. 1995, 1996. */ #include #include "dllmain.h" #include "olealloc.h" #include "resource.h" #include "htmlstr.h" #include "strconst.h" #include "bodyutil.h" #include "plainstm.h" #include "mimeutil.h" #include "util.h" #include "demand.h" ASSERTDATA /* * t y p e d e f s */ enum { HEADER_FROM=0, HEADER_NEWSGROUP, HEADER_TO, HEADER_CC, HEADER_SENT, HEADER_ATTACH, HEADER_SUBJECT, HEADER_MAX }; /* * m a c r o s */ #define WSZ_CB(str) (lstrlenW(str)*sizeof(WCHAR)) #define WSZ_CBNULL(str) ((lstrlenW(str)+1)*sizeof(WCHAR)) #define WSZ_CCH(str) lstrlenW(str) #define WSZ_CCHNULL(str) (lstrlenW(str)+1) /* * c o n s t a n t s */ static const WCHAR c_wszHtmlDIV_Close[] = L"\r\n", c_wszTableTag_Close[] = L"\r\n", c_wszHtml[] = L"\r\n", c_wszHtmlReplyAnchor[] = L"%s\r\n", c_wszHtml_HeaderDIV[] = L"
\r\n%s ", c_wszHtml_DIV_BR_DIV[] = L"

\r\n", c_wszHtml_FromDIV[] = L"
\r\n%s ", c_wszHtml_HeaderDIVFmt_Start[] = L"
\r\n", c_wszHtml_HeaderDIVFmt_Middle[] = L" ", c_wszHtml_HeaderDIVFmt_End[] = L"
", c_wszHtml_HeaderDIVFmt_Plain_Start[]= L"
\r\n", c_wszHtml_HeaderDIVFmt_Plain_Middle[]= L" ", c_wszHtml_HeaderDIVFmt_Plain_End[] = L"
"; static const int c_cchHtml_HeaderDIVFmt_Start = ARRAYSIZE(c_wszHtml_HeaderDIVFmt_Start) - 1, c_cchHtml_HeaderDIVFmt_Middle = ARRAYSIZE(c_wszHtml_HeaderDIVFmt_Middle) - 1, c_cchHtml_HeaderDIVFmt_End = ARRAYSIZE(c_wszHtml_HeaderDIVFmt_End) - 1, c_cchHtml_HeaderDIVFmt_Plain_Start = ARRAYSIZE(c_wszHtml_HeaderDIVFmt_Plain_Start) - 1, c_cchHtml_HeaderDIVFmt_Plain_Middle = ARRAYSIZE(c_wszHtml_HeaderDIVFmt_Plain_Middle) - 1, c_cchHtml_HeaderDIVFmt_Plain_End = ARRAYSIZE(c_wszHtml_HeaderDIVFmt_Plain_End) - 1; // HARDCODED Western headers static const LPWSTR c_rgwszHeaders[HEADER_MAX] = { L"From:", L"Newsgroups:", L"To:", L"Cc:", L"Sent:", L"Attach:", L"Subject:"}; static const int c_rgidsHeaders[HEADER_MAX] ={ idsFromField, idsNewsgroupsField, idsToField, idsCcField, idsDateField, idsAttachField, idsSubjectField}; /* * g l o b a l s */ /* * p r o t o t y p e s */ HRESULT CreatePrintHeader(IMimeMessageW *pMsg, LPWSTR pwszUser, DWORD dwFlags, LPSTREAM pstm); HRESULT CreateMailHeader(IMimeMessageW *pMsg, DWORD dwFlags, LPSTREAM pstm); HRESULT CreateNewsHeader(IMimeMessageW *pMsg, DWORD dwFlags, LPSTREAM pstm); HRESULT GetHeaderLabel(ULONG uHeader, DWORD dwFlags, LPWSTR *ppwszOut); HRESULT GetHeaderText(IMimeMessageW *pMsg, ULONG uHeader, DWORD dwFlags, LPWSTR *ppwszOut); HRESULT CreateHTMLAddressLine(IMimeMessageW *pMsg, DWORD dwAdrTypes, LPWSTR *ppwszOut); void DropAngles(LPWSTR pwszIn, LPWSTR *ppwszOut); /* * f u n c t i o n s */ /* * Trident doesn't do a good job of converting tables->plaintext, so we can't use * a table to construct the header of a re: fw: etc to get nice alignment on the * field boundaries. We can use a table, however for printing. We need to be able to * construct a header (in html source) in 3 flavours: * * HDR_PLAIN (regular text, eg: reply in plain-mode) * HDR_HTML (regular text, with bolded fields, eg: reply in html-mode) * HDR_TABLE (use table to get improved output, eg: printing) * */ HRESULT GetHeaderTable(IMimeMessageW *pMsg, LPWSTR pwszUserName, DWORD dwHdrStyle, LPSTREAM *ppstm) { HRESULT hr = S_OK; LPSTREAM pstm = NULL; BYTE bUniMark = 0xFF; if (pMsg==NULL || ppstm==NULL) IF_FAILEXIT(hr = E_INVALIDARG); IF_FAILEXIT(hr = MimeOleCreateVirtualStream(&pstm)); // Write out the BOM IF_FAILEXIT(hr = pstm->Write(&bUniMark, sizeof(bUniMark), NULL)); bUniMark = 0xFE; IF_FAILEXIT(hr = pstm->Write(&bUniMark, sizeof(bUniMark), NULL)); if (dwHdrStyle & HDR_TABLE) IF_FAILEXIT(hr = CreatePrintHeader(pMsg, pwszUserName, dwHdrStyle, pstm)); else if (dwHdrStyle & HDR_NEWSSTYLE) IF_FAILEXIT(hr = CreateNewsHeader(pMsg, dwHdrStyle, pstm)); else IF_FAILEXIT(hr = CreateMailHeader(pMsg, dwHdrStyle, pstm)); *ppstm = pstm; pstm = NULL; exit: ReleaseObj(pstm); return hr; } HRESULT CreateNewsHeader(IMimeMessageW *pMsg, DWORD dwFlags, LPSTREAM pstm) { HRESULT hr = S_OK; WCHAR wsz[CCHMAX_STRINGRES]; ULONG cch, cb; LPWSTR pwsz = NULL, pwszFrom = NULL, pwszMsgId = NULL, pwszFmtMsgId = NULL, pwszHtml; Assert(pMsg); Assert(pstm); pMsg->GetAddressFormatW(IAT_FROM, AFT_DISPLAY_BOTH, &pwszFrom); MimeOleGetBodyPropW(pMsg, HBODY_ROOT, PIDTOSTR(PID_HDR_MESSAGEID), NOFLAGS, &pwszMsgId); *wsz = 0; cch = LoadStringWrapW(g_hLocRes, idsReplyTextPrefix, wsz, ARRAYSIZE(wsz)); cb = cch*sizeof(WCHAR); // Opie assumes me these Sz function never return NULL. Only an empty buffer if (pwszFrom) cb += WSZ_CB(pwszFrom); else pwszFrom=(LPWSTR)c_szEmptyW; if (pwszMsgId) { // We purposely remove < and > from the msgId so Trident will correctly // format the msgId as a news: link and not a mailto: DropAngles(pwszMsgId, &pwszFmtMsgId); cb += WSZ_CB(pwszFmtMsgId); } else pwszFmtMsgId=(LPWSTR)c_szEmptyW; // Add null cb += sizeof(WCHAR); IF_NULLEXIT(MemAlloc((LPVOID *)&pwsz, cb)); IF_FAILEXIT(hr = pstm->Write(c_wszHtml_DivOpen, WSZ_CB(c_wszHtml_DivOpen), NULL)); // We just write out the e-mail name and news:message-id, // trident does URL autodetection and makes the e-mail into a mailto link and the // news: into a news link. wnsprintfW(pwsz, cb, wsz, pwszFrom, pwszFmtMsgId); IF_FAILEXIT(hr = EscapeStringToHTML(pwsz, &pwszHtml)); pstm->Write(pwszHtml, WSZ_CB(pwszHtml), NULL); MemFree(pwszHtml); IF_FAILEXIT(hr = pstm->Write(c_wszHtml_DivClose, WSZ_CB(c_wszHtml_DivClose), NULL)); exit: if (pwszFrom != c_szEmptyW) MemFree(pwszFrom); MemFree(pwszMsgId); // pwszFmtMsgId was freed when we freed lpsMsgId MemFree(pwsz); return hr; } /*
-----Original Message-----
From: Justin Ferrari
To: John Tafoya; Lauren Antonoff; Dave Haws; John Doe
Date: Tuesday, September 01, 1998 10:40 AM
Subject: Re: A plea to save the life of the News Server combo box

*/ HRESULT CreateMailHeader(IMimeMessageW *pMsg, DWORD dwFlags, LPSTREAM pstm) { ULONG uHeader, cchFmt; WCHAR wsz[CCHMAX_STRINGRES], wszText[CCHMAX_STRINGRES]; LPWSTR pwsz = NULL, pwszText = NULL, pwszLabel = NULL, pwszHtml = NULL; LPCWSTR pwszFmtStart, pwszFmtMiddle, pwszFmtEnd; int cchFmtStart, cchFmtMiddle, cchFmtEnd; HRESULT hr = S_OK; if (pMsg==NULL || pstm==NULL) IF_FAILEXIT(hr = E_INVALIDARG); // emit a table to display the username if (LoadStringWrapW(g_hLocRes, dwFlags & HDR_HTML ? idsReplyHeader_Html_SepBlock : idsReplyHeader_SepBlock, wsz, ARRAYSIZE(wsz))) { *wszText=0; if (dwFlags & HDR_HARDCODED) StrCpyNW(wszText, L"----- Original Message -----", ARRAYSIZE(wszText)); else LoadStringWrapW(g_hLocRes, idsReplySep, wszText, ARRAYSIZE(wszText)); IF_NULLEXIT(pwsz = PszAllocW(WSZ_CCH(wszText) + WSZ_CCH(wsz) + 1)); wnsprintfW(pwsz, (WSZ_CCH(wszText) + WSZ_CCH(wsz) + 1), wsz, wszText); pstm->Write(pwsz, WSZ_CB(pwsz), NULL); SafeMemFree(pwsz); } if (dwFlags & HDR_HTML) { pwszFmtStart = c_wszHtml_HeaderDIVFmt_Start; pwszFmtMiddle = c_wszHtml_HeaderDIVFmt_Middle; pwszFmtEnd = c_wszHtml_HeaderDIVFmt_End; cchFmtStart = c_cchHtml_HeaderDIVFmt_Start; cchFmtMiddle = c_cchHtml_HeaderDIVFmt_Middle; cchFmtEnd = c_cchHtml_HeaderDIVFmt_End; } else { pwszFmtStart = c_wszHtml_HeaderDIVFmt_Plain_Start; pwszFmtMiddle = c_wszHtml_HeaderDIVFmt_Plain_Middle; pwszFmtEnd = c_wszHtml_HeaderDIVFmt_Plain_End; cchFmtStart = c_cchHtml_HeaderDIVFmt_Plain_Start; cchFmtMiddle = c_cchHtml_HeaderDIVFmt_Plain_Middle; cchFmtEnd = c_cchHtml_HeaderDIVFmt_Plain_End; } // write out each header row for (uHeader=0; uHeaderWrite(wsz, WSZ_CB(wsz), NULL); pstm->Write(pwsz, WSZ_CB(pwsz), NULL); pstm->Write(c_wszHtmlDIV_Close, WSZ_CB(c_wszHtmlDIV_Close), NULL); SafeMemFree(pwszLabel); } SafeMemFree(pwsz); } continue; } case HEADER_TO: case HEADER_CC: { if (CreateHTMLAddressLine(pMsg, uHeader == HEADER_TO ? IAT_TO : IAT_CC, &pwsz)==S_OK) { if (GetHeaderLabel(uHeader, dwFlags, &pwszLabel)==S_OK) { wnsprintfW(wsz, ARRAYSIZE(wsz), c_wszHtml_HeaderDIV, pwszLabel); // pszLabel is fixed size pstm->Write(wsz, WSZ_CB(wsz), NULL); pstm->Write(pwsz, WSZ_CB(pwsz), NULL); pstm->Write(c_wszHtmlDIV_Close, WSZ_CB(c_wszHtmlDIV_Close), NULL); SafeMemFree(pwszLabel); } SafeMemFree(pwsz); } continue; } } } // normal headers if (GetHeaderLabel(uHeader, dwFlags, &pwszLabel)==S_OK) { if (GetHeaderText(pMsg, uHeader, dwFlags, &pwszText)==S_OK) { if (*pwszText) // ignore empty strings { int cch = 0; int cchLabel; int cchHtml; IF_FAILEXIT(hr = EscapeStringToHTML(pwszText, &pwszHtml)); cchLabel = WSZ_CCH(pwszLabel); cchHtml = WSZ_CCH(pwszHtml); // 1 for the NULL DWORD cchSize = (cchLabel + cchHtml + cchFmtStart + cchFmtMiddle + cchFmtEnd + 1); IF_NULLEXIT(pwsz = PszAllocW(cchSize)); // Avoid potentially dumping > 1024 into wnsprintfW StrCpyNW(pwsz, pwszFmtStart, cchSize); cch = cchFmtStart; StrCpyNW(&pwsz[cch], pwszLabel, cchSize - cch); cch += cchLabel; StrCpyNW(&pwsz[cch], pwszFmtMiddle, cchSize - cch); cch += cchFmtMiddle; StrCpyNW(&pwsz[cch], pwszHtml, cchSize - cch); cch += cchHtml; StrCpyNW(&pwsz[cch], pwszFmtEnd, cchSize - cch); cch += cchFmtEnd; pstm->Write(pwsz, cch * sizeof(WCHAR), NULL); SafeMemFree(pwsz); SafeMemFree(pwszHtml); } SafeMemFree(pwszText); } SafeMemFree(pwszLabel); } } // Close the
that wraps the whole thing in a font pstm->Write(c_wszHtmlDIV_Close, WSZ_CB(c_wszHtmlDIV_Close), NULL); // add a

to give one line-spacing pstm->Write(c_wszHtml_DIV_BR_DIV, WSZ_CB(c_wszHtml_DIV_BR_DIV), NULL); exit: MemFree(pwszHtml); MemFree(pwszText); MemFree(pwszLabel); MemFree(pwsz); return hr; } HRESULT CreatePrintHeader(IMimeMessageW *pMsg, LPWSTR pwszUser, DWORD dwFlags, LPSTREAM pstm) { ULONG uHeader; WCHAR wsz[CCHMAX_STRINGRES]; LPWSTR pwsz = NULL, pwszText = NULL, pwszLabel = NULL, pwszHtml = NULL; HRESULT hr = S_OK; int cch = 0; if (pMsg==NULL || pstm==NULL) IF_FAILEXIT(hr = E_INVALIDARG); // if no username, use "" so wsprintf is happy if (pwszUser == NULL) pwszUser = (LPWSTR)c_szEmptyW; // Emit an tag for trident autodetector (used on the print-header) pstm->Write(c_wszHtml, WSZ_CB(c_wszHtml), NULL); // emit a table to display the username if (LoadStringWrapW(g_hLocRes, idsPrintTable_UserName, wsz, ARRAYSIZE(wsz))) { IF_FAILEXIT(hr = EscapeStringToHTML(pwszUser, &pwszHtml)); IF_NULLEXIT(pwsz = PszAllocW(WSZ_CCH(wsz) + WSZ_CCH(pwszHtml) + 1)); wnsprintfW(pwsz, (WSZ_CCH(wsz) + WSZ_CCH(pwszHtml) + 1), wsz, pwszHtml); pstm->Write(pwsz, WSZ_CB(pwsz), NULL); SafeMemFree(pwsz); SafeMemFree(pwszHtml); } // start the main-table if (LoadStringWrapW(g_hLocRes, idsPrintTable_Header, wsz, ARRAYSIZE(wsz))) { pstm->Write(wsz, WSZ_CB(wsz), NULL); if (LoadStringWrapW(g_hLocRes, idsPrintTable_HeaderRow, wsz, ARRAYSIZE(wsz))) { // write out each header row for (uHeader=0; uHeaderWrite(pwsz, WSZ_CB(pwsz), NULL); SafeMemFree(pwsz); SafeMemFree(pwszHtml); } SafeMemFree(pwszText); } SafeMemFree(pwszLabel); } } } pstm->Write(c_wszTableTag_Close, WSZ_CB(c_wszTableTag_Close), NULL); } exit: MemFree(pwsz); MemFree(pwszHtml); MemFree(pwszText); MemFree(pwszLabel); return hr; } HRESULT GetHeaderText(IMimeMessageW *pMsg, ULONG uHeader, DWORD dwFlags, LPWSTR *ppwszOut) { PROPVARIANT pv; HBODY *rghAttach = NULL; ULONG cAttach, uAttach, cchSize=0; LPWSTR *rgpwsz = NULL, pwszWrite = NULL, pwszEnd = NULL; HRESULT hr = S_OK; Assert(pMsg); Assert(ppwszOut); *ppwszOut = 0; switch (uHeader) { case HEADER_FROM: pMsg->GetAddressFormatW(IAT_FROM, AFT_DISPLAY_BOTH, ppwszOut); break; case HEADER_NEWSGROUP: MimeOleGetBodyPropW(pMsg, HBODY_ROOT, PIDTOSTR(PID_HDR_NEWSGROUPS), NOFLAGS, ppwszOut); break; case HEADER_TO: pMsg->GetAddressFormatW(IAT_TO, AFT_DISPLAY_BOTH, ppwszOut); break; case HEADER_CC: pMsg->GetAddressFormatW(IAT_CC, AFT_DISPLAY_BOTH, ppwszOut); break; case HEADER_ATTACH: if (pMsg->GetAttachments(&cAttach, &rghAttach)==S_OK && cAttach) { // create cache-string list IF_NULLEXIT(rgpwsz = (LPWSTR*)ZeroAllocate(sizeof(*rgpwsz) * cAttach)); for (uAttach=0; uAttachGetProp(PIDTOSTR(PID_ATT_SENTTIME), 0, &pv))) { LPWSTR lpwsz = NULL; INT cch = 0; IF_NULLEXIT(MemAlloc((LPVOID*)&lpwsz, CCHMAX_STRINGRES * sizeof(*lpwsz))); *lpwsz = 0; // always force a western date if using hardcoded headers // For the formatted date, DTM_FORCEWESTERN flag returns English date and time. // Without DTM_FORCEWESTERN the formatted time may not be representable in ASCII. cch = AthFileTimeToDateTimeW(&pv.filetime, lpwsz, CCHMAX_STRINGRES, DTM_NOSECONDS|DTM_LONGDATE|(dwFlags & HDR_HARDCODED ?DTM_FORCEWESTERN:0)); *ppwszOut = lpwsz; } break; case HEADER_SUBJECT: MimeOleGetBodyPropW(pMsg, HBODY_ROOT, PIDTOSTR(PID_HDR_SUBJECT), NOFLAGS, ppwszOut); break; default: AssertSz(0, "Not Supported"); } exit: // free cached string-list if (rgpwsz) { for (uAttach=0; uAttachGetAddressTypes(dwAdrTypes, IAP_FRIENDLYW|IAP_EMAIL, &rAdrList)); cAddr = rAdrList.cAdrs; // If no addresses, bail if (cAddr == 0) IF_FAILEXIT(hr = E_FAIL); // Allocate array for escaped email addresses IF_NULLEXIT(rgpwszEmail = (LPWSTR*)ZeroAllocate(sizeof(*rgpwszEmail) * cAddr)); // Allocate array for escaped displaynames IF_NULLEXIT(rgpwszFriendly = (LPWSTR*)ZeroAllocate(sizeof(*rgpwszFriendly) * cAddr)); for (i=0; i < cAddr; i++) { // escape all of the names into our name array if (rAdrList.prgAdr[i].pszEmail) { IF_NULLEXIT(pwszEmail = PszToUnicode(CP_ACP, rAdrList.prgAdr[i].pszEmail)); // Escape Into Array IF_FAILEXIT(hr = EscapeStringToHTML(pwszEmail, &rgpwszEmail[i])); // Use Email if no Friendly Name if (NULL == (pwszFriendly = rAdrList.prgAdr[i].pszFriendlyW)) pwszFriendly = pwszEmail; IF_FAILEXIT(hr = EscapeStringToHTML(pwszFriendly, &rgpwszFriendly[i])); // for each address we use 2*email + display + htmlgoo + "; " // if display == null, we use email. We skip if email==NULL (failure case) cb += ( 2*WSZ_CCH(rgpwszEmail[i]) + WSZ_CCH(rgpwszFriendly[i]) + WSZ_CCH(c_wszHtmlReplyAnchor) + 2 ) * sizeof(WCHAR); SafeMemFree(pwszEmail); } } IF_NULLEXIT(MemAlloc((LPVOID *)&pwsz, cb)); pwszWrite = pwsz; pwszEnd = pwszWrite + (cb/sizeof(WCHAR)); for (i=0; i < cAddr; i++) { if (pwszEmailLoop = rgpwszEmail[i]) { pwszFriendly = rgpwszFriendly[i]; Assert (pwszFriendly); wnsprintfW(pwszWrite, (DWORD)(pwszEnd-pwszWrite), c_wszHtmlReplyAnchor, pwszEmailLoop, pwszEmailLoop, pwszFriendly); pwszWrite += WSZ_CCH(pwszWrite); if (i != cAddr-1) { StrCpyNW(pwszWrite, L"; ", (DWORD)(pwszEnd-pwszWrite)); pwszWrite += 2; } } } *ppwszOut = pwsz; pwsz = NULL; // freed by caller exit: if (rgpwszEmail) { for (i=0; i < cAddr; i++) MemFree(rgpwszEmail[i]); MemFree(rgpwszEmail); } if (rgpwszFriendly) { for (i=0; i < cAddr; i++) MemFree(rgpwszFriendly[i]); MemFree(rgpwszFriendly); } MemFree(pwsz); MemFree(pwszEmail); g_pMoleAlloc->FreeAddressList(&rAdrList); return hr; } void GetRGBFromString(DWORD* pRGB, LPSTR pszColor) { DWORD dwShiftAmount = 0, rgb = 0, n; CHAR ch; Assert(pRGB); Assert(lstrlen(pszColor) == lstrlen("RRGGBB")); *pRGB = 0; while (0 != (ch = *pszColor++)) { if (ch >= '0' && ch <= '9') n = ch - '0'; else if(ch >= 'A' && ch <= 'F') n = ch - 'A' + 10; else { Assert(ch >= 'a' && ch <= 'f') n = ch - 'a' + 10; } rgb = (rgb << 4) + n; } rgb = ((rgb & 0x00ff0000) >> 16 ) | (rgb & 0x0000ff00) | ((rgb & 0x000000ff) << 16); *pRGB = rgb; } void GetStringRGB(DWORD rgb, LPSTR pszColor) { INT i; DWORD crTemp; Assert(pszColor); rgb = ((rgb & 0x00ff0000) >> 16 ) | (rgb & 0x0000ff00) | ((rgb & 0x000000ff) << 16); for(i = 0; i < 6; i++) { crTemp = (rgb & (0x00f00000 >> (4*i))) >> (4*(5-i)); pszColor[i] = (CHAR)((crTemp < 10)? (crTemp+'0') : (crTemp+ 'a' - 10)); } pszColor[6] = '\0'; } /* This function drops the enclosing < and > around a msg-id RFC822: msg-id = "<" addr-spec ">" NOTE: The input buffer is MODIFIED and the output pointer points to memory in pszIn! */ void DropAngles(LPWSTR pwszIn, LPWSTR *ppwszOut) { if (pwszIn) { WCHAR ch; LPWSTR pwszCurrent = pwszIn; // First character should be <, but be robust and scan for it while ((ch = *pwszCurrent++) && (ch != L'<')); if (ch) // We are interested in stuff after the angle bracket *ppwszOut = pwszCurrent; else // Perhaps the message-id was malformed and doesn't contain a < *ppwszOut = pwszIn; pwszCurrent = *ppwszOut; // Find the close bracket and null it out while ((ch = *pwszCurrent) && (ch != L'>')) pwszCurrent++; // If we found a >, overwrite it with NULL if (ch) *pwszCurrent = NULL; } }