/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Pierre Phaneuf <pp@ludusdesign.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "prlog.h"
#include "nsCOMPtr.h"
#include "modlmime.h"
#include "mimeobj.h"
#include "mimemsg.h"
#include "mimetric.h" /* for MIME_RichtextConverter */
#include "mimethtm.h"
#include "mimemsig.h"
#include "mimemrel.h"
#include "mimemalt.h"
#include "mimebuf.h"
#include "mimemapl.h"
#include "prprf.h"
#include "mimei.h" /* for moved MimeDisplayData struct */
#include "mimebuf.h"
#include "prmem.h"
#include "plstr.h"
#include "prmem.h"
#include "mimemoz2.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsIServiceManager.h"
#include "comi18n.h"
#include "nsIStringBundle.h"
#include "nsStringGlue.h"
#include "nsMimeStringResources.h"
#include "nsStreamConverter.h"
#include "nsIMsgSend.h"
#include "nsIMsgMailNewsUrl.h"
#include "mozITXTToHTMLConv.h"
#include "nsCExternalHandlerService.h"
#include "nsIMIMEService.h"
#include "nsIImapUrl.h"
#include "nsMsgI18N.h"
#include "nsICharsetConverterManager.h"
#include "nsICharsetAlias.h"
#include "nsMimeTypes.h"
#include "nsIIOService.h"
#include "nsIURI.h"
#include "nsNetCID.h"
#include "nsIMsgWindow.h"
#include "nsMsgUtils.h"
#include "nsIChannel.h"
#include "nsICacheEntryDescriptor.h"
#include "nsICacheSession.h"
#include "nsITransport.h"
#include "mimeebod.h"
#include "mimeeobj.h"
// <for functions="HTML2Plaintext,HTMLSantinize">
#include "nsXPCOM.h"
#include "nsParserCIID.h"
#include "nsIParser.h"
#include "nsIHTMLContentSink.h"
#include "nsIContentSerializer.h"
#include "nsLayoutCID.h"
#include "nsIComponentManager.h"
#include "nsIHTMLToTextSink.h"
#include "mozISanitizingSerializer.h"
// </for>
// <for functions="HTML2Plaintext,HTMLSantinize">
static NS_DEFINE_CID(kParserCID, NS_PARSER_CID);
// </for>
#ifdef HAVE_MIME_DATA_SLOT
#define LOCK_LAST_CACHED_MESSAGE
#endif
void ValidateRealName(nsMsgAttachmentData *aAttach, MimeHeaders *aHdrs);
static MimeHeadersState MIME_HeaderType;
static PRBool MIME_WrapLongLines;
static PRBool MIME_VariableWidthPlaintext;
// For string bundle access routines...
static nsCOMPtr<nsIStringBundle> stringBundle = nsnull;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Attachment handling routines
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
MimeObject *mime_get_main_object(MimeObject* obj);
nsresult
ProcessBodyAsAttachment(MimeObject *obj, nsMsgAttachmentData **data)
{
nsMsgAttachmentData *tmp;
PRInt32 n;
char *disp = nsnull;
char *charset = nsnull;
// Ok, this is the special case when somebody sends an "attachment" as the body
// of an RFC822 message...I really don't think this is the way this should be done.
// I belive this should really be a multipart/mixed message with an empty body part,
// but what can ya do...our friends to the North seem to do this.
//
MimeObject *child = obj;
n = 1;
*data = (nsMsgAttachmentData *)PR_Malloc( (n + 1) * sizeof(nsMsgAttachmentData));
if (!*data)
return NS_ERROR_OUT_OF_MEMORY;
tmp = *data;
memset(*data, 0, (n + 1) * sizeof(nsMsgAttachmentData));
tmp->real_type = child->content_type ? strdup(child->content_type) : NULL;
tmp->real_encoding = child->encoding ? strdup(child->encoding) : NULL;
disp = MimeHeaders_get(child->headers, HEADER_CONTENT_DISPOSITION, PR_FALSE, PR_FALSE);
tmp->real_name = MimeHeaders_get_parameter(disp, "name", &charset, NULL);
if (tmp->real_name)
{
char *fname = NULL;
fname = mime_decode_filename(tmp->real_name, charset, obj->options);
nsMemory::Free(charset);
if (fname && fname != tmp->real_name)
{
PR_Free(tmp->real_name);
tmp->real_name = fname;
}
}
else
{
tmp->real_name = MimeHeaders_get_name(child->headers, obj->options);
}
if ( (!tmp->real_name) && (tmp->real_type) && (PL_strncasecmp(tmp->real_type, "text", 4)) )
ValidateRealName(tmp, child->headers);
char *tmpURL = nsnull;
char *id = nsnull;
char *id_imap = nsnull;
id = mime_part_address (obj);
if (obj->options->missing_parts)
id_imap = mime_imap_part_address (obj);
if (! id)
{
PR_FREEIF(*data);
PR_FREEIF(id_imap);
return NS_ERROR_OUT_OF_MEMORY;
}
if (obj->options && obj->options->url)
{
const char *url = obj->options->url;
nsresult rv;
if (id_imap && id)
{
// if this is an IMAP part.
tmpURL = mime_set_url_imap_part(url, id_imap, id);
rv = nsMimeNewURI(&(tmp->url), tmpURL, nsnull);
}
else
{
// This is just a normal MIME part as usual.
tmpURL = mime_set_url_part(url, id, PR_TRUE);
rv = nsMimeNewURI(&(tmp->url), tmpURL, nsnull);
}
if ( (!tmp->url) || (NS_FAILED(rv)) )
{
PR_FREEIF(*data);
PR_FREEIF(id);
PR_FREEIF(id_imap);
return NS_ERROR_OUT_OF_MEMORY;
}
}
PR_FREEIF(id);
PR_FREEIF(id_imap);
PR_FREEIF(tmpURL);
tmp->description = MimeHeaders_get(child->headers, HEADER_CONTENT_DESCRIPTION, PR_FALSE, PR_FALSE);
return NS_OK;
}
PRInt32
CountTotalMimeAttachments(MimeContainer *aObj)
{
PRInt32 i;
PRInt32 rc = 0;
if ( (!aObj) || (!aObj->children) || (aObj->nchildren <= 0) )
return 0;
if (mime_typep((MimeObject *)aObj, (MimeObjectClass *)&mimeExternalBodyClass))
return 0;
for (i=0; i<aObj->nchildren; i++)
rc += CountTotalMimeAttachments((MimeContainer *)aObj->children[i]) + 1;
return rc;
}
void
ValidateRealName(nsMsgAttachmentData *aAttach, MimeHeaders *aHdrs)
{
// Sanity.
if (!aAttach)
return;
// Do we need to validate?
if ( (aAttach->real_name) && (*(aAttach->real_name)) )
return;
// Internal MIME structures need not be named!
if ( (!aAttach->real_type) || (aAttach->real_type &&
!PL_strncasecmp(aAttach->real_type, "multipart", 9)) )
return;
// Special case...if this is a enclosed RFC822 message, give it a nice
// name.
if (aAttach->real_type && !PL_strcasecmp(aAttach->real_type, MESSAGE_RFC822))
{
NS_ASSERTION(aHdrs, "How comes the object's headers is null!");
if (aHdrs && aHdrs->munged_subject)
aAttach->real_name = PR_smprintf("%s.eml", aHdrs->munged_subject);
else
NS_MsgSACopy(&(aAttach->real_name), "ForwardedMessage.eml");
return;
}
//
// Now validate any other name we have for the attachment!
//
if (!aAttach->real_name || *aAttach->real_name == 0)
{
nsCString newAttachName(NS_LITERAL_CSTRING("attachment"));
nsresult rv = NS_OK;
nsCAutoString contentType (aAttach->real_type);
PRInt32 pos = contentType.FindChar(';');
if (pos > 0)
contentType.SetLength(pos);
nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv));
if (NS_SUCCEEDED(rv))
{
nsCAutoString fileExtension;
rv = mimeFinder->GetPrimaryExtension(contentType, EmptyCString(), fileExtension);
if (NS_SUCCEEDED(rv) && !fileExtension.IsEmpty())
{
newAttachName.Append('.');
newAttachName.Append(fileExtension);
}
}
aAttach->real_name = ToNewCString(newAttachName);
}
}
static PRInt32 attIndex = 0;
nsresult
GenerateAttachmentData(MimeObject *object, const char *aMessageURL, MimeDisplayOptions *options,
PRBool isAnAppleDoublePart, nsMsgAttachmentData *aAttachData)
{
nsCString imappart;
nsCString part;
PRBool isIMAPPart;
PRBool isExternalAttachment = PR_FALSE;
/* be sure the object has not be marked as Not to be an attachment */
if (object->dontShowAsAttachment)
return NS_OK;
part.Adopt(mime_part_address(object));
if (part.IsEmpty())
return NS_ERROR_OUT_OF_MEMORY;
if (options->missing_parts)
imappart.Adopt(mime_imap_part_address(object));
char *urlSpec = nsnull;
if (!imappart.IsEmpty())
{
isIMAPPart = PR_TRUE;
urlSpec = mime_set_url_imap_part(aMessageURL, imappart.get(), part.get());
}
else
{
isIMAPPart = PR_FALSE;
char *no_part_url = nsnull;
if (options->part_to_load && options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)
no_part_url = mime_get_base_url(aMessageURL);
if (no_part_url) {
urlSpec = mime_set_url_part(no_part_url, part.get(), PR_TRUE);
PR_Free(no_part_url);
}
else
{
// if the mime object contains an external attachment URL, then use it, otherwise
// fall back to creating an attachment url based on the message URI and the
// part number.
urlSpec = mime_external_attachment_url(object);
isExternalAttachment = urlSpec ? PR_TRUE : PR_FALSE;
if (!urlSpec)
urlSpec = mime_set_url_part(aMessageURL, part.get(), PR_TRUE);
}
}
if (!urlSpec)
return NS_ERROR_OUT_OF_MEMORY;
if ((options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) && (PL_strncasecmp(aMessageURL, urlSpec, strlen(urlSpec)) == 0))
return NS_OK;
nsMsgAttachmentData *tmp = &(aAttachData[attIndex++]);
tmp->real_type = object->content_type ? strdup(object->content_type) : nsnull;
tmp->real_encoding = object->encoding ? strdup(object->encoding) : nsnull;
tmp->isExternalAttachment = isExternalAttachment;
PRInt32 i;
char *charset = nsnull;
char *disp = MimeHeaders_get(object->headers, HEADER_CONTENT_DISPOSITION, PR_FALSE, PR_FALSE);
if (disp)
{
tmp->real_name = MimeHeaders_get_parameter(disp, "filename", &charset, nsnull);
if (isAnAppleDoublePart)
for (i = 0; i < 2 && !tmp->real_name; i ++)
{
PR_FREEIF(disp);
nsMemory::Free(charset);
disp = MimeHeaders_get(((MimeContainer *)object)->children[i]->headers, HEADER_CONTENT_DISPOSITION, PR_FALSE, PR_FALSE);
tmp->real_name = MimeHeaders_get_parameter(disp, "filename", &charset, nsnull);
}
if (tmp->real_name)
{
// check encoded type
//
// The parameter of Content-Disposition must use RFC 2231.
// But old Netscape 4.x and Outlook Express etc. use RFC2047.
// So we should parse both types.
char *fname = nsnull;
fname = mime_decode_filename(tmp->real_name, charset, options);
nsMemory::Free(charset);
if (fname && fname != tmp->real_name)
{
PR_FREEIF(tmp->real_name);
tmp->real_name = fname;
}
}
PR_FREEIF(disp);
}
disp = MimeHeaders_get(object->headers, HEADER_CONTENT_TYPE, PR_FALSE, PR_FALSE);
if (disp)
{
tmp->x_mac_type = MimeHeaders_get_parameter(disp, PARAM_X_MAC_TYPE, nsnull, nsnull);
tmp->x_mac_creator= MimeHeaders_get_parameter(disp, PARAM_X_MAC_CREATOR, nsnull, nsnull);
if (!tmp->real_name || *tmp->real_name == 0)
{
PR_FREEIF(tmp->real_name);
tmp->real_name = MimeHeaders_get_parameter(disp, "name", &charset, nsnull);
if (isAnAppleDoublePart)
// the data fork is the 2nd part, and we should ALWAYS look there first for the file name
for (i = 1; i >= 0 && !tmp->real_name; i --)
{
PR_FREEIF(disp);
nsMemory::Free(charset);
disp = MimeHeaders_get(((MimeContainer *)object)->children[i]->headers, HEADER_CONTENT_TYPE, PR_FALSE, PR_FALSE);
tmp->real_name = MimeHeaders_get_parameter(disp, "name", &charset, nsnull);
}
if (tmp->real_name)
{
// check encoded type
//
// The parameter of Content-Disposition must use RFC 2231.
// But old Netscape 4.x and Outlook Express etc. use RFC2047.
// So we should parse both types.
char *fname = nsnull;
fname = mime_decode_filename(tmp->real_name, charset, options);
nsMemory::Free(charset);
if (fname && fname != tmp->real_name)
{
PR_Free(tmp->real_name);
tmp->real_name = fname;
}
}
}
PR_FREEIF(disp);
}
tmp->description = MimeHeaders_get(object->headers, HEADER_CONTENT_DESCRIPTION,
PR_FALSE, PR_FALSE);
// Now, do the right thing with the name!
if (!tmp->real_name && PL_strcasecmp(tmp->real_type, MESSAGE_RFC822))
{
/* If this attachment doesn't have a name, just give it one... */
tmp->real_name = MimeGetStringByID(MIME_MSG_DEFAULT_ATTACHMENT_NAME);
if (tmp->real_name)
{
char *newName = PR_smprintf(tmp->real_name, part.get());
if (newName)
{
PR_Free(tmp->real_name);
tmp->real_name = newName;
}
}
else
tmp->real_name = mime_part_address(object);
}
nsCString urlString(urlSpec);
if (tmp->real_name && !tmp->isExternalAttachment)
{
urlString.Append("&filename=");
urlString.Append(tmp->real_name);
#ifdef MOZILLA_INTERNAL_API
if (tmp->real_type && !strcmp(tmp->real_type, "message/rfc822") &&
!StringEndsWith(urlString, NS_LITERAL_CSTRING(".eml"), nsCaseInsensitiveCStringComparator()))
#else
if (tmp->real_type && !strcmp(tmp->real_type, "message/rfc822") &&
!StringEndsWith(urlString, NS_LITERAL_CSTRING(".eml"), CaseInsensitiveCompare))
#endif
urlString.Append(".eml");
}
nsresult rv = nsMimeNewURI(&(tmp->url), urlString.get(), nsnull);
PR_FREEIF(urlSpec);
if ( (NS_FAILED(rv)) || (!tmp->url) )
return NS_ERROR_OUT_OF_MEMORY;
ValidateRealName(tmp, object->headers);
return NS_OK;
}
nsresult
BuildAttachmentList(MimeObject *anObject, nsMsgAttachmentData *aAttachData, const char *aMessageURL)
{
nsresult rv;
PRInt32 i;
MimeContainer *cobj = (MimeContainer *) anObject;
if ( (!anObject) || (!cobj->children) || (!cobj->nchildren) ||
(mime_typep(anObject, (MimeObjectClass *)&mimeExternalBodyClass)))
return NS_OK;
for (i = 0; i < cobj->nchildren ; i++)
{
MimeObject *child = cobj->children[i];
// Skip the first child if it's in fact a message body
if (i == 0) // it's the first child
if (child->content_type) // and it's content-type is one of folowing...
if (!PL_strcasecmp (child->content_type, TEXT_PLAIN) ||
!PL_strcasecmp (child->content_type, TEXT_HTML) ||
!PL_strcasecmp (child->content_type, TEXT_MDL))
{
if (child->headers) // and finally, be sure it doesn't have a content-disposition: attachment
{
char * disp = MimeHeaders_get (child->headers, HEADER_CONTENT_DISPOSITION, PR_TRUE, PR_FALSE);
if (!disp || PL_strcasecmp (disp, "attachment"))
continue;
}
else
continue;
}
// We should generate an attachment for leaf object only but...
PRBool isALeafObject = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeLeafClass);
// ...we will generate an attachment for inline message too.
PRBool isAnInlineMessage = mime_typep(child, (MimeObjectClass *) &mimeMessageClass);
// AppleDouble part need special care: we need to fetch the part as well its two
// children for the needed info as they could be anywhere, eventually, they won't contain
// a name or file name. In any case we need to build only one attachment data
PRBool isAnAppleDoublePart = mime_typep(child, (MimeObjectClass *) &mimeMultipartAppleDoubleClass) &&
((MimeContainer *)child)->nchildren == 2;
if (isALeafObject || isAnInlineMessage || isAnAppleDoublePart)
{
rv = GenerateAttachmentData(child, aMessageURL, anObject->options, isAnAppleDoublePart, aAttachData);
NS_ENSURE_SUCCESS(rv, rv);
}
// Now build the attachment list for the children of our object...
if (!isALeafObject && !isAnAppleDoublePart)
{
rv = BuildAttachmentList((MimeObject *)child, aAttachData, aMessageURL);
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
extern "C" nsresult
MimeGetAttachmentList(MimeObject *tobj, const char *aMessageURL, nsMsgAttachmentData **data)
{
MimeObject *obj;
MimeContainer *cobj;
PRInt32 n;
PRBool isAnInlineMessage;
if (!data)
return 0;
*data = nsnull;
obj = mime_get_main_object(tobj);
if (!obj)
return 0;
if (!mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeContainerClass))
{
if (!PL_strcasecmp(obj->content_type, MESSAGE_RFC822))
return 0;
else
return ProcessBodyAsAttachment(obj, data);
}
isAnInlineMessage = mime_typep(obj, (MimeObjectClass *) &mimeMessageClass);
cobj = (MimeContainer*) obj;
n = CountTotalMimeAttachments(cobj);
if (n <= 0)
return n;
//in case of an inline message (as body), we need an extra slot for the message itself
//that we will fill later...
if (isAnInlineMessage)
n ++;
*data = (nsMsgAttachmentData *)PR_Malloc( (n + 1) * sizeof(nsMsgAttachmentData));
if (!*data)
return NS_ERROR_OUT_OF_MEMORY;
attIndex = 0;
memset(*data, 0, (n + 1) * sizeof(nsMsgAttachmentData));
// Now, build the list!
nsresult rv;
if (isAnInlineMessage)
{
rv = GenerateAttachmentData(obj, aMessageURL, obj->options, PR_FALSE, *data);
NS_ENSURE_SUCCESS(rv, rv);
}
return BuildAttachmentList((MimeObject *) cobj, *data, aMessageURL);
}
extern "C" void
MimeFreeAttachmentList(nsMsgAttachmentData *data)
{
if (data)
{
nsMsgAttachmentData *tmp;
for (tmp = data ; tmp->url ; tmp++)
{
/* Can't do PR_FREEIF on `const' values... */
NS_IF_RELEASE(tmp->url);
if (tmp->real_type) PR_Free((char *) tmp->real_type);
if (tmp->real_encoding) PR_Free((char *) tmp->real_encoding);
if (tmp->real_name) PR_Free((char *) tmp->real_name);
if (tmp->x_mac_type) PR_Free((char *) tmp->x_mac_type);
if (tmp->x_mac_creator) PR_Free((char *) tmp->x_mac_creator);
if (tmp->description) PR_Free((char *) tmp->description);
tmp->url = 0;
tmp->real_type = 0;
tmp->real_name = 0;
tmp->description = 0;
}
PR_Free(data);
}
}
extern "C" void
NotifyEmittersOfAttachmentList(MimeDisplayOptions *opt,
nsMsgAttachmentData *data)
{
PRInt32 i = 0;
struct nsMsgAttachmentData *tmp = data;
if (!tmp)
return;
while (tmp->url)
{
if (!tmp->real_name)
{
++i;
++tmp;
continue;
}
nsCAutoString spec;
if ( tmp->url )
tmp->url->GetSpec(spec);
mimeEmitterStartAttachment(opt, tmp->real_name, tmp->real_type, spec.get(), tmp->isExternalAttachment);
mimeEmitterAddAttachmentField(opt, HEADER_X_MOZILLA_PART_URL, spec.get());
if ( (opt->format_out == nsMimeOutput::nsMimeMessageQuoting) ||
(opt->format_out == nsMimeOutput::nsMimeMessageBodyQuoting) ||
(opt->format_out == nsMimeOutput::nsMimeMessageSaveAs) ||
(opt->format_out == nsMimeOutput::nsMimeMessagePrintOutput))
{
mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_DESCRIPTION, tmp->description);
mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_TYPE, tmp->real_type);
mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_ENCODING, tmp->real_encoding);
/* rhp - for now, just leave these here, but they are really
not necessary
printf("URL for Part : %s\n", spec);
printf("Real Name : %s\n", tmp->real_name);
printf("Desired Type : %s\n", tmp->desired_type);
printf("Real Type : %s\n", tmp->real_type);
printf("Real Encoding : %s\n", tmp->real_encoding);
printf("Description : %s\n", tmp->description);
printf("Mac Type : %s\n", tmp->x_mac_type);
printf("Mac Creator : %s\n", tmp->x_mac_creator);
*/
}
mimeEmitterEndAttachment(opt);
++i;
++tmp;
}
mimeEmitterEndAllAttachments(opt);
}
// Utility to create a nsIURI object...
extern "C" nsresult
nsMimeNewURI(nsIURI** aInstancePtrResult, const char *aSpec, nsIURI *aBase)
{
nsresult res;
if (nsnull == aInstancePtrResult)
return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIIOService> pService(do_GetService(NS_IOSERVICE_CONTRACTID, &res));
if (NS_FAILED(res))
return NS_ERROR_FACTORY_NOT_REGISTERED;
return pService->NewURI(nsDependentCString(aSpec), nsnull, aBase, aInstancePtrResult);
}
extern "C" nsresult
SetMailCharacterSetToMsgWindow(MimeObject *obj, const char *aCharacterSet)
{
nsresult rv = NS_OK;
if (obj && obj->options)
{
mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure);
if (msd)
{
nsIChannel *channel = msd->channel;
if (channel)
{
nsCOMPtr<nsIURI> uri;
channel->GetURI(getter_AddRefs(uri));
if (uri)
{
nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(uri));
if (msgurl)
{
nsCOMPtr<nsIMsgWindow> msgWindow;
msgurl->GetMsgWindow(getter_AddRefs(msgWindow));
if (msgWindow)
rv = msgWindow->SetMailCharacterSet(!PL_strcasecmp(aCharacterSet, "us-ascii") ?
NS_LITERAL_CSTRING("ISO-8859-1") :
nsDependentCString(aCharacterSet));
}
}
}
}
}
return rv;
}
static char *
mime_file_type (const char *filename, void *stream_closure)
{
char *retType = nsnull;
char *ext = nsnull;
nsresult rv;
ext = PL_strrchr(filename, '.');
if (ext)
{
ext++;
nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv));
if (mimeFinder) {
nsCAutoString type;
mimeFinder->GetTypeFromExtension(nsDependentCString(ext), type);
retType = ToNewCString(type);
}
}
return retType;
}
int ConvertUsingEncoderAndDecoder(const char *stringToUse, PRInt32 inLength,
nsIUnicodeEncoder *encoder, nsIUnicodeDecoder *decoder,
char **pConvertedString, PRInt32 *outLength)
{
// buffer size 144 =
// 72 (default line len for compose)
// times 2 (converted byte len might be larger)
const int klocalbufsize = 144;
// do the conversion
PRUnichar *unichars;
PRInt32 unicharLength;
PRInt32 srcLen = inLength;
PRInt32 dstLength = 0;
char *dstPtr;
nsresult rv;
// use this local buffer if possible
PRUnichar localbuf[klocalbufsize+1];
if (inLength > klocalbufsize) {
rv = decoder->GetMaxLength(stringToUse, srcLen, &unicharLength);
// allocate temporary buffer to hold unicode string
unichars = new PRUnichar[unicharLength];
}
else {
unichars = localbuf;
unicharLength = klocalbufsize+1;
}
if (unichars == nsnull) {
rv = NS_ERROR_OUT_OF_MEMORY;
}
else {
// convert to unicode, replacing failed chars with 0xFFFD as in
// the methode used in nsXMLHttpRequest::ConvertBodyToText and nsScanner::Append
//
// We will need several pass to convert the whole string if it has invalid characters
// 'totalChars' is where the sum of the number of converted characters will be done
// 'dataLen' is the number of character left to convert
// 'outLen' is the number of characters still available in the output buffer as input of decoder->Convert
// and the number of characters written in it as output.
PRInt32 totalChars = 0,
inBufferIndex = 0,
outBufferIndex = 0;
PRInt32 dataLen = srcLen,
outLen = unicharLength;
do {
PRInt32 inBufferLength = dataLen;
rv = decoder->Convert(&stringToUse[inBufferIndex],
&inBufferLength,
&unichars[outBufferIndex],
&outLen);
totalChars += outLen;
// Done if conversion successful
if (NS_SUCCEEDED(rv))
break;
// We consume one byte, replace it with U+FFFD
// and try the conversion again.
outBufferIndex += outLen;
unichars[outBufferIndex++] = PRUnichar(0xFFFD);
// totalChars is updated here
outLen = unicharLength - (++totalChars);
inBufferIndex += inBufferLength + 1;
dataLen -= inBufferLength + 1;
decoder->Reset();
// If there is not at least one byte available after the one we
// consumed, we're done
} while ( dataLen > 0 );
rv = encoder->GetMaxLength(unichars, totalChars, &dstLength);
// allocale an output buffer
dstPtr = (char *) PR_Malloc(dstLength + 1);
if (dstPtr == nsnull) {
rv = NS_ERROR_OUT_OF_MEMORY;
}
else {
PRInt32 buffLength = dstLength;
// convert from unicode
rv = encoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Replace, nsnull, '?');
if (NS_SUCCEEDED(rv)) {
rv = encoder->Convert(unichars, &totalChars, dstPtr, &dstLength);
if (NS_SUCCEEDED(rv)) {
PRInt32 finLen = buffLength - dstLength;
rv = encoder->Finish((char *)(dstPtr+dstLength), &finLen);
if (NS_SUCCEEDED(rv)) {
dstLength += finLen;
}
dstPtr[dstLength] = '\0';
*pConvertedString = dstPtr; // set the result string
*outLength = dstLength;
}
}
}
if (inLength > klocalbufsize)
delete [] unichars;
}
return NS_SUCCEEDED(rv) ? 0 : -1;
}
static int
mime_convert_charset (const char *input_line, PRInt32 input_length,
const char *input_charset, const char *output_charset,
char **output_ret, PRInt32 *output_size_ret,
void *stream_closure, nsIUnicodeDecoder *decoder, nsIUnicodeEncoder *encoder)
{
PRInt32 res = -1;
char *convertedString = NULL;
PRInt32 convertedStringLen = 0;
if (encoder && decoder)
{
res = ConvertUsingEncoderAndDecoder(input_line, input_length, encoder, decoder, &convertedString, &convertedStringLen);
}
if (res != 0)
{
*output_ret = 0;
*output_size_ret = 0;
}
else
{
*output_ret = (char *) convertedString;
*output_size_ret = convertedStringLen;
}
return 0;
}
static int
mime_output_fn(const char *buf, PRInt32 size, void *stream_closure)
{
PRUint32 written = 0;
struct mime_stream_data *msd = (struct mime_stream_data *) stream_closure;
if ( (!msd->pluginObj2) && (!msd->output_emitter) )
return -1;
// Fire pending start request
((nsStreamConverter*)msd->pluginObj2)->FirePendingStartRequest();
// Now, write to the WriteBody method if this is a message body and not
// a part retrevial
if (!msd->options->part_to_load || msd->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)
{
if (msd->output_emitter)
{
msd->output_emitter->WriteBody(buf, (PRUint32) size, &written);
}
}
else
{
if (msd->output_emitter)
{
msd->output_emitter->Write(buf, (PRUint32) size, &written);
}
}
return written;
}
extern "C" int
mime_display_stream_write (nsMIMESession *stream,
const char* buf,
PRInt32 size)
{
struct mime_stream_data *msd = (struct mime_stream_data *) ((nsMIMESession *)stream)->data_object;
MimeObject *obj = (msd ? msd->obj : 0);
if (!obj) return -1;
//
// Ok, now check to see if this is a display operation for a MIME Parts on Demand
// enabled call.
//
if (msd->firstCheck)
{
if (msd->channel)
{
nsCOMPtr<nsIURI> aUri;
if (NS_SUCCEEDED(msd->channel->GetURI(getter_AddRefs(aUri))))
{
nsCOMPtr<nsIImapUrl> imapURL = do_QueryInterface(aUri);
if (imapURL)
{
nsImapContentModifiedType cModified;
if (NS_SUCCEEDED(imapURL->GetContentModified(&cModified)))
{
if ( cModified != nsImapContentModifiedTypes::IMAP_CONTENT_NOT_MODIFIED )
msd->options->missing_parts = PR_TRUE;
}
}
}
}
msd->firstCheck = PR_FALSE;
}
return obj->clazz->parse_buffer((char *) buf, size, obj);
}
extern "C" void
mime_display_stream_complete (nsMIMESession *stream)
{
struct mime_stream_data *msd = (struct mime_stream_data *) ((nsMIMESession *)stream)->data_object;
MimeObject *obj = (msd ? msd->obj : 0);
if (obj)
{
int status;
PRBool abortNow = PR_FALSE;
if ((obj->options) && (obj->options->headers == MimeHeadersOnly))
abortNow = PR_TRUE;
status = obj->clazz->parse_eof(obj, abortNow);
obj->clazz->parse_end(obj, (status < 0 ? PR_TRUE : PR_FALSE));
//
// Ok, now we are going to process the attachment data by getting all
// of the attachment info and then driving the emitter with this data.
//
if (!msd->options->part_to_load || msd->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)
{
nsMsgAttachmentData *attachments;
nsresult rv = MimeGetAttachmentList(obj, msd->url_name, &attachments);
if (NS_SUCCEEDED(rv))
{
NotifyEmittersOfAttachmentList(msd->options, attachments);
MimeFreeAttachmentList(attachments);
}
}
// Release the conversion object - this has to be done after
// we finish processing data.
if ( obj->options)
{
NS_IF_RELEASE(obj->options->conv);
}
// Destroy the object now.
PR_ASSERT(msd->options == obj->options);
mime_free(obj);
obj = NULL;
if (msd->options)
{
delete msd->options;
msd->options = 0;
}
}
if (msd->headers)
MimeHeaders_free (msd->headers);
if (msd->url_name)
NS_Free(msd->url_name);
if (msd->orig_url_name)
NS_Free(msd->orig_url_name);
PR_FREEIF(msd);
}
extern "C" void
mime_display_stream_abort (nsMIMESession *stream, int status)
{
struct mime_stream_data *msd = (struct mime_stream_data *) ((nsMIMESession *)stream)->data_object;
MimeObject *obj = (msd ? msd->obj : 0);
if (obj)
{
if (!obj->closed_p)
obj->clazz->parse_eof(obj, PR_TRUE);
if (!obj->parsed_p)
obj->clazz->parse_end(obj, PR_TRUE);
// Destroy code....
PR_ASSERT(msd->options == obj->options);
mime_free(obj);
if (msd->options)
{
delete msd->options;
msd->options = 0;
}
}
if (msd->headers)
MimeHeaders_free (msd->headers);
if (msd->url_name)
NS_Free(msd->url_name);
if (msd->orig_url_name)
NS_Free(msd->orig_url_name);
PR_FREEIF(msd);
}
static int
mime_output_init_fn (const char *type,
const char *charset,
const char *name,
const char *x_mac_type,
const char *x_mac_creator,
void *stream_closure)
{
struct mime_stream_data *msd = (struct mime_stream_data *) stream_closure;
// Now, all of this stream creation is done outside of libmime, so this
// is just a check of the pluginObj member and returning accordingly.
if (!msd->pluginObj2)
return -1;
else
return 0;
}
static void *mime_image_begin(const char *image_url, const char *content_type,
void *stream_closure);
static void mime_image_end(void *image_closure, int status);
static char *mime_image_make_image_html(void *image_data);
static int mime_image_write_buffer(const char *buf, PRInt32 size, void *image_closure);
/* Interface between libmime and inline display of images: the abomination
that is known as "internal-external-reconnect".
*/
class mime_image_stream_data {
public:
mime_image_stream_data();
struct mime_stream_data *msd;
char *url;
nsMIMESession *istream;
nsCOMPtr<nsIOutputStream> memCacheOutputStream;
PRBool m_shouldCacheImage;
};
mime_image_stream_data::mime_image_stream_data()
{
url = nsnull;
istream = nsnull;
msd = nsnull;
m_shouldCacheImage = PR_FALSE;
}
static void *
mime_image_begin(const char *image_url, const char *content_type,
void *stream_closure)
{
struct mime_stream_data *msd = (struct mime_stream_data *) stream_closure;
class mime_image_stream_data *mid;
mid = new mime_image_stream_data;
if (!mid) return nsnull;
mid->msd = msd;
mid->url = (char *) strdup(image_url);
if (!mid->url)
{
PR_Free(mid);
return nsnull;
}
if (msd->channel)
{
nsCOMPtr <nsIURI> uri;
nsresult rv = msd->channel->GetURI(getter_AddRefs(uri));
if (NS_SUCCEEDED(rv) && uri)
{
nsCOMPtr <nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(uri);
if (mailUrl)
{
nsCOMPtr<nsICacheSession> memCacheSession;
mailUrl->GetImageCacheSession(getter_AddRefs(memCacheSession));
if (memCacheSession)
{
nsCOMPtr<nsICacheEntryDescriptor> entry;
// we may need to convert the image_url into just a part url - in any case,
// it has to be the same as what imglib will be asking imap for later
// on so that we'll find this in the memory cache.
rv = memCacheSession->OpenCacheEntry(nsDependentCString(image_url), nsICache::ACCESS_READ_WRITE, nsICache::BLOCKING, getter_AddRefs(entry));
nsCacheAccessMode access;
if (entry)
{
entry->GetAccessGranted(&access);
#ifdef DEBUG_bienvenu
printf("Mime opening cache entry for %s access = %ld\n", image_url, access);
#endif
// if we only got write access, then we should fill in this cache entry
if (access & nsICache::ACCESS_WRITE && !(access & nsICache::ACCESS_READ))
{
mailUrl->CacheCacheEntry(entry);
entry->MarkValid();
// remember the content type as meta data so we can pull it out in the imap code
// to feed the cache entry directly to imglib w/o going through mime.
entry->SetMetaDataElement("contentType", content_type);
rv = entry->OpenOutputStream(0, getter_AddRefs(mid->memCacheOutputStream));
if (NS_FAILED(rv)) return nsnull;
}
}
}
}
}
}
mid->istream = (nsMIMESession *) msd->pluginObj2;
return mid;
}
static void
mime_image_end(void *image_closure, int status)
{
mime_image_stream_data *mid =
(mime_image_stream_data *) image_closure;
PR_ASSERT(mid);
if (!mid)
return;
if (mid->memCacheOutputStream)
mid->memCacheOutputStream->Close();
PR_FREEIF(mid->url);
delete mid;
}
static char *
mime_image_make_image_html(void *image_closure)
{
mime_image_stream_data *mid =
(mime_image_stream_data *) image_closure;
const char *prefix;
/* Wouldn't it be nice if attributes were case-sensitive? */
const char *scaledPrefix = "<P><CENTER><IMG CLASS=\"moz-attached-image\" shrinktofit=\"yes\" SRC=\"";
const char *unscaledPrefix = "<P><CENTER><IMG CLASS=\"moz-attached-image\" SRC=\"";
const char *suffix = "\"></CENTER><P>";
const char *url;
char *buf;
PR_ASSERT(mid);
if (!mid) return 0;
/* Internal-external-reconnect only works when going to the screen. */
if (!mid->istream)
return strdup("<P><CENTER><IMG SRC=\"resource://gre/res/loading-image.gif\" ALT=\"[Image]\"></CENTER><P>");
nsCOMPtr<nsIPrefBranch> prefBranch;
nsCOMPtr<nsIPrefService> prefSvc(do_GetService(NS_PREFSERVICE_CONTRACTID));
PRBool resize = PR_TRUE;
if (prefSvc)
prefSvc->GetBranch("", getter_AddRefs(prefBranch));
if (prefBranch)
prefBranch->GetBoolPref("mail.enable_automatic_image_resizing", &resize); // ignore return value
prefix = resize ? scaledPrefix : unscaledPrefix;
if ( (!mid->url) || (!(*mid->url)) )
url = "";
else
url = mid->url;
PRUint32 buflen = strlen(prefix) + strlen(suffix) + strlen(url) + 20;
buf = (char *) PR_MALLOC (buflen);
if (!buf)
return 0;
*buf = 0;
PL_strcatn (buf, buflen, prefix);
PL_strcatn (buf, buflen, url);
PL_strcatn (buf, buflen, suffix);
return buf;
}
static int
mime_image_write_buffer(const char *buf, PRInt32 size, void *image_closure)
{
mime_image_stream_data *mid =
(mime_image_stream_data *) image_closure;
struct mime_stream_data *msd = mid->msd;
if ( ( (!msd->output_emitter) ) &&
( (!msd->pluginObj2) ) )
return -1;
//
// If we get here, we are just eating the data this time around
// and the returned URL will deal with writing the data to the viewer.
// Just return the size value to the caller.
//
if (mid->memCacheOutputStream)
{
PRUint32 bytesWritten;
mid->memCacheOutputStream->Write(buf, size, &bytesWritten);
}
return size;
}
MimeObject*
mime_get_main_object(MimeObject* obj)
{
MimeContainer *cobj;
if (!(mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeMessageClass)))
{
return obj;
}
cobj = (MimeContainer*) obj;
if (cobj->nchildren != 1) return obj;
obj = cobj->children[0];
while (obj)
{
if ( (!mime_subclass_p(obj->clazz,
(MimeObjectClass*) &mimeMultipartSignedClass)) &&
(PL_strcasecmp(obj->content_type, MULTIPART_SIGNED) != 0)
)
{
return obj;
}
else
{
if (mime_subclass_p(obj->clazz, (MimeObjectClass*)&mimeContainerClass))
{
// We don't care about a signed/smime object; Go inside to the
// thing that we signed or smime'ed
//
cobj = (MimeContainer*) obj;
if (cobj->nchildren > 0)
obj = cobj->children[0];
else
obj = nsnull;
}
else
{
// we received a message with a child object that looks like a signed
// object, but it is not a subclass of mimeContainer, so let's
// return the given child object.
return obj;
}
}
}
return nsnull;
}
PRBool MimeObjectChildIsMessageBody(MimeObject *obj,
PRBool *isAlternativeOrRelated)
{
char *disp = 0;
PRBool bRet = PR_FALSE;
MimeObject *firstChild = 0;
MimeContainer *container = (MimeContainer*) obj;
if (isAlternativeOrRelated)
*isAlternativeOrRelated = PR_FALSE;
if (!container ||
!mime_subclass_p(obj->clazz,
(MimeObjectClass*) &mimeContainerClass))
{
return bRet;
}
else if (mime_subclass_p(obj->clazz, (MimeObjectClass*)
&mimeMultipartRelatedClass))
{
if (isAlternativeOrRelated)
*isAlternativeOrRelated = PR_TRUE;
return bRet;
}
else if (mime_subclass_p(obj->clazz, (MimeObjectClass*)
&mimeMultipartAlternativeClass))
{
if (isAlternativeOrRelated)
*isAlternativeOrRelated = PR_TRUE;
return bRet;
}
if (container->children)
firstChild = container->children[0];
if (!firstChild ||
!firstChild->content_type ||
!firstChild->headers)
return bRet;
disp = MimeHeaders_get (firstChild->headers,
HEADER_CONTENT_DISPOSITION,
PR_TRUE,
PR_FALSE);
if (disp /* && !PL_strcasecmp (disp, "attachment") */)
bRet = PR_FALSE;
else if (!PL_strcasecmp (firstChild->content_type, TEXT_PLAIN) ||
!PL_strcasecmp (firstChild->content_type, TEXT_HTML) ||
!PL_strcasecmp (firstChild->content_type, TEXT_MDL) ||
!PL_strcasecmp (firstChild->content_type, MULTIPART_ALTERNATIVE) ||
!PL_strcasecmp (firstChild->content_type, MULTIPART_RELATED) ||
!PL_strcasecmp (firstChild->content_type, MESSAGE_NEWS) ||
!PL_strcasecmp (firstChild->content_type, MESSAGE_RFC822))
bRet = PR_TRUE;
else
bRet = PR_FALSE;
PR_FREEIF(disp);
return bRet;
}
//
// New Stream Converter Interface
//
// Get the connnection to prefs service manager
nsIPrefBranch *
GetPrefBranch(MimeDisplayOptions *opt)
{
if (!opt)
return nsnull;
return opt->m_prefBranch;
}
// Get the text converter...
mozITXTToHTMLConv *
GetTextConverter(MimeDisplayOptions *opt)
{
if (!opt)
return nsnull;
return opt->conv;
}
MimeDisplayOptions::MimeDisplayOptions()
{
conv = nsnull; // For text conversion...
format_out = 0; // The format out type
url = nsnull;
memset(&headers,0, sizeof(headers));
fancy_headers_p = PR_FALSE;
output_vcard_buttons_p = PR_FALSE;
fancy_links_p = PR_FALSE;
variable_width_plaintext_p = PR_FALSE;
wrap_long_lines_p = PR_FALSE;
rot13_p = PR_FALSE;
part_to_load = nsnull;
write_html_p = PR_FALSE;
decrypt_p = PR_FALSE;
nice_html_only_p = PR_FALSE;
whattodo = 0 ;
default_charset = nsnull;
override_charset = PR_FALSE;
force_user_charset = PR_FALSE;
stream_closure = nsnull;
/* For setting up the display stream, so that the MIME parser can inform
the caller of the type of the data it will be getting. */
output_init_fn = nsnull;
output_fn = nsnull;
output_closure = nsnull;
charset_conversion_fn = nsnull;
rfc1522_conversion_p = PR_FALSE;
file_type_fn = nsnull;
passwd_prompt_fn = nsnull;
html_closure = nsnull;
generate_header_html_fn = nsnull;
generate_post_header_html_fn = nsnull;
generate_footer_html_fn = nsnull;
generate_reference_url_fn = nsnull;
generate_mailto_url_fn = nsnull;
generate_news_url_fn = nsnull;
image_begin = nsnull;
image_end = nsnull;
image_write_buffer = nsnull;
make_image_html = nsnull;
state = nsnull;
#ifdef MIME_DRAFTS
decompose_file_p = PR_FALSE;
done_parsing_outer_headers = PR_FALSE;
is_multipart_msg = PR_FALSE;
decompose_init_count = 0;
signed_p = PR_FALSE;
caller_need_root_headers = PR_FALSE;
decompose_headers_info_fn = nsnull;
decompose_file_init_fn = nsnull;
decompose_file_output_fn = nsnull;
decompose_file_close_fn = nsnull;
#endif /* MIME_DRAFTS */
attachment_icon_layer_id = 0;
missing_parts = PR_FALSE;
show_attachment_inline_p = PR_FALSE;
quote_attachment_inline_p = PR_FALSE;
notify_nested_bodies = PR_FALSE;
}
MimeDisplayOptions::~MimeDisplayOptions()
{
PR_FREEIF(part_to_load);
PR_FREEIF(default_charset);
}
////////////////////////////////////////////////////////////////
// Bridge routines for new stream converter XP-COM interface
////////////////////////////////////////////////////////////////
extern "C" void *
mime_bridge_create_display_stream(
nsIMimeEmitter *newEmitter,
nsStreamConverter *newPluginObj2,
nsIURI *uri,
nsMimeOutputType format_out,
PRUint32 whattodo,
nsIChannel *aChannel)
{
int status = 0;
MimeObject *obj;
struct mime_stream_data *msd;
nsMIMESession *stream = 0;
if (!uri)
return nsnull;
msd = PR_NEWZAP(struct mime_stream_data);
if (!msd)
return NULL;
// Assign the new mime emitter - will handle output operations
msd->output_emitter = newEmitter;
msd->firstCheck = PR_TRUE;
// Store the URL string for this decode operation
nsCAutoString urlString;
nsresult rv;
// Keep a hold of the channel...
msd->channel = aChannel;
rv = uri->GetSpec(urlString);
if (NS_SUCCEEDED(rv))
{
if (!urlString.IsEmpty())
{
msd->url_name = ToNewCString(urlString);
if (!(msd->url_name))
{
PR_FREEIF(msd);
return NULL;
}
nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(uri);
if (msgUrl)
msgUrl->GetOriginalSpec(&msd->orig_url_name);
}
}
msd->format_out = format_out; // output format
msd->pluginObj2 = newPluginObj2; // the plugin object pointer
msd->options = new MimeDisplayOptions;
if (!msd->options)
{
PR_Free(msd);
return 0;
}
// memset(msd->options, 0, sizeof(*msd->options));
msd->options->format_out = format_out; // output format
msd->options->m_prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv))
{
PR_FREEIF(msd);
return nsnull;
}
// Need the text converter...
rv = CallCreateInstance(MOZ_TXTTOHTMLCONV_CONTRACTID, &(msd->options->conv));
if (NS_FAILED(rv))
{
msd->options->m_prefBranch = 0;
PR_FREEIF(msd);
return nsnull;
}
//
// Set the defaults, based on the context, and the output-type.
//
MIME_HeaderType = MimeHeadersAll;
msd->options->write_html_p = PR_TRUE;
switch (format_out)
{
case nsMimeOutput::nsMimeMessageSplitDisplay: // the wrapper HTML output to produce the split header/body display
case nsMimeOutput::nsMimeMessageHeaderDisplay: // the split header/body display
case nsMimeOutput::nsMimeMessageBodyDisplay: // the split header/body display
msd->options->fancy_headers_p = PR_TRUE;
msd->options->output_vcard_buttons_p = PR_TRUE;
msd->options->fancy_links_p = PR_TRUE;
break;
case nsMimeOutput::nsMimeMessageSaveAs: // Save As operations
case nsMimeOutput::nsMimeMessageQuoting: // all HTML quoted/printed output
case nsMimeOutput::nsMimeMessagePrintOutput:
msd->options->fancy_headers_p = PR_TRUE;
msd->options->fancy_links_p = PR_TRUE;
break;
case nsMimeOutput::nsMimeMessageBodyQuoting: // only HTML body quoted output
MIME_HeaderType = MimeHeadersNone;
break;
case nsMimeOutput::nsMimeMessageAttach: // handling attachment storage
msd->options->write_html_p = PR_FALSE;
break;
case nsMimeOutput::nsMimeMessageRaw: // the raw RFC822 data (view source) and attachments
case nsMimeOutput::nsMimeMessageDraftOrTemplate: // Loading drafts & templates
case nsMimeOutput::nsMimeMessageEditorTemplate: // Loading templates into editor
case nsMimeOutput::nsMimeMessageFilterSniffer: // generating an output that can be scan by a message filter
break;
case nsMimeOutput::nsMimeMessageDecrypt:
msd->options->decrypt_p = PR_TRUE;
msd->options->write_html_p = PR_FALSE;
break;
}
////////////////////////////////////////////////////////////
// Now, get the libmime prefs...
////////////////////////////////////////////////////////////
MIME_WrapLongLines = PR_TRUE;
MIME_VariableWidthPlaintext = PR_TRUE;
msd->options->force_user_charset = PR_FALSE;
if (msd->options->m_prefBranch)
{
msd->options->m_prefBranch->GetBoolPref("mail.wrap_long_lines", &MIME_WrapLongLines);
msd->options->m_prefBranch->GetBoolPref("mail.fixed_width_messages", &MIME_VariableWidthPlaintext);
//
// Charset overrides takes place here
//
// We have a bool pref (mail.force_user_charset) to deal with attachments.
// 1) If true - libmime does NO conversion and just passes it through to raptor
// 2) If false, then we try to use the charset of the part and if not available,
// the charset of the root message
//
msd->options->m_prefBranch->GetBoolPref("mail.force_user_charset", &(msd->options->force_user_charset));
msd->options->m_prefBranch->GetBoolPref("mail.inline_attachments", &(msd->options->show_attachment_inline_p));
msd->options->m_prefBranch->GetBoolPref("mail.reply_quote_inline", &(msd->options->quote_attachment_inline_p));
}
/* This pref is written down in with the
opposite sense of what we like to use... */
MIME_VariableWidthPlaintext = !MIME_VariableWidthPlaintext;
msd->options->wrap_long_lines_p = MIME_WrapLongLines;
msd->options->headers = MIME_HeaderType;
// We need to have the URL to be able to support the various
// arguments
status = mime_parse_url_options(msd->url_name, msd->options);
if (status < 0)
{
PR_FREEIF(msd->options->part_to_load);
PR_Free(msd->options);
PR_Free(msd);
return 0;
}
if (msd->options->headers == MimeHeadersMicro &&
(msd->url_name == NULL || (strncmp(msd->url_name, "news:", 5) != 0 &&
strncmp(msd->url_name, "snews:", 6) != 0)) )
msd->options->headers = MimeHeadersMicroPlus;
msd->options->url = msd->url_name;
msd->options->output_init_fn = mime_output_init_fn;
msd->options->output_fn = mime_output_fn;
msd->options->whattodo = whattodo;
msd->options->charset_conversion_fn = mime_convert_charset;
msd->options->rfc1522_conversion_p = PR_TRUE;
msd->options->file_type_fn = mime_file_type;
msd->options->stream_closure = msd;
msd->options->passwd_prompt_fn = 0;
msd->options->image_begin = mime_image_begin;
msd->options->image_end = mime_image_end;
msd->options->make_image_html = mime_image_make_image_html;
msd->options->image_write_buffer = mime_image_write_buffer;
msd->options->variable_width_plaintext_p = MIME_VariableWidthPlaintext;
// If this is a part, then we should emit the HTML to render the data
// (i.e. embedded images)
if (msd->options->part_to_load && msd->options->format_out != nsMimeOutput::nsMimeMessageBodyDisplay)
msd->options->write_html_p = PR_FALSE;
obj = mime_new ((MimeObjectClass *)&mimeMessageClass, (MimeHeaders *) NULL, MESSAGE_RFC822);
if (!obj)
{
delete msd->options;
PR_Free(msd);
return 0;
}
obj->options = msd->options;
msd->obj = obj;
/* Both of these better not be true at the same time. */
PR_ASSERT(! (obj->options->decrypt_p && obj->options->write_html_p));
stream = PR_NEW(nsMIMESession);
if (!stream)
{
delete msd->options;
PR_Free(msd);
PR_Free(obj);
return 0;
}
memset (stream, 0, sizeof (*stream));
stream->name = "MIME Conversion Stream";
stream->complete = mime_display_stream_complete;
stream->abort = mime_display_stream_abort;
stream->put_block = mime_display_stream_write;
stream->data_object = msd;
status = obj->clazz->initialize(obj);
if (status >= 0)
status = obj->clazz->parse_begin(obj);
if (status < 0)
{
PR_Free(stream);
delete msd->options;
PR_Free(msd);
PR_Free(obj);
return 0;
}
return stream;
}
//
// Emitter Wrapper Routines!
//
nsIMimeEmitter *
GetMimeEmitter(MimeDisplayOptions *opt)
{
mime_stream_data *msd = (mime_stream_data *)opt->stream_closure;
if (!msd)
return NULL;
nsIMimeEmitter *ptr = (nsIMimeEmitter *)(msd->output_emitter);
return ptr;
}
mime_stream_data *
GetMSD(MimeDisplayOptions *opt)
{
if (!opt)
return nsnull;
mime_stream_data *msd = (mime_stream_data *)opt->stream_closure;
return msd;
}
PRBool
NoEmitterProcessing(nsMimeOutputType format_out)
{
if ( (format_out == nsMimeOutput::nsMimeMessageDraftOrTemplate) ||
(format_out == nsMimeOutput::nsMimeMessageEditorTemplate))
return PR_TRUE;
else
return PR_FALSE;
}
extern "C" nsresult
mimeEmitterAddAttachmentField(MimeDisplayOptions *opt, const char *field, const char *value)
{
// Check for draft processing...
if (NoEmitterProcessing(opt->format_out))
return NS_OK;
mime_stream_data *msd = GetMSD(opt);
if (!msd)
return NS_ERROR_FAILURE;
if (msd->output_emitter)
{
nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
return emitter->AddAttachmentField(field, value);
}
return NS_ERROR_FAILURE;
}
extern "C" nsresult
mimeEmitterAddHeaderField(MimeDisplayOptions *opt, const char *field, const char *value)
{
// Check for draft processing...
if (NoEmitterProcessing(opt->format_out))
return NS_OK;
mime_stream_data *msd = GetMSD(opt);
if (!msd)
return NS_ERROR_FAILURE;
if (msd->output_emitter)
{
nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
return emitter->AddHeaderField(field, value);
}
return NS_ERROR_FAILURE;
}
extern "C" nsresult
mimeEmitterAddAllHeaders(MimeDisplayOptions *opt, const char *allheaders, const PRInt32 allheadersize)
{
// Check for draft processing...
if (NoEmitterProcessing(opt->format_out))
return NS_OK;
mime_stream_data *msd = GetMSD(opt);
if (!msd)
return NS_ERROR_FAILURE;
if (msd->output_emitter)
{
nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
return emitter->AddAllHeaders(Substring(allheaders,
allheaders + allheadersize));
}
return NS_ERROR_FAILURE;
}
extern "C" nsresult
mimeEmitterStartAttachment(MimeDisplayOptions *opt, const char *name, const char *contentType, const char *url,
PRBool aIsExternalAttachment)
{
// Check for draft processing...
if (NoEmitterProcessing(opt->format_out))
return NS_OK;
mime_stream_data *msd = GetMSD(opt);
if (!msd)
return NS_ERROR_FAILURE;
if (msd->output_emitter)
{
nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
return emitter->StartAttachment(name, contentType, url, aIsExternalAttachment);
}
return NS_ERROR_FAILURE;
}
extern "C" nsresult
mimeEmitterEndAttachment(MimeDisplayOptions *opt)
{
// Check for draft processing...
if (NoEmitterProcessing(opt->format_out))
return NS_OK;
mime_stream_data *msd = GetMSD(opt);
if (!msd)
return NS_ERROR_FAILURE;
if (msd->output_emitter)
{
nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
if (emitter)
return emitter->EndAttachment();
else
return NS_OK;
}
return NS_ERROR_FAILURE;
}
extern "C" nsresult
mimeEmitterEndAllAttachments(MimeDisplayOptions *opt)
{
// Check for draft processing...
if (NoEmitterProcessing(opt->format_out))
return NS_OK;
mime_stream_data *msd = GetMSD(opt);
if (!msd)
return NS_ERROR_FAILURE;
if (msd->output_emitter)
{
nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
if (emitter)
return emitter->EndAllAttachments();
else
return NS_OK;
}
return NS_ERROR_FAILURE;
}
extern "C" nsresult
mimeEmitterStartBody(MimeDisplayOptions *opt, PRBool bodyOnly, const char *msgID, const char *outCharset)
{
// Check for draft processing...
if (NoEmitterProcessing(opt->format_out))
return NS_OK;
mime_stream_data *msd = GetMSD(opt);
if (!msd)
return NS_ERROR_FAILURE;
if (msd->output_emitter)
{
nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
return emitter->StartBody(bodyOnly, msgID, outCharset);
}
return NS_ERROR_FAILURE;
}
extern "C" nsresult
mimeEmitterEndBody(MimeDisplayOptions *opt)
{
// Check for draft processing...
if (NoEmitterProcessing(opt->format_out))
return NS_OK;
mime_stream_data *msd = GetMSD(opt);
if (!msd)
return NS_ERROR_FAILURE;
if (msd->output_emitter)
{
nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
return emitter->EndBody();
}
return NS_ERROR_FAILURE;
}
extern "C" nsresult
mimeEmitterEndHeader(MimeDisplayOptions *opt)
{
// Check for draft processing...
if (NoEmitterProcessing(opt->format_out))
return NS_OK;
mime_stream_data *msd = GetMSD(opt);
if (!msd)
return NS_ERROR_FAILURE;
if (msd->output_emitter)
{
nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
return emitter->EndHeader();
}
return NS_ERROR_FAILURE;
}
extern "C" nsresult
mimeEmitterUpdateCharacterSet(MimeDisplayOptions *opt, const char *aCharset)
{
// Check for draft processing...
if (NoEmitterProcessing(opt->format_out))
return NS_OK;
mime_stream_data *msd = GetMSD(opt);
if (!msd)
return NS_ERROR_FAILURE;
if (msd->output_emitter)
{
nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
return emitter->UpdateCharacterSet(aCharset);
}
return NS_ERROR_FAILURE;
}
extern "C" nsresult
mimeEmitterStartHeader(MimeDisplayOptions *opt, PRBool rootMailHeader, PRBool headerOnly, const char *msgID,
const char *outCharset)
{
// Check for draft processing...
if (NoEmitterProcessing(opt->format_out))
return NS_OK;
mime_stream_data *msd = GetMSD(opt);
if (!msd)
return NS_ERROR_FAILURE;
if (msd->output_emitter)
{
nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
return emitter->StartHeader(rootMailHeader, headerOnly, msgID, outCharset);
}
return NS_ERROR_FAILURE;
}
extern "C" nsresult
mimeSetNewURL(nsMIMESession *stream, char *url)
{
if ( (!stream) || (!url) || (!*url) )
return NS_ERROR_FAILURE;
mime_stream_data *msd = (mime_stream_data *)stream->data_object;
if (!msd)
return NS_ERROR_FAILURE;
char *tmpPtr = strdup(url);
if (!tmpPtr)
return NS_ERROR_FAILURE;
PR_FREEIF(msd->url_name);
msd->url_name = strdup(tmpPtr);
return NS_OK;
}
#define MIME_URL "chrome://messenger/locale/mime.properties"
extern "C"
char *
MimeGetStringByID(PRInt32 stringID)
{
char *tempString = nsnull;
const char *resultString = "???";
nsresult res = NS_OK;
if (!stringBundle)
{
const char* propertyURL = MIME_URL;
nsCOMPtr<nsIStringBundleService> sBundleService =
do_GetService(NS_STRINGBUNDLE_CONTRACTID, &res);
if (NS_SUCCEEDED(res) && (nsnull != sBundleService))
{
res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(stringBundle));
}
}
if (stringBundle)
{
nsString v;
res = stringBundle->GetStringFromID(stringID, getter_Copies(v));
if (NS_SUCCEEDED(res))
tempString = ToNewUTF8String(v);
}
if (!tempString)
tempString = strdup(resultString);
return tempString;
}
void PR_CALLBACK
ResetChannelCharset(MimeObject *obj)
{
if (obj->options && obj->options->stream_closure &&
obj->options->default_charset && obj->headers )
{
mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure);
char *ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, PR_FALSE, PR_FALSE);
if ( (ct) && (msd) && (msd->channel) )
{
char *ptr = strstr(ct, "charset=");
if (ptr)
{
// First, setup the channel!
msd->channel->SetContentType(nsDependentCString(ct));
// Second, if this is a Save As operation, then we need to convert
// to override the output charset!
mime_stream_data *msd = GetMSD(obj->options);
if ( (msd) && (msd->format_out == nsMimeOutput::nsMimeMessageSaveAs) )
{
// Extract the charset alone
char *cSet = nsnull;
if (*(ptr+8) == '"')
cSet = strdup(ptr+9);
else
cSet = strdup(ptr+8);
if (cSet)
{
char *ptr2 = cSet;
while ( (*cSet) && (*cSet != ' ') && (*cSet != ';') &&
(*cSet != '\r') && (*cSet != '\n') && (*cSet != '"') )
ptr2++;
if (*cSet) {
PR_FREEIF(obj->options->default_charset);
obj->options->default_charset = strdup(cSet);
obj->options->override_charset = PR_TRUE;
}
PR_FREEIF(cSet);
}
}
}
PR_FREEIF(ct);
}
}
}
////////////////////////////////////////////////////////////
// Function to get up mail/news fontlang
////////////////////////////////////////////////////////////
nsresult GetMailNewsFont(MimeObject *obj, PRBool styleFixed, PRInt32 *fontPixelSize,
PRInt32 *fontSizePercentage, nsCString& fontLang)
{
nsresult rv = NS_OK;
nsIPrefBranch *prefBranch = GetPrefBranch(obj->options);
if (prefBranch) {
MimeInlineText *text = (MimeInlineText *) obj;
nsCAutoString charset;
// get a charset
if (!text->initializeCharset)
((MimeInlineTextClass*)&mimeInlineTextClass)->initialize_charset(obj);
if (!text->charset || !(*text->charset))
charset.Assign("us-ascii");
else
charset.Assign(text->charset);
nsCOMPtr<nsICharsetConverterManager> charSetConverterManager2;
nsCOMPtr<nsIAtom> langGroupAtom;
nsCAutoString prefStr;
ToLowerCase(charset);
charSetConverterManager2 = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
if ( NS_FAILED(rv))
return rv;
// get a language, e.g. x-western, ja
rv = charSetConverterManager2->GetCharsetLangGroup(charset.get(), getter_AddRefs(langGroupAtom));
if (NS_FAILED(rv))
return rv;
rv = langGroupAtom->ToUTF8String(fontLang);
if (NS_FAILED(rv))
return rv;
// get a font size from pref
prefStr.Assign(!styleFixed ? "font.size.variable." : "font.size.fixed.");
prefStr.Append(fontLang);
rv = prefBranch->GetIntPref(prefStr.get(), fontPixelSize);
if (NS_FAILED(rv))
return rv;
nsCOMPtr<nsIPrefBranch> prefDefBranch;
nsCOMPtr<nsIPrefService> prefSvc(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
if(prefSvc)
rv = prefSvc->GetDefaultBranch("", getter_AddRefs(prefDefBranch));
if(!prefDefBranch)
return rv;
// get original font size
PRInt32 originalSize;
rv = prefDefBranch->GetIntPref(prefStr.get(), &originalSize);
if (NS_FAILED(rv))
return rv;
// calculate percentage
*fontSizePercentage = originalSize ?
(PRInt32)((float)*fontPixelSize / (float)originalSize * 100) : 0;
}
return NS_OK;
}
/* This function syncronously converts an HTML document (as string)
to plaintext (as string) using the Gecko converter.
flags: see nsIDocumentEncoder.h
*/
// TODO: |printf|s?
/* <copy from="mozilla/parser/htmlparser/test/outsinks/Convert.cpp"
author="akk"
adapted-by="Ben Bucksch"
comment=" 'This code would not have been possible without akk.' ;-P.
No, really. "
> */
nsresult
HTML2Plaintext(const nsString& inString, nsString& outString,
PRUint32 flags, PRUint32 wrapCol)
{
nsresult rv = NS_OK;
#if DEBUG_BenB
printf("Converting HTML to plaintext\n");
char* charstar = ToNewUTF8String(inString);
printf("HTML source is:\n--------------------\n%s--------------------\n",
charstar);
delete[] charstar;
#endif
// Create a parser
nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID);
NS_ENSURE_TRUE(parser, NS_ERROR_FAILURE);
// Create the appropriate output sink
nsCOMPtr<nsIContentSink> sink =
do_CreateInstance(NS_PLAINTEXTSINK_CONTRACTID);
NS_ENSURE_TRUE(sink, NS_ERROR_FAILURE);
nsCOMPtr<nsIHTMLToTextSink> textSink(do_QueryInterface(sink));
NS_ENSURE_TRUE(textSink, NS_ERROR_FAILURE);
textSink->Initialize(&outString, flags, wrapCol);
parser->SetContentSink(sink);
rv = parser->Parse(inString, 0, NS_LITERAL_CSTRING("text/html"), PR_TRUE);
// Aah! How can NS_ERROR and NS_ABORT_IF_FALSE be no-ops in release builds???
if (NS_FAILED(rv))
{
NS_ERROR("Parse() failed!");
return rv;
}
#if DEBUG_BenB
charstar = ToNewUTF8String(outString);
printf("Plaintext is:\n--------------------\n%s--------------------\n",
charstar);
delete[] charstar;
#endif
return rv;
}
// </copy>
/* This function syncronously sanitizes an HTML document (string->string)
using the Gecko ContentSink mozISanitizingHTMLSerializer.
flags: currently unused
allowedTags: see mozSanitizingHTMLSerializer::ParsePrefs()
*/
// copied from HTML2Plaintext above
nsresult
HTMLSanitize(const nsString& inString, nsString& outString,
PRUint32 flags, const nsAString& allowedTags)
{
nsresult rv = NS_OK;
#if DEBUG_BenB
printf("Sanitizing HTML\n");
char* charstar = ToNewUTF8String(inString);
printf("Original HTML is:\n--------------------\n%s--------------------\n",
charstar);
delete[] charstar;
#endif
// Create a parser
nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID);
NS_ENSURE_TRUE(parser, NS_ERROR_FAILURE);
// Create the appropriate output sink
nsCOMPtr<nsIContentSink> sink =
do_CreateInstance(MOZ_SANITIZINGHTMLSERIALIZER_CONTRACTID);
NS_ENSURE_TRUE(sink, NS_ERROR_FAILURE);
nsCOMPtr<mozISanitizingHTMLSerializer> sanSink(do_QueryInterface(sink));
NS_ENSURE_TRUE(sanSink, NS_ERROR_FAILURE);
sanSink->Initialize(&outString, flags, allowedTags);
parser->SetContentSink(sink);
rv = parser->Parse(inString, 0, NS_LITERAL_CSTRING("text/html"), PR_TRUE);
if (NS_FAILED(rv))
{
NS_ERROR("Parse() failed!");
return rv;
}
#if DEBUG_BenB
charstar = ToNewUTF8String(outString);
printf("Sanitized HTML is:\n--------------------\n%s--------------------\n",
charstar);
delete[] charstar;
#endif
return rv;
}