Back to index

lightning-sunbird  0.9+nobinonly
nsMimeHtmlEmitter.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Henrik Gemal <mozilla@gemal.dk>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either of the GNU General Public License Version 2 or later (the "GPL"),
00027  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 #include "nsCOMPtr.h"
00039 #include <stdio.h>
00040 #include "nsMimeRebuffer.h"
00041 #include "nsMimeHtmlEmitter.h"
00042 #include "plstr.h"
00043 #include "nsMailHeaders.h"
00044 #include "nscore.h"
00045 #include "nsEmitterUtils.h"
00046 #include "nsIPrefService.h"
00047 #include "nsIPrefBranch.h"
00048 #include "nsEscape.h"
00049 #include "nsIMimeStreamConverter.h"
00050 #include "nsIMsgWindow.h"
00051 #include "nsIMsgMailNewsUrl.h"
00052 #include "nsXPIDLString.h"
00053 #include "nsMimeTypes.h"
00054 #include "prtime.h"
00055 #include "nsReadableUtils.h"
00056 #include "prprf.h"
00057 #include "nsIStringEnumerator.h"
00058 #include "nsStringEnumerator.h"
00059 
00060 // hack: include this to fix opening news attachments.
00061 #include "nsINntpUrl.h"
00062 
00063 #include "nsIMimeConverter.h"
00064 #include "nsMsgMimeCID.h"
00065 #include "nsDateTimeFormatCID.h"
00066 
00067 static NS_DEFINE_CID(kDateTimeFormatCID,    NS_DATETIMEFORMAT_CID);
00068 #define VIEW_ALL_HEADERS 2
00069 
00070 /*
00071  * nsMimeHtmlEmitter definitions....
00072  */
00073 nsMimeHtmlDisplayEmitter::nsMimeHtmlDisplayEmitter() : nsMimeBaseEmitter()
00074 {
00075   mFirst = PR_TRUE;
00076   mSkipAttachment = PR_FALSE;
00077 }
00078 
00079 nsMimeHtmlDisplayEmitter::~nsMimeHtmlDisplayEmitter(void)
00080 {
00081 }
00082 
00083 nsresult nsMimeHtmlDisplayEmitter::Init()
00084 {
00085   return NS_OK;
00086 }
00087 
00088 PRBool nsMimeHtmlDisplayEmitter::BroadCastHeadersAndAttachments()
00089 {
00090   // try to get a header sink if there is one....
00091   nsCOMPtr<nsIMsgHeaderSink> headerSink; 
00092   nsresult rv = GetHeaderSink(getter_AddRefs(headerSink));
00093   if (NS_SUCCEEDED(rv) && headerSink && mDocHeader)
00094     return PR_TRUE;
00095   else
00096     return PR_FALSE;
00097 }
00098 
00099 nsresult 
00100 nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTMLPrefix()
00101 {
00102   if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput))
00103     return nsMimeBaseEmitter::WriteHeaderFieldHTMLPrefix();
00104   else
00105     return NS_OK;
00106 }
00107 
00108 nsresult
00109 nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTML(const char *field, const char *value)
00110 {
00111   if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput))
00112     return nsMimeBaseEmitter::WriteHeaderFieldHTML(field, value);
00113   else
00114     return NS_OK;
00115 }
00116 
00117 nsresult
00118 nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTMLPostfix()
00119 {
00120   if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput))
00121     return nsMimeBaseEmitter::WriteHeaderFieldHTMLPostfix();
00122   else
00123     return NS_OK;
00124 }
00125 
00126 nsresult
00127 nsMimeHtmlDisplayEmitter::GetHeaderSink(nsIMsgHeaderSink ** aHeaderSink)
00128 {
00129   nsresult rv = NS_OK;
00130   if ( (mChannel) && (!mHeaderSink) )
00131   {
00132     nsCOMPtr<nsIURI> uri;
00133     mChannel->GetURI(getter_AddRefs(uri));
00134     if (uri)
00135     {
00136       nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(uri));
00137       if (msgurl)
00138       {
00139         msgurl->GetMsgHeaderSink(getter_AddRefs(mHeaderSink));
00140         if (!mHeaderSink)  // if the url is not overriding the header sink, then just get the one from the msg window
00141         {
00142         nsCOMPtr<nsIMsgWindow> msgWindow;
00143         msgurl->GetMsgWindow(getter_AddRefs(msgWindow));
00144         if (msgWindow)
00145           msgWindow->GetMsgHeaderSink(getter_AddRefs(mHeaderSink));
00146       }
00147     }
00148   }
00149   }
00150 
00151   *aHeaderSink = mHeaderSink;
00152   NS_IF_ADDREF(*aHeaderSink);
00153   return rv;
00154 }
00155 
00156 nsresult nsMimeHtmlDisplayEmitter::BroadcastHeaders(nsIMsgHeaderSink * aHeaderSink, PRInt32 aHeaderMode, PRBool aFromNewsgroup)
00157 {
00158   // two string enumerators to pass out to the header sink
00159   nsCOMPtr<nsIUTF8StringEnumerator> headerNameEnumerator;
00160   nsCOMPtr<nsIUTF8StringEnumerator> headerValueEnumerator;
00161 
00162   // CStringArrays which we can pass into the enumerators
00163   nsCStringArray headerNameArray;
00164   nsCStringArray headerValueArray;
00165   nsXPIDLCString extraExpandedHeaders;
00166   nsCStringArray extraExpandedHeadersArray;
00167   nsCAutoString convertedDateString;
00168 
00169   PRBool displayOriginalDate = PR_FALSE;
00170   nsresult rv;
00171   nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
00172   if (pPrefBranch)
00173   {
00174     pPrefBranch->GetBoolPref("mailnews.display.original_date", &displayOriginalDate);
00175     pPrefBranch->GetCharPref("mailnews.headers.extraExpandedHeaders", getter_Copies(extraExpandedHeaders));
00176     // todo - should make this upper case
00177     if (!extraExpandedHeaders.IsEmpty())
00178     {
00179       ToLowerCase(extraExpandedHeaders);
00180       extraExpandedHeadersArray.ParseString(extraExpandedHeaders, " ");
00181     }
00182 
00183   }
00184 
00185   for (PRInt32 i=0; i<mHeaderArray->Count(); i++)
00186   {
00187     headerInfoType * headerInfo = (headerInfoType *) mHeaderArray->ElementAt(i);
00188     if ( (!headerInfo) || (!headerInfo->name) || (!(*headerInfo->name)) || (!headerInfo->value) || (!(*headerInfo->value)))
00189       continue;
00190 
00191     const char * headerValue = headerInfo->value;
00192 
00193     // optimization: if we aren't in view all header view mode, we only show a small set of the total # of headers.
00194     // don't waste time sending those out to the UI since the UI is going to ignore them anyway. 
00195     if (aHeaderMode != VIEW_ALL_HEADERS && (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer)) 
00196     {
00197       nsDependentCString headerStr(headerInfo->name);
00198       if (nsCRT::strcasecmp("to", headerInfo->name) && nsCRT::strcasecmp("from", headerInfo->name) &&
00199           nsCRT::strcasecmp("cc", headerInfo->name) && nsCRT::strcasecmp("newsgroups", headerInfo->name) &&
00200           nsCRT::strcasecmp("bcc", headerInfo->name) && nsCRT::strcasecmp("followup-to", headerInfo->name) &&
00201           nsCRT::strcasecmp("reply-to", headerInfo->name) && nsCRT::strcasecmp("subject", headerInfo->name) &&
00202           nsCRT::strcasecmp("organization", headerInfo->name) && nsCRT::strcasecmp("user-agent", headerInfo->name) &&
00203           nsCRT::strcasecmp("content-base", headerInfo->name) && nsCRT::strcasecmp("sender", headerInfo->name) &&
00204           nsCRT::strcasecmp("date", headerInfo->name) && nsCRT::strcasecmp("x-mailer", headerInfo->name) &&
00205           nsCRT::strcasecmp("content-type", headerInfo->name) && nsCRT::strcasecmp("message-id", headerInfo->name) &&
00206           nsCRT::strcasecmp("x-newsreader", headerInfo->name) && nsCRT::strcasecmp("x-mimeole", headerInfo->name) &&
00207           // make headerStr lower case because IndexOf is case-sensitive
00208          (!extraExpandedHeadersArray.Count() || (ToLowerCase(headerStr),
00209             extraExpandedHeadersArray.IndexOf(headerStr) == kNotFound)))
00210             continue;
00211     }
00212 
00213     if (!nsCRT::strcasecmp("Date", headerInfo->name) && !displayOriginalDate) 
00214     {
00215       GenerateDateString(headerValue, convertedDateString); 
00216       headerValueArray.AppendCString(convertedDateString);
00217     }
00218     else // append the header value as is
00219       headerValueArray.AppendCString(nsCString(headerValue));
00220 
00221     // XXX: TODO If nsCStringArray were converted over to take nsACStrings instead of nsCStrings, we can just 
00222     // wrap these strings with a nsDependentCString which would avoid making a duplicate copy of the string
00223     // like we are doing here....
00224     headerNameArray.AppendCString(nsCString(headerInfo->name));
00225   }
00226 
00227   // turn our string arrays into enumerators
00228   NS_NewUTF8StringEnumerator(getter_AddRefs(headerNameEnumerator), &headerNameArray);
00229   NS_NewUTF8StringEnumerator(getter_AddRefs(headerValueEnumerator), &headerValueArray);
00230 
00231   aHeaderSink->ProcessHeaders(headerNameEnumerator, headerValueEnumerator, aFromNewsgroup);
00232   return rv;
00233 }
00234 
00235 NS_IMETHODIMP nsMimeHtmlDisplayEmitter::WriteHTMLHeaders()
00236 {
00237   // if we aren't broadcasting headers OR printing...just do whatever
00238   // our base class does...
00239   if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)
00240   {
00241     return nsMimeBaseEmitter::WriteHTMLHeaders();
00242   }
00243   else if (!BroadCastHeadersAndAttachments() || !mDocHeader)
00244   {
00245     // This needs to be here to correct the output format if we are
00246     // not going to broadcast headers to the XUL document.
00247     if (mFormat == nsMimeOutput::nsMimeMessageBodyDisplay)
00248       mFormat = nsMimeOutput::nsMimeMessagePrintOutput;
00249 
00250     return nsMimeBaseEmitter::WriteHTMLHeaders();
00251   }
00252   else
00253     mFirstHeaders = PR_FALSE;
00254  
00255   PRBool bFromNewsgroups = PR_FALSE;
00256   for (PRInt32 j=0; j < mHeaderArray->Count(); j++)
00257   {
00258     headerInfoType *headerInfo = (headerInfoType *)mHeaderArray->ElementAt(j);
00259     if (!(headerInfo && headerInfo->name && *headerInfo->name))
00260       continue;
00261 
00262     if (!nsCRT::strcasecmp("Newsgroups", headerInfo->name))
00263     {
00264       bFromNewsgroups = PR_TRUE;
00265          break;
00266     }
00267   }
00268 
00269   // try to get a header sink if there is one....
00270   nsCOMPtr<nsIMsgHeaderSink> headerSink; 
00271   nsresult rv = GetHeaderSink(getter_AddRefs(headerSink));
00272 
00273   if (headerSink)
00274   {
00275   PRInt32 viewMode = 0;
00276   nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
00277   if (pPrefBranch)
00278     rv = pPrefBranch->GetIntPref("mail.show_headers", &viewMode);
00279 
00280     rv = BroadcastHeaders(headerSink, viewMode, bFromNewsgroups);
00281   } // if header Sink
00282 
00283   return NS_OK;
00284 }
00285 
00286 nsresult nsMimeHtmlDisplayEmitter::GenerateDateString(const char * dateString, nsACString &formattedDate)
00287 {
00288   nsAutoString formattedDateString;
00289   nsresult rv = NS_OK;
00290 
00291   if (!mDateFormater) {
00292     mDateFormater = do_CreateInstance(kDateTimeFormatCID, &rv);
00293     if (NS_FAILED(rv))
00294       return rv;
00295   }
00296 
00297   PRTime messageTime;
00298   PR_ParseTimeString(dateString, PR_FALSE, &messageTime);
00299 
00300   PRTime currentTime = PR_Now();
00301   PRExplodedTime explodedCurrentTime;
00302   PR_ExplodeTime(currentTime, PR_LocalTimeParameters, &explodedCurrentTime);
00303   PRExplodedTime explodedMsgTime;
00304   PR_ExplodeTime(messageTime, PR_LocalTimeParameters, &explodedMsgTime);
00305 
00306   // if the message is from today, don't show the date, only the time. (i.e. 3:15 pm)
00307   // if the message is from the last week, show the day of the week.   (i.e. Mon 3:15 pm)
00308   // in all other cases, show the full date (03/19/01 3:15 pm)
00309   nsDateFormatSelector dateFormat = kDateFormatShort;
00310   if (explodedCurrentTime.tm_year == explodedMsgTime.tm_year &&
00311       explodedCurrentTime.tm_month == explodedMsgTime.tm_month &&
00312       explodedCurrentTime.tm_mday == explodedMsgTime.tm_mday)
00313   {
00314     // same day...
00315     dateFormat = kDateFormatNone;
00316   } 
00317   // the following chunk of code causes us to show a day instead of a number if the message was received
00318   // within the last 7 days. i.e. Mon 5:10pm. We need to add a preference so folks to can enable this behavior
00319   // if they want it. 
00320 /*
00321   else if (LL_CMP(currentTime, >, dateOfMsg))
00322   {
00323     PRInt64 microSecondsPerSecond, secondsInDays, microSecondsInDays;
00324          LL_I2L(microSecondsPerSecond, PR_USEC_PER_SEC);
00325     LL_UI2L(secondsInDays, 60 * 60 * 24 * 7); // how many seconds in 7 days.....
00326          LL_MUL(microSecondsInDays, secondsInDays, microSecondsPerSecond); // turn that into microseconds
00327 
00328     PRInt64 diff;
00329     LL_SUB(diff, currentTime, dateOfMsg);
00330     if (LL_CMP(diff, <=, microSecondsInDays)) // within the same week 
00331       dateFormat = kDateFormatWeekday;
00332   }
00333 */
00334 
00335   if (NS_SUCCEEDED(rv)) 
00336     rv = mDateFormater->FormatPRTime(nsnull /* nsILocale* locale */,
00337                                       dateFormat,
00338                                       kTimeFormatNoSeconds,
00339                                       messageTime,
00340                                       formattedDateString);
00341 
00342   if (NS_SUCCEEDED(rv))
00343     CopyUTF16toUTF8(formattedDateString, formattedDate);
00344 
00345   return rv;
00346 }
00347 
00348 nsresult
00349 nsMimeHtmlDisplayEmitter::EndHeader()
00350 {
00351   if (mDocHeader && (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer))
00352   {
00353     UtilityWriteCRLF("<html>");
00354     UtilityWriteCRLF("<head>");
00355 
00356     const char * val = GetHeaderValue(HEADER_SUBJECT); // do not free this value
00357     if (val)
00358     {
00359       char * subject = nsEscapeHTML(val);
00360       if (subject)
00361       {
00362         PRInt32 bufLen = strlen(subject) + 16;
00363         char *buf = new char[bufLen];
00364         if (!buf)
00365           return NS_ERROR_OUT_OF_MEMORY;
00366         PR_snprintf(buf, bufLen, "<title>%s</title>", subject);
00367         UtilityWriteCRLF(buf);
00368         delete [] buf;
00369         nsMemory::Free(subject);
00370       }
00371     }
00372 
00373     // mscott --> we should refer to the style sheet used in msg display...this one is wrong i think.
00374     // Stylesheet info!
00375     UtilityWriteCRLF("<link rel=\"important stylesheet\" href=\"chrome://messenger/skin/messageBody.css\">");
00376 
00377     UtilityWriteCRLF("</head>");
00378     UtilityWriteCRLF("<body>");
00379   }
00380 
00381   WriteHTMLHeaders();
00382   return NS_OK;
00383 }
00384 
00385 nsresult
00386 nsMimeHtmlDisplayEmitter::StartAttachment(const char *name,
00387                                           const char *contentType,
00388                                           const char *url,
00389                                           PRBool aIsExternalAttachment)
00390 {
00391   nsresult rv = NS_OK;
00392   nsCOMPtr<nsIMsgHeaderSink> headerSink; 
00393   rv = GetHeaderSink(getter_AddRefs(headerSink));
00394   
00395   if (NS_SUCCEEDED(rv) && headerSink)
00396   {    
00397     char * escapedUrl = nsEscape(url, url_Path);
00398     nsXPIDLCString uriString;
00399 
00400     nsCOMPtr<nsIMsgMessageUrl> msgurl (do_QueryInterface(mURL, &rv));
00401     if (NS_SUCCEEDED(rv))
00402     {
00403       // HACK: news urls require us to use the originalSpec. Everyone
00404       // else uses GetURI to get the RDF resource which describes the message.
00405       nsCOMPtr<nsINntpUrl> nntpUrl (do_QueryInterface(mURL, &rv));
00406       if (NS_SUCCEEDED(rv) && nntpUrl)
00407         rv = msgurl->GetOriginalSpec(getter_Copies(uriString));
00408       else
00409         rv = msgurl->GetUri(getter_Copies(uriString));
00410     }
00411 
00412     // we need to convert the attachment name from UTF-8 to unicode before
00413     // we emit it...
00414     nsXPIDLString unicodeHeaderValue;
00415 
00416     rv = NS_ERROR_FAILURE;  // use failure to mean that we couldn't decode
00417     if (mUnicodeConverter)
00418       rv = mUnicodeConverter->DecodeMimeHeader(name,
00419                                              getter_Copies(unicodeHeaderValue));
00420 
00421     if (NS_FAILED(rv))
00422     {
00423       CopyUTF8toUTF16(name, unicodeHeaderValue);
00424 
00425       // but it's not really a failure if we didn't have a converter
00426       // in the first place
00427       if ( !mUnicodeConverter )
00428         rv = NS_OK;
00429     }
00430 
00431     headerSink->HandleAttachment(contentType, url /* was escapedUrl */,
00432                                  unicodeHeaderValue, uriString,
00433                                  aIsExternalAttachment);
00434 
00435     nsCRT::free(escapedUrl);
00436     mSkipAttachment = PR_TRUE;
00437   }
00438   else if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)
00439   {
00440     // then we need to deal with the attachments in the body by inserting
00441     // them into a table..
00442     rv = StartAttachmentInBody(name, contentType, url);
00443   }
00444   else
00445   {
00446     // If we don't need or cannot broadcast attachment info, just ignore it
00447     mSkipAttachment = PR_TRUE;
00448     rv = NS_OK;
00449   }
00450 
00451   return rv;
00452 }
00453 
00454 // Attachment handling routines
00455 // Ok, we are changing the way we handle these now...It used to be that we output 
00456 // HTML to make a clickable link, etc... but now, this should just be informational
00457 // and only show up during printing
00458 // XXX should they also show up during quoting?
00459 nsresult
00460 nsMimeHtmlDisplayEmitter::StartAttachmentInBody(const char *name, const char *contentType, const char *url)
00461 {
00462   mSkipAttachment = PR_FALSE;
00463 
00464   if ( (contentType) &&
00465        ((!strcmp(contentType, APPLICATION_XPKCS7_MIME)) ||
00466         (!strcmp(contentType, APPLICATION_XPKCS7_SIGNATURE)) ||
00467         (!strcmp(contentType, TEXT_VCARD)))
00468      ) {
00469      mSkipAttachment = PR_TRUE;
00470      return NS_OK;
00471   }
00472 
00473   if (!mFirst)
00474     UtilityWrite("<hr width=\"90%\" size=4>");
00475 
00476   mFirst = PR_FALSE;
00477 
00478   UtilityWrite("<center>");
00479   UtilityWrite("<table border>");
00480   UtilityWrite("<tr>");
00481   UtilityWrite("<td>");
00482 
00483   UtilityWrite("<div align=right class=\"headerdisplayname\" style=\"display:inline;\">");
00484 
00485   UtilityWrite(name);
00486 
00487   UtilityWrite("</div>");
00488 
00489   UtilityWrite("</td>");
00490   UtilityWrite("<td>");
00491   UtilityWrite("<table border=0>");
00492   return NS_OK;
00493 }
00494 
00495 nsresult
00496 nsMimeHtmlDisplayEmitter::AddAttachmentField(const char *field, const char *value)
00497 {
00498   if (mSkipAttachment || BroadCastHeadersAndAttachments())
00499     return NS_OK;
00500 
00501   // Don't let bad things happen
00502   if ( !value || !*value )
00503     return NS_OK;
00504 
00505   // Don't output this ugly header...
00506   if (!strcmp(field, HEADER_X_MOZILLA_PART_URL))
00507     return NS_OK;
00508 
00509   char  *newValue = nsEscapeHTML(value);
00510 
00511   UtilityWrite("<tr>");
00512 
00513   UtilityWrite("<td>");
00514   UtilityWrite("<div align=right class=\"headerdisplayname\" style=\"display:inline;\">");
00515 
00516   UtilityWrite(field);
00517   UtilityWrite(":");
00518   UtilityWrite("</div>");
00519   UtilityWrite("</td>");
00520   UtilityWrite("<td>");
00521 
00522   UtilityWrite(newValue);
00523 
00524   UtilityWrite("</td>");
00525   UtilityWrite("</tr>");
00526 
00527   PR_Free(newValue);
00528   return NS_OK;
00529 }
00530 
00531 nsresult
00532 nsMimeHtmlDisplayEmitter::EndAttachment()
00533 {
00534   if (mSkipAttachment)
00535     return NS_OK;
00536 
00537   mSkipAttachment = PR_FALSE; // reset it for next attachment round
00538 
00539   if (BroadCastHeadersAndAttachments())
00540     return NS_OK;
00541 
00542   if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) {
00543     UtilityWrite("</table>");
00544     UtilityWrite("</td>");
00545     UtilityWrite("</tr>");
00546 
00547     UtilityWrite("</table>");
00548     UtilityWrite("</center>");
00549     UtilityWrite("<br>");
00550   }
00551   return NS_OK;
00552 }
00553 
00554 nsresult
00555 nsMimeHtmlDisplayEmitter::EndAllAttachments()
00556 {
00557   nsresult rv = NS_OK;
00558   nsCOMPtr<nsIMsgHeaderSink> headerSink; 
00559   rv = GetHeaderSink(getter_AddRefs(headerSink));
00560   if (headerSink)
00561          headerSink->OnEndAllAttachments();
00562   return rv;
00563 }
00564 
00565 nsresult
00566 nsMimeHtmlDisplayEmitter::WriteBody(const char *buf, PRUint32 size, PRUint32 *amountWritten)
00567 {
00568   Write(buf, size, amountWritten);
00569   return NS_OK;
00570 }
00571 
00572 nsresult
00573 nsMimeHtmlDisplayEmitter::EndBody()
00574 {
00575   if (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer)
00576   {
00577   UtilityWriteCRLF("</body>");
00578   UtilityWriteCRLF("</html>");
00579   }
00580   nsCOMPtr<nsIMsgHeaderSink> headerSink; 
00581   nsresult rv = GetHeaderSink(getter_AddRefs(headerSink));
00582   nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl (do_QueryInterface(mURL, &rv));
00583   if (headerSink)
00584     headerSink->OnEndMsgHeaders(mailnewsUrl);
00585 
00586   return NS_OK;
00587 }
00588 
00589